From 04bbc3bc14296c41c536e35e52d6ca03cc0bc5c3 Mon Sep 17 00:00:00 2001 From: Jonathan Buchanan Date: Mon, 15 Jun 2026 15:00:48 -0700 Subject: [PATCH] =?UTF-8?q?favorites:=20list/add/rm=20starred=20docs=20+?= =?UTF-8?q?=20=E2=98=85=20marker=20in=20ls?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - new `mdocs favorites` command group (alias `favs`): list (default), add , rm (alias remove). - `mdocs ls` marks favorited docs with a leading ★ and includes `favorite` in --json output. - Api client: listFavorites / favorite / unfavorite over the new /api/docs/favorites and PUT|DELETE /api/docs/:id/favorite endpoints. - Documented in `mdocs instructions` and the agent skill. Depends on the server endpoints (mdocs-web-editor favorites PR). Co-Authored-By: Claude Opus 4.8 --- src/api.ts | 5 +++++ src/cli.ts | 52 +++++++++++++++++++++++++++++++++++++++++---- src/instructions.ts | 9 +++++++- src/skill.ts | 2 +- 4 files changed, 62 insertions(+), 6 deletions(-) diff --git a/src/api.ts b/src/api.ts index 4521347..f831af7 100644 --- a/src/api.ts +++ b/src/api.ts @@ -57,6 +57,11 @@ export class Api { pull = (id: string) => this.req(`/api/docs/${encodeURIComponent(id)}/pull`).then((r) => r.json()) readContent = (id: string) => this.req(`/api/docs/${encodeURIComponent(id)}/content`).then((r) => r.text()) listShared = () => this.req('/api/docs/shared').then((r) => r.json()) + listFavorites = () => this.req('/api/docs/favorites').then((r) => r.json()) + favorite = (id: string) => + this.req(`/api/docs/${encodeURIComponent(id)}/favorite`, { method: 'PUT', body: '{}' }).then((r) => r.json()) + unfavorite = (id: string) => + this.req(`/api/docs/${encodeURIComponent(id)}/favorite`, { method: 'DELETE' }).then((r) => r.json()) history = (id: string) => this.req(`/api/docs/${encodeURIComponent(id)}/versions`).then((r) => r.json()) versionContent = (id: string, n: number) => this.req(`/api/docs/${encodeURIComponent(id)}/versions/${n}`).then((r) => r.text()) revert = (id: string, version: number, message?: string) => diff --git a/src/cli.ts b/src/cli.ts index 509886b..3f3278e 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -160,19 +160,23 @@ program return void process.stdout.write(`${JSON.stringify({ docs, workspaces, shared })}\n`) } const wsName = new Map(workspaces.map((w: { id: string; name: string }) => [w.id, w.name])) - const byWs = new Map() - for (const d of docs) { + type LsDoc = { id: string; title: string; workspace_id?: string | null; favorite?: boolean; owner_email?: string } + const byWs = new Map() + for (const d of docs as LsDoc[]) { const k = d.workspace_id ?? 'none' ;(byWs.get(k) ?? byWs.set(k, []).get(k)!).push(d) } if (docs.length === 0 && shared.length === 0) return void process.stdout.write('No documents.\n') + // A leading ★ marks favorites (plain space keeps ids aligned). + const star = (d: LsDoc) => (d.favorite ? '★' : ' ') for (const [wid, list] of byWs) { process.stdout.write(`\n${wsName.get(wid) ?? 'Workspace'}\n`) - for (const d of list) process.stdout.write(` ${d.id} ${d.title}\n`) + for (const d of list) process.stdout.write(` ${star(d)} ${d.id} ${d.title}\n`) } if (shared.length > 0) { process.stdout.write(`\nShared\n`) - for (const d of shared) process.stdout.write(` ${d.id} ${d.title} (${d.owner_email ?? 'shared'})\n`) + for (const d of shared as LsDoc[]) + process.stdout.write(` ${star(d)} ${d.id} ${d.title} (${d.owner_email ?? 'shared'})\n`) } }) @@ -380,6 +384,46 @@ program ) }) +// ---- favorites (starred docs) ---- +const fav = program.command('favorites').alias('favs').description('List and manage your favorite (starred) docs') + +fav + .command('list', { isDefault: true }) + .description('List your favorite docs') + .action(async () => { + const { docs } = await api() + .listFavorites() + .catch((e: ApiError) => fail(e.code, e.message)) + if (program.opts().json) return void process.stdout.write(`${JSON.stringify(docs)}\n`) + if (docs.length === 0) { + return void process.stdout.write('No favorites yet. Star one with `mdocs favorites add `.\n') + } + for (const d of docs) process.stdout.write(` ★ ${d.id} ${d.title}\n`) + }) + +fav + .command('add ') + .description('Add a doc to your favorites') + .action(async (docId: string) => { + await api() + .favorite(docId) + .catch((e: ApiError) => fail(e.code, e.message)) + if (program.opts().json) return void process.stdout.write(`${JSON.stringify({ id: docId, favorite: true })}\n`) + process.stdout.write(`★ Favorited ${docId}\n`) + }) + +fav + .command('rm ') + .alias('remove') + .description('Remove a doc from your favorites') + .action(async (docId: string) => { + await api() + .unfavorite(docId) + .catch((e: ApiError) => fail(e.code, e.message)) + if (program.opts().json) return void process.stdout.write(`${JSON.stringify({ id: docId, favorite: false })}\n`) + process.stdout.write(`☆ Unfavorited ${docId}\n`) + }) + // ---- trash (recently deleted) ---- const daysLeft = (deletedAt: string, retentionDays: number): string => { const days = Math.ceil((new Date(deletedAt).getTime() + retentionDays * 86400_000 - Date.now()) / 86400_000) diff --git a/src/instructions.ts b/src/instructions.ts index a0a8ee6..3e13a88 100644 --- a/src/instructions.ts +++ b/src/instructions.ts @@ -28,7 +28,8 @@ stable JSON when you pass --json. Exit codes are meaningful (see below). ## Commands - mdocs ls [--json] - List accessible docs. JSON: [{"id","title","workspaceId","createdAt","updatedAt"}] + List accessible docs. A leading ★ marks favorites. JSON includes "favorite". + [{"id","title","workspaceId","createdAt","updatedAt","favorite"}] - mdocs workspaces [--json] (alias: ws; same as "ws list") List your workspaces with ids (for "new --workspace"). - mdocs ws create [--json] @@ -55,6 +56,12 @@ stable JSON when you pass --json. Exit codes are meaningful (see below). - mdocs share --link [--role viewer|editor] Create a shareable link (anyone signed in who opens it gets access). Prints the URL. JSON: {"url","role"}. +- mdocs favorites [list] [--json] (alias: favs) + List your favorite (starred) docs. JSON: same shape as "ls" rows. +- mdocs favorites add + Star a doc so it shows under Favorites (in the app and "mdocs favorites"). +- mdocs favorites rm (alias: remove) + Unstar a doc. - mdocs history [--json] (alias: log) Show version history: each version's number, time, author, source, message. JSON: [{"n","createdAt","authorEmail","source","message","contentHash"}] diff --git a/src/skill.ts b/src/skill.ts index e28e95d..46a928a 100644 --- a/src/skill.ts +++ b/src/skill.ts @@ -33,7 +33,7 @@ it — it lists every command, flag, JSON shape, and exit code: mdocs push doc.md --message "why" # 3-way merge back; re-pull if it conflicts Other commands: mdocs ws (list/create workspaces), mdocs new (create a doc from a -file), mdocs history / revert, mdocs whoami. +file), mdocs history / revert, mdocs favorites (star/list docs), mdocs whoami. ## Auth - Headless/agent: set MDOCS_TOKEN (a user generates it) and optionally MDOCS_SERVER.