@magic-markdown/cli 0.3.19 → 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 +167 -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.19";
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,7 @@ 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 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.
11658
11661
 
11659
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.
11660
11663
 
@@ -11665,18 +11668,18 @@ npx --yes --package=@magic-markdown/cli@latest mdocs bridge setup \\
11665
11668
  --root-id <root-id> \\
11666
11669
  --url <magic-url> \\
11667
11670
  --actor-name "<agent name>" \\
11668
- --request-token
11671
+ --claim <claim-token>
11669
11672
  \`\`\`
11670
11673
 
11671
- 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 ...\`.
11672
11675
 
11673
- 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:
11674
11677
 
11675
11678
  \`\`\`bash
11676
- 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
11677
11680
  \`\`\`
11678
11681
 
11679
- \`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.
11680
11683
 
11681
11684
  For sandboxes, VPSs, or local machines that support startup hooks, generate a durable runner after setup:
11682
11685
 
@@ -11684,7 +11687,7 @@ For sandboxes, VPSs, or local machines that support startup hooks, generate a du
11684
11687
  npx --yes --package=@magic-markdown/cli@latest mdocs bridge startup --root . --json
11685
11688
  \`\`\`
11686
11689
 
11687
- 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.
11688
11691
 
11689
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.
11690
11693
 
@@ -13777,10 +13780,13 @@ function optionalFolderTarget(value) {
13777
13780
 
13778
13781
  // src/bridge.ts
13779
13782
  import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
13780
- 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";
13781
13786
  var BRIDGE_CONFIG_PATH = ".mdocs/bridge.json";
13782
13787
  var BRIDGE_STATUS_PATH = ".mdocs/bridge-status.json";
13783
13788
  var BRIDGE_HEARTBEAT_INTERVAL_MS = 1e4;
13789
+ var BRIDGE_TOKEN_STORE_PATH = join4(homedir(), ".mdocs", "bridge-tokens.json");
13784
13790
  function bridgeSetupIdentity(input) {
13785
13791
  const actorName = input.actorName?.trim() || "Agent";
13786
13792
  return {
@@ -13838,7 +13844,7 @@ async function runBridgeResume(options) {
13838
13844
  baseUrl,
13839
13845
  intervalMs: options.intervalMs ?? config2?.intervalMs ?? 1e3,
13840
13846
  token: options.token,
13841
- requestToken: options.requestToken || !options.token,
13847
+ requestToken: Boolean(options.requestToken),
13842
13848
  pairingTimeoutMs: options.pairingTimeoutMs,
13843
13849
  once: options.once,
13844
13850
  resume: true
@@ -13878,10 +13884,18 @@ async function runBridge(options) {
13878
13884
  let lastHeartbeatSentMs = 0;
13879
13885
  let lastError;
13880
13886
  await writeCurrentBridgeStatus("starting");
13881
- let token = options.token;
13882
- 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)) {
13883
13895
  try {
13884
- 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);
13885
13899
  } catch (error) {
13886
13900
  lastError = errorMessage(error);
13887
13901
  await writeCurrentBridgeStatus("error");
@@ -14373,46 +14387,34 @@ async function runBridge(options) {
14373
14387
  }
14374
14388
  }
14375
14389
  async function requestBridgeToken(options, rootId) {
14376
- const response = await fetch(new URL("/api/bridge-requests", options.baseUrl), {
14377
- method: "POST",
14378
- headers: { "Content-Type": "application/json" },
14379
- body: JSON.stringify({
14380
- workspaceId: options.workspaceId,
14381
- rootId,
14382
- actorId: options.actorId,
14383
- actorName: options.actorName,
14384
- folderId: options.folderId,
14385
- sourceName: options.sourceName,
14386
- role: "edit"
14387
- })
14388
- }).catch((error) => {
14389
- throw new Error(`Failed to create bridge approval request: ${error instanceof Error ? error.message : String(error)}`);
14390
- });
14391
- if (!response.ok) {
14392
- throw new Error(`Failed to create bridge approval request: ${response.status} ${await response.text()}`);
14393
- }
14394
- const created = await response.json();
14395
- if (!created.requestId || !created.pollToken || !created.approveUrl) {
14396
- throw new Error("Bridge approval request response was missing requestId, pollToken, or approveUrl.");
14397
- }
14398
- 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}
14399
14398
  `);
14400
- 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}.
14401
14400
  `);
14401
+ }
14402
14402
  const deadline = Date.now() + (options.pairingTimeoutMs ?? 1e3 * 60 * 15);
14403
+ let firstPoll = true;
14403
14404
  while (Date.now() < deadline) {
14404
- await delay(2e3);
14405
+ if (firstPoll) firstPoll = false;
14406
+ else await delay(2e3);
14405
14407
  const pollResponse = await fetch(new URL(`/api/bridge-requests/${encodeURIComponent(created.requestId)}/token`, options.baseUrl), {
14406
14408
  headers: { Authorization: `Bearer ${created.pollToken}` }
14407
14409
  }).catch((error) => {
14408
- 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)}`);
14409
14411
  });
14410
14412
  const payload = await pollResponse.json().catch(() => ({}));
14411
14413
  if (pollResponse.status === 202) continue;
14412
14414
  if (pollResponse.ok && payload.token) {
14413
- 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}` : ""}.
14414
14416
  `);
14415
- return payload.token;
14417
+ return { token: payload.token, expiresAt: payload.expiresAt };
14416
14418
  }
14417
14419
  if (pollResponse.status === 409 && payload.status === "rejected") {
14418
14420
  throw new Error("Bridge approval request was rejected.");
@@ -14420,9 +14422,43 @@ async function requestBridgeToken(options, rootId) {
14420
14422
  if (pollResponse.status === 410 || payload.status === "expired") {
14421
14423
  throw new Error("Bridge approval request expired before it was approved.");
14422
14424
  }
14423
- 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()}`);
14424
14439
  }
14425
- throw new Error("Bridge approval timed out before a token was issued.");
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()}`);
14460
+ }
14461
+ return await response.json();
14426
14462
  }
14427
14463
  async function readBridgeConfig(root) {
14428
14464
  const io = new NodeWorkspaceIO(root);
@@ -14448,6 +14484,49 @@ async function writeBridgeStatus(root, status) {
14448
14484
  await io.writeTextAtomic(BRIDGE_STATUS_PATH, `${JSON.stringify(status, null, 2)}
14449
14485
  `);
14450
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
+ }
14451
14530
  async function fetchCanonicalSnapshot(options, rootId) {
14452
14531
  const response = await fetch(new URL(`/api/workspaces/${encodeURIComponent(options.workspaceId)}/roots/${encodeURIComponent(rootId)}/sync`, options.baseUrl), {
14453
14532
  headers: authHeaders(options.token)
@@ -14666,9 +14745,9 @@ function readDocumentPayload(payload) {
14666
14745
  // src/bridge-startup.ts
14667
14746
  import { execFile as execFile2 } from "node:child_process";
14668
14747
  import { createHash as createHash3 } from "node:crypto";
14669
- import { chmod, copyFile, mkdir as mkdir4, readFile as readFile6, stat as stat3, writeFile as writeFile4 } from "node:fs/promises";
14670
- import { homedir, platform } from "node:os";
14671
- 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";
14672
14751
  import { promisify as promisify2 } from "node:util";
14673
14752
  var execFileAsync2 = promisify2(execFile2);
14674
14753
  async function runBridgeStartup(options) {
@@ -14676,13 +14755,13 @@ async function runBridgeStartup(options) {
14676
14755
  await assertBridgeConfigured(root);
14677
14756
  const requestedMode = readBridgeStartupMode(options.mode);
14678
14757
  const mode = requestedMode === "auto" ? await autoBridgeStartupMode() : requestedMode;
14679
- const startupDir = join4(root, ".mdocs", "startup");
14680
- await mkdir4(startupDir, { recursive: true });
14681
- 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");
14682
14761
  const runnerArgs = bridgeResumeArgs(root, options);
14683
14762
  const runnerScript = bridgeRunnerScript(root, runnerArgs);
14684
- await writeFile4(runnerPath, runnerScript, "utf8");
14685
- await chmod(runnerPath, 493);
14763
+ await writeFile5(runnerPath, runnerScript, "utf8");
14764
+ await chmod2(runnerPath, 493);
14686
14765
  const nativePlan = mode === "launchd" ? await writeLaunchdPlan(root, startupDir, runnerPath) : mode === "systemd" ? await writeSystemdPlan(root, startupDir, runnerPath) : void 0;
14687
14766
  if (options.install && !nativePlan) {
14688
14767
  throw new CliError("usage_error", "Shell startup mode does not have a native service to install.", {
@@ -14702,11 +14781,11 @@ async function runBridgeStartup(options) {
14702
14781
  installCommands: nativePlan?.installCommands ?? [],
14703
14782
  notes: [
14704
14783
  "Sandbox providers such as Daytona, Sprites, and Modal should run startupCommand from their on-start hook.",
14705
- "The runner stores no bridge token. If you use manual tokens, provide MDOCS_BRIDGE_TOKEN through the platform secret environment.",
14706
- "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."
14707
14786
  ]
14708
14787
  };
14709
- 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)}
14710
14789
  `, "utf8");
14711
14790
  return result;
14712
14791
  }
@@ -14733,7 +14812,7 @@ exec npx --yes --package=@magic-markdown/cli@latest mdocs ${shellCommand(args)}
14733
14812
  }
14734
14813
  function bridgeResumeArgs(root, options) {
14735
14814
  const args = ["bridge", "resume", "--root", root, "--forever"];
14736
- if (options.requestToken !== false) args.push("--request-token");
14815
+ if (options.requestToken === true) args.push("--request-token");
14737
14816
  if (options.baseUrl) args.push("--url", options.baseUrl);
14738
14817
  if (options.intervalMs) args.push("--interval", String(options.intervalMs));
14739
14818
  return args;
@@ -14752,9 +14831,9 @@ async function autoBridgeStartupMode() {
14752
14831
  return "shell";
14753
14832
  }
14754
14833
  async function assertBridgeConfigured(root) {
14755
- const configPath = join4(root, ".mdocs", "bridge.json");
14834
+ const configPath = join5(root, ".mdocs", "bridge.json");
14756
14835
  try {
14757
- const config2 = JSON.parse(await readFile6(configPath, "utf8"));
14836
+ const config2 = JSON.parse(await readFile7(configPath, "utf8"));
14758
14837
  if (config2.schemaVersion !== 1) throw new Error("invalid schema");
14759
14838
  } catch {
14760
14839
  throw new CliError("usage_error", "This root does not have a bridge configuration yet.", {
@@ -14764,11 +14843,11 @@ async function assertBridgeConfigured(root) {
14764
14843
  }
14765
14844
  async function writeLaunchdPlan(root, startupDir, runnerPath) {
14766
14845
  const label = serviceLabel(root);
14767
- const servicePath = join4(startupDir, `${label}.plist`);
14768
- const installPath = join4(homedir(), "Library", "LaunchAgents", `${label}.plist`);
14769
- const outLog = join4(root, ".mdocs", "bridge.log");
14770
- const errLog = join4(root, ".mdocs", "bridge.err.log");
14771
- 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");
14772
14851
  const target = `gui/$(id -u)`;
14773
14852
  return {
14774
14853
  servicePath,
@@ -14782,7 +14861,7 @@ async function writeLaunchdPlan(root, startupDir, runnerPath) {
14782
14861
  `launchctl kickstart -k ${target}/${label}`
14783
14862
  ],
14784
14863
  install: async () => {
14785
- await mkdir4(dirname5(installPath), { recursive: true });
14864
+ await mkdir5(dirname5(installPath), { recursive: true });
14786
14865
  await copyFile(servicePath, installPath);
14787
14866
  const uid = typeof process.getuid === "function" ? process.getuid() : void 0;
14788
14867
  if (uid === void 0) return;
@@ -14796,9 +14875,9 @@ async function writeLaunchdPlan(root, startupDir, runnerPath) {
14796
14875
  }
14797
14876
  async function writeSystemdPlan(root, startupDir, runnerPath) {
14798
14877
  const unitName = `${serviceLabel(root)}.service`;
14799
- const servicePath = join4(startupDir, unitName);
14800
- const installPath = join4(homedir(), ".config", "systemd", "user", unitName);
14801
- 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");
14802
14881
  return {
14803
14882
  servicePath,
14804
14883
  installPath,
@@ -14809,7 +14888,7 @@ async function writeSystemdPlan(root, startupDir, runnerPath) {
14809
14888
  shellCommand(["systemctl", "--user", "enable", "--now", unitName])
14810
14889
  ],
14811
14890
  install: async () => {
14812
- await mkdir4(dirname5(installPath), { recursive: true });
14891
+ await mkdir5(dirname5(installPath), { recursive: true });
14813
14892
  await copyFile(servicePath, installPath);
14814
14893
  await runCommand("systemctl", ["--user", "daemon-reload"]);
14815
14894
  await runCommand("systemctl", ["--user", "enable", "--now", unitName]);
@@ -15082,7 +15161,7 @@ async function main() {
15082
15161
  root,
15083
15162
  mode: typeof parsed.flags.mode === "string" ? parsed.flags.mode : void 0,
15084
15163
  install: Boolean(parsed.flags.install),
15085
- requestToken: !parsed.flags["no-request-token"],
15164
+ requestToken: Boolean(parsed.flags["request-token"] || parsed.flags.pair),
15086
15165
  baseUrl: optionalBridgeBaseUrl(parsed.flags),
15087
15166
  intervalMs: typeof parsed.flags.interval === "string" ? Number(parsed.flags.interval) : void 0
15088
15167
  });
@@ -15227,7 +15306,7 @@ async function readRequiredTextFlag2(flags, cwd, names) {
15227
15306
  async function readOptionalTextFlag2(flags, cwd, names) {
15228
15307
  for (const name of names) {
15229
15308
  const fileValue = flags[`${name}-file`];
15230
- if (typeof fileValue === "string") return readFile7(resolve6(cwd, fileValue), "utf8");
15309
+ if (typeof fileValue === "string") return readFile8(resolve6(cwd, fileValue), "utf8");
15231
15310
  const value = flags[name];
15232
15311
  if (typeof value === "string") return value;
15233
15312
  }
@@ -15297,18 +15376,18 @@ Commands:
15297
15376
  remote events|history|restore Poll events, list commits, restore snapshots
15298
15377
  remote library|create-folder|update-folder|move-root|invite-folder
15299
15378
  Organize the joined project library
15300
- bridge setup --workspace <id> --root . --url <base-url> [--folder-id <id>] --request-token
15301
- Initialize, validate, request approval,
15379
+ bridge setup --workspace <id> --root . --url <base-url> [--folder-id <id>] --claim <claim-token>
15380
+ Initialize, validate, claim,
15302
15381
  and start an agent filesystem bridge
15303
- bridge resume --root . --forever --request-token [--once]
15382
+ bridge resume --root . --forever [--once]
15304
15383
  Backfill missed Magic changes, then keep
15305
15384
  the bridge running unless --once is set
15306
15385
  bridge startup|daemon --root . [--mode auto|shell|launchd|systemd] [--install]
15307
15386
  Write a bridge runner and startup hook
15308
15387
  for sandboxes, launchd, or systemd
15309
- bridge --workspace <id> --root . --url <base-url> --request-token
15310
- Request human approval, then sync an
15311
- 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
15312
15391
  bridge --workspace <id> --root . --url <base-url> --token <bridge-token>
15313
15392
  Sync an approved local root with the workspace
15314
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.19",
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",