@magic-markdown/cli 0.3.18 → 0.3.20

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 (2) hide show
  1. package/dist/index.js +169 -88
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -270,7 +270,7 @@ var require_punycode = __commonJS({
270
270
  });
271
271
 
272
272
  // src/index.ts
273
- import { readFile as readFile7 } from "node:fs/promises";
273
+ import { readFile as readFile8 } from "node:fs/promises";
274
274
  import { resolve as resolve6 } from "node:path";
275
275
 
276
276
  // ../core/src/types.ts
@@ -11263,7 +11263,7 @@ var RemoteDocumentIO = class {
11263
11263
  };
11264
11264
 
11265
11265
  // src/agent.ts
11266
- var CLI_VERSION = "0.3.18";
11266
+ var CLI_VERSION = "0.3.20";
11267
11267
  var CLI_PACKAGE_NAME = "@magic-markdown/cli";
11268
11268
  var AGENT_COMMANDS = [
11269
11269
  {
@@ -11534,34 +11534,36 @@ var AGENT_COMMANDS = [
11534
11534
  },
11535
11535
  {
11536
11536
  name: "bridge setup",
11537
- summary: "Initialize, validate, request approval for, and start an agent filesystem bridge.",
11538
- usage: "mdocs bridge setup --workspace <id> --root <path> --root-id <id> [--folder-id <id>] --url <base-url> --actor-name <agent-name> --request-token",
11537
+ summary: "Initialize, validate, claim, and start an agent filesystem bridge.",
11538
+ usage: "mdocs bridge setup --workspace <id> --root <path> --root-id <id> [--folder-id <id>] --url <base-url> --actor-name <agent-name> --claim <claim-token>",
11539
11539
  output: "long-running",
11540
11540
  mutates: true,
11541
11541
  examples: [
11542
- "mdocs bridge setup --workspace workspace_abc --root . --root-id agentfs-hermes --url https://magic.example.com --actor-name Hermes --request-token"
11542
+ "mdocs bridge setup --workspace workspace_abc --root . --root-id agentfs-hermes --url https://magic.example.com --actor-name Hermes --claim mdocsclaim_abc123"
11543
11543
  ],
11544
11544
  notes: [
11545
11545
  "Runs init and doctor before starting the bridge.",
11546
- "--request-token opens a human approval URL and avoids pasting bridge secrets into an agent session.",
11546
+ "--claim exchanges a one-time binding claim for the durable bridge token over HTTPS, then stores the durable token in the local CLI profile.",
11547
+ "--request-token remains available as a legacy human-approval fallback, but Magic binding packets should prefer --claim.",
11547
11548
  "--token (or MDOCS_BRIDGE_TOKEN) still works when a scoped bridge token is already available."
11548
11549
  ]
11549
11550
  },
11550
11551
  {
11551
11552
  name: "bridge resume",
11552
11553
  summary: "Backfill missed Magic changes after an agent restart, then keep the bridge running.",
11553
- usage: "mdocs bridge resume --root <path> [--url <base-url>] [--workspace <id>] [--root-id <id>] [--request-token] [--forever] [--once]",
11554
+ usage: "mdocs bridge resume --root <path> [--url <base-url>] [--workspace <id>] [--root-id <id>] [--forever] [--once]",
11554
11555
  output: "long-running",
11555
11556
  mutates: true,
11556
11557
  examples: [
11557
- "mdocs bridge resume --root . --forever --request-token",
11558
- "mdocs bridge resume --root . --once --request-token"
11558
+ "mdocs bridge resume --root . --forever",
11559
+ "mdocs bridge resume --root . --once"
11559
11560
  ],
11560
11561
  notes: [
11561
11562
  "Reads non-secret connection defaults from .mdocs/bridge.json written by bridge setup.",
11563
+ "Reads the durable bridge token from the local CLI profile written by claim-backed bridge setup.",
11562
11564
  "Use this after a sandbox/container/agent session restarts. It backfills canonical Magic changes before publishing local edits.",
11563
11565
  "--forever is the intended startup/sandbox command: keep it running for live collaboration and liveness heartbeats.",
11564
- "--request-token opens a human approval URL when no MDOCS_BRIDGE_TOKEN is available; do not ask users to paste bridge tokens by default.",
11566
+ "If the local CLI profile token is missing, rerun bridge setup from a fresh Magic binding packet. Do not ask users to paste bridge tokens by default.",
11565
11567
  "--once performs only the backfill/reconcile step and exits."
11566
11568
  ]
11567
11569
  },
@@ -11580,18 +11582,19 @@ var AGENT_COMMANDS = [
11580
11582
  "Run this after bridge setup has written .mdocs/bridge.json.",
11581
11583
  "Sandbox providers should put the returned startupCommand in their on-start hook.",
11582
11584
  "--install installs and starts the generated launchd/systemd user service when the host supports it.",
11583
- "The generated runner stores no bridge token; provide MDOCS_BRIDGE_TOKEN through platform secrets if you use manual tokens."
11585
+ "The generated runner stores no bridge token; it reuses the durable token saved by bridge setup in the local CLI profile."
11584
11586
  ]
11585
11587
  },
11586
11588
  {
11587
11589
  name: "bridge",
11588
11590
  summary: "Sync a local Markdown root with a Magic workspace over WebSocket (long-running).",
11589
- usage: "mdocs bridge --workspace <id> --root <path> --url <base-url> --request-token",
11591
+ usage: "mdocs bridge --workspace <id> --root <path> --url <base-url> --claim <claim-token>",
11590
11592
  output: "long-running",
11591
11593
  mutates: true,
11592
- examples: ["mdocs bridge --workspace workspace_abc --root . --root-id root_abc --url https://magic.example.com --request-token"],
11594
+ examples: ["mdocs bridge --workspace workspace_abc --root . --root-id root_abc --url https://magic.example.com --claim mdocsclaim_abc123"],
11593
11595
  notes: [
11594
- "--request-token opens a human approval URL and avoids pasting bridge secrets into an agent session.",
11596
+ "--claim exchanges a one-time binding claim for the durable bridge token over HTTPS, then stores the durable token in the local CLI profile.",
11597
+ "--request-token remains available as a legacy human-approval fallback.",
11595
11598
  "--token (or MDOCS_BRIDGE_TOKEN) still works when a scoped bridge token is already available.",
11596
11599
  "--url (or MDOCS_BASE_URL) is required; the bridge never assumes a localhost server."
11597
11600
  ]
@@ -11654,7 +11657,9 @@ Run \`mdocs help <command>\` (or \`mdocs <command> --help\`) for usage, examples
11654
11657
 
11655
11658
  ## Filesystem Bridge / Resume
11656
11659
 
11657
- When a human asks you to bind an editable local Markdown filesystem to Magic Markdown, use the latest official package and the human-approval token flow:
11660
+ When a human asks you to bind an editable Markdown filesystem to Magic Markdown, use the latest official package and the one-time claim from the binding packet. The claim is not the durable bridge token; the CLI exchanges it over HTTPS and stores the durable token in the local CLI profile.
11661
+
11662
+ Bind the path-addressable Markdown root. Some agent platforms expose a native or projected filesystem backed by SQLite or another virtual store; treat that projection as normal files and run setup from the projected directory. Never point \`--root\` at the SQLite database file or raw state tables. If the platform only exposes agent state through APIs or tables, first mirror the intended Markdown files into a path-addressable workspace directory, then bind that directory.
11658
11663
 
11659
11664
  \`\`\`bash
11660
11665
  npx --yes --package=@magic-markdown/cli@latest mdocs bridge setup \\
@@ -11663,18 +11668,18 @@ npx --yes --package=@magic-markdown/cli@latest mdocs bridge setup \\
11663
11668
  --root-id <root-id> \\
11664
11669
  --url <magic-url> \\
11665
11670
  --actor-name "<agent name>" \\
11666
- --request-token
11671
+ --claim <claim-token>
11667
11672
  \`\`\`
11668
11673
 
11669
- Return the Magic approval link and keep the bridge process running after approval. Success looks like \`mdocs bridge connected ...\`.
11674
+ Keep the bridge process running. Success looks like \`mdocs bridge connected ...\`.
11670
11675
 
11671
- If your sandbox, container, terminal, or agent session restarts after setup, do not ask for a raw bridge token. Start the latest CLI again and resume from the same root:
11676
+ If your sandbox, container, terminal, or agent session restarts after setup, do not ask for a raw bridge token or a new approval. Start the latest CLI again and resume from the same root:
11672
11677
 
11673
11678
  \`\`\`bash
11674
- npx --yes --package=@magic-markdown/cli@latest mdocs bridge resume --root . --forever --request-token
11679
+ npx --yes --package=@magic-markdown/cli@latest mdocs bridge resume --root . --forever
11675
11680
  \`\`\`
11676
11681
 
11677
- \`bridge resume --forever\` reads non-secret defaults from \`.mdocs/bridge.json\`, backfills missed Magic changes before publishing local edits, and then keeps polling/watching with liveness heartbeats. Use \`--once\` only when the user asked for a one-shot backfill rather than a live bridge.
11682
+ \`bridge resume --forever\` reads non-secret defaults from \`.mdocs/bridge.json\`, reads the durable bridge token from the local CLI profile, backfills missed Magic changes before publishing local edits, and then keeps polling/watching with liveness heartbeats. Use \`--once\` only when the user asked for a one-shot backfill rather than a live bridge. If the local profile token is missing, rerun \`bridge setup\` from a fresh Magic binding packet.
11678
11683
 
11679
11684
  For sandboxes, VPSs, or local machines that support startup hooks, generate a durable runner after setup:
11680
11685
 
@@ -11682,7 +11687,7 @@ For sandboxes, VPSs, or local machines that support startup hooks, generate a du
11682
11687
  npx --yes --package=@magic-markdown/cli@latest mdocs bridge startup --root . --json
11683
11688
  \`\`\`
11684
11689
 
11685
- Put the returned \`startupCommand\` into the provider's on-start hook. On local macOS or systemd Linux hosts, \`mdocs bridge startup --root . --install\` writes and starts a user service. The generated runner stores no bridge token; use platform secrets for \`MDOCS_BRIDGE_TOKEN\` if you manage tokens manually.
11690
+ Put the returned \`startupCommand\` into the provider's on-start hook. On local macOS or systemd Linux hosts, \`mdocs bridge startup --root . --install\` writes and starts a user service. The generated runner stores no bridge token; it reuses the durable token saved by \`bridge setup\` in the local CLI profile.
11686
11691
 
11687
11692
  If Magic and the local root both changed the same document while you were offline, the bridge keeps the Magic canonical path, saves your local divergent version as a \`.conflict-...\` Markdown copy, and lets the conflict copy sync as a normal file. If Magic reports a server-side conflict, wait for the human to resolve it in Magic Markdown before retrying content pushes.
11688
11693
 
@@ -13775,10 +13780,13 @@ function optionalFolderTarget(value) {
13775
13780
 
13776
13781
  // src/bridge.ts
13777
13782
  import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
13778
- import { basename as basename2, resolve as resolve4 } from "node:path";
13783
+ import { chmod, mkdir as mkdir4, readFile as readFile6, writeFile as writeFile4 } from "node:fs/promises";
13784
+ import { homedir } from "node:os";
13785
+ import { basename as basename2, join as join4, resolve as resolve4 } from "node:path";
13779
13786
  var BRIDGE_CONFIG_PATH = ".mdocs/bridge.json";
13780
13787
  var BRIDGE_STATUS_PATH = ".mdocs/bridge-status.json";
13781
13788
  var BRIDGE_HEARTBEAT_INTERVAL_MS = 1e4;
13789
+ var BRIDGE_TOKEN_STORE_PATH = join4(homedir(), ".mdocs", "bridge-tokens.json");
13782
13790
  function bridgeSetupIdentity(input) {
13783
13791
  const actorName = input.actorName?.trim() || "Agent";
13784
13792
  return {
@@ -13836,7 +13844,7 @@ async function runBridgeResume(options) {
13836
13844
  baseUrl,
13837
13845
  intervalMs: options.intervalMs ?? config2?.intervalMs ?? 1e3,
13838
13846
  token: options.token,
13839
- requestToken: options.requestToken || !options.token,
13847
+ requestToken: Boolean(options.requestToken),
13840
13848
  pairingTimeoutMs: options.pairingTimeoutMs,
13841
13849
  once: options.once,
13842
13850
  resume: true
@@ -13876,10 +13884,18 @@ async function runBridge(options) {
13876
13884
  let lastHeartbeatSentMs = 0;
13877
13885
  let lastError;
13878
13886
  await writeCurrentBridgeStatus("starting");
13879
- let token = options.token;
13880
- if (!token && options.requestToken) {
13887
+ let token = options.token ?? await readStoredBridgeToken(options, rootId).catch(() => void 0);
13888
+ if (!token && options.resume && !options.requestToken) {
13889
+ const message = "Missing stored bridge token. Re-run mdocs bridge setup from a fresh Magic binding packet.";
13890
+ lastError = message;
13891
+ await writeCurrentBridgeStatus("error");
13892
+ throw new Error(message);
13893
+ }
13894
+ if (!token && (options.claimToken || options.requestToken)) {
13881
13895
  try {
13882
- token = await requestBridgeToken(options, rootId);
13896
+ const issued = await requestBridgeToken(options, rootId);
13897
+ token = issued.token;
13898
+ await writeStoredBridgeToken(options, rootId, issued).catch(() => void 0);
13883
13899
  } catch (error) {
13884
13900
  lastError = errorMessage(error);
13885
13901
  await writeCurrentBridgeStatus("error");
@@ -14371,46 +14387,34 @@ async function runBridge(options) {
14371
14387
  }
14372
14388
  }
14373
14389
  async function requestBridgeToken(options, rootId) {
14374
- const response = await fetch(new URL("/api/bridge-requests", options.baseUrl), {
14375
- method: "POST",
14376
- headers: { "Content-Type": "application/json" },
14377
- body: JSON.stringify({
14378
- workspaceId: options.workspaceId,
14379
- rootId,
14380
- actorId: options.actorId,
14381
- actorName: options.actorName,
14382
- folderId: options.folderId,
14383
- sourceName: options.sourceName,
14384
- role: "edit"
14385
- })
14386
- }).catch((error) => {
14387
- throw new Error(`Failed to create bridge approval request: ${error instanceof Error ? error.message : String(error)}`);
14388
- });
14389
- if (!response.ok) {
14390
- throw new Error(`Failed to create bridge approval request: ${response.status} ${await response.text()}`);
14391
- }
14392
- const created = await response.json();
14393
- if (!created.requestId || !created.pollToken || !created.approveUrl) {
14394
- throw new Error("Bridge approval request response was missing requestId, pollToken, or approveUrl.");
14395
- }
14396
- process.stdout.write(`mdocs bridge approval needed: ${created.approveUrl}
14390
+ const created = options.claimToken ? await exchangeBridgeClaim(options) : await createBridgeApprovalRequest(options, rootId);
14391
+ if (!created.requestId || !created.pollToken) {
14392
+ throw new Error("Bridge token request response was missing requestId or pollToken.");
14393
+ }
14394
+ if (created.autoApproved) {
14395
+ process.stdout.write("mdocs bridge claim accepted. Finishing bind...\n");
14396
+ } else if (created.approveUrl) {
14397
+ process.stdout.write(`mdocs bridge approval needed: ${created.approveUrl}
14397
14398
  `);
14398
- if (created.expiresAt) process.stdout.write(`Waiting for approval until ${created.expiresAt}.
14399
+ if (created.expiresAt) process.stdout.write(`Waiting for approval until ${created.expiresAt}.
14399
14400
  `);
14401
+ }
14400
14402
  const deadline = Date.now() + (options.pairingTimeoutMs ?? 1e3 * 60 * 15);
14403
+ let firstPoll = true;
14401
14404
  while (Date.now() < deadline) {
14402
- await delay(2e3);
14405
+ if (firstPoll) firstPoll = false;
14406
+ else await delay(2e3);
14403
14407
  const pollResponse = await fetch(new URL(`/api/bridge-requests/${encodeURIComponent(created.requestId)}/token`, options.baseUrl), {
14404
14408
  headers: { Authorization: `Bearer ${created.pollToken}` }
14405
14409
  }).catch((error) => {
14406
- throw new Error(`Bridge approval polling failed: ${error instanceof Error ? error.message : String(error)}`);
14410
+ throw new Error(`Bridge token polling failed: ${error instanceof Error ? error.message : String(error)}`);
14407
14411
  });
14408
14412
  const payload = await pollResponse.json().catch(() => ({}));
14409
14413
  if (pollResponse.status === 202) continue;
14410
14414
  if (pollResponse.ok && payload.token) {
14411
- process.stdout.write(`mdocs bridge approved${payload.expiresAt ? `; token expires ${payload.expiresAt}` : ""}.
14415
+ process.stdout.write(`mdocs bridge token received${payload.expiresAt ? `; token expires ${payload.expiresAt}` : ""}.
14412
14416
  `);
14413
- return payload.token;
14417
+ return { token: payload.token, expiresAt: payload.expiresAt };
14414
14418
  }
14415
14419
  if (pollResponse.status === 409 && payload.status === "rejected") {
14416
14420
  throw new Error("Bridge approval request was rejected.");
@@ -14418,9 +14422,43 @@ async function requestBridgeToken(options, rootId) {
14418
14422
  if (pollResponse.status === 410 || payload.status === "expired") {
14419
14423
  throw new Error("Bridge approval request expired before it was approved.");
14420
14424
  }
14421
- throw new Error(`Bridge approval polling failed with status ${pollResponse.status}.`);
14425
+ throw new Error(`Bridge token polling failed with status ${pollResponse.status}.`);
14426
+ }
14427
+ throw new Error("Bridge token request timed out before a token was issued.");
14428
+ }
14429
+ async function exchangeBridgeClaim(options) {
14430
+ const response = await fetch(new URL("/api/bridge-claims/exchange", options.baseUrl), {
14431
+ method: "POST",
14432
+ headers: { "Content-Type": "application/json" },
14433
+ body: JSON.stringify({ claimToken: options.claimToken })
14434
+ }).catch((error) => {
14435
+ throw new Error(`Failed to exchange bridge claim: ${error instanceof Error ? error.message : String(error)}`);
14436
+ });
14437
+ if (!response.ok) {
14438
+ throw new Error(`Failed to exchange bridge claim: ${response.status} ${await response.text()}`);
14439
+ }
14440
+ return await response.json();
14441
+ }
14442
+ async function createBridgeApprovalRequest(options, rootId) {
14443
+ const response = await fetch(new URL("/api/bridge-requests", options.baseUrl), {
14444
+ method: "POST",
14445
+ headers: { "Content-Type": "application/json" },
14446
+ body: JSON.stringify({
14447
+ workspaceId: options.workspaceId,
14448
+ rootId,
14449
+ actorId: options.actorId,
14450
+ actorName: options.actorName,
14451
+ folderId: options.folderId,
14452
+ sourceName: options.sourceName,
14453
+ role: "edit"
14454
+ })
14455
+ }).catch((error) => {
14456
+ throw new Error(`Failed to create bridge approval request: ${error instanceof Error ? error.message : String(error)}`);
14457
+ });
14458
+ if (!response.ok) {
14459
+ throw new Error(`Failed to create bridge approval request: ${response.status} ${await response.text()}`);
14422
14460
  }
14423
- throw new Error("Bridge approval timed out before a token was issued.");
14461
+ return await response.json();
14424
14462
  }
14425
14463
  async function readBridgeConfig(root) {
14426
14464
  const io = new NodeWorkspaceIO(root);
@@ -14446,6 +14484,49 @@ async function writeBridgeStatus(root, status) {
14446
14484
  await io.writeTextAtomic(BRIDGE_STATUS_PATH, `${JSON.stringify(status, null, 2)}
14447
14485
  `);
14448
14486
  }
14487
+ async function readStoredBridgeToken(options, rootId) {
14488
+ const store = await readBridgeTokenStore();
14489
+ const entry = store.tokens[bridgeTokenStoreKey(options, rootId)];
14490
+ if (!entry?.token) return void 0;
14491
+ if (entry.expiresAt && Date.parse(entry.expiresAt) <= Date.now() + 6e4) return void 0;
14492
+ return entry.token;
14493
+ }
14494
+ async function writeStoredBridgeToken(options, rootId, issued) {
14495
+ const store = await readBridgeTokenStore();
14496
+ store.tokens[bridgeTokenStoreKey(options, rootId)] = {
14497
+ token: issued.token,
14498
+ expiresAt: issued.expiresAt,
14499
+ updatedAt: (/* @__PURE__ */ new Date()).toISOString()
14500
+ };
14501
+ await mkdir4(join4(homedir(), ".mdocs"), { recursive: true, mode: 448 });
14502
+ await writeFile4(BRIDGE_TOKEN_STORE_PATH, `${JSON.stringify(store, null, 2)}
14503
+ `, "utf8");
14504
+ await chmod(BRIDGE_TOKEN_STORE_PATH, 384);
14505
+ }
14506
+ async function readBridgeTokenStore() {
14507
+ try {
14508
+ const parsed = JSON.parse(await readFile6(BRIDGE_TOKEN_STORE_PATH, "utf8"));
14509
+ if (parsed.schemaVersion === 1 && parsed.tokens && typeof parsed.tokens === "object") {
14510
+ return { schemaVersion: 1, tokens: parsed.tokens };
14511
+ }
14512
+ } catch {
14513
+ }
14514
+ return { schemaVersion: 1, tokens: {} };
14515
+ }
14516
+ function bridgeTokenStoreKey(options, rootId) {
14517
+ return createHash2("sha256").update(JSON.stringify([normalizeBridgeBaseUrl(options.baseUrl), options.workspaceId, rootId, options.actorId])).digest("hex").slice(0, 32);
14518
+ }
14519
+ function normalizeBridgeBaseUrl(baseUrl) {
14520
+ try {
14521
+ const url = new URL(baseUrl);
14522
+ url.pathname = url.pathname.replace(/\/+$/, "");
14523
+ url.search = "";
14524
+ url.hash = "";
14525
+ return url.toString();
14526
+ } catch {
14527
+ return baseUrl;
14528
+ }
14529
+ }
14449
14530
  async function fetchCanonicalSnapshot(options, rootId) {
14450
14531
  const response = await fetch(new URL(`/api/workspaces/${encodeURIComponent(options.workspaceId)}/roots/${encodeURIComponent(rootId)}/sync`, options.baseUrl), {
14451
14532
  headers: authHeaders(options.token)
@@ -14664,9 +14745,9 @@ function readDocumentPayload(payload) {
14664
14745
  // src/bridge-startup.ts
14665
14746
  import { execFile as execFile2 } from "node:child_process";
14666
14747
  import { createHash as createHash3 } from "node:crypto";
14667
- import { chmod, copyFile, mkdir as mkdir4, readFile as readFile6, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
14668
- import { homedir, platform } from "node:os";
14669
- import { dirname as dirname5, join as join4, resolve as resolve5 } from "node:path";
14748
+ import { chmod as chmod2, copyFile, mkdir as mkdir5, readFile as readFile7, stat as stat3, writeFile as writeFile5 } from "node:fs/promises";
14749
+ import { homedir as homedir2, platform } from "node:os";
14750
+ import { dirname as dirname5, join as join5, resolve as resolve5 } from "node:path";
14670
14751
  import { promisify as promisify2 } from "node:util";
14671
14752
  var execFileAsync2 = promisify2(execFile2);
14672
14753
  async function runBridgeStartup(options) {
@@ -14674,13 +14755,13 @@ async function runBridgeStartup(options) {
14674
14755
  await assertBridgeConfigured(root);
14675
14756
  const requestedMode = readBridgeStartupMode(options.mode);
14676
14757
  const mode = requestedMode === "auto" ? await autoBridgeStartupMode() : requestedMode;
14677
- const startupDir = join4(root, ".mdocs", "startup");
14678
- await mkdir4(startupDir, { recursive: true });
14679
- const runnerPath = join4(startupDir, "bridge-runner.sh");
14758
+ const startupDir = join5(root, ".mdocs", "startup");
14759
+ await mkdir5(startupDir, { recursive: true });
14760
+ const runnerPath = join5(startupDir, "bridge-runner.sh");
14680
14761
  const runnerArgs = bridgeResumeArgs(root, options);
14681
14762
  const runnerScript = bridgeRunnerScript(root, runnerArgs);
14682
- await writeFile4(runnerPath, runnerScript, "utf8");
14683
- await chmod(runnerPath, 493);
14763
+ await writeFile5(runnerPath, runnerScript, "utf8");
14764
+ await chmod2(runnerPath, 493);
14684
14765
  const nativePlan = mode === "launchd" ? await writeLaunchdPlan(root, startupDir, runnerPath) : mode === "systemd" ? await writeSystemdPlan(root, startupDir, runnerPath) : void 0;
14685
14766
  if (options.install && !nativePlan) {
14686
14767
  throw new CliError("usage_error", "Shell startup mode does not have a native service to install.", {
@@ -14700,11 +14781,11 @@ async function runBridgeStartup(options) {
14700
14781
  installCommands: nativePlan?.installCommands ?? [],
14701
14782
  notes: [
14702
14783
  "Sandbox providers such as Daytona, Sprites, and Modal should run startupCommand from their on-start hook.",
14703
- "The runner stores no bridge token. If you use manual tokens, provide MDOCS_BRIDGE_TOKEN through the platform secret environment.",
14704
- "If no MDOCS_BRIDGE_TOKEN is present, the runner uses --request-token and logs a Magic approval URL."
14784
+ "The runner stores no bridge token; it reuses the durable token saved by bridge setup in the local CLI profile.",
14785
+ "If you move the root to a different machine or profile, run bridge setup from a fresh Magic binding packet again."
14705
14786
  ]
14706
14787
  };
14707
- await writeFile4(join4(startupDir, "bridge-startup.json"), `${JSON.stringify(result, null, 2)}
14788
+ await writeFile5(join5(startupDir, "bridge-startup.json"), `${JSON.stringify(result, null, 2)}
14708
14789
  `, "utf8");
14709
14790
  return result;
14710
14791
  }
@@ -14731,7 +14812,7 @@ exec npx --yes --package=@magic-markdown/cli@latest mdocs ${shellCommand(args)}
14731
14812
  }
14732
14813
  function bridgeResumeArgs(root, options) {
14733
14814
  const args = ["bridge", "resume", "--root", root, "--forever"];
14734
- if (options.requestToken !== false) args.push("--request-token");
14815
+ if (options.requestToken === true) args.push("--request-token");
14735
14816
  if (options.baseUrl) args.push("--url", options.baseUrl);
14736
14817
  if (options.intervalMs) args.push("--interval", String(options.intervalMs));
14737
14818
  return args;
@@ -14750,9 +14831,9 @@ async function autoBridgeStartupMode() {
14750
14831
  return "shell";
14751
14832
  }
14752
14833
  async function assertBridgeConfigured(root) {
14753
- const configPath = join4(root, ".mdocs", "bridge.json");
14834
+ const configPath = join5(root, ".mdocs", "bridge.json");
14754
14835
  try {
14755
- const config2 = JSON.parse(await readFile6(configPath, "utf8"));
14836
+ const config2 = JSON.parse(await readFile7(configPath, "utf8"));
14756
14837
  if (config2.schemaVersion !== 1) throw new Error("invalid schema");
14757
14838
  } catch {
14758
14839
  throw new CliError("usage_error", "This root does not have a bridge configuration yet.", {
@@ -14762,11 +14843,11 @@ async function assertBridgeConfigured(root) {
14762
14843
  }
14763
14844
  async function writeLaunchdPlan(root, startupDir, runnerPath) {
14764
14845
  const label = serviceLabel(root);
14765
- const servicePath = join4(startupDir, `${label}.plist`);
14766
- const installPath = join4(homedir(), "Library", "LaunchAgents", `${label}.plist`);
14767
- const outLog = join4(root, ".mdocs", "bridge.log");
14768
- const errLog = join4(root, ".mdocs", "bridge.err.log");
14769
- await writeFile4(servicePath, launchdPlist(label, root, runnerPath, outLog, errLog), "utf8");
14846
+ const servicePath = join5(startupDir, `${label}.plist`);
14847
+ const installPath = join5(homedir2(), "Library", "LaunchAgents", `${label}.plist`);
14848
+ const outLog = join5(root, ".mdocs", "bridge.log");
14849
+ const errLog = join5(root, ".mdocs", "bridge.err.log");
14850
+ await writeFile5(servicePath, launchdPlist(label, root, runnerPath, outLog, errLog), "utf8");
14770
14851
  const target = `gui/$(id -u)`;
14771
14852
  return {
14772
14853
  servicePath,
@@ -14780,7 +14861,7 @@ async function writeLaunchdPlan(root, startupDir, runnerPath) {
14780
14861
  `launchctl kickstart -k ${target}/${label}`
14781
14862
  ],
14782
14863
  install: async () => {
14783
- await mkdir4(dirname5(installPath), { recursive: true });
14864
+ await mkdir5(dirname5(installPath), { recursive: true });
14784
14865
  await copyFile(servicePath, installPath);
14785
14866
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
14786
14867
  if (uid === void 0) return;
@@ -14794,9 +14875,9 @@ async function writeLaunchdPlan(root, startupDir, runnerPath) {
14794
14875
  }
14795
14876
  async function writeSystemdPlan(root, startupDir, runnerPath) {
14796
14877
  const unitName = `${serviceLabel(root)}.service`;
14797
- const servicePath = join4(startupDir, unitName);
14798
- const installPath = join4(homedir(), ".config", "systemd", "user", unitName);
14799
- await writeFile4(servicePath, systemdUnit(unitName, root, runnerPath), "utf8");
14878
+ const servicePath = join5(startupDir, unitName);
14879
+ const installPath = join5(homedir2(), ".config", "systemd", "user", unitName);
14880
+ await writeFile5(servicePath, systemdUnit(unitName, root, runnerPath), "utf8");
14800
14881
  return {
14801
14882
  servicePath,
14802
14883
  installPath,
@@ -14807,7 +14888,7 @@ async function writeSystemdPlan(root, startupDir, runnerPath) {
14807
14888
  shellCommand(["systemctl", "--user", "enable", "--now", unitName])
14808
14889
  ],
14809
14890
  install: async () => {
14810
- await mkdir4(dirname5(installPath), { recursive: true });
14891
+ await mkdir5(dirname5(installPath), { recursive: true });
14811
14892
  await copyFile(servicePath, installPath);
14812
14893
  await runCommand("systemctl", ["--user", "daemon-reload"]);
14813
14894
  await runCommand("systemctl", ["--user", "enable", "--now", unitName]);
@@ -15080,7 +15161,7 @@ async function main() {
15080
15161
  root,
15081
15162
  mode: typeof parsed.flags.mode === "string" ? parsed.flags.mode : void 0,
15082
15163
  install: Boolean(parsed.flags.install),
15083
- requestToken: !parsed.flags["no-request-token"],
15164
+ requestToken: Boolean(parsed.flags["request-token"] || parsed.flags.pair),
15084
15165
  baseUrl: optionalBridgeBaseUrl(parsed.flags),
15085
15166
  intervalMs: typeof parsed.flags.interval === "string" ? Number(parsed.flags.interval) : void 0
15086
15167
  });
@@ -15225,7 +15306,7 @@ async function readRequiredTextFlag2(flags, cwd, names) {
15225
15306
  async function readOptionalTextFlag2(flags, cwd, names) {
15226
15307
  for (const name of names) {
15227
15308
  const fileValue = flags[`${name}-file`];
15228
- if (typeof fileValue === "string") return readFile7(resolve6(cwd, fileValue), "utf8");
15309
+ if (typeof fileValue === "string") return readFile8(resolve6(cwd, fileValue), "utf8");
15229
15310
  const value = flags[name];
15230
15311
  if (typeof value === "string") return value;
15231
15312
  }
@@ -15295,18 +15376,18 @@ Commands:
15295
15376
  remote events|history|restore Poll events, list commits, restore snapshots
15296
15377
  remote library|create-folder|update-folder|move-root|invite-folder
15297
15378
  Organize the joined project library
15298
- bridge setup --workspace <id> --root . --url <base-url> [--folder-id <id>] --request-token
15299
- Initialize, validate, request approval,
15379
+ bridge setup --workspace <id> --root . --url <base-url> [--folder-id <id>] --claim <claim-token>
15380
+ Initialize, validate, claim,
15300
15381
  and start an agent filesystem bridge
15301
- bridge resume --root . --forever --request-token [--once]
15382
+ bridge resume --root . --forever [--once]
15302
15383
  Backfill missed Magic changes, then keep
15303
15384
  the bridge running unless --once is set
15304
15385
  bridge startup|daemon --root . [--mode auto|shell|launchd|systemd] [--install]
15305
15386
  Write a bridge runner and startup hook
15306
15387
  for sandboxes, launchd, or systemd
15307
- bridge --workspace <id> --root . --url <base-url> --request-token
15308
- Request human approval, then sync an
15309
- approved local root with the workspace
15388
+ bridge --workspace <id> --root . --url <base-url> --claim <claim-token>
15389
+ Claim, then sync an approved local root
15390
+ with the workspace
15310
15391
  bridge --workspace <id> --root . --url <base-url> --token <bridge-token>
15311
15392
  Sync an approved local root with the workspace
15312
15393
  (or set MDOCS_BRIDGE_TOKEN)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magic-markdown/cli",
3
- "version": "0.3.18",
3
+ "version": "0.3.20",
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",