@magic-markdown/cli 0.3.11 → 0.3.12
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 +327 -13
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10997,7 +10997,7 @@ var RemoteDocumentIO = class {
|
|
|
10997
10997
|
};
|
|
10998
10998
|
|
|
10999
10999
|
// src/agent.ts
|
|
11000
|
-
var CLI_VERSION = "0.3.
|
|
11000
|
+
var CLI_VERSION = "0.3.12";
|
|
11001
11001
|
var CLI_PACKAGE_NAME = "@magic-markdown/cli";
|
|
11002
11002
|
var AGENT_COMMANDS = [
|
|
11003
11003
|
{
|
|
@@ -11279,6 +11279,23 @@ var AGENT_COMMANDS = [
|
|
|
11279
11279
|
"--token (or MDOCS_BRIDGE_TOKEN) still works when a scoped bridge token is already available."
|
|
11280
11280
|
]
|
|
11281
11281
|
},
|
|
11282
|
+
{
|
|
11283
|
+
name: "bridge resume",
|
|
11284
|
+
summary: "Backfill missed Magic changes after an agent restart, then keep the bridge running.",
|
|
11285
|
+
usage: "mdocs bridge resume --root <path> [--url <base-url>] [--workspace <id>] [--root-id <id>] [--request-token] [--once]",
|
|
11286
|
+
output: "long-running",
|
|
11287
|
+
mutates: true,
|
|
11288
|
+
examples: [
|
|
11289
|
+
"mdocs bridge resume --root . --request-token",
|
|
11290
|
+
"mdocs bridge resume --root . --once --request-token"
|
|
11291
|
+
],
|
|
11292
|
+
notes: [
|
|
11293
|
+
"Reads non-secret connection defaults from .mdocs/bridge.json written by bridge setup.",
|
|
11294
|
+
"Use this after a sandbox/container/agent session restarts. It backfills canonical Magic changes before publishing local edits.",
|
|
11295
|
+
"--request-token opens a human approval URL when no MDOCS_BRIDGE_TOKEN is available; do not ask users to paste bridge tokens by default.",
|
|
11296
|
+
"--once performs only the backfill/reconcile step and exits."
|
|
11297
|
+
]
|
|
11298
|
+
},
|
|
11282
11299
|
{
|
|
11283
11300
|
name: "bridge",
|
|
11284
11301
|
summary: "Sync a local Markdown root with a Magic workspace over WebSocket (long-running).",
|
|
@@ -11347,6 +11364,32 @@ Run \`mdocs help <command>\` (or \`mdocs <command> --help\`) for usage, examples
|
|
|
11347
11364
|
5. For a document, run \`mdocs context <path|docId> --summary --json\` locally or \`mdocs remote context <path|docId> --summary --json\` for a joined share before reading full content. Then page Markdown with \`--start-line\` / \`--end-line\` and \`--no-review\` when you only need document text.
|
|
11348
11365
|
6. Pull review state separately with \`mdocs review <path|docId> --json\` locally or \`mdocs remote review <path|docId> --json\` for a joined share. Use \`mdocs comment\` / \`mdocs remote comment\` for review notes and \`mdocs suggest\` / \`mdocs remote suggest\` for proposed replacements. Do not insert comments, CriticMarkup, directives, or Magic markers into Markdown files.
|
|
11349
11366
|
|
|
11367
|
+
## Filesystem Bridge / Resume
|
|
11368
|
+
|
|
11369
|
+
When a human asks you to bind an editable local Markdown filesystem to Magic Markdown, use the latest official package and the human-approval token flow:
|
|
11370
|
+
|
|
11371
|
+
\`\`\`bash
|
|
11372
|
+
npx --yes --package=@magic-markdown/cli@latest mdocs bridge setup \\
|
|
11373
|
+
--root . \\
|
|
11374
|
+
--workspace <workspace-id> \\
|
|
11375
|
+
--root-id <root-id> \\
|
|
11376
|
+
--url <magic-url> \\
|
|
11377
|
+
--actor-name "<agent name>" \\
|
|
11378
|
+
--request-token
|
|
11379
|
+
\`\`\`
|
|
11380
|
+
|
|
11381
|
+
Return the Magic approval link and keep the bridge process running after approval. Success looks like \`mdocs bridge connected ...\`.
|
|
11382
|
+
|
|
11383
|
+
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:
|
|
11384
|
+
|
|
11385
|
+
\`\`\`bash
|
|
11386
|
+
npx --yes --package=@magic-markdown/cli@latest mdocs bridge resume --root . --request-token
|
|
11387
|
+
\`\`\`
|
|
11388
|
+
|
|
11389
|
+
\`bridge resume\` reads non-secret defaults from \`.mdocs/bridge.json\`, backfills missed Magic changes before publishing local edits, and then keeps polling/watching. Use \`--once\` only when the user asked for a one-shot backfill rather than a live bridge.
|
|
11390
|
+
|
|
11391
|
+
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.
|
|
11392
|
+
|
|
11350
11393
|
## Editing Rules
|
|
11351
11394
|
|
|
11352
11395
|
- Comments and suggestions are sidecar operations. They should not modify clean Markdown until a suggestion is accepted.
|
|
@@ -13402,6 +13445,7 @@ function optionalFolderTarget(value) {
|
|
|
13402
13445
|
// src/bridge.ts
|
|
13403
13446
|
import { createHash as createHash2, randomUUID as randomUUID3 } from "node:crypto";
|
|
13404
13447
|
import { basename as basename2, resolve as resolve4 } from "node:path";
|
|
13448
|
+
var BRIDGE_CONFIG_PATH = ".mdocs/bridge.json";
|
|
13405
13449
|
function bridgeSetupIdentity(input) {
|
|
13406
13450
|
const actorName = input.actorName?.trim() || "Agent";
|
|
13407
13451
|
return {
|
|
@@ -13426,7 +13470,43 @@ async function runBridgeSetup(options) {
|
|
|
13426
13470
|
actorId: identity.actorId,
|
|
13427
13471
|
actorName: identity.actorName,
|
|
13428
13472
|
sourceName: identity.sourceName,
|
|
13429
|
-
requestToken: options.requestToken || !options.token
|
|
13473
|
+
requestToken: options.requestToken || !options.token,
|
|
13474
|
+
resume: true
|
|
13475
|
+
});
|
|
13476
|
+
}
|
|
13477
|
+
async function runBridgeResume(options) {
|
|
13478
|
+
const root = resolve4(options.root);
|
|
13479
|
+
const config2 = await readBridgeConfig(root);
|
|
13480
|
+
const identity = bridgeSetupIdentity({
|
|
13481
|
+
actorId: options.actorId ?? config2?.actorId,
|
|
13482
|
+
actorName: options.actorName ?? config2?.actorName,
|
|
13483
|
+
sourceName: options.sourceName ?? config2?.sourceName
|
|
13484
|
+
});
|
|
13485
|
+
const workspaceId = options.workspaceId ?? config2?.workspaceId;
|
|
13486
|
+
const rootId = options.rootId ?? options.sourceId ?? config2?.rootId;
|
|
13487
|
+
const baseUrl = options.baseUrl ?? config2?.baseUrl;
|
|
13488
|
+
if (!workspaceId || !rootId || !baseUrl) {
|
|
13489
|
+
throw new Error("Missing bridge resume configuration. Run mdocs bridge setup --workspace <id> --root <path> --root-id <id> --url <base-url> first.");
|
|
13490
|
+
}
|
|
13491
|
+
await runBridge({
|
|
13492
|
+
root,
|
|
13493
|
+
workspaceId,
|
|
13494
|
+
rootId,
|
|
13495
|
+
sourceId: options.sourceId,
|
|
13496
|
+
sourceName: identity.sourceName,
|
|
13497
|
+
folderId: options.folderId ?? config2?.folderId,
|
|
13498
|
+
canonicalPrefix: options.canonicalPrefix ?? config2?.canonicalPrefix,
|
|
13499
|
+
replicaPrefix: options.replicaPrefix ?? config2?.replicaPrefix,
|
|
13500
|
+
claimToken: options.claimToken,
|
|
13501
|
+
actorId: identity.actorId,
|
|
13502
|
+
actorName: identity.actorName,
|
|
13503
|
+
baseUrl,
|
|
13504
|
+
intervalMs: options.intervalMs ?? config2?.intervalMs ?? 1e3,
|
|
13505
|
+
token: options.token,
|
|
13506
|
+
requestToken: options.requestToken || !options.token,
|
|
13507
|
+
pairingTimeoutMs: options.pairingTimeoutMs,
|
|
13508
|
+
once: options.once,
|
|
13509
|
+
resume: true
|
|
13430
13510
|
});
|
|
13431
13511
|
}
|
|
13432
13512
|
async function runBridge(options) {
|
|
@@ -13459,8 +13539,24 @@ async function runBridge(options) {
|
|
|
13459
13539
|
const registeredRoot = token ? await fetchScopedRoot({ ...options, token }, rootId) : await registerRoot(options, root, rootId, mapping);
|
|
13460
13540
|
lastAppliedHead = lastAppliedHead ?? registeredRoot?.canonical.head;
|
|
13461
13541
|
await writeSourceState(lastAppliedHead);
|
|
13542
|
+
await writeBridgeConfig(root, {
|
|
13543
|
+
schemaVersion: 1,
|
|
13544
|
+
workspaceId: options.workspaceId,
|
|
13545
|
+
rootId,
|
|
13546
|
+
baseUrl: options.baseUrl,
|
|
13547
|
+
actorId: options.actorId,
|
|
13548
|
+
actorName: options.actorName,
|
|
13549
|
+
sourceName: options.sourceName,
|
|
13550
|
+
folderId: options.folderId,
|
|
13551
|
+
canonicalPrefix: options.canonicalPrefix,
|
|
13552
|
+
replicaPrefix: options.replicaPrefix,
|
|
13553
|
+
intervalMs: options.intervalMs,
|
|
13554
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13555
|
+
});
|
|
13556
|
+
const backfilled = await resumeFromCanonical();
|
|
13557
|
+
if (options.once) return;
|
|
13462
13558
|
connect();
|
|
13463
|
-
if (claimMode) await primeLocalSignatures();
|
|
13559
|
+
if (claimMode && !backfilled) await primeLocalSignatures();
|
|
13464
13560
|
else await publishSnapshot("initial");
|
|
13465
13561
|
const timer = setInterval(() => {
|
|
13466
13562
|
void publishSnapshot("poll").catch((error) => {
|
|
@@ -13536,6 +13632,87 @@ async function runBridge(options) {
|
|
|
13536
13632
|
}
|
|
13537
13633
|
}
|
|
13538
13634
|
}
|
|
13635
|
+
async function resumeFromCanonical() {
|
|
13636
|
+
if (!options.resume && !token) return false;
|
|
13637
|
+
const remote = await fetchCanonicalSnapshot({ ...options, token }, rootId).catch(() => void 0);
|
|
13638
|
+
if (!remote) return false;
|
|
13639
|
+
const baseDocs = lastAppliedHead ? await fetchCommitDocuments({ ...options, token }, rootId, lastAppliedHead).catch(() => /* @__PURE__ */ new Map()) : /* @__PURE__ */ new Map();
|
|
13640
|
+
const remoteDocIds = new Set(remote.docs.map((doc) => doc.docId));
|
|
13641
|
+
let applied = 0;
|
|
13642
|
+
let keptLocal = 0;
|
|
13643
|
+
let conflicted = 0;
|
|
13644
|
+
for (const doc of remote.docs) {
|
|
13645
|
+
const base = baseDocs.get(doc.docId);
|
|
13646
|
+
const decision = await reconcileRemoteDocument(doc, base);
|
|
13647
|
+
if (decision === "applied") applied += 1;
|
|
13648
|
+
if (decision === "kept_local") keptLocal += 1;
|
|
13649
|
+
if (decision === "conflict") conflicted += 1;
|
|
13650
|
+
}
|
|
13651
|
+
for (const [docId, base] of baseDocs) {
|
|
13652
|
+
if (remoteDocIds.has(docId)) continue;
|
|
13653
|
+
const decision = await reconcileRemoteDelete(docId, base);
|
|
13654
|
+
if (decision === "applied") applied += 1;
|
|
13655
|
+
if (decision === "conflict") conflicted += 1;
|
|
13656
|
+
}
|
|
13657
|
+
lastAppliedHead = remote.head ?? lastAppliedHead;
|
|
13658
|
+
await writeSourceState(lastAppliedHead);
|
|
13659
|
+
process.stdout.write(
|
|
13660
|
+
`mdocs bridge resume ${root}: applied ${applied}, kept local ${keptLocal}, conflicts ${conflicted}, head ${lastAppliedHead ?? "unknown"}
|
|
13661
|
+
`
|
|
13662
|
+
);
|
|
13663
|
+
return true;
|
|
13664
|
+
}
|
|
13665
|
+
async function reconcileRemoteDocument(remote, base) {
|
|
13666
|
+
const remoteSignature = documentSignature(remote.markdown, remote.sidecar);
|
|
13667
|
+
const baseSignature = base ? documentSignature(base.markdown, base.sidecar) : void 0;
|
|
13668
|
+
const localState = await localStateForCanonical(remote);
|
|
13669
|
+
const localSignature = localState ? documentSignature(localState.markdown, localState.sidecar) : void 0;
|
|
13670
|
+
if (!localState) {
|
|
13671
|
+
await applyCanonicalDocument(remote);
|
|
13672
|
+
return "applied";
|
|
13673
|
+
}
|
|
13674
|
+
if (localSignature === remoteSignature && localState.path === remote.path && localState.docId === remote.docId) {
|
|
13675
|
+
signatures.set(remote.docId, remoteSignature);
|
|
13676
|
+
seenDocs.add(remote.docId);
|
|
13677
|
+
return "noop";
|
|
13678
|
+
}
|
|
13679
|
+
if (localSignature === remoteSignature && localState.path === remote.path) {
|
|
13680
|
+
await applyCanonicalDocument(remote);
|
|
13681
|
+
return "applied";
|
|
13682
|
+
}
|
|
13683
|
+
if (baseSignature && localSignature === baseSignature) {
|
|
13684
|
+
await applyCanonicalDocument(remote);
|
|
13685
|
+
return "applied";
|
|
13686
|
+
}
|
|
13687
|
+
if (baseSignature && remoteSignature === baseSignature) {
|
|
13688
|
+
signatures.set(remote.docId, baseSignature);
|
|
13689
|
+
seenDocs.add(remote.docId);
|
|
13690
|
+
return "kept_local";
|
|
13691
|
+
}
|
|
13692
|
+
signatures.set(remote.docId, baseSignature ?? remoteSignature);
|
|
13693
|
+
await applyCanonicalDocument(remote);
|
|
13694
|
+
return "conflict";
|
|
13695
|
+
}
|
|
13696
|
+
async function reconcileRemoteDelete(docId, base) {
|
|
13697
|
+
const localState = await getDocumentState(io, docId).catch(() => void 0);
|
|
13698
|
+
if (!localState) return "noop";
|
|
13699
|
+
const baseSignature = documentSignature(base.markdown, base.sidecar);
|
|
13700
|
+
const localSignature = documentSignature(localState.markdown, localState.sidecar);
|
|
13701
|
+
if (localSignature !== baseSignature) {
|
|
13702
|
+
const copyPath = conflictCopyPath(localState.path);
|
|
13703
|
+
await io.writeTextAtomic(copyPath, localState.markdown);
|
|
13704
|
+
await seedConflictCopySidecar(copyPath, localState);
|
|
13705
|
+
process.stderr.write(`local conflict ${localState.path}; local version saved as ${copyPath}, canonical delete applied
|
|
13706
|
+
`);
|
|
13707
|
+
}
|
|
13708
|
+
await io.deleteFile(localState.path).catch(() => void 0);
|
|
13709
|
+
await io.deleteFile(sidecarPath(docId)).catch(() => void 0);
|
|
13710
|
+
await removeManifestDocument(docId, localState.path);
|
|
13711
|
+
signatures.delete(docId);
|
|
13712
|
+
seenDocs.delete(docId);
|
|
13713
|
+
pendingDeletes.delete(docId);
|
|
13714
|
+
return localSignature === baseSignature ? "applied" : "conflict";
|
|
13715
|
+
}
|
|
13539
13716
|
async function handleIncoming(message) {
|
|
13540
13717
|
if (message.type === "file-changed") {
|
|
13541
13718
|
await applyCanonicalDocument(readDocumentPayload(message.payload));
|
|
@@ -13561,7 +13738,7 @@ async function runBridge(options) {
|
|
|
13561
13738
|
`);
|
|
13562
13739
|
if (!payload.docId) return;
|
|
13563
13740
|
pendingDocs.delete(payload.docId);
|
|
13564
|
-
const canonical = await
|
|
13741
|
+
const canonical = await fetchCanonicalDocument2(payload.docId);
|
|
13565
13742
|
if (canonical) await applyCanonicalDocument(canonical);
|
|
13566
13743
|
}
|
|
13567
13744
|
if (message.type === "sync-error") {
|
|
@@ -13576,7 +13753,7 @@ async function runBridge(options) {
|
|
|
13576
13753
|
}
|
|
13577
13754
|
async function applyCanonicalDocument(payload) {
|
|
13578
13755
|
const remoteSignature = documentSignature(payload.markdown, payload.sidecar);
|
|
13579
|
-
const localState = await
|
|
13756
|
+
const localState = await localStateForCanonical(payload);
|
|
13580
13757
|
const localSignature = localState ? documentSignature(localState.markdown, localState.sidecar) : void 0;
|
|
13581
13758
|
if (localSignature && signatures.has(payload.docId) && localSignature !== signatures.get(payload.docId) && localSignature !== remoteSignature) {
|
|
13582
13759
|
const copyPath = conflictCopyPath(payload.path);
|
|
@@ -13598,9 +13775,14 @@ async function runBridge(options) {
|
|
|
13598
13775
|
if (localState && localState.path !== payload.path) {
|
|
13599
13776
|
await io.deleteFile(localState.path).catch(() => void 0);
|
|
13600
13777
|
}
|
|
13778
|
+
if (localState && localState.docId !== payload.docId) {
|
|
13779
|
+
await io.deleteFile(sidecarPath(localState.docId)).catch(() => void 0);
|
|
13780
|
+
await removeManifestDocument(localState.docId, localState.path);
|
|
13781
|
+
}
|
|
13601
13782
|
await io.writeTextAtomic(payload.path, payload.markdown);
|
|
13602
13783
|
await io.writeTextAtomic(sidecarPath(payload.docId), `${JSON.stringify(payload.sidecar, null, 2)}
|
|
13603
13784
|
`);
|
|
13785
|
+
await upsertManifestDocument(payload);
|
|
13604
13786
|
signatures.set(payload.docId, remoteSignature);
|
|
13605
13787
|
deniedSignatures.delete(payload.docId);
|
|
13606
13788
|
seenDocs.add(payload.docId);
|
|
@@ -13636,7 +13818,14 @@ async function runBridge(options) {
|
|
|
13636
13818
|
updatedAt: now
|
|
13637
13819
|
});
|
|
13638
13820
|
}
|
|
13639
|
-
async function
|
|
13821
|
+
async function localStateForCanonical(payload) {
|
|
13822
|
+
const byDocId = await getDocumentState(io, payload.docId).catch(() => void 0);
|
|
13823
|
+
if (byDocId) return byDocId;
|
|
13824
|
+
const map = await getWorkspaceMap(io).catch(() => void 0);
|
|
13825
|
+
const byPath = map?.docs.find((doc) => doc.path === payload.path);
|
|
13826
|
+
return byPath ? getDocumentState(io, byPath.docId).catch(() => void 0) : void 0;
|
|
13827
|
+
}
|
|
13828
|
+
async function fetchCanonicalDocument2(docId) {
|
|
13640
13829
|
try {
|
|
13641
13830
|
const response = await fetch(
|
|
13642
13831
|
new URL(
|
|
@@ -13701,6 +13890,34 @@ async function runBridge(options) {
|
|
|
13701
13890
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13702
13891
|
});
|
|
13703
13892
|
}
|
|
13893
|
+
async function upsertManifestDocument(payload) {
|
|
13894
|
+
const manifest = await readManifest(io).catch(() => void 0);
|
|
13895
|
+
if (!manifest) return;
|
|
13896
|
+
await writeManifest(io, {
|
|
13897
|
+
...manifest,
|
|
13898
|
+
docs: [
|
|
13899
|
+
...manifest.docs.filter((doc) => doc.docId !== payload.docId && doc.path !== payload.path),
|
|
13900
|
+
{
|
|
13901
|
+
docId: payload.docId,
|
|
13902
|
+
path: payload.path,
|
|
13903
|
+
title: payload.sidecar.title || titleFromMarkdown(payload.path, payload.markdown),
|
|
13904
|
+
contentHash: contentHashForText(payload.markdown),
|
|
13905
|
+
currentSha: payload.canonicalHead ?? payload.currentSha,
|
|
13906
|
+
updatedAt: payload.sidecar.updatedAt
|
|
13907
|
+
}
|
|
13908
|
+
].sort((left, right) => left.path < right.path ? -1 : left.path > right.path ? 1 : 0),
|
|
13909
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13910
|
+
});
|
|
13911
|
+
}
|
|
13912
|
+
async function removeManifestDocument(docId, path) {
|
|
13913
|
+
const manifest = await readManifest(io).catch(() => void 0);
|
|
13914
|
+
if (!manifest) return;
|
|
13915
|
+
await writeManifest(io, {
|
|
13916
|
+
...manifest,
|
|
13917
|
+
docs: manifest.docs.filter((doc) => doc.docId !== docId && doc.path !== path),
|
|
13918
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
13919
|
+
});
|
|
13920
|
+
}
|
|
13704
13921
|
}
|
|
13705
13922
|
async function requestBridgeToken(options, rootId) {
|
|
13706
13923
|
const response = await fetch(new URL("/api/bridge-requests", options.baseUrl), {
|
|
@@ -13754,6 +13971,83 @@ async function requestBridgeToken(options, rootId) {
|
|
|
13754
13971
|
}
|
|
13755
13972
|
throw new Error("Bridge approval timed out before a token was issued.");
|
|
13756
13973
|
}
|
|
13974
|
+
async function readBridgeConfig(root) {
|
|
13975
|
+
const io = new NodeWorkspaceIO(root);
|
|
13976
|
+
try {
|
|
13977
|
+
const config2 = JSON.parse(await io.readText(BRIDGE_CONFIG_PATH));
|
|
13978
|
+
if (config2.schemaVersion !== 1 || typeof config2.workspaceId !== "string" || typeof config2.rootId !== "string" || typeof config2.baseUrl !== "string" || typeof config2.actorId !== "string") {
|
|
13979
|
+
return void 0;
|
|
13980
|
+
}
|
|
13981
|
+
return config2;
|
|
13982
|
+
} catch {
|
|
13983
|
+
return void 0;
|
|
13984
|
+
}
|
|
13985
|
+
}
|
|
13986
|
+
async function writeBridgeConfig(root, config2) {
|
|
13987
|
+
const io = new NodeWorkspaceIO(root);
|
|
13988
|
+
await io.mkdir(".mdocs");
|
|
13989
|
+
await io.writeTextAtomic(BRIDGE_CONFIG_PATH, `${JSON.stringify(config2, null, 2)}
|
|
13990
|
+
`);
|
|
13991
|
+
}
|
|
13992
|
+
async function fetchCanonicalSnapshot(options, rootId) {
|
|
13993
|
+
const response = await fetch(new URL(`/api/workspaces/${encodeURIComponent(options.workspaceId)}/roots/${encodeURIComponent(rootId)}/sync`, options.baseUrl), {
|
|
13994
|
+
headers: authHeaders(options.token)
|
|
13995
|
+
});
|
|
13996
|
+
if (!response.ok) return void 0;
|
|
13997
|
+
const snapshot = await response.json();
|
|
13998
|
+
const docs = await Promise.all(
|
|
13999
|
+
(snapshot.tree?.docs ?? []).map((doc) => typeof doc.docId === "string" ? doc.docId : void 0).filter((docId) => Boolean(docId)).map((docId) => fetchCanonicalDocument(options, rootId, docId))
|
|
14000
|
+
);
|
|
14001
|
+
return {
|
|
14002
|
+
head: snapshot.tree?.root?.canonical.head,
|
|
14003
|
+
docs: docs.filter((doc) => Boolean(doc))
|
|
14004
|
+
};
|
|
14005
|
+
}
|
|
14006
|
+
async function fetchCommitDocuments(options, rootId, headId) {
|
|
14007
|
+
const response = await fetch(
|
|
14008
|
+
new URL(
|
|
14009
|
+
`/api/workspaces/${encodeURIComponent(options.workspaceId)}/roots/${encodeURIComponent(rootId)}/commits/${encodeURIComponent(headId)}?content=1`,
|
|
14010
|
+
options.baseUrl
|
|
14011
|
+
),
|
|
14012
|
+
{ headers: authHeaders(options.token) }
|
|
14013
|
+
);
|
|
14014
|
+
if (!response.ok) return /* @__PURE__ */ new Map();
|
|
14015
|
+
const payload = await response.json();
|
|
14016
|
+
const docs = (payload.docs ?? []).map((doc) => readCommitDocumentPayload(doc, options.workspaceId, rootId, headId)).filter((doc) => Boolean(doc));
|
|
14017
|
+
return new Map(docs.map((doc) => [doc.docId, doc]));
|
|
14018
|
+
}
|
|
14019
|
+
async function fetchCanonicalDocument(options, rootId, docId) {
|
|
14020
|
+
try {
|
|
14021
|
+
const response = await fetch(
|
|
14022
|
+
new URL(
|
|
14023
|
+
`/api/workspaces/${encodeURIComponent(options.workspaceId)}/roots/${encodeURIComponent(rootId)}/documents/${encodeURIComponent(docId)}`,
|
|
14024
|
+
options.baseUrl
|
|
14025
|
+
),
|
|
14026
|
+
{ headers: authHeaders(options.token) }
|
|
14027
|
+
);
|
|
14028
|
+
if (!response.ok) return void 0;
|
|
14029
|
+
const document = await response.json();
|
|
14030
|
+
return readDocumentPayload({ ...document, canonicalHead: document.currentSha });
|
|
14031
|
+
} catch {
|
|
14032
|
+
return void 0;
|
|
14033
|
+
}
|
|
14034
|
+
}
|
|
14035
|
+
function readCommitDocumentPayload(payload, workspaceId, rootId, headId) {
|
|
14036
|
+
if (!payload || typeof payload !== "object") return void 0;
|
|
14037
|
+
const value = payload;
|
|
14038
|
+
if (typeof value.markdown !== "string" || !value.sidecar || typeof value.sidecar !== "object") return void 0;
|
|
14039
|
+
return readDocumentPayload({
|
|
14040
|
+
workspaceId,
|
|
14041
|
+
rootId,
|
|
14042
|
+
docId: value.docId,
|
|
14043
|
+
path: value.path,
|
|
14044
|
+
title: value.title,
|
|
14045
|
+
markdown: value.markdown,
|
|
14046
|
+
sidecar: value.sidecar,
|
|
14047
|
+
currentSha: headId,
|
|
14048
|
+
canonicalHead: headId
|
|
14049
|
+
});
|
|
14050
|
+
}
|
|
13757
14051
|
function parseBridgeSyncMessage(data) {
|
|
13758
14052
|
if (!data.trim()) return void 0;
|
|
13759
14053
|
try {
|
|
@@ -14063,9 +14357,9 @@ async function main() {
|
|
|
14063
14357
|
return;
|
|
14064
14358
|
}
|
|
14065
14359
|
case "bridge": {
|
|
14066
|
-
const
|
|
14067
|
-
|
|
14068
|
-
|
|
14360
|
+
const root = resolve5(String(parsed.flags.root ?? cwd));
|
|
14361
|
+
const baseOptions = {
|
|
14362
|
+
root,
|
|
14069
14363
|
rootId: typeof parsed.flags["root-id"] === "string" ? parsed.flags["root-id"] : void 0,
|
|
14070
14364
|
sourceId: typeof parsed.flags.source === "string" ? parsed.flags.source : void 0,
|
|
14071
14365
|
sourceName: typeof parsed.flags["source-name"] === "string" ? parsed.flags["source-name"] : void 0,
|
|
@@ -14075,11 +14369,24 @@ async function main() {
|
|
|
14075
14369
|
claimToken: typeof parsed.flags.claim === "string" ? parsed.flags.claim : void 0,
|
|
14076
14370
|
actorId: typeof parsed.flags.actor === "string" ? parsed.flags.actor : void 0,
|
|
14077
14371
|
actorName: typeof parsed.flags["actor-name"] === "string" ? parsed.flags["actor-name"] : void 0,
|
|
14078
|
-
baseUrl: bridgeBaseUrl(parsed.flags),
|
|
14079
14372
|
intervalMs: Number(parsed.flags.interval ?? 1e3),
|
|
14080
14373
|
token: typeof parsed.flags.token === "string" ? parsed.flags.token : process.env.MDOCS_BRIDGE_TOKEN || void 0,
|
|
14081
14374
|
requestToken: Boolean(parsed.flags["request-token"] || parsed.flags.pair),
|
|
14082
|
-
pairingTimeoutMs: typeof parsed.flags["pairing-timeout-ms"] === "string" ? Number(parsed.flags["pairing-timeout-ms"]) : void 0
|
|
14375
|
+
pairingTimeoutMs: typeof parsed.flags["pairing-timeout-ms"] === "string" ? Number(parsed.flags["pairing-timeout-ms"]) : void 0,
|
|
14376
|
+
once: Boolean(parsed.flags.once)
|
|
14377
|
+
};
|
|
14378
|
+
if (subcommand === "resume") {
|
|
14379
|
+
await runBridgeResume({
|
|
14380
|
+
...baseOptions,
|
|
14381
|
+
workspaceId: typeof parsed.flags.workspace === "string" ? parsed.flags.workspace : void 0,
|
|
14382
|
+
baseUrl: optionalBridgeBaseUrl(parsed.flags)
|
|
14383
|
+
});
|
|
14384
|
+
return;
|
|
14385
|
+
}
|
|
14386
|
+
const options = {
|
|
14387
|
+
...baseOptions,
|
|
14388
|
+
workspaceId: typeof parsed.flags.workspace === "string" ? parsed.flags.workspace : "workspace_demo",
|
|
14389
|
+
baseUrl: bridgeBaseUrl(parsed.flags)
|
|
14083
14390
|
};
|
|
14084
14391
|
if (subcommand === "setup") await runBridgeSetup(options);
|
|
14085
14392
|
else await runBridge({ ...options, actorId: options.actorId ?? "actor_local" });
|
|
@@ -14183,13 +14490,17 @@ function requiredFlag2(flags, name) {
|
|
|
14183
14490
|
return value;
|
|
14184
14491
|
}
|
|
14185
14492
|
function bridgeBaseUrl(flags) {
|
|
14186
|
-
|
|
14187
|
-
const configured = process.env.MDOCS_BASE_URL?.trim();
|
|
14493
|
+
const configured = optionalBridgeBaseUrl(flags);
|
|
14188
14494
|
if (configured) return configured;
|
|
14189
14495
|
throw new CliError("usage_error", "Missing --url <base-url> for mdocs bridge.", {
|
|
14190
14496
|
hint: "Pass the Magic Markdown origin explicitly (for example --url https://magic.example.com) or set MDOCS_BASE_URL. The bridge does not assume a localhost server."
|
|
14191
14497
|
});
|
|
14192
14498
|
}
|
|
14499
|
+
function optionalBridgeBaseUrl(flags) {
|
|
14500
|
+
if (typeof flags.url === "string" && flags.url.trim()) return flags.url.trim();
|
|
14501
|
+
const configured = process.env.MDOCS_BASE_URL?.trim();
|
|
14502
|
+
return configured || void 0;
|
|
14503
|
+
}
|
|
14193
14504
|
async function readRequiredTextFlag2(flags, cwd, names) {
|
|
14194
14505
|
const value = await readOptionalTextFlag2(flags, cwd, names);
|
|
14195
14506
|
if (value === void 0) {
|
|
@@ -14274,6 +14585,9 @@ Commands:
|
|
|
14274
14585
|
bridge setup --workspace <id> --root . --url <base-url> [--folder-id <id>] --request-token
|
|
14275
14586
|
Initialize, validate, request approval,
|
|
14276
14587
|
and start an agent filesystem bridge
|
|
14588
|
+
bridge resume --root . --request-token [--once]
|
|
14589
|
+
Backfill missed Magic changes, then keep
|
|
14590
|
+
the bridge running unless --once is set
|
|
14277
14591
|
bridge --workspace <id> --root . --url <base-url> --request-token
|
|
14278
14592
|
Request human approval, then sync an
|
|
14279
14593
|
approved local root with the workspace
|
package/package.json
CHANGED