@archme/cli 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/cli.js +78 -31
  2. package/package.json +1 -1
package/dist/cli.js CHANGED
@@ -8666,12 +8666,6 @@ var init_enroll = __esm({
8666
8666
  init_execa();
8667
8667
  init_credentials();
8668
8668
  init_defaults();
8669
- if (import.meta.url === `file://${process.argv[1]}`) {
8670
- runLogin().catch((err2) => {
8671
- console.error(err2 instanceof Error ? err2.message : err2);
8672
- process.exit(1);
8673
- });
8674
- }
8675
8669
  }
8676
8670
  });
8677
8671
 
@@ -8726,13 +8720,43 @@ var init_paths = __esm({
8726
8720
  }
8727
8721
  });
8728
8722
 
8723
+ // src/daemon/supervision.ts
8724
+ function isSupervised(env) {
8725
+ return env[SUPERVISED_ENV_VAR] === "1";
8726
+ }
8727
+ function resolveExitCode(code, env) {
8728
+ return isSupervised(env) && PERMANENT_EXIT_CODES.has(code) ? 0 : code;
8729
+ }
8730
+ function supervisedStopNotice(code, env) {
8731
+ if (!isSupervised(env) || !PERMANENT_EXIT_CODES.has(code)) return null;
8732
+ return "this cannot be fixed by restarting, so the service is stopping rather than retrying; re-run `archme daemon start` once resolved";
8733
+ }
8734
+ var PERMANENT_EXIT_CODES, SUPERVISED_ENV_VAR;
8735
+ var init_supervision = __esm({
8736
+ "src/daemon/supervision.ts"() {
8737
+ "use strict";
8738
+ PERMANENT_EXIT_CODES = /* @__PURE__ */ new Set([
8739
+ 1,
8740
+ // missing/unreadable config
8741
+ 2,
8742
+ // no stored credentials — needs `archme login`
8743
+ 78
8744
+ // several accounts on one machine — needs a human to choose
8745
+ ]);
8746
+ SUPERVISED_ENV_VAR = "ARCHME_SUPERVISED";
8747
+ }
8748
+ });
8749
+
8729
8750
  // src/daemon/service/unitFiles.ts
8751
+ function unitEnv(env) {
8752
+ return { ...env, [SUPERVISED_ENV_VAR]: "1" };
8753
+ }
8730
8754
  function xmlEscape(s) {
8731
8755
  return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
8732
8756
  }
8733
8757
  function renderLaunchdPlist(opts) {
8734
8758
  const argLines = [opts.nodeBin, opts.cliEntry, "daemon", "run"].map((a2) => ` <string>${xmlEscape(a2)}</string>`).join("\n");
8735
- const envLines = Object.entries(opts.env).map(
8759
+ const envLines = Object.entries(unitEnv(opts.env)).map(
8736
8760
  ([k, v]) => ` <key>${xmlEscape(k)}</key>
8737
8761
  <string>${xmlEscape(v)}</string>`
8738
8762
  ).join("\n");
@@ -8750,8 +8774,8 @@ ${argLines}
8750
8774
  <true/>
8751
8775
  <key>KeepAlive</key>
8752
8776
  <dict>
8753
- <key>Crashed</key>
8754
- <true/>
8777
+ <key>SuccessfulExit</key>
8778
+ <false/>
8755
8779
  </dict>
8756
8780
  <key>ThrottleInterval</key>
8757
8781
  <integer>10</integer>
@@ -8774,7 +8798,7 @@ function systemdArgQuote(s) {
8774
8798
  return `"${s.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
8775
8799
  }
8776
8800
  function renderSystemdUnit(opts) {
8777
- const envLines = Object.entries(opts.env).map(([k, v]) => `Environment="${k}=${systemdEnvEscape(v)}"`).join("\n");
8801
+ const envLines = Object.entries(unitEnv(opts.env)).map(([k, v]) => `Environment="${k}=${systemdEnvEscape(v)}"`).join("\n");
8778
8802
  const execStart = [
8779
8803
  systemdArgQuote(opts.nodeBin),
8780
8804
  systemdArgQuote(opts.cliEntry),
@@ -8801,6 +8825,7 @@ var init_unitFiles = __esm({
8801
8825
  "src/daemon/service/unitFiles.ts"() {
8802
8826
  "use strict";
8803
8827
  init_paths();
8828
+ init_supervision();
8804
8829
  }
8805
8830
  });
8806
8831
 
@@ -9581,6 +9606,9 @@ var init_daemonModel = __esm({
9581
9606
  });
9582
9607
 
9583
9608
  // src/scan/spawn-claude.ts
9609
+ function stripNulBytes(arg) {
9610
+ return arg.includes("\0") ? arg.replaceAll("\0", "\uFFFD") : arg;
9611
+ }
9584
9612
  function buildClaudeArgs(opts) {
9585
9613
  const tools = (opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS).join(",");
9586
9614
  const args = [
@@ -9607,7 +9635,7 @@ function buildClaudeArgs(opts) {
9607
9635
  if (opts.model) {
9608
9636
  args.push("--model", opts.model);
9609
9637
  }
9610
- return args;
9638
+ return args.map(stripNulBytes);
9611
9639
  }
9612
9640
  function spawnClaude(opts) {
9613
9641
  requireBinary("claude");
@@ -14189,15 +14217,17 @@ async function acquireSingleInstanceLock(pidfile) {
14189
14217
  }
14190
14218
  };
14191
14219
  }
14192
- var SingleInstanceLockedError;
14220
+ var SINGLE_INSTANCE_EXIT_CODE, LOCK_HELD_MESSAGE, SingleInstanceLockedError;
14193
14221
  var init_singleInstanceLock = __esm({
14194
14222
  "src/daemon/service/singleInstanceLock.ts"() {
14195
14223
  "use strict";
14224
+ SINGLE_INSTANCE_EXIT_CODE = 64;
14225
+ LOCK_HELD_MESSAGE = "already holds";
14196
14226
  SingleInstanceLockedError = class extends Error {
14197
14227
  heldByPid;
14198
14228
  constructor(pidfile, heldByPid) {
14199
14229
  super(
14200
- heldByPid != null ? `another archme daemon already holds ${pidfile} (pid ${heldByPid})` : `another archme daemon already holds ${pidfile}`
14230
+ heldByPid != null ? `another archme daemon ${LOCK_HELD_MESSAGE} ${pidfile} (pid ${heldByPid})` : `another archme daemon ${LOCK_HELD_MESSAGE} ${pidfile}`
14201
14231
  );
14202
14232
  this.heldByPid = heldByPid;
14203
14233
  this.name = "SingleInstanceLockedError";
@@ -14881,9 +14911,13 @@ function rateLimitDeferPlan(text, nowMs = Date.now(), defaultTimeZone = localTim
14881
14911
  }
14882
14912
  return null;
14883
14913
  }
14914
+ function stripPromptEcho(haystack, prompt) {
14915
+ if (!prompt || prompt.length < 40) return haystack;
14916
+ return haystack.split(prompt).join(" ");
14917
+ }
14884
14918
  function rateLimitDeferPlanForExit(channels, nowMs = Date.now(), defaultTimeZone = localTimeZone()) {
14885
- const haystack = `${channels.message}
14886
- ${channels.stderr}`;
14919
+ const haystack = stripPromptEcho(`${channels.message}
14920
+ ${channels.stderr}`, channels.prompt);
14887
14921
  return rateLimitDeferPlan(haystack, nowMs, defaultTimeZone);
14888
14922
  }
14889
14923
  var RATE_LIMIT_RE, MONTHS, RATE_LIMIT_TENTATIVE_PAUSE_MS, DATED_RESET_ROLLOVER_GRACE_MS, RATE_LIMIT_PAUSE_PADDING_MS, RATE_LIMIT_SHAPED_RE;
@@ -15680,7 +15714,6 @@ import { randomUUID } from "node:crypto";
15680
15714
  import { mkdtemp as mkdtemp3, mkdir as mkdir10, writeFile as writeFile10, rm as rm6, stat as stat8, chmod as chmod3 } from "node:fs/promises";
15681
15715
  import { tmpdir as tmpdir3 } from "node:os";
15682
15716
  import { join as join18 } from "node:path";
15683
- import { pathToFileURL } from "node:url";
15684
15717
  import { homedir as homedir5 } from "node:os";
15685
15718
  function attributeKillCause(result, recorded) {
15686
15719
  if (recorded) return recorded;
@@ -16428,6 +16461,7 @@ async function runJob(ctx, job, claimDeviceId) {
16428
16461
  };
16429
16462
  let metrics = { durations_ms: timings };
16430
16463
  let logs = { stdout: "", stderr: "" };
16464
+ let sentPrompt;
16431
16465
  const cleanupFns = [];
16432
16466
  const heartbeat = setInterval(() => {
16433
16467
  rpcHeartbeat(ctx, job.id, deviceId);
@@ -16561,6 +16595,7 @@ async function runJob(ctx, job, claimDeviceId) {
16561
16595
  }
16562
16596
  const bugsFolderId = await ensureBugsFolder(ctx.supabase, job.project_id);
16563
16597
  const prompt = buildDiffPrompt(job, projectSlug, diff, worktreeDir, affectedSeed, bugsFolderId);
16598
+ sentPrompt = prompt;
16564
16599
  if (cancelled.has(job.id)) {
16565
16600
  log(`cancelled before claude spawn, aborting`);
16566
16601
  return;
@@ -16677,9 +16712,14 @@ async function runJob(ctx, job, claimDeviceId) {
16677
16712
  return;
16678
16713
  }
16679
16714
  const msg = err2 instanceof Error ? err2.message : String(err2);
16680
- const haystack = `${msg}
16681
- ${logs.stderr}`;
16682
- const plan = rateLimitDeferPlanForExit({ message: msg, stderr: logs.stderr, stdout: logs.stdout });
16715
+ const haystack = stripPromptEcho(`${msg}
16716
+ ${logs.stderr}`, sentPrompt);
16717
+ const plan = rateLimitDeferPlanForExit({
16718
+ message: msg,
16719
+ stderr: logs.stderr,
16720
+ stdout: logs.stdout,
16721
+ prompt: sentPrompt
16722
+ });
16683
16723
  if (plan) {
16684
16724
  const { parsed, pauseUntil, info } = plan;
16685
16725
  if (!parsed) {
@@ -16862,6 +16902,11 @@ async function signInAccount(creds, makeAuth) {
16862
16902
  }
16863
16903
  return { active, lastErr, usedFallback };
16864
16904
  }
16905
+ function exitDaemon(code) {
16906
+ const notice = supervisedStopNotice(code, process.env);
16907
+ if (notice) console.error(notice);
16908
+ process.exit(resolveExitCode(code, process.env));
16909
+ }
16865
16910
  async function runDaemon() {
16866
16911
  let lockHandle = null;
16867
16912
  try {
@@ -16869,7 +16914,7 @@ async function runDaemon() {
16869
16914
  } catch (err2) {
16870
16915
  if (err2 instanceof SingleInstanceLockedError) {
16871
16916
  console.error(String(err2.message));
16872
- process.exit(64);
16917
+ process.exit(SINGLE_INSTANCE_EXIT_CODE);
16873
16918
  }
16874
16919
  throw err2;
16875
16920
  }
@@ -16885,7 +16930,7 @@ async function runDaemon() {
16885
16930
  } catch {
16886
16931
  }
16887
16932
  }
16888
- process.exit(1);
16933
+ exitDaemon(1);
16889
16934
  }
16890
16935
  throw err2;
16891
16936
  }
@@ -16916,7 +16961,7 @@ async function runDaemon() {
16916
16961
  } catch {
16917
16962
  }
16918
16963
  }
16919
- process.exit(2);
16964
+ exitDaemon(2);
16920
16965
  }
16921
16966
  let credentials;
16922
16967
  try {
@@ -16930,7 +16975,7 @@ async function runDaemon() {
16930
16975
  } catch {
16931
16976
  }
16932
16977
  }
16933
- process.exit(78);
16978
+ exitDaemon(78);
16934
16979
  }
16935
16980
  throw err2;
16936
16981
  }
@@ -16945,7 +16990,7 @@ async function runDaemon() {
16945
16990
  } catch {
16946
16991
  }
16947
16992
  }
16948
- process.exit(75);
16993
+ exitDaemon(75);
16949
16994
  }
16950
16995
  const auth = active.auth;
16951
16996
  const client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
@@ -17127,6 +17172,7 @@ var init_run = __esm({
17127
17172
  init_dispatch();
17128
17173
  init_defaults();
17129
17174
  init_singleInstanceLock();
17175
+ init_supervision();
17130
17176
  init_paths();
17131
17177
  init_semaphore();
17132
17178
  init_bearer();
@@ -17327,12 +17373,6 @@ ${ANCHOR_VERIFICATION}`;
17327
17373
  return daemonVersion;
17328
17374
  }
17329
17375
  };
17330
- if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
17331
- runDaemon().catch((err2) => {
17332
- console.error("fatal:", err2);
17333
- process.exit(1);
17334
- });
17335
- }
17336
17376
  }
17337
17377
  });
17338
17378
 
@@ -19267,7 +19307,14 @@ async function registerLocalWatch(args) {
19267
19307
  p_project_id: projectId,
19268
19308
  p_repo_owner: target.repoOwner,
19269
19309
  p_repo_name: target.repoName,
19270
- p_branch: target.repoBranch
19310
+ p_branch: target.repoBranch,
19311
+ // The ArchMe branch has to be RECORDED, not re-derived by each reader.
19312
+ // The poller resolves it from the row (`commit_update_archme_branch ??
19313
+ // branch`, mirroring the webhook); with the column unset it fell back to
19314
+ // the git branch, so registration checked `main-scan` while polling read
19315
+ // `main`, found no marker, and idled forever with everything looking
19316
+ // healthy. Migration 111.
19317
+ p_archme_branch: target.archmeBranch
19271
19318
  });
19272
19319
  if (watchErr || typeof watchedRepoId !== "string") {
19273
19320
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archme/cli",
3
- "version": "0.4.3",
3
+ "version": "0.4.5",
4
4
  "description": "Map your codebase into a living architecture diagram: run `archme init` in a repo to scan it, `archme daemon start` to keep it current on every commit.",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE",