@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.
- package/dist/index.js +169 -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,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
|
|
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
|
-
--
|
|
11671
|
+
--claim <claim-token>
|
|
11667
11672
|
\`\`\`
|
|
11668
11673
|
|
|
11669
|
-
|
|
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
|
|
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;
|
|
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 {
|
|
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
|
|
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
|
-
|
|
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
|
|
14375
|
-
|
|
14376
|
-
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
|
|
14380
|
-
|
|
14381
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
14668
|
-
import { homedir, platform } from "node:os";
|
|
14669
|
-
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";
|
|
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 =
|
|
14678
|
-
await
|
|
14679
|
-
const runnerPath =
|
|
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
|
|
14683
|
-
await
|
|
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
|
|
14704
|
-
"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."
|
|
14705
14786
|
]
|
|
14706
14787
|
};
|
|
14707
|
-
await
|
|
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
|
|
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 =
|
|
14834
|
+
const configPath = join5(root, ".mdocs", "bridge.json");
|
|
14754
14835
|
try {
|
|
14755
|
-
const config2 = JSON.parse(await
|
|
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 =
|
|
14766
|
-
const installPath =
|
|
14767
|
-
const outLog =
|
|
14768
|
-
const errLog =
|
|
14769
|
-
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");
|
|
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
|
|
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 =
|
|
14798
|
-
const installPath =
|
|
14799
|
-
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");
|
|
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
|
|
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:
|
|
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
|
|
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>] --
|
|
15299
|
-
Initialize, validate,
|
|
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
|
|
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> --
|
|
15308
|
-
|
|
15309
|
-
|
|
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