Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 12 additions & 5 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,17 @@ Rust workspace with 4 crates:
- **flicknote-auth** — Supabase GoTrue authentication (OTP + OAuth2/PKCE)
- **flicknote-sync** — Daemon application host, typed RPC boundary, backend ownership, and PowerSync ↔ Supabase sync

### modify vs replace
### MCP interface

FlickNote MCP is the formal model interface for note operations. The CLI remains
for human and operational workflows; content and section mutations are not CLI
commands.

Every MCP structured result must have an object root, and each advertised output
schema must be precise and derived from its boundary DTO. Arbitrary JSON schema
terms must use object form rather than bare boolean terms. Every MCP change must
pass the repository-wide strict-client output-schema contract test.

- `flicknote modify <id>` — edit-mode: exact-string replace via `===BEFORE===`/`===AFTER===` blocks, plus metadata
- `flicknote replace <id> --section <section-id>` — replaces one complete section subtree, including its heading; it does not change note metadata

## Build & Test

Expand Down Expand Up @@ -80,9 +87,9 @@ Commit scope: `ci`

The `skills/` directory contains command reference docs for AI agents:

- `skills/flicknote.md` — FlickNote CLI command reference
- `skills/flicknote.md` — concise MCP-first FlickNote guidance

Agent quick reference is deployed via `ttal sync` to the runtime agent rules.
The bundled skill is installed with `flicknote skill install`.

## Commit Style

Expand Down
39 changes: 17 additions & 22 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ Daemon-backed note management CLI with local-first sync. The CLI and MCP server
- **Add & capture notes** — text, URLs (auto-detected as links), files
- **List & search notes** — filter by type, project, or keyword (`find`)
- **Get note details** — retrieve by numeric short ID; view heading structure with `--tree`
- **Edit notes** — modify exact text, or replace, append, insert, remove, and rename sections by ID
- **Edit notes** — human editor, append, content, and metadata workflows; structured content and section mutations are provided by MCP
- **MCP server** — typed local note, source, and project tools over stdio
- **Archive notes** — archive and unarchive
- **Authentication** — email OTP or OAuth (Google/Apple) via Supabase
Expand Down Expand Up @@ -93,20 +93,13 @@ flicknote unshare <note-id>
flicknote project share <project-id>
flicknote project unshare <project-id>

# Edit note content
# Precision edit (exact-string replace)
cat <<'EDIT' | flicknote modify <note-id>
===BEFORE===
typo here
===AFTER===
fixed here
EDIT
# Edit note metadata
flicknote modify <note-id> --project myproject
flicknote modify <note-id> --project myproject --flagged
flicknote modify <note-id> --unflagged

# Replace one section, including its heading and child sections
echo "## Heading
body" | flicknote replace <note-id> --section <section-id>

# For a whole-note rewrite, archive the old note and create a new note.
# Content and section mutations use the structured MCP interface. The MCP
# schemas carry exact before/after fields and section-scoped operations.

# Append
echo "more content" | flicknote append <note-id>
Expand Down Expand Up @@ -140,14 +133,16 @@ start it as a subprocess:
}
```

The MCP server requires the local daemon. It exposes typed note, note-source,
and project tools. Note content
and exact `before`/`after` edits are JSON fields, so callers do not need shell
heredocs. Note tools accept numeric short IDs and do not expose internal UUIDs;
project tools use project names. `note_source` reads stored source data, while
`note_get` reads editable note content. Every data tool uses the running daemon;
the MCP process never opens SQLite. The server does not start the daemon
automatically.
The MCP server requires the local daemon. It exposes typed note, discovery,
note-source, and project tools. Note content and exact `before`/`after` edits
are structured JSON fields, so callers do not need shell heredocs. Note tools
accept numeric short IDs and do not expose internal UUIDs; project tools use
project names. `note_source` reads stored source data, while `note_get` reads
editable note content. Every data tool uses the running daemon; the MCP process
never opens SQLite. The server does not start the daemon automatically.

The Gateway CLI command remains available for internal development and
maintenance requests; it is not the formal agent interface.

## Configuration

Expand Down
135 changes: 0 additions & 135 deletions RULE.md

This file was deleted.

43 changes: 11 additions & 32 deletions flicknote-cli/src/commands/delete.rs
Original file line number Diff line number Diff line change
@@ -1,45 +1,24 @@
use clap::Args;
use flicknote_core::error::CliError;
use flicknote_core::services::dto::{NoteArchiveResult, NoteMutationResult};
use flicknote_core::services::dto::NoteArchiveResult;
use flicknote_sync::ipc::{AppRequest, DaemonClient};

use super::util::{display_summary_id, print_section_tree};

#[derive(Args)]
pub(crate) struct DeleteArgs {
/// Note ID. Use the numeric short ID shown in list/detail. Full UUIDs are also accepted for compatibility.
id: String,
/// Remove a specific section by section ID (2-char base62) instead of deleting the note
#[arg(short = 's', long = "section")]
section: Option<String>,
}

pub(crate) async fn run(daemon: &DaemonClient<'_>, args: &DeleteArgs) -> Result<(), CliError> {
if let Some(ref section_id) = args.section {
let result: NoteMutationResult = daemon
.call(AppRequest::NoteDeleteSection {
id: args.id.clone(),
section: section_id.clone(),
})
.await?;
println!(
"Removed section {} from note {}.\n",
section_id,
display_summary_id(&result.note)
);
print_section_tree(&result.sections);
} else {
let result: NoteArchiveResult = daemon
.call(AppRequest::NoteArchive {
id: args.id.clone(),
})
.await?;
let display_id = result
.short_id
.map(|id| id.to_string())
.unwrap_or(result.uuid);
println!("Deleted note {}.", display_id);
}

let result: NoteArchiveResult = daemon
.call(AppRequest::NoteArchive {
id: args.id.clone(),
})
.await?;
let display_id = result
.short_id
.map(|id| id.to_string())
.unwrap_or(result.uuid);
println!("Deleted note {}.", display_id);
Ok(())
}
5 changes: 2 additions & 3 deletions flicknote-cli/src/commands/detail.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use clap::Args;
use flicknote_core::error::CliError;
use flicknote_core::services::dto::NoteDetail;
use flicknote_core::types::Note;
use flicknote_core::services::dto::{NoteDetail, NoteRecord};
use flicknote_sync::ipc::{AppRequest, DaemonClient};

use super::util::{display_summary_id, note_json, print_section_tree};
Expand Down Expand Up @@ -40,7 +39,7 @@ pub(crate) async fn run(daemon: &DaemonClient<'_>, args: &DetailArgs) -> Result<
return Ok(());
}
if args.json {
let note: Note = daemon
let note: NoteRecord = daemon
.call(AppRequest::NoteRecord {
id: detail.note.uuid.clone(),
archived: args.archived,
Expand Down
56 changes: 0 additions & 56 deletions flicknote-cli/src/commands/insert.rs

This file was deleted.

3 changes: 0 additions & 3 deletions flicknote-cli/src/commands/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,12 @@ pub(crate) mod entity;
pub(crate) mod find;
pub(crate) mod gateway;
pub(crate) mod import;
pub(crate) mod insert;
pub(crate) mod list;
pub(crate) mod login;
pub(crate) mod logout;
pub(crate) mod modify;
pub(crate) mod open;
pub(crate) mod project;
pub(crate) mod rename;
pub(crate) mod replace;
pub(crate) mod restore;
pub(crate) mod share;
pub(crate) mod skill;
Expand Down
Loading