@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.
- package/dist/index.js +167 -88
- 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
|
|
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.
|
|
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,
|
|
11538
|
-
usage: "mdocs bridge setup --workspace <id> --root <path> --root-id <id> [--folder-id <id>] --url <base-url> --actor-name <agent-name> --
|
|
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 --
|
|
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
|
-
"--
|
|
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>] [--
|
|
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
|
|
11558
|
-
"mdocs bridge resume --root . --once
|
|
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
|
-
"
|
|
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;
|
|
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> --
|
|
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 --
|
|
11594
|
+
examples: ["mdocs bridge --workspace workspace_abc --root . --root-id root_abc --url https://magic.example.com --claim mdocsclaim_abc123"],
|
|
11593
11595
|
notes: [
|
|
11594
|
-
"--
|
|
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
|
|
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
|
-
--
|
|
11671
|
+
--claim <claim-token>
|
|
11669
11672
|
\`\`\`
|
|
11670
11673
|
|
|
11671
|
-
|
|
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
|
|
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;
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
14382
|
-
|
|
14383
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
14670
|
-
import { homedir, platform } from "node:os";
|
|
14671
|
-
import { dirname as dirname5, join as
|
|
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 =
|
|
14680
|
-
await
|
|
14681
|
-
const runnerPath =
|
|
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
|
|
14685
|
-
await
|
|
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
|
|
14706
|
-
"If
|
|
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
|
|
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
|
|
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 =
|
|
14834
|
+
const configPath = join5(root, ".mdocs", "bridge.json");
|
|
14756
14835
|
try {
|
|
14757
|
-
const config2 = JSON.parse(await
|
|
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 =
|
|
14768
|
-
const installPath =
|
|
14769
|
-
const outLog =
|
|
14770
|
-
const errLog =
|
|
14771
|
-
await
|
|
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
|
|
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 =
|
|
14800
|
-
const installPath =
|
|
14801
|
-
await
|
|
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
|
|
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:
|
|
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
|
|
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>] --
|
|
15301
|
-
Initialize, validate,
|
|
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
|
|
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> --
|
|
15310
|
-
|
|
15311
|
-
|
|
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