@archme/cli 0.4.4 → 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.
- package/README.md +26 -0
- package/dist/cli.js +717 -111
- 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(
|
|
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
|
-
|
|
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((
|
|
1165
|
+
const step = (i2) => new Promise((resolve6, reject) => {
|
|
1166
1166
|
if (i2 === pathEnv.length)
|
|
1167
|
-
return opt.all && found.length ?
|
|
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
|
-
|
|
1172
|
+
resolve6(subStep(p, i2, 0));
|
|
1173
1173
|
});
|
|
1174
|
-
const subStep = (p, i2, ii) => new Promise((
|
|
1174
|
+
const subStep = (p, i2, ii) => new Promise((resolve6, reject) => {
|
|
1175
1175
|
if (ii === pathExt.length)
|
|
1176
|
-
return
|
|
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
|
|
1183
|
+
return resolve6(p + ext2);
|
|
1184
1184
|
}
|
|
1185
|
-
return
|
|
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((
|
|
2266
|
-
Object.assign(methods, { resolve:
|
|
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
|
|
7564
|
-
return { resolve:
|
|
7563
|
+
const resolve6 = promise.resolve.bind(promise);
|
|
7564
|
+
return { resolve: resolve6, promises };
|
|
7565
7565
|
};
|
|
7566
|
-
waitForConcurrentStreams = async ({ resolve:
|
|
7567
|
-
|
|
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((
|
|
8639
|
+
await new Promise((resolve6) => setTimeout(resolve6, intervalMs));
|
|
8640
8640
|
const poll = await postJson("/poll", {
|
|
8641
8641
|
device_code: data.device_code
|
|
8642
8642
|
});
|
|
@@ -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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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>
|
|
8754
|
-
<
|
|
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
|
|
|
@@ -8852,7 +8877,7 @@ var init_launchctl = __esm({
|
|
|
8852
8877
|
if (r.exitCode === 0 || r.exitCode === 17) return;
|
|
8853
8878
|
if (r.exitCode === 5) {
|
|
8854
8879
|
await this.opts.spawner.run("launchctl", ["bootout", SERVICE_TARGET(this.opts.uid)]);
|
|
8855
|
-
await new Promise((
|
|
8880
|
+
await new Promise((resolve6) => setTimeout(resolve6, 500));
|
|
8856
8881
|
r = await bootstrap();
|
|
8857
8882
|
if (r.exitCode === 0 || r.exitCode === 17) return;
|
|
8858
8883
|
}
|
|
@@ -8997,6 +9022,13 @@ var init_systemctl = __esm({
|
|
|
8997
9022
|
});
|
|
8998
9023
|
|
|
8999
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
|
+
});
|
|
9000
9032
|
import { userInfo } from "node:os";
|
|
9001
9033
|
function pickController(platform3 = process.platform, env = process.env, spawner = execaSpawner) {
|
|
9002
9034
|
const unitPath = resolveUnitPath(platform3, env);
|
|
@@ -9231,7 +9263,7 @@ async function tailLog(opts = {}) {
|
|
|
9231
9263
|
}
|
|
9232
9264
|
}
|
|
9233
9265
|
if (!opts.follow) return;
|
|
9234
|
-
await new Promise((
|
|
9266
|
+
await new Promise((resolve6, reject) => {
|
|
9235
9267
|
let fileWatcher = null;
|
|
9236
9268
|
let dirWatcher = null;
|
|
9237
9269
|
const dir = dirname6(path7);
|
|
@@ -9295,7 +9327,7 @@ async function tailLog(opts = {}) {
|
|
|
9295
9327
|
const handleSigint = () => {
|
|
9296
9328
|
fileWatcher?.close();
|
|
9297
9329
|
dirWatcher?.close();
|
|
9298
|
-
|
|
9330
|
+
resolve6();
|
|
9299
9331
|
};
|
|
9300
9332
|
process.once("SIGINT", handleSigint);
|
|
9301
9333
|
});
|
|
@@ -14192,15 +14224,17 @@ async function acquireSingleInstanceLock(pidfile) {
|
|
|
14192
14224
|
}
|
|
14193
14225
|
};
|
|
14194
14226
|
}
|
|
14195
|
-
var SingleInstanceLockedError;
|
|
14227
|
+
var SINGLE_INSTANCE_EXIT_CODE, LOCK_HELD_MESSAGE, SingleInstanceLockedError;
|
|
14196
14228
|
var init_singleInstanceLock = __esm({
|
|
14197
14229
|
"src/daemon/service/singleInstanceLock.ts"() {
|
|
14198
14230
|
"use strict";
|
|
14231
|
+
SINGLE_INSTANCE_EXIT_CODE = 64;
|
|
14232
|
+
LOCK_HELD_MESSAGE = "already holds";
|
|
14199
14233
|
SingleInstanceLockedError = class extends Error {
|
|
14200
14234
|
heldByPid;
|
|
14201
14235
|
constructor(pidfile, heldByPid) {
|
|
14202
14236
|
super(
|
|
14203
|
-
heldByPid != null ? `another archme daemon
|
|
14237
|
+
heldByPid != null ? `another archme daemon ${LOCK_HELD_MESSAGE} ${pidfile} (pid ${heldByPid})` : `another archme daemon ${LOCK_HELD_MESSAGE} ${pidfile}`
|
|
14204
14238
|
);
|
|
14205
14239
|
this.heldByPid = heldByPid;
|
|
14206
14240
|
this.name = "SingleInstanceLockedError";
|
|
@@ -14231,7 +14265,7 @@ var init_semaphore = __esm({
|
|
|
14231
14265
|
acquire() {
|
|
14232
14266
|
const slot = this.tryAcquire();
|
|
14233
14267
|
if (slot) return Promise.resolve(slot);
|
|
14234
|
-
return new Promise((
|
|
14268
|
+
return new Promise((resolve6) => this.waiters.push(resolve6));
|
|
14235
14269
|
}
|
|
14236
14270
|
makeRelease() {
|
|
14237
14271
|
let released = false;
|
|
@@ -15687,7 +15721,6 @@ import { randomUUID } from "node:crypto";
|
|
|
15687
15721
|
import { mkdtemp as mkdtemp3, mkdir as mkdir10, writeFile as writeFile10, rm as rm6, stat as stat8, chmod as chmod3 } from "node:fs/promises";
|
|
15688
15722
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
15689
15723
|
import { join as join18 } from "node:path";
|
|
15690
|
-
import { pathToFileURL } from "node:url";
|
|
15691
15724
|
import { homedir as homedir5 } from "node:os";
|
|
15692
15725
|
function attributeKillCause(result, recorded) {
|
|
15693
15726
|
if (recorded) return recorded;
|
|
@@ -16092,12 +16125,12 @@ async function rpcHeartbeat(ctx, jobId, deviceId) {
|
|
|
16092
16125
|
);
|
|
16093
16126
|
if (error) console.error(`[${jobId}] heartbeat error:`, error.message);
|
|
16094
16127
|
}
|
|
16095
|
-
async function rpcComplete(ctx, jobId, deviceId,
|
|
16128
|
+
async function rpcComplete(ctx, jobId, deviceId, summary2, kind, changed, metrics, logs, advanceMarker = true, affected) {
|
|
16096
16129
|
const extra = {
|
|
16097
16130
|
// `kind` drives lifecycle (squash, marker, retry). `changed` is the
|
|
16098
16131
|
// decoupled what-changed verdict the Check Run / sticky comment render
|
|
16099
16132
|
// (migration 083): a structural-only job is kind=applied but changed=false.
|
|
16100
|
-
result: { summary, kind, changed },
|
|
16133
|
+
result: { summary: summary2, kind, changed },
|
|
16101
16134
|
metrics,
|
|
16102
16135
|
logs,
|
|
16103
16136
|
advance_marker: advanceMarker
|
|
@@ -16631,8 +16664,8 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
16631
16664
|
if (runResult.isError) {
|
|
16632
16665
|
throw new Error(`claude reported is_error in its JSON result (no successful completion)`);
|
|
16633
16666
|
}
|
|
16634
|
-
const
|
|
16635
|
-
if (
|
|
16667
|
+
const summary2 = runResult.summary.trim().slice(0, 4e3);
|
|
16668
|
+
if (summary2) log(`claude> ${summary2.slice(0, 500).replace(/\n/g, " ")}`);
|
|
16636
16669
|
metrics.model_served = runResult.modelServed;
|
|
16637
16670
|
metrics.usage = runResult.usage;
|
|
16638
16671
|
metrics.cost_usd = runResult.costUsd;
|
|
@@ -16659,7 +16692,7 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
16659
16692
|
throw new Error(outcome.reason);
|
|
16660
16693
|
}
|
|
16661
16694
|
const changed = outcome.changed;
|
|
16662
|
-
log(`done (${outcome.kind}, changed=${changed}, ${writes} commit(s) [${diffWrites} diff / ${structuralWrites} structural], ${reads ?? "?"} mcp tool call(s)): ${
|
|
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, " ")}`);
|
|
16663
16696
|
const totalMs = Date.now() - startedAt;
|
|
16664
16697
|
markPhase("complete", { kind: outcome.kind, changed, total_ms: totalMs });
|
|
16665
16698
|
metrics.total_ms = totalMs;
|
|
@@ -16667,7 +16700,7 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
16667
16700
|
ctx,
|
|
16668
16701
|
job.id,
|
|
16669
16702
|
deviceId,
|
|
16670
|
-
|
|
16703
|
+
summary2,
|
|
16671
16704
|
outcome.kind,
|
|
16672
16705
|
changed,
|
|
16673
16706
|
metrics,
|
|
@@ -16876,6 +16909,11 @@ async function signInAccount(creds, makeAuth) {
|
|
|
16876
16909
|
}
|
|
16877
16910
|
return { active, lastErr, usedFallback };
|
|
16878
16911
|
}
|
|
16912
|
+
function exitDaemon(code) {
|
|
16913
|
+
const notice = supervisedStopNotice(code, process.env);
|
|
16914
|
+
if (notice) console.error(notice);
|
|
16915
|
+
process.exit(resolveExitCode(code, process.env));
|
|
16916
|
+
}
|
|
16879
16917
|
async function runDaemon() {
|
|
16880
16918
|
let lockHandle = null;
|
|
16881
16919
|
try {
|
|
@@ -16883,7 +16921,7 @@ async function runDaemon() {
|
|
|
16883
16921
|
} catch (err2) {
|
|
16884
16922
|
if (err2 instanceof SingleInstanceLockedError) {
|
|
16885
16923
|
console.error(String(err2.message));
|
|
16886
|
-
process.exit(
|
|
16924
|
+
process.exit(SINGLE_INSTANCE_EXIT_CODE);
|
|
16887
16925
|
}
|
|
16888
16926
|
throw err2;
|
|
16889
16927
|
}
|
|
@@ -16899,7 +16937,7 @@ async function runDaemon() {
|
|
|
16899
16937
|
} catch {
|
|
16900
16938
|
}
|
|
16901
16939
|
}
|
|
16902
|
-
|
|
16940
|
+
exitDaemon(1);
|
|
16903
16941
|
}
|
|
16904
16942
|
throw err2;
|
|
16905
16943
|
}
|
|
@@ -16930,7 +16968,7 @@ async function runDaemon() {
|
|
|
16930
16968
|
} catch {
|
|
16931
16969
|
}
|
|
16932
16970
|
}
|
|
16933
|
-
|
|
16971
|
+
exitDaemon(2);
|
|
16934
16972
|
}
|
|
16935
16973
|
let credentials;
|
|
16936
16974
|
try {
|
|
@@ -16944,7 +16982,7 @@ async function runDaemon() {
|
|
|
16944
16982
|
} catch {
|
|
16945
16983
|
}
|
|
16946
16984
|
}
|
|
16947
|
-
|
|
16985
|
+
exitDaemon(78);
|
|
16948
16986
|
}
|
|
16949
16987
|
throw err2;
|
|
16950
16988
|
}
|
|
@@ -16959,7 +16997,7 @@ async function runDaemon() {
|
|
|
16959
16997
|
} catch {
|
|
16960
16998
|
}
|
|
16961
16999
|
}
|
|
16962
|
-
|
|
17000
|
+
exitDaemon(75);
|
|
16963
17001
|
}
|
|
16964
17002
|
const auth = active.auth;
|
|
16965
17003
|
const client = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
|
@@ -17141,6 +17179,7 @@ var init_run = __esm({
|
|
|
17141
17179
|
init_dispatch();
|
|
17142
17180
|
init_defaults();
|
|
17143
17181
|
init_singleInstanceLock();
|
|
17182
|
+
init_supervision();
|
|
17144
17183
|
init_paths();
|
|
17145
17184
|
init_semaphore();
|
|
17146
17185
|
init_bearer();
|
|
@@ -17341,12 +17380,6 @@ ${ANCHOR_VERIFICATION}`;
|
|
|
17341
17380
|
return daemonVersion;
|
|
17342
17381
|
}
|
|
17343
17382
|
};
|
|
17344
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
17345
|
-
runDaemon().catch((err2) => {
|
|
17346
|
-
console.error("fatal:", err2);
|
|
17347
|
-
process.exit(1);
|
|
17348
|
-
});
|
|
17349
|
-
}
|
|
17350
17383
|
}
|
|
17351
17384
|
});
|
|
17352
17385
|
|
|
@@ -18138,14 +18171,14 @@ async function runScan(opts, deps = {}) {
|
|
|
18138
18171
|
log(` supabase: ${supabaseUrl}`);
|
|
18139
18172
|
log(` mcp base: ${mcpUrl}`);
|
|
18140
18173
|
log(` repo: ${opts.repoPath}`);
|
|
18141
|
-
const
|
|
18174
|
+
const authed2 = await createAuthedClient({
|
|
18142
18175
|
supabaseUrl,
|
|
18143
18176
|
supabaseAnonKey: supabaseAnon,
|
|
18144
18177
|
clientId,
|
|
18145
18178
|
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
18146
18179
|
});
|
|
18147
|
-
log(` signed in as ${
|
|
18148
|
-
const { supabase, userId } =
|
|
18180
|
+
log(` signed in as ${authed2.identity}`);
|
|
18181
|
+
const { supabase, userId } = authed2;
|
|
18149
18182
|
let headSha;
|
|
18150
18183
|
try {
|
|
18151
18184
|
headSha = await repoHeadSha(opts.repoPath);
|
|
@@ -18244,7 +18277,7 @@ async function runScan(opts, deps = {}) {
|
|
|
18244
18277
|
const result = await proc;
|
|
18245
18278
|
const exitCode = result.exitCode ?? 0;
|
|
18246
18279
|
const signal = result.signal ?? null;
|
|
18247
|
-
const
|
|
18280
|
+
const summary2 = (finalResultText || (result.stdout ? String(result.stdout) : "")).trim().slice(-4e3);
|
|
18248
18281
|
if (signal) {
|
|
18249
18282
|
log(`claude exited ${exitCode} (signal: ${signal})`);
|
|
18250
18283
|
} else {
|
|
@@ -18255,8 +18288,8 @@ async function runScan(opts, deps = {}) {
|
|
|
18255
18288
|
log(` stories: ${counts.stories}`);
|
|
18256
18289
|
let signals2 = null;
|
|
18257
18290
|
try {
|
|
18258
|
-
await new Promise((
|
|
18259
|
-
transcriptStream.end((err2) => err2 ? reject(err2) :
|
|
18291
|
+
await new Promise((resolve6, reject) => {
|
|
18292
|
+
transcriptStream.end((err2) => err2 ? reject(err2) : resolve6());
|
|
18260
18293
|
});
|
|
18261
18294
|
const reportPath = join19(transcriptDir, "report.json");
|
|
18262
18295
|
const orphansTxtPath = join19(transcriptDir, "orphans.txt");
|
|
@@ -18322,7 +18355,7 @@ async function runScan(opts, deps = {}) {
|
|
|
18322
18355
|
orgSlug: resolved2.orgSlug,
|
|
18323
18356
|
projectSlug: resolved2.projectSlug,
|
|
18324
18357
|
webUrl,
|
|
18325
|
-
summary
|
|
18358
|
+
summary: summary2
|
|
18326
18359
|
};
|
|
18327
18360
|
} finally {
|
|
18328
18361
|
await cleanupMcp().catch(() => {
|
|
@@ -18933,6 +18966,9 @@ var init_githubAppSetup = __esm({
|
|
|
18933
18966
|
|
|
18934
18967
|
// src/daemon/prompt.ts
|
|
18935
18968
|
import { createInterface } from "node:readline/promises";
|
|
18969
|
+
function stdinIsInteractive() {
|
|
18970
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
18971
|
+
}
|
|
18936
18972
|
async function withReadline(fn) {
|
|
18937
18973
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
18938
18974
|
try {
|
|
@@ -19215,9 +19251,9 @@ async function runLocalWatch(opts) {
|
|
|
19215
19251
|
log(` repo: ${target.repo}`);
|
|
19216
19252
|
log(` branch: ${target.repoBranch} \u2192 archme branch ${target.archmeBranch}`);
|
|
19217
19253
|
const cfg = resolveConfig();
|
|
19218
|
-
let
|
|
19254
|
+
let authed2;
|
|
19219
19255
|
try {
|
|
19220
|
-
|
|
19256
|
+
authed2 = await createAuthedClient({
|
|
19221
19257
|
supabaseUrl: cfg.supabaseUrl,
|
|
19222
19258
|
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
19223
19259
|
clientId: cfg.archmeDaemonClientId,
|
|
@@ -19227,8 +19263,8 @@ async function runLocalWatch(opts) {
|
|
|
19227
19263
|
console.error(`archme local-watch: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
19228
19264
|
return 3;
|
|
19229
19265
|
}
|
|
19230
|
-
const { supabase, userId } =
|
|
19231
|
-
const orgId =
|
|
19266
|
+
const { supabase, userId } = authed2;
|
|
19267
|
+
const orgId = authed2.orgId;
|
|
19232
19268
|
if (!orgId) {
|
|
19233
19269
|
console.error("archme local-watch: this login is not paired with an org \u2014 run `archme login`");
|
|
19234
19270
|
return 3;
|
|
@@ -19468,22 +19504,22 @@ async function runInit(opts, deps = {}) {
|
|
|
19468
19504
|
} else {
|
|
19469
19505
|
log("\u2713 already signed in");
|
|
19470
19506
|
}
|
|
19471
|
-
const
|
|
19507
|
+
const authed2 = await authenticate({
|
|
19472
19508
|
supabaseUrl: cfg.supabaseUrl,
|
|
19473
19509
|
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
19474
19510
|
clientId: cfg.archmeDaemonClientId,
|
|
19475
19511
|
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
19476
19512
|
});
|
|
19477
|
-
log(` signed in as ${
|
|
19513
|
+
log(` signed in as ${authed2.identity}`);
|
|
19478
19514
|
let fullSlug = opts.project;
|
|
19479
19515
|
if (!fullSlug) {
|
|
19480
|
-
const orgId =
|
|
19516
|
+
const orgId = authed2.orgId;
|
|
19481
19517
|
if (!orgId) {
|
|
19482
19518
|
throw new Error(
|
|
19483
19519
|
"could not resolve your organization \u2014 run `archme login` again, or pass --project <org>/<slug>"
|
|
19484
19520
|
);
|
|
19485
19521
|
}
|
|
19486
|
-
const orgSlug = await resolveOrgSlug(
|
|
19522
|
+
const orgSlug = await resolveOrgSlug(authed2.supabase, orgId);
|
|
19487
19523
|
if (!orgSlug) {
|
|
19488
19524
|
throw new Error(
|
|
19489
19525
|
`paired org ${orgId} no longer exists \u2014 run \`archme login\` again, or pass --project <org>/<slug>`
|
|
@@ -19492,8 +19528,8 @@ async function runInit(opts, deps = {}) {
|
|
|
19492
19528
|
fullSlug = defaultProjectSlug(orgSlug, repoPath);
|
|
19493
19529
|
}
|
|
19494
19530
|
const project = await resolveProject(
|
|
19495
|
-
|
|
19496
|
-
|
|
19531
|
+
authed2.supabase,
|
|
19532
|
+
authed2.userId,
|
|
19497
19533
|
fullSlug
|
|
19498
19534
|
);
|
|
19499
19535
|
log(
|
|
@@ -19501,7 +19537,7 @@ async function runInit(opts, deps = {}) {
|
|
|
19501
19537
|
);
|
|
19502
19538
|
const scanBranch = initScanBranch(project.defaultBranch);
|
|
19503
19539
|
const detail = await fetchArchitecture(
|
|
19504
|
-
|
|
19540
|
+
authed2.supabase,
|
|
19505
19541
|
project.projectId,
|
|
19506
19542
|
scanBranch
|
|
19507
19543
|
);
|
|
@@ -19515,8 +19551,8 @@ async function runInit(opts, deps = {}) {
|
|
|
19515
19551
|
await runInstallScript(script, repoPath);
|
|
19516
19552
|
ran.push("agent-config");
|
|
19517
19553
|
const watched = await watchRepo({
|
|
19518
|
-
supabase:
|
|
19519
|
-
orgId:
|
|
19554
|
+
supabase: authed2.supabase,
|
|
19555
|
+
orgId: authed2.orgId,
|
|
19520
19556
|
projectId: project.projectId,
|
|
19521
19557
|
repoSlug: git4.repoSlug,
|
|
19522
19558
|
branch: git4.branch,
|
|
@@ -19583,18 +19619,18 @@ Your board: ${webUrl}`);
|
|
|
19583
19619
|
startGitHubAppInstall: () => (deps.buildInstallUrl ?? buildInstallUrl)({
|
|
19584
19620
|
supabaseUrl: cfg.supabaseUrl,
|
|
19585
19621
|
accessToken: getCurrentAccessToken() ?? "",
|
|
19586
|
-
orgId:
|
|
19622
|
+
orgId: authed2.orgId
|
|
19587
19623
|
}),
|
|
19588
19624
|
waitForInstallation: () => (deps.waitForInstallation ?? waitForInstallation)({
|
|
19589
|
-
hasInstallation: () => (deps.orgHasInstallation ?? orgHasInstallation)(
|
|
19625
|
+
hasInstallation: () => (deps.orgHasInstallation ?? orgHasInstallation)(authed2.supabase, authed2.orgId),
|
|
19590
19626
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
19591
19627
|
now: () => Date.now(),
|
|
19592
19628
|
log
|
|
19593
19629
|
}),
|
|
19594
19630
|
watchRepo: async () => {
|
|
19595
19631
|
const w = await watchRepo({
|
|
19596
|
-
supabase:
|
|
19597
|
-
orgId:
|
|
19632
|
+
supabase: authed2.supabase,
|
|
19633
|
+
orgId: authed2.orgId,
|
|
19598
19634
|
projectId: project.projectId,
|
|
19599
19635
|
repoSlug: git4.repoSlug,
|
|
19600
19636
|
branch: git4.branch,
|
|
@@ -19616,8 +19652,8 @@ Your board: ${webUrl}`);
|
|
|
19616
19652
|
});
|
|
19617
19653
|
if (!target.ok) return { ok: false, reason: target.error };
|
|
19618
19654
|
const res = await (deps.registerLocalWatch ?? registerLocalWatch)({
|
|
19619
|
-
supabase:
|
|
19620
|
-
orgId:
|
|
19655
|
+
supabase: authed2.supabase,
|
|
19656
|
+
orgId: authed2.orgId,
|
|
19621
19657
|
projectId: project.projectId,
|
|
19622
19658
|
repoPath: git4.root,
|
|
19623
19659
|
originUrl: git4.originUrl,
|
|
@@ -19826,12 +19862,575 @@ var init_dispatch4 = __esm({
|
|
|
19826
19862
|
}
|
|
19827
19863
|
});
|
|
19828
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
|
+
|
|
19829
20428
|
// src/daemon/localWatchDispatch.ts
|
|
19830
20429
|
var localWatchDispatch_exports = {};
|
|
19831
20430
|
__export(localWatchDispatch_exports, {
|
|
19832
|
-
EX_INFRA: () =>
|
|
19833
|
-
EX_OK: () =>
|
|
19834
|
-
EX_USAGE: () =>
|
|
20431
|
+
EX_INFRA: () => EX_INFRA4,
|
|
20432
|
+
EX_OK: () => EX_OK5,
|
|
20433
|
+
EX_USAGE: () => EX_USAGE5,
|
|
19835
20434
|
dispatchLocalWatch: () => dispatchLocalWatch,
|
|
19836
20435
|
parseLocalWatchArgs: () => parseLocalWatchArgs,
|
|
19837
20436
|
printLocalWatchHelp: () => printLocalWatchHelp
|
|
@@ -19921,12 +20520,12 @@ async function dispatchLocalWatch(argv) {
|
|
|
19921
20520
|
const parsed = parseLocalWatchArgs(argv);
|
|
19922
20521
|
if (parsed.kind === "help") {
|
|
19923
20522
|
printLocalWatchHelp();
|
|
19924
|
-
return
|
|
20523
|
+
return EX_OK5;
|
|
19925
20524
|
}
|
|
19926
20525
|
if (parsed.kind === "error") {
|
|
19927
20526
|
console.error(parsed.message);
|
|
19928
20527
|
printLocalWatchHelp();
|
|
19929
|
-
return
|
|
20528
|
+
return EX_USAGE5;
|
|
19930
20529
|
}
|
|
19931
20530
|
const mod = await Promise.resolve().then(() => (init_localWatch(), localWatch_exports));
|
|
19932
20531
|
switch (parsed.kind) {
|
|
@@ -19940,13 +20539,13 @@ async function dispatchLocalWatch(argv) {
|
|
|
19940
20539
|
return await mod.runLocalWatch(parsed);
|
|
19941
20540
|
}
|
|
19942
20541
|
}
|
|
19943
|
-
var
|
|
20542
|
+
var EX_OK5, EX_INFRA4, EX_USAGE5, PROJECT_SLUG_RE3, REPO_SLUG_RE;
|
|
19944
20543
|
var init_localWatchDispatch = __esm({
|
|
19945
20544
|
"src/daemon/localWatchDispatch.ts"() {
|
|
19946
20545
|
"use strict";
|
|
19947
|
-
|
|
19948
|
-
|
|
19949
|
-
|
|
20546
|
+
EX_OK5 = 0;
|
|
20547
|
+
EX_INFRA4 = 3;
|
|
20548
|
+
EX_USAGE5 = 78;
|
|
19950
20549
|
PROJECT_SLUG_RE3 = /^[^/\s]+\/[^/\s]+$/;
|
|
19951
20550
|
REPO_SLUG_RE = /^[^/\s]+\/[^/\s]+$/;
|
|
19952
20551
|
}
|
|
@@ -19954,7 +20553,7 @@ var init_localWatchDispatch = __esm({
|
|
|
19954
20553
|
|
|
19955
20554
|
// src/doctor/collect.ts
|
|
19956
20555
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
19957
|
-
import { join as
|
|
20556
|
+
import { join as join24 } from "node:path";
|
|
19958
20557
|
function binaryPresent(name) {
|
|
19959
20558
|
try {
|
|
19960
20559
|
requireBinary(name);
|
|
@@ -19970,7 +20569,7 @@ async function git3(cwd, args) {
|
|
|
19970
20569
|
async function readMcpPin(repoRoot) {
|
|
19971
20570
|
const empty = { installed: false, pin: { project: null, branch: null } };
|
|
19972
20571
|
try {
|
|
19973
|
-
const raw = await readFile13(
|
|
20572
|
+
const raw = await readFile13(join24(repoRoot, ".mcp.json"), "utf8");
|
|
19974
20573
|
const parsed = JSON.parse(raw);
|
|
19975
20574
|
const servers = parsed.mcpServers ?? {};
|
|
19976
20575
|
const entry = Object.entries(servers).find(([name]) => /archme/i.test(name));
|
|
@@ -20038,17 +20637,17 @@ async function collectFacts(cwd) {
|
|
|
20038
20637
|
);
|
|
20039
20638
|
try {
|
|
20040
20639
|
const cfg = resolveConfig();
|
|
20041
|
-
const
|
|
20640
|
+
const authed2 = await createAuthedClient({
|
|
20042
20641
|
supabaseUrl: cfg.supabaseUrl,
|
|
20043
20642
|
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
20044
20643
|
clientId: cfg.archmeDaemonClientId,
|
|
20045
20644
|
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
20046
20645
|
});
|
|
20047
|
-
if (!
|
|
20646
|
+
if (!authed2.orgId) return facts;
|
|
20048
20647
|
facts.signedIn = true;
|
|
20049
|
-
facts.orgId =
|
|
20050
|
-
facts.identity =
|
|
20051
|
-
const supabase =
|
|
20648
|
+
facts.orgId = authed2.orgId;
|
|
20649
|
+
facts.identity = authed2.identity;
|
|
20650
|
+
const supabase = authed2.supabase;
|
|
20052
20651
|
const slug = facts.pinnedProject;
|
|
20053
20652
|
if (slug) {
|
|
20054
20653
|
const [orgSlug, projectSlug] = slug.split("/");
|
|
@@ -20334,18 +20933,18 @@ optional.`;
|
|
|
20334
20933
|
});
|
|
20335
20934
|
|
|
20336
20935
|
// src/doctor/dispatch.ts
|
|
20337
|
-
var
|
|
20338
|
-
__export(
|
|
20339
|
-
EX_INFRA: () =>
|
|
20340
|
-
EX_OK: () =>
|
|
20341
|
-
EX_USAGE: () =>
|
|
20936
|
+
var dispatch_exports6 = {};
|
|
20937
|
+
__export(dispatch_exports6, {
|
|
20938
|
+
EX_INFRA: () => EX_INFRA5,
|
|
20939
|
+
EX_OK: () => EX_OK6,
|
|
20940
|
+
EX_USAGE: () => EX_USAGE6,
|
|
20342
20941
|
dispatchDoctor: () => dispatchDoctor,
|
|
20343
20942
|
parseDoctorArgs: () => parseDoctorArgs,
|
|
20344
20943
|
printDoctorHelp: () => printDoctorHelp
|
|
20345
20944
|
});
|
|
20346
20945
|
import { mkdtemp as mkdtemp5, rm as rm9, writeFile as writeFile13 } from "node:fs/promises";
|
|
20347
20946
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
20348
|
-
import { join as
|
|
20947
|
+
import { join as join25 } from "node:path";
|
|
20349
20948
|
function parseDoctorArgs(argv) {
|
|
20350
20949
|
const args = { plain: false, help: false };
|
|
20351
20950
|
for (const a2 of argv) {
|
|
@@ -20377,11 +20976,11 @@ async function dispatchDoctor(argv) {
|
|
|
20377
20976
|
if (!parsed.ok) {
|
|
20378
20977
|
console.error(parsed.message);
|
|
20379
20978
|
printDoctorHelp();
|
|
20380
|
-
return
|
|
20979
|
+
return EX_USAGE6;
|
|
20381
20980
|
}
|
|
20382
20981
|
if (parsed.args.help) {
|
|
20383
20982
|
printDoctorHelp();
|
|
20384
|
-
return
|
|
20983
|
+
return EX_OK6;
|
|
20385
20984
|
}
|
|
20386
20985
|
const facts = await collectFacts(process.cwd());
|
|
20387
20986
|
const issues = diagnose(facts);
|
|
@@ -20397,17 +20996,17 @@ async function dispatchDoctor(argv) {
|
|
|
20397
20996
|
console.log("");
|
|
20398
20997
|
}
|
|
20399
20998
|
}
|
|
20400
|
-
if (parsed.args.plain) return
|
|
20999
|
+
if (parsed.args.plain) return EX_OK6;
|
|
20401
21000
|
try {
|
|
20402
21001
|
requireBinary("claude");
|
|
20403
21002
|
} catch {
|
|
20404
21003
|
console.log(
|
|
20405
21004
|
"Install Claude Code to ask about any of this: npm i -g @anthropic-ai/claude-code"
|
|
20406
21005
|
);
|
|
20407
|
-
return
|
|
21006
|
+
return EX_OK6;
|
|
20408
21007
|
}
|
|
20409
|
-
const dir = await mkdtemp5(
|
|
20410
|
-
const mcpConfigPath =
|
|
21008
|
+
const dir = await mkdtemp5(join25(tmpdir5(), "archme-doctor-"));
|
|
21009
|
+
const mcpConfigPath = join25(dir, "mcp.json");
|
|
20411
21010
|
await writeFile13(mcpConfigPath, JSON.stringify({ mcpServers: {} }), { mode: 384 });
|
|
20412
21011
|
try {
|
|
20413
21012
|
const result = await spawnClaude({
|
|
@@ -20420,13 +21019,13 @@ async function dispatchDoctor(argv) {
|
|
|
20420
21019
|
// is a much larger surface than the problem justifies.
|
|
20421
21020
|
allowedTools: ["Read", "Glob", "Grep"]
|
|
20422
21021
|
});
|
|
20423
|
-
return result.exitCode === 0 ?
|
|
21022
|
+
return result.exitCode === 0 ? EX_OK6 : EX_INFRA5;
|
|
20424
21023
|
} finally {
|
|
20425
21024
|
await rm9(dir, { recursive: true, force: true });
|
|
20426
21025
|
}
|
|
20427
21026
|
}
|
|
20428
|
-
var
|
|
20429
|
-
var
|
|
21027
|
+
var EX_OK6, EX_INFRA5, EX_USAGE6;
|
|
21028
|
+
var init_dispatch6 = __esm({
|
|
20430
21029
|
"src/doctor/dispatch.ts"() {
|
|
20431
21030
|
"use strict";
|
|
20432
21031
|
init_collect();
|
|
@@ -20434,9 +21033,9 @@ var init_dispatch5 = __esm({
|
|
|
20434
21033
|
init_prompt2();
|
|
20435
21034
|
init_spawn_claude();
|
|
20436
21035
|
init_preflight();
|
|
20437
|
-
|
|
20438
|
-
|
|
20439
|
-
|
|
21036
|
+
EX_OK6 = 0;
|
|
21037
|
+
EX_INFRA5 = 3;
|
|
21038
|
+
EX_USAGE6 = 78;
|
|
20440
21039
|
}
|
|
20441
21040
|
});
|
|
20442
21041
|
|
|
@@ -20494,6 +21093,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
20494
21093
|
"keys",
|
|
20495
21094
|
"scan",
|
|
20496
21095
|
"init",
|
|
21096
|
+
"uninstall",
|
|
20497
21097
|
"local-watch",
|
|
20498
21098
|
"doctor"
|
|
20499
21099
|
]);
|
|
@@ -20592,12 +21192,16 @@ async function main(argv) {
|
|
|
20592
21192
|
const { dispatchInit: dispatchInit2 } = await Promise.resolve().then(() => (init_dispatch4(), dispatch_exports4));
|
|
20593
21193
|
return await dispatchInit2(r.args);
|
|
20594
21194
|
}
|
|
21195
|
+
case "uninstall": {
|
|
21196
|
+
const { dispatchUninstall: dispatchUninstall2 } = await Promise.resolve().then(() => (init_dispatch5(), dispatch_exports5));
|
|
21197
|
+
return await dispatchUninstall2(r.args);
|
|
21198
|
+
}
|
|
20595
21199
|
case "local-watch": {
|
|
20596
21200
|
const { dispatchLocalWatch: dispatchLocalWatch2 } = await Promise.resolve().then(() => (init_localWatchDispatch(), localWatchDispatch_exports));
|
|
20597
21201
|
return await dispatchLocalWatch2(r.args);
|
|
20598
21202
|
}
|
|
20599
21203
|
case "doctor": {
|
|
20600
|
-
const { dispatchDoctor: dispatchDoctor2 } = await Promise.resolve().then(() => (
|
|
21204
|
+
const { dispatchDoctor: dispatchDoctor2 } = await Promise.resolve().then(() => (init_dispatch6(), dispatch_exports6));
|
|
20601
21205
|
return await dispatchDoctor2(r.args);
|
|
20602
21206
|
}
|
|
20603
21207
|
case "logout": {
|
|
@@ -20624,6 +21228,8 @@ Other commands:
|
|
|
20624
21228
|
archme scan <repo-path> Single-pass scan into an explicit project/branch
|
|
20625
21229
|
archme local-watch [repo-path] Watch a repo without a GitHub App
|
|
20626
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
|
|
20627
21233
|
archme doctor Check this machine's setup and explain it
|
|
20628
21234
|
archme login | logout Authenticate with archme
|
|
20629
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.
|
|
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",
|