@archme/cli 0.4.5 → 0.5.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.
Files changed (3) hide show
  1. package/README.md +26 -0
  2. package/dist/cli.js +667 -87
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -42,6 +42,28 @@ Watching a repo needs the ArchMe GitHub App installed on it; `archme init` tells
42
42
  you if that step is still missing. Not up for hosting the agent yourself? Get in
43
43
  touch at [archme.io](https://archme.io) and we'll run it for you.
44
44
 
45
+ ## 3. Remove it
46
+
47
+ ```bash
48
+ archme uninstall
49
+ ```
50
+
51
+ From inside the repo. It lists everything it will change and asks before doing
52
+ any of it:
53
+
54
+ - **The agent config** — the `archme` MCP server from `.mcp.json`, the ArchMe
55
+ block from `CLAUDE.md`, the architecture skill. Your own config in those files
56
+ is left alone, and a file we created is deleted rather than left empty.
57
+ - **Watching** — so commits stop updating the architecture. Removing the config
58
+ on its own would leave the board moving while looking uninstalled, which is
59
+ why this is the default; `--keep-watching` opts out.
60
+ - **The daemon** — but only if no other repo is left for it to serve. It's one
61
+ service per account, so uninstalling one repo never stops the others.
62
+
63
+ Your architecture on archme.io and this machine's login survive (`archme logout`
64
+ for the latter). The ArchMe GitHub App installation is org-wide, so it stays —
65
+ remove it on github.com if you want it gone.
66
+
45
67
  ---
46
68
 
47
69
  ## Other commands
@@ -49,6 +71,7 @@ touch at [archme.io](https://archme.io) and we'll run it for you.
49
71
  | Command | Purpose |
50
72
  |---|---|
51
73
  | `archme init [repo-path]` | Guided setup + scan (see above); `--force` re-scans |
74
+ | `archme uninstall [repo-path]` | Stop ArchMe for a repo: agent config, watching, and the daemon if idle |
52
75
  | `archme scan <repo-path>` | Low-level single-pass scan into an explicit `--project` / `--branch` |
53
76
  | `archme daemon <sub>` | `start` · `stop` · `restart` · `status` · `logs` · `enable` · `disable` · `install` · `uninstall` · `run` |
54
77
  | `archme analyze <sub>` | Tree-sitter code analysis: `outline` · `repo` · `diff` · `match` · `prefetch` |
@@ -61,6 +84,9 @@ Every command takes `--help`. Useful flags:
61
84
  (scans are interactive by default, so you can steer them as they go).
62
85
  - `archme daemon status` — is it installed, running, and signed in?
63
86
  `archme daemon logs -f` follows the log.
87
+ - `archme uninstall --keep-watching` — removes the agent config but leaves the
88
+ repo watched and the daemon running (see above); `--yes` skips the
89
+ confirmation. Run it from anywhere in the repo — it resolves the repo root.
64
90
  - `archme analyze outline <file>` — JSON symbol outline for one file; `repo`
65
91
  outlines everything, `diff <range>` only what a git range touched. Supports
66
92
  TypeScript, TSX, JavaScript, Python, Go, Java, Ruby, Rust, Swift, and Kotlin.
package/dist/cli.js CHANGED
@@ -1091,12 +1091,12 @@ var require_isexe = __commonJS({
1091
1091
  if (typeof Promise !== "function") {
1092
1092
  throw new TypeError("callback not provided");
1093
1093
  }
1094
- return new Promise(function(resolve5, reject) {
1094
+ return new Promise(function(resolve6, reject) {
1095
1095
  isexe(path7, options || {}, function(er, is) {
1096
1096
  if (er) {
1097
1097
  reject(er);
1098
1098
  } else {
1099
- resolve5(is);
1099
+ resolve6(is);
1100
1100
  }
1101
1101
  });
1102
1102
  });
@@ -1162,27 +1162,27 @@ var require_which = __commonJS({
1162
1162
  opt = {};
1163
1163
  const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt);
1164
1164
  const found = [];
1165
- const step = (i2) => new Promise((resolve5, reject) => {
1165
+ const step = (i2) => new Promise((resolve6, reject) => {
1166
1166
  if (i2 === pathEnv.length)
1167
- return opt.all && found.length ? resolve5(found) : reject(getNotFoundError(cmd));
1167
+ return opt.all && found.length ? resolve6(found) : reject(getNotFoundError(cmd));
1168
1168
  const ppRaw = pathEnv[i2];
1169
1169
  const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw;
1170
1170
  const pCmd = path7.join(pathPart, cmd);
1171
1171
  const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd : pCmd;
1172
- resolve5(subStep(p, i2, 0));
1172
+ resolve6(subStep(p, i2, 0));
1173
1173
  });
1174
- const subStep = (p, i2, ii) => new Promise((resolve5, reject) => {
1174
+ const subStep = (p, i2, ii) => new Promise((resolve6, reject) => {
1175
1175
  if (ii === pathExt.length)
1176
- return resolve5(step(i2 + 1));
1176
+ return resolve6(step(i2 + 1));
1177
1177
  const ext2 = pathExt[ii];
1178
1178
  isexe(p + ext2, { pathExt: pathExtExe }, (er, is) => {
1179
1179
  if (!er && is) {
1180
1180
  if (opt.all)
1181
1181
  found.push(p + ext2);
1182
1182
  else
1183
- return resolve5(p + ext2);
1183
+ return resolve6(p + ext2);
1184
1184
  }
1185
- return resolve5(subStep(p, i2, ii + 1));
1185
+ return resolve6(subStep(p, i2, ii + 1));
1186
1186
  });
1187
1187
  });
1188
1188
  return cb ? step(0).then((res) => cb(null, res), cb) : step(0);
@@ -2262,8 +2262,8 @@ var init_deferred = __esm({
2262
2262
  "node_modules/execa/lib/utils/deferred.js"() {
2263
2263
  createDeferred = () => {
2264
2264
  const methods = {};
2265
- const promise = new Promise((resolve5, reject) => {
2266
- Object.assign(methods, { resolve: resolve5, reject });
2265
+ const promise = new Promise((resolve6, reject) => {
2266
+ Object.assign(methods, { resolve: resolve6, reject });
2267
2267
  });
2268
2268
  return Object.assign(promise, methods);
2269
2269
  };
@@ -7560,11 +7560,11 @@ var init_concurrent = __esm({
7560
7560
  const promises = weakMap.get(stream);
7561
7561
  const promise = createDeferred();
7562
7562
  promises.push(promise);
7563
- const resolve5 = promise.resolve.bind(promise);
7564
- return { resolve: resolve5, promises };
7563
+ const resolve6 = promise.resolve.bind(promise);
7564
+ return { resolve: resolve6, promises };
7565
7565
  };
7566
- waitForConcurrentStreams = async ({ resolve: resolve5, promises }, subprocess) => {
7567
- resolve5();
7566
+ waitForConcurrentStreams = async ({ resolve: resolve6, promises }, subprocess) => {
7567
+ resolve6();
7568
7568
  const [isSubprocessExit] = await Promise.race([
7569
7569
  Promise.allSettled([true, subprocess]),
7570
7570
  Promise.all([false, ...promises])
@@ -8636,7 +8636,7 @@ async function runLogin() {
8636
8636
  const expiresAt = Date.now() + data.expires_in * 1e3;
8637
8637
  let intervalMs = Math.max(data.interval, 1) * 1e3;
8638
8638
  while (Date.now() < expiresAt) {
8639
- await new Promise((resolve5) => setTimeout(resolve5, intervalMs));
8639
+ await new Promise((resolve6) => setTimeout(resolve6, intervalMs));
8640
8640
  const poll = await postJson("/poll", {
8641
8641
  device_code: data.device_code
8642
8642
  });
@@ -8877,7 +8877,7 @@ var init_launchctl = __esm({
8877
8877
  if (r.exitCode === 0 || r.exitCode === 17) return;
8878
8878
  if (r.exitCode === 5) {
8879
8879
  await this.opts.spawner.run("launchctl", ["bootout", SERVICE_TARGET(this.opts.uid)]);
8880
- await new Promise((resolve5) => setTimeout(resolve5, 500));
8880
+ await new Promise((resolve6) => setTimeout(resolve6, 500));
8881
8881
  r = await bootstrap();
8882
8882
  if (r.exitCode === 0 || r.exitCode === 17) return;
8883
8883
  }
@@ -9022,6 +9022,13 @@ var init_systemctl = __esm({
9022
9022
  });
9023
9023
 
9024
9024
  // src/daemon/service/serviceController.ts
9025
+ var serviceController_exports = {};
9026
+ __export(serviceController_exports, {
9027
+ orchestrateStart: () => orchestrateStart,
9028
+ orchestrateStop: () => orchestrateStop,
9029
+ pickController: () => pickController,
9030
+ reinstallIfChanged: () => reinstallIfChanged
9031
+ });
9025
9032
  import { userInfo } from "node:os";
9026
9033
  function pickController(platform3 = process.platform, env = process.env, spawner = execaSpawner) {
9027
9034
  const unitPath = resolveUnitPath(platform3, env);
@@ -9256,7 +9263,7 @@ async function tailLog(opts = {}) {
9256
9263
  }
9257
9264
  }
9258
9265
  if (!opts.follow) return;
9259
- await new Promise((resolve5, reject) => {
9266
+ await new Promise((resolve6, reject) => {
9260
9267
  let fileWatcher = null;
9261
9268
  let dirWatcher = null;
9262
9269
  const dir = dirname6(path7);
@@ -9320,7 +9327,7 @@ async function tailLog(opts = {}) {
9320
9327
  const handleSigint = () => {
9321
9328
  fileWatcher?.close();
9322
9329
  dirWatcher?.close();
9323
- resolve5();
9330
+ resolve6();
9324
9331
  };
9325
9332
  process.once("SIGINT", handleSigint);
9326
9333
  });
@@ -14258,7 +14265,7 @@ var init_semaphore = __esm({
14258
14265
  acquire() {
14259
14266
  const slot = this.tryAcquire();
14260
14267
  if (slot) return Promise.resolve(slot);
14261
- return new Promise((resolve5) => this.waiters.push(resolve5));
14268
+ return new Promise((resolve6) => this.waiters.push(resolve6));
14262
14269
  }
14263
14270
  makeRelease() {
14264
14271
  let released = false;
@@ -16118,12 +16125,12 @@ async function rpcHeartbeat(ctx, jobId, deviceId) {
16118
16125
  );
16119
16126
  if (error) console.error(`[${jobId}] heartbeat error:`, error.message);
16120
16127
  }
16121
- async function rpcComplete(ctx, jobId, deviceId, summary, kind, changed, metrics, logs, advanceMarker = true, affected) {
16128
+ async function rpcComplete(ctx, jobId, deviceId, summary2, kind, changed, metrics, logs, advanceMarker = true, affected) {
16122
16129
  const extra = {
16123
16130
  // `kind` drives lifecycle (squash, marker, retry). `changed` is the
16124
16131
  // decoupled what-changed verdict the Check Run / sticky comment render
16125
16132
  // (migration 083): a structural-only job is kind=applied but changed=false.
16126
- result: { summary, kind, changed },
16133
+ result: { summary: summary2, kind, changed },
16127
16134
  metrics,
16128
16135
  logs,
16129
16136
  advance_marker: advanceMarker
@@ -16657,8 +16664,8 @@ async function runJob(ctx, job, claimDeviceId) {
16657
16664
  if (runResult.isError) {
16658
16665
  throw new Error(`claude reported is_error in its JSON result (no successful completion)`);
16659
16666
  }
16660
- const summary = runResult.summary.trim().slice(0, 4e3);
16661
- if (summary) log(`claude> ${summary.slice(0, 500).replace(/\n/g, " ")}`);
16667
+ const summary2 = runResult.summary.trim().slice(0, 4e3);
16668
+ if (summary2) log(`claude> ${summary2.slice(0, 500).replace(/\n/g, " ")}`);
16662
16669
  metrics.model_served = runResult.modelServed;
16663
16670
  metrics.usage = runResult.usage;
16664
16671
  metrics.cost_usd = runResult.costUsd;
@@ -16685,7 +16692,7 @@ async function runJob(ctx, job, claimDeviceId) {
16685
16692
  throw new Error(outcome.reason);
16686
16693
  }
16687
16694
  const changed = outcome.changed;
16688
- log(`done (${outcome.kind}, changed=${changed}, ${writes} commit(s) [${diffWrites} diff / ${structuralWrites} structural], ${reads ?? "?"} mcp tool call(s)): ${summary.slice(0, 200).replace(/\n/g, " ")}`);
16695
+ log(`done (${outcome.kind}, changed=${changed}, ${writes} commit(s) [${diffWrites} diff / ${structuralWrites} structural], ${reads ?? "?"} mcp tool call(s)): ${summary2.slice(0, 200).replace(/\n/g, " ")}`);
16689
16696
  const totalMs = Date.now() - startedAt;
16690
16697
  markPhase("complete", { kind: outcome.kind, changed, total_ms: totalMs });
16691
16698
  metrics.total_ms = totalMs;
@@ -16693,7 +16700,7 @@ async function runJob(ctx, job, claimDeviceId) {
16693
16700
  ctx,
16694
16701
  job.id,
16695
16702
  deviceId,
16696
- summary,
16703
+ summary2,
16697
16704
  outcome.kind,
16698
16705
  changed,
16699
16706
  metrics,
@@ -18164,14 +18171,14 @@ async function runScan(opts, deps = {}) {
18164
18171
  log(` supabase: ${supabaseUrl}`);
18165
18172
  log(` mcp base: ${mcpUrl}`);
18166
18173
  log(` repo: ${opts.repoPath}`);
18167
- const authed = await createAuthedClient({
18174
+ const authed2 = await createAuthedClient({
18168
18175
  supabaseUrl,
18169
18176
  supabaseAnonKey: supabaseAnon,
18170
18177
  clientId,
18171
18178
  preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
18172
18179
  });
18173
- log(` signed in as ${authed.identity}`);
18174
- const { supabase, userId } = authed;
18180
+ log(` signed in as ${authed2.identity}`);
18181
+ const { supabase, userId } = authed2;
18175
18182
  let headSha;
18176
18183
  try {
18177
18184
  headSha = await repoHeadSha(opts.repoPath);
@@ -18270,7 +18277,7 @@ async function runScan(opts, deps = {}) {
18270
18277
  const result = await proc;
18271
18278
  const exitCode = result.exitCode ?? 0;
18272
18279
  const signal = result.signal ?? null;
18273
- const summary = (finalResultText || (result.stdout ? String(result.stdout) : "")).trim().slice(-4e3);
18280
+ const summary2 = (finalResultText || (result.stdout ? String(result.stdout) : "")).trim().slice(-4e3);
18274
18281
  if (signal) {
18275
18282
  log(`claude exited ${exitCode} (signal: ${signal})`);
18276
18283
  } else {
@@ -18281,8 +18288,8 @@ async function runScan(opts, deps = {}) {
18281
18288
  log(` stories: ${counts.stories}`);
18282
18289
  let signals2 = null;
18283
18290
  try {
18284
- await new Promise((resolve5, reject) => {
18285
- transcriptStream.end((err2) => err2 ? reject(err2) : resolve5());
18291
+ await new Promise((resolve6, reject) => {
18292
+ transcriptStream.end((err2) => err2 ? reject(err2) : resolve6());
18286
18293
  });
18287
18294
  const reportPath = join19(transcriptDir, "report.json");
18288
18295
  const orphansTxtPath = join19(transcriptDir, "orphans.txt");
@@ -18348,7 +18355,7 @@ async function runScan(opts, deps = {}) {
18348
18355
  orgSlug: resolved2.orgSlug,
18349
18356
  projectSlug: resolved2.projectSlug,
18350
18357
  webUrl,
18351
- summary
18358
+ summary: summary2
18352
18359
  };
18353
18360
  } finally {
18354
18361
  await cleanupMcp().catch(() => {
@@ -18959,6 +18966,9 @@ var init_githubAppSetup = __esm({
18959
18966
 
18960
18967
  // src/daemon/prompt.ts
18961
18968
  import { createInterface } from "node:readline/promises";
18969
+ function stdinIsInteractive() {
18970
+ return Boolean(process.stdin.isTTY && process.stdout.isTTY);
18971
+ }
18962
18972
  async function withReadline(fn) {
18963
18973
  const rl = createInterface({ input: process.stdin, output: process.stdout });
18964
18974
  try {
@@ -19241,9 +19251,9 @@ async function runLocalWatch(opts) {
19241
19251
  log(` repo: ${target.repo}`);
19242
19252
  log(` branch: ${target.repoBranch} \u2192 archme branch ${target.archmeBranch}`);
19243
19253
  const cfg = resolveConfig();
19244
- let authed;
19254
+ let authed2;
19245
19255
  try {
19246
- authed = await createAuthedClient({
19256
+ authed2 = await createAuthedClient({
19247
19257
  supabaseUrl: cfg.supabaseUrl,
19248
19258
  supabaseAnonKey: cfg.supabaseAnonKey,
19249
19259
  clientId: cfg.archmeDaemonClientId,
@@ -19253,8 +19263,8 @@ async function runLocalWatch(opts) {
19253
19263
  console.error(`archme local-watch: ${err2 instanceof Error ? err2.message : String(err2)}`);
19254
19264
  return 3;
19255
19265
  }
19256
- const { supabase, userId } = authed;
19257
- const orgId = authed.orgId;
19266
+ const { supabase, userId } = authed2;
19267
+ const orgId = authed2.orgId;
19258
19268
  if (!orgId) {
19259
19269
  console.error("archme local-watch: this login is not paired with an org \u2014 run `archme login`");
19260
19270
  return 3;
@@ -19494,22 +19504,22 @@ async function runInit(opts, deps = {}) {
19494
19504
  } else {
19495
19505
  log("\u2713 already signed in");
19496
19506
  }
19497
- const authed = await authenticate({
19507
+ const authed2 = await authenticate({
19498
19508
  supabaseUrl: cfg.supabaseUrl,
19499
19509
  supabaseAnonKey: cfg.supabaseAnonKey,
19500
19510
  clientId: cfg.archmeDaemonClientId,
19501
19511
  preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
19502
19512
  });
19503
- log(` signed in as ${authed.identity}`);
19513
+ log(` signed in as ${authed2.identity}`);
19504
19514
  let fullSlug = opts.project;
19505
19515
  if (!fullSlug) {
19506
- const orgId = authed.orgId;
19516
+ const orgId = authed2.orgId;
19507
19517
  if (!orgId) {
19508
19518
  throw new Error(
19509
19519
  "could not resolve your organization \u2014 run `archme login` again, or pass --project <org>/<slug>"
19510
19520
  );
19511
19521
  }
19512
- const orgSlug = await resolveOrgSlug(authed.supabase, orgId);
19522
+ const orgSlug = await resolveOrgSlug(authed2.supabase, orgId);
19513
19523
  if (!orgSlug) {
19514
19524
  throw new Error(
19515
19525
  `paired org ${orgId} no longer exists \u2014 run \`archme login\` again, or pass --project <org>/<slug>`
@@ -19518,8 +19528,8 @@ async function runInit(opts, deps = {}) {
19518
19528
  fullSlug = defaultProjectSlug(orgSlug, repoPath);
19519
19529
  }
19520
19530
  const project = await resolveProject(
19521
- authed.supabase,
19522
- authed.userId,
19531
+ authed2.supabase,
19532
+ authed2.userId,
19523
19533
  fullSlug
19524
19534
  );
19525
19535
  log(
@@ -19527,7 +19537,7 @@ async function runInit(opts, deps = {}) {
19527
19537
  );
19528
19538
  const scanBranch = initScanBranch(project.defaultBranch);
19529
19539
  const detail = await fetchArchitecture(
19530
- authed.supabase,
19540
+ authed2.supabase,
19531
19541
  project.projectId,
19532
19542
  scanBranch
19533
19543
  );
@@ -19541,8 +19551,8 @@ async function runInit(opts, deps = {}) {
19541
19551
  await runInstallScript(script, repoPath);
19542
19552
  ran.push("agent-config");
19543
19553
  const watched = await watchRepo({
19544
- supabase: authed.supabase,
19545
- orgId: authed.orgId,
19554
+ supabase: authed2.supabase,
19555
+ orgId: authed2.orgId,
19546
19556
  projectId: project.projectId,
19547
19557
  repoSlug: git4.repoSlug,
19548
19558
  branch: git4.branch,
@@ -19609,18 +19619,18 @@ Your board: ${webUrl}`);
19609
19619
  startGitHubAppInstall: () => (deps.buildInstallUrl ?? buildInstallUrl)({
19610
19620
  supabaseUrl: cfg.supabaseUrl,
19611
19621
  accessToken: getCurrentAccessToken() ?? "",
19612
- orgId: authed.orgId
19622
+ orgId: authed2.orgId
19613
19623
  }),
19614
19624
  waitForInstallation: () => (deps.waitForInstallation ?? waitForInstallation)({
19615
- hasInstallation: () => (deps.orgHasInstallation ?? orgHasInstallation)(authed.supabase, authed.orgId),
19625
+ hasInstallation: () => (deps.orgHasInstallation ?? orgHasInstallation)(authed2.supabase, authed2.orgId),
19616
19626
  sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
19617
19627
  now: () => Date.now(),
19618
19628
  log
19619
19629
  }),
19620
19630
  watchRepo: async () => {
19621
19631
  const w = await watchRepo({
19622
- supabase: authed.supabase,
19623
- orgId: authed.orgId,
19632
+ supabase: authed2.supabase,
19633
+ orgId: authed2.orgId,
19624
19634
  projectId: project.projectId,
19625
19635
  repoSlug: git4.repoSlug,
19626
19636
  branch: git4.branch,
@@ -19642,8 +19652,8 @@ Your board: ${webUrl}`);
19642
19652
  });
19643
19653
  if (!target.ok) return { ok: false, reason: target.error };
19644
19654
  const res = await (deps.registerLocalWatch ?? registerLocalWatch)({
19645
- supabase: authed.supabase,
19646
- orgId: authed.orgId,
19655
+ supabase: authed2.supabase,
19656
+ orgId: authed2.orgId,
19647
19657
  projectId: project.projectId,
19648
19658
  repoPath: git4.root,
19649
19659
  originUrl: git4.originUrl,
@@ -19852,12 +19862,575 @@ var init_dispatch4 = __esm({
19852
19862
  }
19853
19863
  });
19854
19864
 
19865
+ // src/uninstall/edits.ts
19866
+ function stripArchmeClaudeBlock(content) {
19867
+ const start = content.indexOf(START_MARKER);
19868
+ if (start === -1) return UNCHANGED;
19869
+ const end = content.indexOf(END_MARKER, start);
19870
+ if (end === -1) return UNCHANGED;
19871
+ const before = content.slice(0, start).replace(/\n+$/, "");
19872
+ const after = content.slice(end + END_MARKER.length).replace(/^\n+/, "");
19873
+ if (!before.trim() && !after.trim()) return { kind: "delete" };
19874
+ if (!before.trim()) return { kind: "rewrite", content: `${after.replace(/\n*$/, "")}
19875
+ ` };
19876
+ if (!after.trim()) return { kind: "rewrite", content: `${before}
19877
+ ` };
19878
+ return { kind: "rewrite", content: `${before}
19879
+
19880
+ ${after.replace(/\n*$/, "")}
19881
+ ` };
19882
+ }
19883
+ function parseObject(raw) {
19884
+ try {
19885
+ const parsed = JSON.parse(raw);
19886
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
19887
+ return parsed;
19888
+ } catch {
19889
+ return null;
19890
+ }
19891
+ }
19892
+ function render(value) {
19893
+ return `${JSON.stringify(value, null, 2)}
19894
+ `;
19895
+ }
19896
+ function stripArchmeMcpServer(content) {
19897
+ const root = parseObject(content);
19898
+ if (!root) return UNCHANGED;
19899
+ const servers = root.mcpServers;
19900
+ if (!servers || typeof servers !== "object" || Array.isArray(servers)) return UNCHANGED;
19901
+ const entries = servers;
19902
+ if (!(MCP_SERVER_NAME2 in entries)) return UNCHANGED;
19903
+ delete entries[MCP_SERVER_NAME2];
19904
+ const onlyKey = Object.keys(root).length === 1 && "mcpServers" in root;
19905
+ if (onlyKey && Object.keys(entries).length === 0) return { kind: "delete" };
19906
+ return { kind: "rewrite", content: render(root) };
19907
+ }
19908
+ function isArchmeHook(hook) {
19909
+ const command = hook?.command;
19910
+ return typeof command === "string" && command.includes(NUDGE_HOOK_FILE);
19911
+ }
19912
+ function stripArchmeHooks(content) {
19913
+ const root = parseObject(content);
19914
+ if (!root) return UNCHANGED;
19915
+ const hooks = root.hooks;
19916
+ if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return UNCHANGED;
19917
+ const byEvent = hooks;
19918
+ const pre = byEvent.PreToolUse;
19919
+ if (!Array.isArray(pre)) return UNCHANGED;
19920
+ let changed = false;
19921
+ const kept = [];
19922
+ for (const entry of pre) {
19923
+ const inner = entry?.hooks;
19924
+ if (!Array.isArray(inner) || !inner.some(isArchmeHook)) {
19925
+ kept.push(entry);
19926
+ continue;
19927
+ }
19928
+ changed = true;
19929
+ const survivors = inner.filter((h2) => !isArchmeHook(h2));
19930
+ if (survivors.length > 0) kept.push({ ...entry, hooks: survivors });
19931
+ }
19932
+ if (!changed) return UNCHANGED;
19933
+ if (kept.length > 0) byEvent.PreToolUse = kept;
19934
+ else delete byEvent.PreToolUse;
19935
+ if (Object.keys(byEvent).length === 0) delete root.hooks;
19936
+ if (Object.keys(root).length === 0) return { kind: "delete" };
19937
+ return { kind: "rewrite", content: render(root) };
19938
+ }
19939
+ var UNCHANGED, START_MARKER, END_MARKER, NUDGE_HOOK_FILE, MCP_SERVER_NAME2;
19940
+ var init_edits = __esm({
19941
+ "src/uninstall/edits.ts"() {
19942
+ "use strict";
19943
+ UNCHANGED = { kind: "unchanged" };
19944
+ START_MARKER = "<!-- ARCHME-START -->";
19945
+ END_MARKER = "<!-- ARCHME-END -->";
19946
+ NUDGE_HOOK_FILE = "archme-search-nudge.mjs";
19947
+ MCP_SERVER_NAME2 = "archme";
19948
+ }
19949
+ });
19950
+
19951
+ // src/uninstall/watching.ts
19952
+ function decideDaemonRemoval(input) {
19953
+ if (!input.installed) {
19954
+ return { action: "keep", reason: "no daemon service is installed on this machine" };
19955
+ }
19956
+ if (input.keepWatching) {
19957
+ return { action: "keep", reason: "--keep-watching: this repo is still being watched" };
19958
+ }
19959
+ if (input.remaining.watchedRepos === null) {
19960
+ return {
19961
+ action: "keep",
19962
+ reason: "could not check what else it serves, so it was left running"
19963
+ };
19964
+ }
19965
+ const { watchedRepos, localEntries } = input.remaining;
19966
+ if (watchedRepos > 0 || localEntries > 0) {
19967
+ const parts = [];
19968
+ if (watchedRepos > 0) parts.push(`${watchedRepos} watched repo${watchedRepos === 1 ? "" : "s"}`);
19969
+ if (localEntries > 0) {
19970
+ parts.push(`${localEntries} locally-polled repo${localEntries === 1 ? "" : "s"}`);
19971
+ }
19972
+ return { action: "keep", reason: `still serving ${parts.join(" and ")}` };
19973
+ }
19974
+ return { action: "remove", reason: "nothing is left for it to do" };
19975
+ }
19976
+ function rowsForSlug(rows, slug) {
19977
+ const wanted = slug.toLowerCase();
19978
+ return rows.filter((r) => r.slug.toLowerCase() === wanted);
19979
+ }
19980
+ var init_watching = __esm({
19981
+ "src/uninstall/watching.ts"() {
19982
+ "use strict";
19983
+ }
19984
+ });
19985
+
19986
+ // src/uninstall/backend.ts
19987
+ var backend_exports = {};
19988
+ __export(backend_exports, {
19989
+ createDefaultBackend: () => createDefaultBackend
19990
+ });
19991
+ function labelFor(row) {
19992
+ const org = row.projects?.organizations?.slug;
19993
+ const project = row.projects?.slug;
19994
+ const where = org && project ? `${org}/${project}` : `${row.repo_owner}/${row.repo_name}`;
19995
+ return `${where} @ ${row.branch}`;
19996
+ }
19997
+ function createDefaultBackend() {
19998
+ let clientPromise = null;
19999
+ const supabase = () => clientPromise ??= authed();
20000
+ return {
20001
+ async gitRoot(dir) {
20002
+ try {
20003
+ const { stdout } = await execa("git", ["rev-parse", "--show-toplevel"], { cwd: dir });
20004
+ return stdout.trim() || null;
20005
+ } catch {
20006
+ return null;
20007
+ }
20008
+ },
20009
+ async repoSlug(repoPath) {
20010
+ const origin = await readOriginUrl(repoPath);
20011
+ return origin ? parseGitHubRepo(origin) : null;
20012
+ },
20013
+ async listWatchedRepos() {
20014
+ const { supabase: client } = await supabase();
20015
+ const { data, error } = await client.from("watched_repos").select("id, repo_owner, repo_name, branch, projects(slug, organizations(slug))").is("deleted_at", null);
20016
+ if (error) throw new Error(error.message);
20017
+ return (data ?? []).map((row) => ({
20018
+ id: row.id,
20019
+ slug: `${row.repo_owner}/${row.repo_name}`,
20020
+ label: labelFor(row)
20021
+ }));
20022
+ },
20023
+ async unwatch(ids) {
20024
+ if (ids.length === 0) return;
20025
+ const { supabase: client } = await supabase();
20026
+ const now = (/* @__PURE__ */ new Date()).toISOString();
20027
+ const { error } = await client.from("watched_repos").update({ deleted_at: now, updated_at: now }).in("id", ids);
20028
+ if (error) throw new Error(error.message);
20029
+ },
20030
+ async localRepoSlugs() {
20031
+ return (await loadLocalRepos()).map((e) => slugOf(e));
20032
+ },
20033
+ async removeLocalRepos(slug) {
20034
+ const repos = await loadLocalRepos();
20035
+ const kept = removeLocalReposBySlug(repos, slug);
20036
+ const dropped = repos.length - kept.length;
20037
+ if (dropped > 0) await saveLocalRepos(kept);
20038
+ return dropped;
20039
+ },
20040
+ async daemonInstalled() {
20041
+ const { pickController: pickController2 } = await Promise.resolve().then(() => (init_serviceController(), serviceController_exports));
20042
+ return await pickController2().unitExists();
20043
+ },
20044
+ async removeDaemon() {
20045
+ const { pickController: pickController2, orchestrateStop: orchestrateStop2 } = await Promise.resolve().then(() => (init_serviceController(), serviceController_exports));
20046
+ const controller = pickController2();
20047
+ await orchestrateStop2(controller);
20048
+ await controller.uninstall();
20049
+ }
20050
+ };
20051
+ }
20052
+ async function authed() {
20053
+ const cfg = resolveConfig();
20054
+ return await createAuthedClient({
20055
+ supabaseUrl: cfg.supabaseUrl,
20056
+ supabaseAnonKey: cfg.supabaseAnonKey,
20057
+ clientId: cfg.archmeDaemonClientId,
20058
+ preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
20059
+ });
20060
+ }
20061
+ var init_backend = __esm({
20062
+ "src/uninstall/backend.ts"() {
20063
+ "use strict";
20064
+ init_execa();
20065
+ init_authedClient();
20066
+ init_defaults();
20067
+ init_localGit();
20068
+ init_github_remote();
20069
+ init_localRepos();
20070
+ }
20071
+ });
20072
+
20073
+ // src/uninstall/run-uninstall.ts
20074
+ var run_uninstall_exports = {};
20075
+ __export(run_uninstall_exports, {
20076
+ EX_OK: () => EX_OK3,
20077
+ EX_USAGE: () => EX_USAGE3,
20078
+ runUninstall: () => runUninstall
20079
+ });
20080
+ import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
20081
+ import { join as join23 } from "node:path";
20082
+ function readIfExists(path7) {
20083
+ try {
20084
+ return existsSync3(path7) ? readFileSync5(path7, "utf8") : null;
20085
+ } catch {
20086
+ return null;
20087
+ }
20088
+ }
20089
+ function isParseableObject(raw) {
20090
+ try {
20091
+ const parsed = JSON.parse(raw);
20092
+ return Boolean(parsed) && typeof parsed === "object" && !Array.isArray(parsed);
20093
+ } catch {
20094
+ return false;
20095
+ }
20096
+ }
20097
+ function applyEdit(absolute, edit) {
20098
+ if (edit.kind === "rewrite") writeFileSync3(absolute, edit.content);
20099
+ else if (edit.kind === "delete") rmSync(absolute, { force: true });
20100
+ }
20101
+ async function runUninstall(opts) {
20102
+ const log = opts.log ?? ((m) => console.log(m));
20103
+ const error = opts.error ?? ((m) => console.error(m));
20104
+ const backend = opts.backend ?? await defaultBackend();
20105
+ const repoPath = await backend.gitRoot(opts.repoPath).catch(() => null) ?? opts.repoPath;
20106
+ if (repoPath !== opts.repoPath) log(`Using the repo root: ${repoPath}`);
20107
+ const abs = (rel) => join23(repoPath, rel);
20108
+ const actions = [];
20109
+ const skipped = [];
20110
+ const mcpRaw = readIfExists(abs(".mcp.json"));
20111
+ if (mcpRaw !== null) {
20112
+ const edit = stripArchmeMcpServer(mcpRaw);
20113
+ if (edit.kind !== "unchanged") {
20114
+ actions.push({
20115
+ path: ".mcp.json",
20116
+ label: edit.kind === "delete" ? `delete (it holds only the ${MCP_SERVER_NAME2} server)` : `remove the ${MCP_SERVER_NAME2} server`,
20117
+ apply: () => applyEdit(abs(".mcp.json"), edit)
20118
+ });
20119
+ } else if (!isParseableObject(mcpRaw)) {
20120
+ skipped.push(
20121
+ `.mcp.json \u2014 could not be parsed as JSON; if it has an ${MCP_SERVER_NAME2} server, remove it by hand`
20122
+ );
20123
+ }
20124
+ }
20125
+ const claudeRaw = readIfExists(abs("CLAUDE.md"));
20126
+ if (claudeRaw !== null) {
20127
+ const edit = stripArchmeClaudeBlock(claudeRaw);
20128
+ if (edit.kind !== "unchanged") {
20129
+ actions.push({
20130
+ path: "CLAUDE.md",
20131
+ label: edit.kind === "delete" ? "delete (it holds only the ArchitectMe block)" : "remove the ArchitectMe block",
20132
+ apply: () => applyEdit(abs("CLAUDE.md"), edit)
20133
+ });
20134
+ } else if (claudeRaw.includes(START_MARKER)) {
20135
+ skipped.push("CLAUDE.md \u2014 the ArchitectMe block has no closing marker; remove it by hand");
20136
+ }
20137
+ }
20138
+ const skillPath = ".claude/skills/architecture/SKILL.md";
20139
+ if (existsSync3(abs(skillPath))) {
20140
+ actions.push({
20141
+ path: skillPath,
20142
+ label: "delete the architecture skill",
20143
+ apply: () => rmSync(abs(skillPath), { force: true })
20144
+ });
20145
+ }
20146
+ const hookPath = `.claude/hooks/${NUDGE_HOOK_FILE}`;
20147
+ if (existsSync3(abs(hookPath))) {
20148
+ actions.push({
20149
+ path: hookPath,
20150
+ label: "delete the retired search-nudge hook",
20151
+ apply: () => rmSync(abs(hookPath), { force: true })
20152
+ });
20153
+ }
20154
+ const settingsRaw = readIfExists(abs(".claude/settings.json"));
20155
+ if (settingsRaw !== null) {
20156
+ const edit = stripArchmeHooks(settingsRaw);
20157
+ if (edit.kind !== "unchanged") {
20158
+ actions.push({
20159
+ path: ".claude/settings.json",
20160
+ label: edit.kind === "delete" ? "delete (it holds only the ArchitectMe hook)" : "unregister the search-nudge hook",
20161
+ apply: () => applyEdit(abs(".claude/settings.json"), edit)
20162
+ });
20163
+ } else if (!isParseableObject(settingsRaw)) {
20164
+ skipped.push(
20165
+ ".claude/settings.json \u2014 could not be parsed as JSON; if it registers the ArchMe hook, remove that entry by hand"
20166
+ );
20167
+ }
20168
+ }
20169
+ for (const note of skipped) log(`Left alone: ${note}`);
20170
+ const keepWatching = opts.keepWatching ?? false;
20171
+ const slug = keepWatching ? null : await backend.repoSlug(repoPath).catch(() => null);
20172
+ let allWatched = null;
20173
+ let watchLookupError = null;
20174
+ if (slug) {
20175
+ try {
20176
+ allWatched = await backend.listWatchedRepos();
20177
+ } catch (err2) {
20178
+ watchLookupError = err2 instanceof Error ? err2.message : String(err2);
20179
+ }
20180
+ }
20181
+ const toUnwatch = allWatched ? rowsForSlug(allWatched, slug) : [];
20182
+ let localForSlug = 0;
20183
+ let allLocal = [];
20184
+ if (slug) {
20185
+ allLocal = await backend.localRepoSlugs().catch(() => []);
20186
+ localForSlug = allLocal.filter((s) => s.toLowerCase() === slug.toLowerCase()).length;
20187
+ }
20188
+ if (actions.length === 0 && toUnwatch.length === 0 && localForSlug === 0) {
20189
+ log(`No ArchitectMe agent config found in ${repoPath}. Nothing to remove.`);
20190
+ if (watchLookupError) log(watchingUnknownNote(watchLookupError));
20191
+ return { exitCode: EX_OK3, removed: [], unwatched: [] };
20192
+ }
20193
+ const daemonInstalled = await backend.daemonInstalled().catch(() => false);
20194
+ const plan = [
20195
+ ...actions.map((a2) => ` ${a2.path} \u2014 ${a2.label}`),
20196
+ ...toUnwatch.map((r) => ` ${r.label} \u2014 stop watching (no more commits will update it)`),
20197
+ ...localForSlug > 0 ? [` ${slug} \u2014 stop polling from this machine (${localForSlug} registry entry)`] : [],
20198
+ // Stated up front, not just reported after: a repo-scoped command removing
20199
+ // a machine-wide service is the most surprising thing here. Whether it
20200
+ // actually goes is still decided afterwards, against what really happened.
20201
+ ...daemonInstalled && !keepWatching ? [" the daemon service \u2014 removed only if this leaves it nothing to serve"] : []
20202
+ ];
20203
+ if (!opts.yes) {
20204
+ const interactive = opts.interactive ?? stdinIsInteractive();
20205
+ if (!interactive && !opts.confirm) {
20206
+ error(
20207
+ "archme uninstall: refusing to edit files without confirmation.\nThere is nobody to ask (stdin is not a terminal). Re-run with --yes."
20208
+ );
20209
+ return { exitCode: EX_USAGE3, removed: [], unwatched: [] };
20210
+ }
20211
+ const confirm = opts.confirm ?? (async (lines) => {
20212
+ log(`This will change, in ${repoPath} and on your ArchMe account:
20213
+ `);
20214
+ lines.forEach((l) => log(l));
20215
+ log("");
20216
+ return await promptYesNo("Uninstall ArchitectMe for this repo?");
20217
+ });
20218
+ if (!await confirm(plan)) {
20219
+ log("Cancelled \u2014 nothing was changed.");
20220
+ return { exitCode: EX_OK3, removed: [], unwatched: [] };
20221
+ }
20222
+ }
20223
+ const removed = [];
20224
+ for (const action of actions) {
20225
+ action.apply();
20226
+ removed.push(action.path);
20227
+ log(`Removed: ${action.path} \u2014 ${action.label}`);
20228
+ }
20229
+ for (const dir of PRUNABLE_DIRS) {
20230
+ const path7 = abs(dir);
20231
+ try {
20232
+ if (existsSync3(path7) && readdirSync(path7).length === 0) rmSync(path7, { recursive: true });
20233
+ } catch {
20234
+ }
20235
+ }
20236
+ const unwatched = [];
20237
+ if (toUnwatch.length > 0) {
20238
+ try {
20239
+ await backend.unwatch(toUnwatch.map((r) => r.id));
20240
+ unwatched.push(...toUnwatch.map((r) => r.id));
20241
+ for (const row of toUnwatch) log(`Stopped watching: ${row.label}`);
20242
+ } catch (err2) {
20243
+ watchLookupError = err2 instanceof Error ? err2.message : String(err2);
20244
+ }
20245
+ }
20246
+ let localRemoved = 0;
20247
+ if (slug && localForSlug > 0) {
20248
+ try {
20249
+ localRemoved = await backend.removeLocalRepos(slug);
20250
+ if (localRemoved > 0) log(`Stopped local polling: ${slug}`);
20251
+ } catch {
20252
+ log(`Could not update the local watch registry \u2014 \`archme local-watch --stop ${slug}\` will.`);
20253
+ }
20254
+ }
20255
+ const unwatchFailed = watchLookupError !== null;
20256
+ const remaining = {
20257
+ watchedRepos: allWatched && !unwatchFailed ? allWatched.length - unwatched.length : null,
20258
+ localEntries: allLocal.length - localRemoved
20259
+ };
20260
+ const daemonPlan = decideDaemonRemoval({ installed: daemonInstalled, keepWatching, remaining });
20261
+ if (daemonPlan.action === "remove") {
20262
+ try {
20263
+ await backend.removeDaemon();
20264
+ log(`Removed the daemon service \u2014 ${daemonPlan.reason}.`);
20265
+ } catch (err2) {
20266
+ log(
20267
+ `Could not remove the daemon service (${err2 instanceof Error ? err2.message : String(err2)}).
20268
+ archme daemon uninstall`
20269
+ );
20270
+ }
20271
+ } else {
20272
+ log(`Daemon left in place \u2014 ${daemonPlan.reason}.`);
20273
+ }
20274
+ log(
20275
+ summary({
20276
+ keepWatching,
20277
+ slug,
20278
+ unwatchFailed,
20279
+ watchLookupError,
20280
+ removedConfig: removed.length > 0,
20281
+ checkedAndNotWatched: Boolean(slug) && !unwatchFailed && toUnwatch.length === 0
20282
+ })
20283
+ );
20284
+ return { exitCode: EX_OK3, removed, unwatched };
20285
+ }
20286
+ function watchingUnknownNote(reason) {
20287
+ return `
20288
+ Could not check whether this repo is still being watched (${reason}).
20289
+ If it is, commits will keep updating the architecture. Sign in and re-run to stop it:
20290
+ archme login && archme uninstall`;
20291
+ }
20292
+ function summary(input) {
20293
+ const lines = input.removedConfig ? ["\nArchitectMe agent config removed from this repo."] : ["\nNo ArchitectMe agent config was here to remove."];
20294
+ if (input.keepWatching) {
20295
+ lines.push(
20296
+ "This repo is STILL being watched (--keep-watching), so commits keep updating its architecture."
20297
+ );
20298
+ } else if (input.unwatchFailed && input.watchLookupError) {
20299
+ lines.push(watchingUnknownNote(input.watchLookupError).trimStart());
20300
+ } else if (!input.slug) {
20301
+ lines.push(
20302
+ "No github.com remote here, so there was no watched repo to stop. Check archme.io \u2192 Settings \u2192 Integrations if you expected one."
20303
+ );
20304
+ } else if (input.checkedAndNotWatched) {
20305
+ lines.push(`${input.slug} was not being watched, so no commits were updating it.`);
20306
+ }
20307
+ lines.push(
20308
+ "\nYour architecture on archme.io and this machine's login are untouched:",
20309
+ " archme logout sign this machine out",
20310
+ "\nThe GitHub App installation is org-wide, so it stays. Remove it on github.com if you want it gone."
20311
+ );
20312
+ return lines.join("\n");
20313
+ }
20314
+ async function defaultBackend() {
20315
+ const { createDefaultBackend: createDefaultBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
20316
+ return createDefaultBackend2();
20317
+ }
20318
+ var EX_OK3, EX_USAGE3, PRUNABLE_DIRS;
20319
+ var init_run_uninstall = __esm({
20320
+ "src/uninstall/run-uninstall.ts"() {
20321
+ "use strict";
20322
+ init_prompt();
20323
+ init_edits();
20324
+ init_watching();
20325
+ EX_OK3 = 0;
20326
+ EX_USAGE3 = 78;
20327
+ PRUNABLE_DIRS = [
20328
+ ".claude/skills/architecture",
20329
+ ".claude/skills",
20330
+ ".claude/hooks",
20331
+ ".claude"
20332
+ ];
20333
+ }
20334
+ });
20335
+
20336
+ // src/uninstall/dispatch.ts
20337
+ var dispatch_exports5 = {};
20338
+ __export(dispatch_exports5, {
20339
+ EX_INFRA: () => EX_INFRA3,
20340
+ EX_OK: () => EX_OK4,
20341
+ EX_USAGE: () => EX_USAGE4,
20342
+ dispatchUninstall: () => dispatchUninstall,
20343
+ parseUninstallArgs: () => parseUninstallArgs,
20344
+ printUninstallHelp: () => printUninstallHelp
20345
+ });
20346
+ import { resolve as resolve5 } from "node:path";
20347
+ function parseUninstallArgs(argv) {
20348
+ if (argv.some((a2) => a2 === "--help" || a2 === "-h" || a2 === "help")) return { kind: "help" };
20349
+ let repoPath;
20350
+ let yes = false;
20351
+ let keepWatching = false;
20352
+ for (const arg of argv) {
20353
+ if (arg === "--yes" || arg === "-y") {
20354
+ yes = true;
20355
+ } else if (arg === "--keep-watching") {
20356
+ keepWatching = true;
20357
+ } else if (arg.startsWith("-")) {
20358
+ return { kind: "error", message: `unknown option '${arg}'` };
20359
+ } else if (repoPath === void 0) {
20360
+ repoPath = arg;
20361
+ } else {
20362
+ return { kind: "error", message: `unexpected argument '${arg}'` };
20363
+ }
20364
+ }
20365
+ return { kind: "run", repoPath, yes, keepWatching };
20366
+ }
20367
+ function printUninstallHelp() {
20368
+ console.log(`archme uninstall \u2014 stop ArchitectMe for a repo
20369
+
20370
+ Undoes what \`archme init\` set up, and shows you the whole list before it does:
20371
+
20372
+ 1. Agent config \u2014 the archme MCP server in .mcp.json, the ArchitectMe block in
20373
+ CLAUDE.md, the architecture skill, and the retired search-nudge hook older
20374
+ versions registered in .claude/settings.json.
20375
+ 2. Watching \u2014 stops this repo producing jobs, both via the GitHub App and via
20376
+ local polling. Without this the board keeps updating while looking removed.
20377
+ 3. The daemon \u2014 removed only if it has no other repo left to serve. It is one
20378
+ service per account, so uninstalling one repo never stops the others.
20379
+
20380
+ Your architecture on archme.io and this machine's login survive. The GitHub App
20381
+ installation is org-wide and stays; remove it on github.com if you want it gone.
20382
+
20383
+ Usage:
20384
+ archme uninstall [repo-path] [options]
20385
+
20386
+ repo-path Defaults to the current directory.
20387
+
20388
+ Options:
20389
+ -y, --yes Do not ask for confirmation
20390
+ --keep-watching Remove the agent config only; leave the repo watched
20391
+ and the daemon running
20392
+ -h, --help Show this help`);
20393
+ }
20394
+ async function dispatchUninstall(argv) {
20395
+ const parsed = parseUninstallArgs(argv);
20396
+ if (parsed.kind === "help") {
20397
+ printUninstallHelp();
20398
+ return EX_OK4;
20399
+ }
20400
+ if (parsed.kind === "error") {
20401
+ console.error(`archme uninstall: ${parsed.message}`);
20402
+ printUninstallHelp();
20403
+ return EX_USAGE4;
20404
+ }
20405
+ try {
20406
+ const { runUninstall: runUninstall2 } = await Promise.resolve().then(() => (init_run_uninstall(), run_uninstall_exports));
20407
+ const result = await runUninstall2({
20408
+ repoPath: resolve5(parsed.repoPath ?? process.cwd()),
20409
+ yes: parsed.yes,
20410
+ keepWatching: parsed.keepWatching
20411
+ });
20412
+ return result.exitCode;
20413
+ } catch (err2) {
20414
+ console.error(`archme uninstall: ${err2 instanceof Error ? err2.message : String(err2)}`);
20415
+ return EX_INFRA3;
20416
+ }
20417
+ }
20418
+ var EX_OK4, EX_INFRA3, EX_USAGE4;
20419
+ var init_dispatch5 = __esm({
20420
+ "src/uninstall/dispatch.ts"() {
20421
+ "use strict";
20422
+ EX_OK4 = 0;
20423
+ EX_INFRA3 = 3;
20424
+ EX_USAGE4 = 78;
20425
+ }
20426
+ });
20427
+
19855
20428
  // src/daemon/localWatchDispatch.ts
19856
20429
  var localWatchDispatch_exports = {};
19857
20430
  __export(localWatchDispatch_exports, {
19858
- EX_INFRA: () => EX_INFRA3,
19859
- EX_OK: () => EX_OK3,
19860
- EX_USAGE: () => EX_USAGE3,
20431
+ EX_INFRA: () => EX_INFRA4,
20432
+ EX_OK: () => EX_OK5,
20433
+ EX_USAGE: () => EX_USAGE5,
19861
20434
  dispatchLocalWatch: () => dispatchLocalWatch,
19862
20435
  parseLocalWatchArgs: () => parseLocalWatchArgs,
19863
20436
  printLocalWatchHelp: () => printLocalWatchHelp
@@ -19947,12 +20520,12 @@ async function dispatchLocalWatch(argv) {
19947
20520
  const parsed = parseLocalWatchArgs(argv);
19948
20521
  if (parsed.kind === "help") {
19949
20522
  printLocalWatchHelp();
19950
- return EX_OK3;
20523
+ return EX_OK5;
19951
20524
  }
19952
20525
  if (parsed.kind === "error") {
19953
20526
  console.error(parsed.message);
19954
20527
  printLocalWatchHelp();
19955
- return EX_USAGE3;
20528
+ return EX_USAGE5;
19956
20529
  }
19957
20530
  const mod = await Promise.resolve().then(() => (init_localWatch(), localWatch_exports));
19958
20531
  switch (parsed.kind) {
@@ -19966,13 +20539,13 @@ async function dispatchLocalWatch(argv) {
19966
20539
  return await mod.runLocalWatch(parsed);
19967
20540
  }
19968
20541
  }
19969
- var EX_OK3, EX_INFRA3, EX_USAGE3, PROJECT_SLUG_RE3, REPO_SLUG_RE;
20542
+ var EX_OK5, EX_INFRA4, EX_USAGE5, PROJECT_SLUG_RE3, REPO_SLUG_RE;
19970
20543
  var init_localWatchDispatch = __esm({
19971
20544
  "src/daemon/localWatchDispatch.ts"() {
19972
20545
  "use strict";
19973
- EX_OK3 = 0;
19974
- EX_INFRA3 = 3;
19975
- EX_USAGE3 = 78;
20546
+ EX_OK5 = 0;
20547
+ EX_INFRA4 = 3;
20548
+ EX_USAGE5 = 78;
19976
20549
  PROJECT_SLUG_RE3 = /^[^/\s]+\/[^/\s]+$/;
19977
20550
  REPO_SLUG_RE = /^[^/\s]+\/[^/\s]+$/;
19978
20551
  }
@@ -19980,7 +20553,7 @@ var init_localWatchDispatch = __esm({
19980
20553
 
19981
20554
  // src/doctor/collect.ts
19982
20555
  import { readFile as readFile13 } from "node:fs/promises";
19983
- import { join as join23 } from "node:path";
20556
+ import { join as join24 } from "node:path";
19984
20557
  function binaryPresent(name) {
19985
20558
  try {
19986
20559
  requireBinary(name);
@@ -19996,7 +20569,7 @@ async function git3(cwd, args) {
19996
20569
  async function readMcpPin(repoRoot) {
19997
20570
  const empty = { installed: false, pin: { project: null, branch: null } };
19998
20571
  try {
19999
- const raw = await readFile13(join23(repoRoot, ".mcp.json"), "utf8");
20572
+ const raw = await readFile13(join24(repoRoot, ".mcp.json"), "utf8");
20000
20573
  const parsed = JSON.parse(raw);
20001
20574
  const servers = parsed.mcpServers ?? {};
20002
20575
  const entry = Object.entries(servers).find(([name]) => /archme/i.test(name));
@@ -20064,17 +20637,17 @@ async function collectFacts(cwd) {
20064
20637
  );
20065
20638
  try {
20066
20639
  const cfg = resolveConfig();
20067
- const authed = await createAuthedClient({
20640
+ const authed2 = await createAuthedClient({
20068
20641
  supabaseUrl: cfg.supabaseUrl,
20069
20642
  supabaseAnonKey: cfg.supabaseAnonKey,
20070
20643
  clientId: cfg.archmeDaemonClientId,
20071
20644
  preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
20072
20645
  });
20073
- if (!authed.orgId) return facts;
20646
+ if (!authed2.orgId) return facts;
20074
20647
  facts.signedIn = true;
20075
- facts.orgId = authed.orgId;
20076
- facts.identity = authed.identity;
20077
- const supabase = authed.supabase;
20648
+ facts.orgId = authed2.orgId;
20649
+ facts.identity = authed2.identity;
20650
+ const supabase = authed2.supabase;
20078
20651
  const slug = facts.pinnedProject;
20079
20652
  if (slug) {
20080
20653
  const [orgSlug, projectSlug] = slug.split("/");
@@ -20360,18 +20933,18 @@ optional.`;
20360
20933
  });
20361
20934
 
20362
20935
  // src/doctor/dispatch.ts
20363
- var dispatch_exports5 = {};
20364
- __export(dispatch_exports5, {
20365
- EX_INFRA: () => EX_INFRA4,
20366
- EX_OK: () => EX_OK4,
20367
- EX_USAGE: () => EX_USAGE4,
20936
+ var dispatch_exports6 = {};
20937
+ __export(dispatch_exports6, {
20938
+ EX_INFRA: () => EX_INFRA5,
20939
+ EX_OK: () => EX_OK6,
20940
+ EX_USAGE: () => EX_USAGE6,
20368
20941
  dispatchDoctor: () => dispatchDoctor,
20369
20942
  parseDoctorArgs: () => parseDoctorArgs,
20370
20943
  printDoctorHelp: () => printDoctorHelp
20371
20944
  });
20372
20945
  import { mkdtemp as mkdtemp5, rm as rm9, writeFile as writeFile13 } from "node:fs/promises";
20373
20946
  import { tmpdir as tmpdir5 } from "node:os";
20374
- import { join as join24 } from "node:path";
20947
+ import { join as join25 } from "node:path";
20375
20948
  function parseDoctorArgs(argv) {
20376
20949
  const args = { plain: false, help: false };
20377
20950
  for (const a2 of argv) {
@@ -20403,11 +20976,11 @@ async function dispatchDoctor(argv) {
20403
20976
  if (!parsed.ok) {
20404
20977
  console.error(parsed.message);
20405
20978
  printDoctorHelp();
20406
- return EX_USAGE4;
20979
+ return EX_USAGE6;
20407
20980
  }
20408
20981
  if (parsed.args.help) {
20409
20982
  printDoctorHelp();
20410
- return EX_OK4;
20983
+ return EX_OK6;
20411
20984
  }
20412
20985
  const facts = await collectFacts(process.cwd());
20413
20986
  const issues = diagnose(facts);
@@ -20423,17 +20996,17 @@ async function dispatchDoctor(argv) {
20423
20996
  console.log("");
20424
20997
  }
20425
20998
  }
20426
- if (parsed.args.plain) return EX_OK4;
20999
+ if (parsed.args.plain) return EX_OK6;
20427
21000
  try {
20428
21001
  requireBinary("claude");
20429
21002
  } catch {
20430
21003
  console.log(
20431
21004
  "Install Claude Code to ask about any of this: npm i -g @anthropic-ai/claude-code"
20432
21005
  );
20433
- return EX_OK4;
21006
+ return EX_OK6;
20434
21007
  }
20435
- const dir = await mkdtemp5(join24(tmpdir5(), "archme-doctor-"));
20436
- const mcpConfigPath = join24(dir, "mcp.json");
21008
+ const dir = await mkdtemp5(join25(tmpdir5(), "archme-doctor-"));
21009
+ const mcpConfigPath = join25(dir, "mcp.json");
20437
21010
  await writeFile13(mcpConfigPath, JSON.stringify({ mcpServers: {} }), { mode: 384 });
20438
21011
  try {
20439
21012
  const result = await spawnClaude({
@@ -20446,13 +21019,13 @@ async function dispatchDoctor(argv) {
20446
21019
  // is a much larger surface than the problem justifies.
20447
21020
  allowedTools: ["Read", "Glob", "Grep"]
20448
21021
  });
20449
- return result.exitCode === 0 ? EX_OK4 : EX_INFRA4;
21022
+ return result.exitCode === 0 ? EX_OK6 : EX_INFRA5;
20450
21023
  } finally {
20451
21024
  await rm9(dir, { recursive: true, force: true });
20452
21025
  }
20453
21026
  }
20454
- var EX_OK4, EX_INFRA4, EX_USAGE4;
20455
- var init_dispatch5 = __esm({
21027
+ var EX_OK6, EX_INFRA5, EX_USAGE6;
21028
+ var init_dispatch6 = __esm({
20456
21029
  "src/doctor/dispatch.ts"() {
20457
21030
  "use strict";
20458
21031
  init_collect();
@@ -20460,9 +21033,9 @@ var init_dispatch5 = __esm({
20460
21033
  init_prompt2();
20461
21034
  init_spawn_claude();
20462
21035
  init_preflight();
20463
- EX_OK4 = 0;
20464
- EX_INFRA4 = 3;
20465
- EX_USAGE4 = 78;
21036
+ EX_OK6 = 0;
21037
+ EX_INFRA5 = 3;
21038
+ EX_USAGE6 = 78;
20466
21039
  }
20467
21040
  });
20468
21041
 
@@ -20520,6 +21093,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
20520
21093
  "keys",
20521
21094
  "scan",
20522
21095
  "init",
21096
+ "uninstall",
20523
21097
  "local-watch",
20524
21098
  "doctor"
20525
21099
  ]);
@@ -20618,12 +21192,16 @@ async function main(argv) {
20618
21192
  const { dispatchInit: dispatchInit2 } = await Promise.resolve().then(() => (init_dispatch4(), dispatch_exports4));
20619
21193
  return await dispatchInit2(r.args);
20620
21194
  }
21195
+ case "uninstall": {
21196
+ const { dispatchUninstall: dispatchUninstall2 } = await Promise.resolve().then(() => (init_dispatch5(), dispatch_exports5));
21197
+ return await dispatchUninstall2(r.args);
21198
+ }
20621
21199
  case "local-watch": {
20622
21200
  const { dispatchLocalWatch: dispatchLocalWatch2 } = await Promise.resolve().then(() => (init_localWatchDispatch(), localWatchDispatch_exports));
20623
21201
  return await dispatchLocalWatch2(r.args);
20624
21202
  }
20625
21203
  case "doctor": {
20626
- const { dispatchDoctor: dispatchDoctor2 } = await Promise.resolve().then(() => (init_dispatch5(), dispatch_exports5));
21204
+ const { dispatchDoctor: dispatchDoctor2 } = await Promise.resolve().then(() => (init_dispatch6(), dispatch_exports6));
20627
21205
  return await dispatchDoctor2(r.args);
20628
21206
  }
20629
21207
  case "logout": {
@@ -20650,6 +21228,8 @@ Other commands:
20650
21228
  archme scan <repo-path> Single-pass scan into an explicit project/branch
20651
21229
  archme local-watch [repo-path] Watch a repo without a GitHub App
20652
21230
  (--list|--stop|--resume)
21231
+ archme uninstall [repo-path] Stop ArchitectMe for a repo: config,
21232
+ watching, and the daemon if it's now idle
20653
21233
  archme doctor Check this machine's setup and explain it
20654
21234
  archme login | logout Authenticate with archme
20655
21235
  archme keys list|create|revoke Manage MCP API keys
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@archme/cli",
3
- "version": "0.4.5",
3
+ "version": "0.5.0",
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",