@magic-markdown/cli 0.3.5 → 0.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +8 -6
  2. package/dist/index.js +147 -19
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -22,19 +22,21 @@ The package is a single self-contained bundle with no runtime dependencies. It r
22
22
  ## Quick start for agents
23
23
 
24
24
  ```bash
25
+ npx --yes --package=@magic-markdown/cli@latest mdocs version --json
26
+
25
27
  # Join a shared document and announce presence
26
- mdocs join <share-url> --doc <docId> --name "Claude Code" --json
28
+ npx --yes --package=@magic-markdown/cli@latest mdocs join <share-url> --doc <docId> --name "Claude Code" --json
27
29
 
28
30
  # Orient without dumping the whole document
29
- mdocs remote context --summary --json
31
+ npx --yes --package=@magic-markdown/cli@latest mdocs remote context --summary --json
30
32
 
31
33
  # Page document text separately from review state
32
- mdocs remote context --start-line 1 --end-line 120 --no-review --json
33
- mdocs remote review --json
34
+ npx --yes --package=@magic-markdown/cli@latest mdocs remote context --start-line 1 --end-line 120 --no-review --json
35
+ npx --yes --package=@magic-markdown/cli@latest mdocs remote review --json
34
36
 
35
37
  # Leave a comment or propose an edit (ranges are 1-based, inclusive)
36
- mdocs remote comment --range 12:14 --body "Tighten this paragraph." --json
37
- mdocs remote suggest --range 12:14 --with-file replacement.md --message "Clearer phrasing" --json
38
+ npx --yes --package=@magic-markdown/cli@latest mdocs remote comment --range 12:14 --body "Tighten this paragraph." --json
39
+ npx --yes --package=@magic-markdown/cli@latest mdocs remote suggest --range 12:14 --with-file replacement.md --message "Clearer phrasing" --json
38
40
  ```
39
41
 
40
42
  Run `mdocs agent guide` for the full agent workflow, `mdocs help <command>` for any command, or `mdocs agent commands --json` for the machine-readable command contract.
package/dist/index.js CHANGED
@@ -10990,7 +10990,7 @@ var RemoteDocumentIO = class {
10990
10990
  };
10991
10991
 
10992
10992
  // src/agent.ts
10993
- var CLI_VERSION = "0.3.5";
10993
+ var CLI_VERSION = "0.3.7";
10994
10994
  var CLI_PACKAGE_NAME = "@magic-markdown/cli";
10995
10995
  var AGENT_COMMANDS = [
10996
10996
  {
@@ -11009,6 +11009,14 @@ var AGENT_COMMANDS = [
11009
11009
  mutates: false,
11010
11010
  examples: ["mdocs agent commands --json"]
11011
11011
  },
11012
+ {
11013
+ name: "agent-guide",
11014
+ summary: "Alias for mdocs agent guide.",
11015
+ usage: "mdocs agent-guide [--json]",
11016
+ output: "text",
11017
+ mutates: false,
11018
+ examples: ["mdocs agent-guide", "mdocs agent-guide --json"]
11019
+ },
11012
11020
  {
11013
11021
  name: "init",
11014
11022
  summary: "Create .mdocs metadata and index Markdown documents.",
@@ -11249,14 +11257,33 @@ var AGENT_COMMANDS = [
11249
11257
  "mdocs checkpoint restore checkpoint_abc123 --json"
11250
11258
  ]
11251
11259
  },
11260
+ {
11261
+ name: "bridge setup",
11262
+ summary: "Initialize, validate, request approval for, and start an agent filesystem bridge.",
11263
+ usage: "mdocs bridge setup --workspace <id> --root <path> --root-id <id> --url <base-url> --actor-name <agent-name> --request-token",
11264
+ output: "long-running",
11265
+ mutates: true,
11266
+ examples: [
11267
+ "mdocs bridge setup --workspace workspace_abc --root . --root-id agentfs-hermes --url https://magic.example.com --actor-name Hermes --request-token"
11268
+ ],
11269
+ notes: [
11270
+ "Runs init and doctor before starting the bridge.",
11271
+ "--request-token opens a human approval URL and avoids pasting bridge secrets into an agent session.",
11272
+ "--token (or MDOCS_BRIDGE_TOKEN) still works when a scoped bridge token is already available."
11273
+ ]
11274
+ },
11252
11275
  {
11253
11276
  name: "bridge",
11254
11277
  summary: "Sync a local Markdown root with a Magic workspace over WebSocket (long-running).",
11255
- usage: "mdocs bridge --workspace <id> --root <path> --url <base-url>",
11278
+ usage: "mdocs bridge --workspace <id> --root <path> --url <base-url> --request-token",
11256
11279
  output: "long-running",
11257
11280
  mutates: true,
11258
- examples: ["mdocs bridge --workspace workspace_abc --root . --root-id root_abc --url https://magic.example.com"],
11259
- notes: ["--url (or MDOCS_BASE_URL) is required; the bridge never assumes a localhost server."]
11281
+ examples: ["mdocs bridge --workspace workspace_abc --root . --root-id root_abc --url https://magic.example.com --request-token"],
11282
+ notes: [
11283
+ "--request-token opens a human approval URL and avoids pasting bridge secrets into an agent session.",
11284
+ "--token (or MDOCS_BRIDGE_TOKEN) still works when a scoped bridge token is already available.",
11285
+ "--url (or MDOCS_BASE_URL) is required; the bridge never assumes a localhost server."
11286
+ ]
11260
11287
  },
11261
11288
  {
11262
11289
  name: "serve-mcp",
@@ -11296,14 +11323,10 @@ Magic Markdown keeps Markdown files clean and stores collaboration metadata in .
11296
11323
 
11297
11324
  ## Install / Bootstrap
11298
11325
 
11299
- The only official package is \`@magic-markdown/cli\` (bins \`mdocs\` and \`magic-markdown\`). A different, unrelated package is published as \`@mdocs/cli\` \u2014 do not install it. This snippet uses \`mdocs\` only after verifying its identity, otherwise it runs the official package through npx without a global install:
11326
+ The only official package is \`@magic-markdown/cli\` (bins \`mdocs\` and \`magic-markdown\`). A different, unrelated package is published as \`@mdocs/cli\` \u2014 do not install it. Agent sessions should bootstrap through the latest official package so they do not reuse a stale global \`mdocs\`:
11300
11327
 
11301
11328
  \`\`\`bash
11302
- MDOCS="mdocs"
11303
- if ! command -v mdocs >/dev/null 2>&1 || ! mdocs version --json 2>/dev/null | grep -qF '"name": "@magic-markdown/cli"'; then
11304
- MDOCS="npx --yes --package=@magic-markdown/cli@latest mdocs"
11305
- fi
11306
- $MDOCS version --json
11329
+ npx --yes --package=@magic-markdown/cli@latest mdocs version --json
11307
11330
  \`\`\`
11308
11331
 
11309
11332
  Run \`mdocs help <command>\` (or \`mdocs <command> --help\`) for usage, examples, and notes on any command, or \`mdocs agent commands --json\` for the full machine-readable contract.
@@ -13363,10 +13386,38 @@ function optionalFolderTarget(value) {
13363
13386
  // src/bridge.ts
13364
13387
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
13365
13388
  import { basename as basename2, resolve as resolve4 } from "node:path";
13389
+ function bridgeSetupIdentity(input) {
13390
+ const actorName = input.actorName?.trim() || "Agent";
13391
+ return {
13392
+ actorId: input.actorId?.trim() || `agent:${agentSlug(actorName)}`,
13393
+ actorName,
13394
+ sourceName: input.sourceName?.trim() || agentFilesystemDisplayName(actorName)
13395
+ };
13396
+ }
13397
+ async function runBridgeSetup(options) {
13398
+ const root = resolve4(options.root);
13399
+ const identity = bridgeSetupIdentity(options);
13400
+ const io = new NodeWorkspaceIO(root);
13401
+ const manifest = await indexWorkspace(io);
13402
+ process.stdout.write(`mdocs bridge setup indexed ${manifest.docs.length} Markdown document${manifest.docs.length === 1 ? "" : "s"} in ${root}
13403
+ `);
13404
+ const doctor = await runDoctor(io, root);
13405
+ process.stdout.write(`mdocs doctor ${doctor.ok ? "ok" : "has warnings"}: ${doctor.checks.map((check) => check.details).join(" ")}
13406
+ `);
13407
+ await runBridge({
13408
+ ...options,
13409
+ root,
13410
+ actorId: identity.actorId,
13411
+ actorName: identity.actorName,
13412
+ sourceName: identity.sourceName,
13413
+ requestToken: options.requestToken || !options.token
13414
+ });
13415
+ }
13366
13416
  async function runBridge(options) {
13367
13417
  const root = resolve4(options.root);
13368
13418
  const localManifestSource = await readLocalSource(root);
13369
13419
  const rootId = options.rootId ?? options.sourceId ?? localManifestSource?.sourceId ?? rootIdForPath(root);
13420
+ const token = options.token ?? (options.requestToken ? await requestBridgeToken(options, rootId) : void 0);
13370
13421
  const replicaId = `replica_${createHash2("sha256").update(`${root}:${options.actorId}`).digest("hex").slice(0, 12)}`;
13371
13422
  const replicaKind = actorKindForBridge(options.actorId) === "agent" ? "agent_runtime" : "local";
13372
13423
  const mapping = createSourceMapping({
@@ -13389,7 +13440,7 @@ async function runBridge(options) {
13389
13440
  const claimMode = Boolean(options.claimToken || localManifestSource?.canonicalHead);
13390
13441
  let lastAppliedHead = localManifestSource?.canonicalHead;
13391
13442
  let socket;
13392
- const registeredRoot = options.token ? await fetchScopedRoot(options, rootId) : await registerRoot(options, root, rootId, mapping);
13443
+ const registeredRoot = token ? await fetchScopedRoot({ ...options, token }, rootId) : await registerRoot(options, root, rootId, mapping);
13393
13444
  lastAppliedHead = lastAppliedHead ?? registeredRoot?.canonical.head;
13394
13445
  await writeSourceState(lastAppliedHead);
13395
13446
  connect();
@@ -13412,7 +13463,7 @@ async function runBridge(options) {
13412
13463
  url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
13413
13464
  url.searchParams.set("actorId", options.actorId);
13414
13465
  url.searchParams.set("sourceId", sourceId);
13415
- if (options.token) url.searchParams.set("token", options.token);
13466
+ if (token) url.searchParams.set("token", token);
13416
13467
  socket = new WebSocket(url);
13417
13468
  socket.addEventListener("open", () => {
13418
13469
  process.stdout.write(`mdocs bridge connected ${root} -> ${options.workspaceId}/${rootId}
@@ -13574,7 +13625,7 @@ async function runBridge(options) {
13574
13625
  `/api/workspaces/${encodeURIComponent(options.workspaceId)}/roots/${encodeURIComponent(rootId)}/documents/${encodeURIComponent(docId)}`,
13575
13626
  options.baseUrl
13576
13627
  ),
13577
- { headers: authHeaders(options.token) }
13628
+ { headers: authHeaders(token) }
13578
13629
  );
13579
13630
  if (!response.ok) return void 0;
13580
13631
  const document = await response.json();
@@ -13633,6 +13684,60 @@ async function runBridge(options) {
13633
13684
  });
13634
13685
  }
13635
13686
  }
13687
+ async function requestBridgeToken(options, rootId) {
13688
+ const response = await fetch(new URL("/api/bridge-requests", options.baseUrl), {
13689
+ method: "POST",
13690
+ headers: { "Content-Type": "application/json" },
13691
+ body: JSON.stringify({
13692
+ workspaceId: options.workspaceId,
13693
+ rootId,
13694
+ actorId: options.actorId,
13695
+ actorName: options.actorName,
13696
+ sourceName: options.sourceName,
13697
+ role: "edit"
13698
+ })
13699
+ }).catch((error) => {
13700
+ throw new Error(`Failed to create bridge approval request: ${error instanceof Error ? error.message : String(error)}`);
13701
+ });
13702
+ if (!response.ok) {
13703
+ throw new Error(`Failed to create bridge approval request: ${response.status} ${await response.text()}`);
13704
+ }
13705
+ const created = await response.json();
13706
+ if (!created.requestId || !created.pollToken || !created.approveUrl) {
13707
+ throw new Error("Bridge approval request response was missing requestId, pollToken, or approveUrl.");
13708
+ }
13709
+ process.stdout.write(`mdocs bridge approval needed: ${created.approveUrl}
13710
+ `);
13711
+ if (created.expiresAt) process.stdout.write(`Waiting for approval until ${created.expiresAt}.
13712
+ `);
13713
+ const deadline = Date.now() + (options.pairingTimeoutMs ?? 1e3 * 60 * 15);
13714
+ while (Date.now() < deadline) {
13715
+ await delay(2e3);
13716
+ const pollResponse = await fetch(new URL(`/api/bridge-requests/${encodeURIComponent(created.requestId)}/token`, options.baseUrl), {
13717
+ headers: { Authorization: `Bearer ${created.pollToken}` }
13718
+ }).catch((error) => {
13719
+ throw new Error(`Bridge approval polling failed: ${error instanceof Error ? error.message : String(error)}`);
13720
+ });
13721
+ const payload = await pollResponse.json().catch(() => ({}));
13722
+ if (pollResponse.status === 202) continue;
13723
+ if (pollResponse.ok && payload.token) {
13724
+ process.stdout.write(`mdocs bridge approved${payload.expiresAt ? `; token expires ${payload.expiresAt}` : ""}.
13725
+ `);
13726
+ return payload.token;
13727
+ }
13728
+ if (pollResponse.status === 409 && payload.status === "rejected") {
13729
+ throw new Error("Bridge approval request was rejected.");
13730
+ }
13731
+ if (pollResponse.status === 410 || payload.status === "expired") {
13732
+ throw new Error("Bridge approval request expired before it was approved.");
13733
+ }
13734
+ throw new Error(`Bridge approval polling failed with status ${pollResponse.status}.`);
13735
+ }
13736
+ throw new Error("Bridge approval timed out before a token was issued.");
13737
+ }
13738
+ function delay(ms) {
13739
+ return new Promise((resolve6) => setTimeout(resolve6, ms));
13740
+ }
13636
13741
  function authHeaders(token) {
13637
13742
  return token ? { Authorization: `Bearer ${token}` } : {};
13638
13743
  }
@@ -13717,6 +13822,15 @@ function agentNameFromActorId(actorId, rootName) {
13717
13822
  if (nameFromId) return nameFromId;
13718
13823
  return "Agent";
13719
13824
  }
13825
+ function agentFilesystemDisplayName(agentName) {
13826
+ const trimmed = agentName.trim();
13827
+ if (!trimmed) return "Agent Filesystem";
13828
+ return /\bagent$/i.test(trimmed) ? `${trimmed} Filesystem` : `${trimmed} Agent Filesystem`;
13829
+ }
13830
+ function agentSlug(agentName) {
13831
+ const slug = agentName.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/(^-|-$)/g, "");
13832
+ return slug || "agent";
13833
+ }
13720
13834
  function mergePermissions(permissions, permission) {
13721
13835
  return [...permissions.filter((candidate) => candidate.actorId !== permission.actorId), permission];
13722
13836
  }
@@ -13915,8 +14029,12 @@ async function main() {
13915
14029
  await runAgentCommand(io, cwd, subcommand, parsed);
13916
14030
  return;
13917
14031
  }
14032
+ case "agent-guide": {
14033
+ print(parsed.flags.json ? getAgentGuidePayload() : getAgentSkillMarkdown(), parsed.flags);
14034
+ return;
14035
+ }
13918
14036
  case "bridge": {
13919
- await runBridge({
14037
+ const options = {
13920
14038
  root: resolve5(String(parsed.flags.root ?? cwd)),
13921
14039
  workspaceId: String(parsed.flags.workspace ?? "workspace_demo"),
13922
14040
  rootId: typeof parsed.flags["root-id"] === "string" ? parsed.flags["root-id"] : void 0,
@@ -13925,12 +14043,16 @@ async function main() {
13925
14043
  canonicalPrefix: typeof parsed.flags["canonical-prefix"] === "string" ? parsed.flags["canonical-prefix"] : void 0,
13926
14044
  replicaPrefix: typeof parsed.flags["replica-prefix"] === "string" ? parsed.flags["replica-prefix"] : void 0,
13927
14045
  claimToken: typeof parsed.flags.claim === "string" ? parsed.flags.claim : void 0,
13928
- actorId: String(parsed.flags.actor ?? "actor_local"),
14046
+ actorId: typeof parsed.flags.actor === "string" ? parsed.flags.actor : void 0,
13929
14047
  actorName: typeof parsed.flags["actor-name"] === "string" ? parsed.flags["actor-name"] : void 0,
13930
14048
  baseUrl: bridgeBaseUrl(parsed.flags),
13931
14049
  intervalMs: Number(parsed.flags.interval ?? 1e3),
13932
- token: typeof parsed.flags.token === "string" ? parsed.flags.token : process.env.MDOCS_BRIDGE_TOKEN || void 0
13933
- });
14050
+ token: typeof parsed.flags.token === "string" ? parsed.flags.token : process.env.MDOCS_BRIDGE_TOKEN || void 0,
14051
+ requestToken: Boolean(parsed.flags["request-token"] || parsed.flags.pair),
14052
+ pairingTimeoutMs: typeof parsed.flags["pairing-timeout-ms"] === "string" ? Number(parsed.flags["pairing-timeout-ms"]) : void 0
14053
+ };
14054
+ if (subcommand === "setup") await runBridgeSetup(options);
14055
+ else await runBridge({ ...options, actorId: options.actorId ?? "actor_local" });
13934
14056
  return;
13935
14057
  }
13936
14058
  case "doctor": {
@@ -14086,6 +14208,7 @@ function help() {
14086
14208
 
14087
14209
  Commands:
14088
14210
  agent guide Print agent SKILL.md workflow guidance
14211
+ agent-guide Alias for agent guide
14089
14212
  agent commands --json Print machine-readable command metadata
14090
14213
  init [path] Initialize .mdocs and index Markdown files
14091
14214
  map --json Print workspace map
@@ -14118,10 +14241,15 @@ Commands:
14118
14241
  remote events|history|restore Poll events, list commits, restore snapshots
14119
14242
  remote library|create-folder|update-folder|move-root|invite-folder
14120
14243
  Organize the joined project library
14244
+ bridge setup --workspace <id> --root . --url <base-url> --request-token
14245
+ Initialize, validate, request approval,
14246
+ and start an agent filesystem bridge
14247
+ bridge --workspace <id> --root . --url <base-url> --request-token
14248
+ Request human approval, then sync an
14249
+ approved local root with the workspace
14121
14250
  bridge --workspace <id> --root . --url <base-url> --token <bridge-token>
14122
14251
  Sync an approved local root with the workspace
14123
- (--token from the web "Bind agent filesystem"
14124
- dialog, or MDOCS_BRIDGE_TOKEN)
14252
+ (or set MDOCS_BRIDGE_TOKEN)
14125
14253
  bridge --claim <token> --root . --canonical-prefix prompts --replica-prefix packages/agent/prompts
14126
14254
  Claim an existing repo path as a Magic-canonical source mapping
14127
14255
  doctor --json Validate sidecar mappings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic-markdown/cli",
3
- "version": "0.3.5",
3
+ "version": "0.3.7",
4
4
  "description": "Magic Markdown agent CLI (mdocs): read, review, comment on, suggest edits to, and sync clean Markdown workspaces.",
5
5
  "type": "module",
6
6
  "license": "MIT",