@linkedclaw/cli 0.1.7 → 0.2.0

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/bin.js CHANGED
@@ -6357,19 +6357,409 @@ function registerArenaCommands(program2) {
6357
6357
  });
6358
6358
  }
6359
6359
 
6360
+ // src/auth/decide.ts
6361
+ import { existsSync as existsSync2 } from "fs";
6362
+ import { hostname, platform } from "os";
6363
+ function detectLabel(packageVersion) {
6364
+ const host = hostname();
6365
+ return truncate(
6366
+ `linkedclaw-cli/${packageVersion} on ${platform()} (host: ${host})`,
6367
+ 120
6368
+ );
6369
+ }
6370
+ function truncate(s, n) {
6371
+ return s.length <= n ? s : s.slice(0, n - 1) + "\u2026";
6372
+ }
6373
+ function isHeadless(env = process.env) {
6374
+ if (env.SSH_CLIENT || env.SSH_TTY) return true;
6375
+ if (env.CODESPACES === "true") return true;
6376
+ if (env.LINKEDCLAW_FORCE_DEVICE_FLOW === "1") return true;
6377
+ if (existsSync2("/.dockerenv") || existsSync2("/run/.containerenv")) return true;
6378
+ if (platform() === "linux") {
6379
+ if (!env.DISPLAY && !env.WAYLAND_DISPLAY) return true;
6380
+ }
6381
+ return false;
6382
+ }
6383
+
6384
+ // src/auth/loopback.ts
6385
+ import { createServer } from "http";
6386
+
6387
+ // src/auth/pkce.ts
6388
+ import { createHash as createHash2, randomBytes } from "crypto";
6389
+ function generateVerifier() {
6390
+ return randomBytes(64).toString("base64url");
6391
+ }
6392
+ function deriveChallenge(verifier) {
6393
+ return createHash2("sha256").update(verifier).digest("base64url");
6394
+ }
6395
+ function generateState() {
6396
+ return randomBytes(16).toString("base64url");
6397
+ }
6398
+
6399
+ // src/auth/loopback.ts
6400
+ var LoopbackError = class extends Error {
6401
+ constructor(message, recoverable) {
6402
+ super(message);
6403
+ this.recoverable = recoverable;
6404
+ this.name = "LoopbackError";
6405
+ }
6406
+ recoverable;
6407
+ };
6408
+ var SUCCESS_HTML = `<!doctype html>
6409
+ <html><head><meta charset="utf-8"><title>LinkedClaw \u2014 Authorized</title></head>
6410
+ <body style="font-family:system-ui,sans-serif;text-align:center;padding:48px">
6411
+ <h2>\u2705 Authorized</h2>
6412
+ <p>You can close this tab and return to your terminal.</p>
6413
+ </body></html>`;
6414
+ var ERROR_HTML = (msg) => `<!doctype html>
6415
+ <html><head><meta charset="utf-8"><title>LinkedClaw \u2014 Error</title></head>
6416
+ <body style="font-family:system-ui,sans-serif;text-align:center;padding:48px">
6417
+ <h2>\u274C Authorization failed</h2>
6418
+ <p>${msg}</p>
6419
+ <p>Return to your terminal \u2014 the CLI will retry or fall back automatically.</p>
6420
+ </body></html>`;
6421
+ async function runLoopback(opts) {
6422
+ const verifier = generateVerifier();
6423
+ const challenge = deriveChallenge(verifier);
6424
+ const state = generateState();
6425
+ const server = createServer();
6426
+ await new Promise((resolve3, reject) => {
6427
+ server.once("error", reject);
6428
+ server.listen(0, "127.0.0.1", () => {
6429
+ server.removeListener("error", reject);
6430
+ resolve3();
6431
+ });
6432
+ });
6433
+ const port = server.address().port;
6434
+ const redirectUri = `http://127.0.0.1:${port}/cb`;
6435
+ let initResp;
6436
+ try {
6437
+ initResp = await initiate(opts.cloudUrl, {
6438
+ client_label: opts.clientLabel,
6439
+ requested_scope: opts.requestedScope,
6440
+ redirect_uri: redirectUri,
6441
+ code_challenge: challenge,
6442
+ code_challenge_method: "S256",
6443
+ state
6444
+ });
6445
+ } catch (err) {
6446
+ server.close();
6447
+ throw new LoopbackError(
6448
+ `Failed to start authorization: ${err.message}`,
6449
+ // network failure shouldn't fall through to device flow — it'll fail there too.
6450
+ false
6451
+ );
6452
+ }
6453
+ try {
6454
+ await opts.openBrowser(initResp.authorize_url);
6455
+ } catch {
6456
+ server.close();
6457
+ throw new LoopbackError(
6458
+ "Could not open browser; switching to device flow.",
6459
+ true
6460
+ );
6461
+ }
6462
+ if (opts.onUserPrompt) opts.onUserPrompt(initResp.authorize_url);
6463
+ const timeoutMs = opts.timeoutMs ?? 6e4;
6464
+ const callbackParams = await waitForCallback(server, timeoutMs).catch((err) => {
6465
+ server.close();
6466
+ throw err;
6467
+ });
6468
+ server.close();
6469
+ if (callbackParams.error) {
6470
+ throw new LoopbackError(`Authorization denied: ${callbackParams.error}`, false);
6471
+ }
6472
+ if (!callbackParams.code) {
6473
+ throw new LoopbackError("Authorization callback missing code.", true);
6474
+ }
6475
+ if (callbackParams.state !== state) {
6476
+ throw new LoopbackError("Authorization callback state mismatch.", false);
6477
+ }
6478
+ const tokenResp = await exchangeToken(opts.cloudUrl, {
6479
+ grant_type: "authorization_code",
6480
+ code: callbackParams.code,
6481
+ code_verifier: verifier
6482
+ });
6483
+ return {
6484
+ apiKey: tokenResp.api_key,
6485
+ userId: tokenResp.user_id,
6486
+ handle: tokenResp.handle ?? null,
6487
+ scope: tokenResp.scope,
6488
+ keyId: tokenResp.key_id
6489
+ };
6490
+ }
6491
+ function waitForCallback(server, timeoutMs) {
6492
+ return new Promise((resolve3, reject) => {
6493
+ const timer = setTimeout(() => {
6494
+ reject(new LoopbackError("Browser callback timed out.", true));
6495
+ }, timeoutMs);
6496
+ server.on("request", (req, res) => {
6497
+ const url = new URL(req.url ?? "/", `http://127.0.0.1`);
6498
+ if (url.pathname !== "/cb") {
6499
+ res.statusCode = 404;
6500
+ res.end();
6501
+ return;
6502
+ }
6503
+ const params = {
6504
+ code: url.searchParams.get("code") ?? void 0,
6505
+ state: url.searchParams.get("state") ?? void 0,
6506
+ error: url.searchParams.get("error") ?? void 0
6507
+ };
6508
+ const html = params.error ? ERROR_HTML(params.error) : params.code ? SUCCESS_HTML : ERROR_HTML("Missing code parameter");
6509
+ res.statusCode = params.code ? 200 : 400;
6510
+ res.setHeader("Content-Type", "text/html; charset=utf-8");
6511
+ res.end(html);
6512
+ clearTimeout(timer);
6513
+ resolve3(params);
6514
+ });
6515
+ });
6516
+ }
6517
+ async function initiate(cloudUrl, body) {
6518
+ const resp = await fetch(`${cloudUrl}/api/v1/auth/oauth/initiate`, {
6519
+ method: "POST",
6520
+ headers: { "Content-Type": "application/json" },
6521
+ body: JSON.stringify(body)
6522
+ });
6523
+ if (resp.status !== 201) {
6524
+ const detail = await safeDetail(resp);
6525
+ throw new Error(`/auth/oauth/initiate ${resp.status}: ${detail}`);
6526
+ }
6527
+ return await resp.json();
6528
+ }
6529
+ async function exchangeToken(cloudUrl, body) {
6530
+ const resp = await fetch(`${cloudUrl}/api/v1/auth/oauth/token`, {
6531
+ method: "POST",
6532
+ headers: { "Content-Type": "application/json" },
6533
+ body: JSON.stringify(body)
6534
+ });
6535
+ if (resp.status !== 200) {
6536
+ const detail = await safeDetail(resp);
6537
+ throw new LoopbackError(`Token exchange failed: ${detail}`, false);
6538
+ }
6539
+ return await resp.json();
6540
+ }
6541
+ async function safeDetail(resp) {
6542
+ try {
6543
+ const j = await resp.json();
6544
+ return j.detail ?? `${resp.status}`;
6545
+ } catch {
6546
+ return `${resp.status}`;
6547
+ }
6548
+ }
6549
+
6550
+ // src/auth/device.ts
6551
+ import { setTimeout as sleep } from "timers/promises";
6552
+ var DeviceFlowError = class extends Error {
6553
+ constructor(message, code) {
6554
+ super(message);
6555
+ this.code = code;
6556
+ this.name = "DeviceFlowError";
6557
+ }
6558
+ code;
6559
+ };
6560
+ async function runDeviceFlow(opts) {
6561
+ const init = await issueDeviceCode(opts.cloudUrl, {
6562
+ client_label: opts.clientLabel,
6563
+ requested_scope: opts.requestedScope
6564
+ });
6565
+ if (opts.openBrowser) {
6566
+ try {
6567
+ await opts.openBrowser(init.verification_uri_complete);
6568
+ } catch {
6569
+ }
6570
+ }
6571
+ opts.onUserPrompt(init);
6572
+ const deadline = Date.now() + init.expires_in * 1e3;
6573
+ let interval = init.interval;
6574
+ while (Date.now() < deadline) {
6575
+ const before = Date.now();
6576
+ const result = await pollOnce(
6577
+ opts.cloudUrl,
6578
+ init.device_code,
6579
+ opts.pollOverride
6580
+ );
6581
+ if (result.kind === "approved") {
6582
+ return {
6583
+ apiKey: result.api_key,
6584
+ userId: result.user_id,
6585
+ handle: result.handle ?? null,
6586
+ scope: result.scope,
6587
+ keyId: result.key_id
6588
+ };
6589
+ }
6590
+ if (result.kind === "slow_down") {
6591
+ interval = Math.min(interval * 2, 30);
6592
+ }
6593
+ const elapsed = Date.now() - before;
6594
+ const wait = Math.max(0, interval * 1e3 - elapsed);
6595
+ await sleep(wait);
6596
+ }
6597
+ throw new DeviceFlowError(
6598
+ "Login window expired. Run `linkedclaw login` to retry.",
6599
+ "expired_token"
6600
+ );
6601
+ }
6602
+ async function pollOnce(cloudUrl, deviceCode, override) {
6603
+ const raw = override ? await override(deviceCode) : await rawPoll(cloudUrl, deviceCode);
6604
+ return interpretPoll(raw);
6605
+ }
6606
+ async function rawPoll(cloudUrl, deviceCode) {
6607
+ const resp = await fetch(`${cloudUrl}/api/v1/auth/device/poll`, {
6608
+ method: "POST",
6609
+ headers: { "Content-Type": "application/json" },
6610
+ body: JSON.stringify({ device_code: deviceCode })
6611
+ });
6612
+ if (resp.status === 400) {
6613
+ let detail = "invalid_grant";
6614
+ try {
6615
+ const j = await resp.json();
6616
+ if (j.detail) detail = j.detail;
6617
+ } catch {
6618
+ }
6619
+ if (detail === "slow_down") return { __slow: true };
6620
+ throw new DeviceFlowError(_humanize(detail), detail);
6621
+ }
6622
+ if (resp.status !== 200) {
6623
+ throw new DeviceFlowError(`Poll failed: HTTP ${resp.status}`, "http_error");
6624
+ }
6625
+ return await resp.json();
6626
+ }
6627
+ function interpretPoll(raw) {
6628
+ if (raw && typeof raw === "object" && "__slow" in raw) {
6629
+ return { kind: "slow_down" };
6630
+ }
6631
+ const obj = raw ?? {};
6632
+ if (obj.status === "pending") return { kind: "pending" };
6633
+ if (obj.status === "approved") {
6634
+ return {
6635
+ kind: "approved",
6636
+ api_key: String(obj.api_key),
6637
+ user_id: String(obj.user_id),
6638
+ handle: obj.handle ?? null,
6639
+ scope: String(obj.scope),
6640
+ key_id: String(obj.key_id)
6641
+ };
6642
+ }
6643
+ throw new DeviceFlowError(`Unexpected poll status: ${obj.status}`, "protocol_error");
6644
+ }
6645
+ function _humanize(code) {
6646
+ switch (code) {
6647
+ case "expired_token":
6648
+ return "Login window expired. Run `linkedclaw login` to retry.";
6649
+ case "access_denied":
6650
+ return "Authorization denied by user.";
6651
+ case "invalid_grant":
6652
+ return "Authorization session is no longer valid.";
6653
+ default:
6654
+ return code;
6655
+ }
6656
+ }
6657
+ async function issueDeviceCode(cloudUrl, body) {
6658
+ const resp = await fetch(`${cloudUrl}/api/v1/auth/device/code`, {
6659
+ method: "POST",
6660
+ headers: { "Content-Type": "application/json" },
6661
+ body: JSON.stringify(body)
6662
+ });
6663
+ if (resp.status !== 201) {
6664
+ let detail = `${resp.status}`;
6665
+ try {
6666
+ const j = await resp.json();
6667
+ if (j.detail) detail = j.detail;
6668
+ } catch {
6669
+ }
6670
+ throw new DeviceFlowError(`/auth/device/code ${resp.status}: ${detail}`, "init_failed");
6671
+ }
6672
+ return await resp.json();
6673
+ }
6674
+
6360
6675
  // src/commands/auth.ts
6676
+ var CLI_VERSION = "0.2.0";
6361
6677
  function registerAuthCommands(program2) {
6362
- program2.command("login").description("Store API key in ~/.linkedclaw/config.yaml").option("--api-key <key>", "API key (otherwise read from stdin)").option("--cloud-url <url>", "Override cloud URL").action(async (opts) => {
6678
+ program2.command("login").description(
6679
+ "Authenticate via browser (loopback PKCE; falls back to device code if headless)."
6680
+ ).option("--api-key <key>", "Skip browser; store key directly").option("--paste", "Skip browser; prompt for key on stdin").option("--device", "Force device flow (skip loopback)").option("--scope <scope>", "Requested scope: full | read | invoke", "full").option("--label <label>", "Client label override (default: auto-detected)").option("--cloud-url <url>", "Override cloud URL").action(async (opts) => {
6363
6681
  await runCommand(async () => {
6364
- let apiKey = opts.apiKey;
6365
- if (!apiKey) {
6366
- apiKey = await readLine("Paste API key: ");
6367
- }
6368
- if (!apiKey) throw new Error("empty api key");
6369
6682
  const prev = readFileConfig();
6370
- const next = { ...prev, apiKey, ...opts.cloudUrl ? { cloudUrl: opts.cloudUrl } : {} };
6371
- writeFileConfig(next);
6372
- return { ok: true, path: configPath() };
6683
+ const cloudUrl = opts.cloudUrl ?? prev.cloudUrl ?? process.env.LINKEDCLAW_CLOUD_URL ?? DEFAULT_CLOUD_URL;
6684
+ if (opts.apiKey || opts.paste) {
6685
+ let apiKey = opts.apiKey;
6686
+ if (!apiKey) apiKey = await readLine("Paste API key: ");
6687
+ if (!apiKey) throw new Error("empty api key");
6688
+ writeFileConfig({ ...prev, apiKey, cloudUrl });
6689
+ return { ok: true, mode: "paste", path: configPath() };
6690
+ }
6691
+ const clientLabel = opts.label ?? detectLabel(CLI_VERSION);
6692
+ const requestedScope = opts.scope ?? "full";
6693
+ const openBrowser = async (url) => {
6694
+ const open = (await import("open")).default;
6695
+ await open(url);
6696
+ };
6697
+ const forceDevice = Boolean(opts.device) || isHeadless();
6698
+ if (!forceDevice) {
6699
+ try {
6700
+ const result2 = await runLoopback({
6701
+ cloudUrl,
6702
+ clientLabel,
6703
+ requestedScope,
6704
+ openBrowser,
6705
+ onUserPrompt: (url) => {
6706
+ process.stderr.write(
6707
+ `Opened browser to ${url}
6708
+ Approve the request to continue.
6709
+ `
6710
+ );
6711
+ }
6712
+ });
6713
+ writeFileConfig({ ...prev, apiKey: result2.apiKey, cloudUrl });
6714
+ return {
6715
+ ok: true,
6716
+ mode: "loopback",
6717
+ path: configPath(),
6718
+ user_id: result2.userId,
6719
+ handle: result2.handle,
6720
+ scope: result2.scope,
6721
+ key_id: result2.keyId
6722
+ };
6723
+ } catch (err) {
6724
+ if (err instanceof LoopbackError && err.recoverable) {
6725
+ process.stderr.write(`${err.message}
6726
+ `);
6727
+ } else {
6728
+ throw err;
6729
+ }
6730
+ }
6731
+ }
6732
+ const result = await runDeviceFlow({
6733
+ cloudUrl,
6734
+ clientLabel,
6735
+ requestedScope,
6736
+ openBrowser,
6737
+ onUserPrompt: (info) => {
6738
+ process.stderr.write(
6739
+ `
6740
+ \u{1F513} LinkedClaw login
6741
+
6742
+ Open this URL in your browser:
6743
+ ${info.verification_uri_complete}
6744
+
6745
+ Or visit ${info.verification_uri} and enter:
6746
+ ${info.user_code}
6747
+
6748
+ Waiting for approval... (${Math.floor(info.expires_in / 60)}m ${info.expires_in % 60}s remaining)
6749
+ `
6750
+ );
6751
+ }
6752
+ });
6753
+ writeFileConfig({ ...prev, apiKey: result.apiKey, cloudUrl });
6754
+ return {
6755
+ ok: true,
6756
+ mode: "device",
6757
+ path: configPath(),
6758
+ user_id: result.userId,
6759
+ handle: result.handle,
6760
+ scope: result.scope,
6761
+ key_id: result.keyId
6762
+ };
6373
6763
  });
6374
6764
  });
6375
6765
  program2.command("register").description("Open browser to create a LinkedClaw account, then paste your API key").option("--no-browser", "Print URL instead of attempting to open the browser").option("--cloud-url <url>", "Override cloud URL").action(async (opts) => {
@@ -6439,7 +6829,7 @@ function tryParseJson(v) {
6439
6829
 
6440
6830
  // src/commands/converge.ts
6441
6831
  import { spawnSync } from "child_process";
6442
- import { existsSync as existsSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
6832
+ import { existsSync as existsSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync2 } from "fs";
6443
6833
  import { isAbsolute as isAbsolute2, join as join5, relative, resolve as resolve2 } from "path";
6444
6834
 
6445
6835
  // src/converge/api.ts
@@ -6574,7 +6964,7 @@ function makeConvergeApi(cloudUrl, apiKey) {
6574
6964
  }
6575
6965
 
6576
6966
  // src/converge/hash.ts
6577
- import { createHash as createHash2 } from "crypto";
6967
+ import { createHash as createHash3 } from "crypto";
6578
6968
  function encodeString(s) {
6579
6969
  let out = '"';
6580
6970
  for (let i = 0; i < s.length; i++) {
@@ -6600,7 +6990,7 @@ function canonicalize(value) {
6600
6990
  return "{" + keys.map((k) => encodeString(k) + ":" + canonicalize(value[k])).join(",") + "}";
6601
6991
  }
6602
6992
  function sha256OfCanonicalJson(value) {
6603
- const h = createHash2("sha256");
6993
+ const h = createHash3("sha256");
6604
6994
  h.update(canonicalize(value));
6605
6995
  return "sha256:" + h.digest("hex");
6606
6996
  }
@@ -6634,15 +7024,15 @@ function acquireLock(stagingDir) {
6634
7024
  }
6635
7025
 
6636
7026
  // src/converge/staging.ts
6637
- import { createHash as createHash3 } from "crypto";
6638
- import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync, writeFileSync as writeFileSync2 } from "fs";
7027
+ import { createHash as createHash4 } from "crypto";
7028
+ import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync, writeFileSync as writeFileSync2 } from "fs";
6639
7029
  import { dirname as dirname2, join as join3 } from "path";
6640
7030
  import { load as yamlLoad2, dump as yamlDump2 } from "js-yaml";
6641
7031
  function stagingPathFor(stagingDir, cruxId) {
6642
7032
  return join3(stagingDir, `${cruxId}.md`);
6643
7033
  }
6644
7034
  function listCruxFiles(stagingDir) {
6645
- if (!existsSync2(stagingDir)) return [];
7035
+ if (!existsSync3(stagingDir)) return [];
6646
7036
  return readdirSync(stagingDir).filter(
6647
7037
  (f) => f.endsWith(".md") && !f.startsWith(".")
6648
7038
  );
@@ -6685,17 +7075,17 @@ function writeStaging(path2, doc) {
6685
7075
  writeFileSync2(path2, dumpStaging(doc), "utf8");
6686
7076
  }
6687
7077
  function computePaBodyHash(body) {
6688
- return "sha256:" + createHash3("sha256").update(Buffer.from(body, "utf8")).digest("hex");
7078
+ return "sha256:" + createHash4("sha256").update(Buffer.from(body, "utf8")).digest("hex");
6689
7079
  }
6690
7080
 
6691
7081
  // src/converge/workspace.ts
6692
- import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
7082
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync5, writeFileSync as writeFileSync3 } from "fs";
6693
7083
  import { dirname as dirname3, isAbsolute, join as join4, resolve } from "path";
6694
7084
  import { load as yamlLoad3, dump as yamlDump3 } from "js-yaml";
6695
7085
  var META_FILENAME = ".run-meta.yaml";
6696
7086
  function readRunMeta(stagingDir) {
6697
7087
  const metaPath = join4(stagingDir, META_FILENAME);
6698
- if (!existsSync3(metaPath)) return null;
7088
+ if (!existsSync4(metaPath)) return null;
6699
7089
  return yamlLoad3(readFileSync5(metaPath, "utf8"));
6700
7090
  }
6701
7091
  function writeRunMeta(stagingDir, meta) {
@@ -6705,7 +7095,7 @@ function writeRunMeta(stagingDir, meta) {
6705
7095
  function searchUpward(startDir, maxLevels = 5) {
6706
7096
  let dir = startDir;
6707
7097
  for (let i = 0; i < maxLevels; i++) {
6708
- if (existsSync3(join4(dir, META_FILENAME))) return dir;
7098
+ if (existsSync4(join4(dir, META_FILENAME))) return dir;
6709
7099
  const parent = dirname3(dir);
6710
7100
  if (parent === dir) break;
6711
7101
  dir = parent;
@@ -6958,7 +7348,7 @@ async function buildCruxDecisionRequest(api, ws, cruxId, action, opts = {}) {
6958
7348
  let synthesisEdited = false;
6959
7349
  if (action === "accept") {
6960
7350
  const stagingPath = safeStagingPathFor(ws.stagingDir, cruxId);
6961
- if (existsSync4(stagingPath)) {
7351
+ if (existsSync5(stagingPath)) {
6962
7352
  const doc = readStaging(stagingPath);
6963
7353
  synthesisEdited = computePaBodyHash(doc.body) !== doc.frontmatter.pa_body_hash;
6964
7354
  if (synthesisEdited) acceptedSynthesisText = doc.body;
@@ -6989,7 +7379,7 @@ async function postCruxDecision(api, ws, cruxId, action, opts = {}) {
6989
7379
  }
6990
7380
  async function materializeAcceptedCrux(ctx, api, ws, cruxId, payload, opts = {}) {
6991
7381
  const stagingPath = safeStagingPathFor(ws.stagingDir, cruxId);
6992
- if (!existsSync4(stagingPath)) return { warning: `staging_not_found: ${stagingPath}` };
7382
+ if (!existsSync5(stagingPath)) return { warning: `staging_not_found: ${stagingPath}` };
6993
7383
  const doc = readStaging(stagingPath);
6994
7384
  const fm = doc.frontmatter;
6995
7385
  const sourceDebate = await api.getDebate(ws.sourceDebateId);
@@ -7033,11 +7423,11 @@ async function materializeAcceptedCrux(ctx, api, ws, cruxId, payload, opts = {})
7033
7423
  const synthSlug = extractSynthesisSlug(body);
7034
7424
  const finalDir = join5(ws.targetCorpus, "converged", topicSlug);
7035
7425
  const finalPath = safeAcceptedPath(finalDir, cruxId, synthSlug);
7036
- if (existsSync4(finalPath) && readStaging(finalPath).body !== acceptedDoc.body) {
7426
+ if (existsSync5(finalPath) && readStaging(finalPath).body !== acceptedDoc.body) {
7037
7427
  return { warning: `sync_conflict: ${finalPath}` };
7038
7428
  }
7039
7429
  mkdirSync4(finalDir, { recursive: true });
7040
- if (!existsSync4(finalPath) || dumpStaging(readStaging(finalPath)) !== dumpStaging(acceptedDoc)) {
7430
+ if (!existsSync5(finalPath) || dumpStaging(readStaging(finalPath)) !== dumpStaging(acceptedDoc)) {
7041
7431
  writeStaging(finalPath, acceptedDoc);
7042
7432
  }
7043
7433
  unlinkSync2(stagingPath);
@@ -7101,7 +7491,7 @@ async function syncTerminalDecisions(ctx, api, ws, opts = {}) {
7101
7491
  if (result.warning) warnings.push(`${cid}: ${result.warning}`);
7102
7492
  continue;
7103
7493
  }
7104
- if (existsSync4(stagingPath)) {
7494
+ if (existsSync5(stagingPath)) {
7105
7495
  unlinkSync2(stagingPath);
7106
7496
  cleaned.push(cid);
7107
7497
  }
@@ -7217,7 +7607,7 @@ function registerConvergeCommands(program2) {
7217
7607
  const body = buildPaBody(op);
7218
7608
  const newPaBodyHash = computePaBodyHash(body);
7219
7609
  const path2 = safeStagingPathFor(ws.stagingDir, c.crux_id);
7220
- const existingDoc = existsSync4(path2) ? readStaging(path2) : null;
7610
+ const existingDoc = existsSync5(path2) ? readStaging(path2) : null;
7221
7611
  if (existingDoc && existingDoc.frontmatter.pa_body_hash === newPaBodyHash) continue;
7222
7612
  const fm = {
7223
7613
  debate_id: ws.sourceDebateId,
@@ -8768,9 +9158,9 @@ async function resolveManifestOpt(manifestOpt, manifestId, intention, defaultInt
8768
9158
 
8769
9159
  // src/bin.ts
8770
9160
  var pkgPath = join6(dirname4(fileURLToPath(import.meta.url)), "..", "package.json");
8771
- var CLI_VERSION = JSON.parse(readFileSync8(pkgPath, "utf8")).version;
9161
+ var CLI_VERSION2 = JSON.parse(readFileSync8(pkgPath, "utf8")).version;
8772
9162
  var program = new Command();
8773
- program.name("linkedclaw").description("Official LinkedClaw CLI \u2014 any agent can shell out to hire providers, invoke, or gig task").version(`cli ${CLI_VERSION}`);
9163
+ program.name("linkedclaw").description("Official LinkedClaw CLI \u2014 any agent can shell out to hire providers, invoke, or gig task").version(`cli ${CLI_VERSION2}`);
8774
9164
  registerAuthCommands(program);
8775
9165
  registerRequesterCommands(program);
8776
9166
  registerProviderCommands(program);