@archme/cli 0.4.5 → 0.5.1
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 +706 -98
- 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
|
});
|
|
@@ -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((
|
|
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((
|
|
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
|
-
|
|
9330
|
+
resolve6();
|
|
9324
9331
|
};
|
|
9325
9332
|
process.once("SIGINT", handleSigint);
|
|
9326
9333
|
});
|
|
@@ -12131,21 +12138,32 @@ var init_affected = __esm({
|
|
|
12131
12138
|
}
|
|
12132
12139
|
});
|
|
12133
12140
|
|
|
12141
|
+
// ../shared/storyNaming.ts
|
|
12142
|
+
var STORY_NAMING_GUIDANCE;
|
|
12143
|
+
var init_storyNaming = __esm({
|
|
12144
|
+
"../shared/storyNaming.ts"() {
|
|
12145
|
+
STORY_NAMING_GUIDANCE = `Story titles:
|
|
12146
|
+
- **Leaves are actions.** A leaf story is a flow, so title it verb + object, imperative: \`sign up with email\`, \`refund a charge\`, \`retry a failed job\`. Not the noun phrase for the same flow (\`email signup\`, \`charge refunds\`, \`failed job retry\`).
|
|
12147
|
+
- **Groups are areas.** A parent story is a feature area, so title it as a noun phrase naming what its leaves have in common: \`auth\`, \`billing\`, \`job queue\`. Not an action (\`authenticate users\`).
|
|
12148
|
+
- Keep both as short as the hierarchy allows \u2014 the group supplies the context, so a leaf never restates it.
|
|
12149
|
+
- Good: "web app" > "auth" > "sign up with email"
|
|
12150
|
+
- Bad: "new user signs up with email on the web app"
|
|
12151
|
+
- **An existing convention wins.** If a story's siblings already agree on a different form, match them \u2014 cohesion across the tree is the goal, and the rules above are only the default when there is nothing to match. Restyle only titles you are already editing for another reason; a rename lands in someone's diff, so never sweep the tree for form alone.`;
|
|
12152
|
+
}
|
|
12153
|
+
});
|
|
12154
|
+
|
|
12134
12155
|
// ../shared/scanPrompts.ts
|
|
12135
12156
|
function buildFormatRefStories(opts) {
|
|
12136
12157
|
const draftBlock = opts.persistentDrafts ? FORMAT_REF_DRAFT_STORIES_BLOCK : "";
|
|
12137
12158
|
const uncoveredSubFlowRule = opts.persistentDrafts ? CROSS_REF_UNCOVERED_DRAFT_STUB : CROSS_REF_UNCOVERED_REAL_STORY;
|
|
12138
|
-
const hierarchyExample = opts.persistentDrafts ? `- Example: storyUpdates: [{add: [{id: "auth", title: "Authentication", description: "User auth flows", draft: true, userStories: [{id: "login", title: "
|
|
12159
|
+
const hierarchyExample = opts.persistentDrafts ? `- Example: storyUpdates: [{add: [{id: "auth", title: "Authentication", description: "User auth flows", draft: true, userStories: [{id: "login", title: "log in with email", draft: true, acceptanceCriteria: ["< 1s response"]}]}]}]` : `- Example: storyUpdates: [{add: [{id: "auth", title: "Authentication", description: "User auth flows", userStories: [{id: "login", title: "log in with email", acceptanceCriteria: ["< 1s response"], sequence: "sequenceDiagram\\n ..."}]}]}]`;
|
|
12139
12160
|
return `Recursive story hierarchy:
|
|
12140
12161
|
- Stories can contain child stories via userStories: [...] \u2014 parent stories act as feature groups
|
|
12141
12162
|
- Use top-level stories with description, links, and nested children to organize by feature area
|
|
12142
12163
|
${hierarchyExample}
|
|
12143
12164
|
- Removing a parent story cascades to all descendants
|
|
12144
12165
|
|
|
12145
|
-
|
|
12146
|
-
- Keep titles as short as possible \u2014 rely on the parent group for context.
|
|
12147
|
-
- Good: "web app" > "auth" > "sign up with email"
|
|
12148
|
-
- Bad: "new user signs up with email on the web app"
|
|
12166
|
+
${STORY_NAMING_GUIDANCE}
|
|
12149
12167
|
|
|
12150
12168
|
Story description format (markdown, 1-4 sentences \u2014 render target is a narrow side panel):
|
|
12151
12169
|
- Write for someone who knows the product but isn't reading the code. Say what the system does and what the user/operator experiences at the highest useful altitude, not how it's implemented. A tl;dr, not a rehash.
|
|
@@ -12198,14 +12216,15 @@ storyUpdates targeting:
|
|
|
12198
12216
|
- add: stories to add or update (merged by ID if story already exists)
|
|
12199
12217
|
- remove: story IDs to remove
|
|
12200
12218
|
- Examples:
|
|
12201
|
-
- Add at root: storyUpdates: [{add: [{id: "us1", title: "
|
|
12202
|
-
- Add under story group: storyUpdates: [{under: "auth", add: [{id: "login", title: "
|
|
12219
|
+
- Add at root: storyUpdates: [{add: [{id: "us1", title: "log in", ...}]}]
|
|
12220
|
+
- Add under story group: storyUpdates: [{under: "auth", add: [{id: "login", title: "log in with email", ...}]}]
|
|
12203
12221
|
- Remove: storyUpdates: [{remove: ["old_story"]}]
|
|
12204
12222
|
- Combined: storyUpdates: [{under: "auth", add: [{id: "new_flow", ...}], remove: ["old_flow"]}]`;
|
|
12205
12223
|
}
|
|
12206
12224
|
var ANCHOR_RULES, ANCHOR_DISCOVERY, ANCHOR_VERIFICATION, FORMAT_REF_COMPONENTS, FORMAT_REF_CONNECTIONS, FORMAT_REF_DRAFT_STORIES_BLOCK, CROSS_REF_UNCOVERED_DRAFT_STUB, CROSS_REF_UNCOVERED_REAL_STORY, FORMAT_REF_STORIES;
|
|
12207
12225
|
var init_scanPrompts = __esm({
|
|
12208
12226
|
"../shared/scanPrompts.ts"() {
|
|
12227
|
+
init_storyNaming();
|
|
12209
12228
|
ANCHOR_RULES = `Every component must have either \`anchors\` (non-empty array) or \`unanchored: true\`. No bare components.
|
|
12210
12229
|
|
|
12211
12230
|
Pick the anchor kind based on how the component lives in code:
|
|
@@ -12630,12 +12649,15 @@ async function getLanguage(lang, cacheRoot = defaultCacheRoot()) {
|
|
|
12630
12649
|
return language;
|
|
12631
12650
|
}
|
|
12632
12651
|
async function getParser(lang, cacheRoot) {
|
|
12652
|
+
const cached = parserCache.get(lang);
|
|
12653
|
+
if (cached) return cached;
|
|
12633
12654
|
const language = await getLanguage(lang, cacheRoot);
|
|
12634
12655
|
const parser = new Parser();
|
|
12635
12656
|
parser.setLanguage(language);
|
|
12657
|
+
parserCache.set(lang, parser);
|
|
12636
12658
|
return parser;
|
|
12637
12659
|
}
|
|
12638
|
-
var initialized, languageCache;
|
|
12660
|
+
var initialized, languageCache, parserCache;
|
|
12639
12661
|
var init_parser = __esm({
|
|
12640
12662
|
"src/analyze/tree-sitter/parser.ts"() {
|
|
12641
12663
|
"use strict";
|
|
@@ -12643,6 +12665,7 @@ var init_parser = __esm({
|
|
|
12643
12665
|
init_grammars();
|
|
12644
12666
|
initialized = false;
|
|
12645
12667
|
languageCache = /* @__PURE__ */ new Map();
|
|
12668
|
+
parserCache = /* @__PURE__ */ new Map();
|
|
12646
12669
|
}
|
|
12647
12670
|
});
|
|
12648
12671
|
|
|
@@ -12970,22 +12993,34 @@ var init_extract = __esm({
|
|
|
12970
12993
|
|
|
12971
12994
|
// src/analyze/tree-sitter/symbols.ts
|
|
12972
12995
|
import { Query } from "web-tree-sitter";
|
|
12996
|
+
async function getQuery(lang) {
|
|
12997
|
+
const cached = queryCache.get(lang);
|
|
12998
|
+
if (cached) return cached;
|
|
12999
|
+
const query = new Query(await getLanguage(lang), SYMBOL_QUERIES[lang]);
|
|
13000
|
+
queryCache.set(lang, query);
|
|
13001
|
+
return query;
|
|
13002
|
+
}
|
|
12973
13003
|
async function extractSymbols(lang, source) {
|
|
12974
13004
|
const parser = await getParser(lang);
|
|
12975
13005
|
const tree = parser.parse(source);
|
|
12976
13006
|
if (!tree) {
|
|
12977
13007
|
return [];
|
|
12978
13008
|
}
|
|
12979
|
-
|
|
12980
|
-
|
|
12981
|
-
|
|
13009
|
+
try {
|
|
13010
|
+
const query = await getQuery(lang);
|
|
13011
|
+
return symbolsFromMatches(query.matches(tree.rootNode));
|
|
13012
|
+
} finally {
|
|
13013
|
+
tree.delete();
|
|
13014
|
+
}
|
|
12982
13015
|
}
|
|
13016
|
+
var queryCache;
|
|
12983
13017
|
var init_symbols = __esm({
|
|
12984
13018
|
"src/analyze/tree-sitter/symbols.ts"() {
|
|
12985
13019
|
"use strict";
|
|
12986
13020
|
init_parser();
|
|
12987
13021
|
init_queries();
|
|
12988
13022
|
init_extract();
|
|
13023
|
+
queryCache = /* @__PURE__ */ new Map();
|
|
12989
13024
|
}
|
|
12990
13025
|
});
|
|
12991
13026
|
|
|
@@ -14258,7 +14293,7 @@ var init_semaphore = __esm({
|
|
|
14258
14293
|
acquire() {
|
|
14259
14294
|
const slot = this.tryAcquire();
|
|
14260
14295
|
if (slot) return Promise.resolve(slot);
|
|
14261
|
-
return new Promise((
|
|
14296
|
+
return new Promise((resolve6) => this.waiters.push(resolve6));
|
|
14262
14297
|
}
|
|
14263
14298
|
makeRelease() {
|
|
14264
14299
|
let released = false;
|
|
@@ -16118,12 +16153,12 @@ async function rpcHeartbeat(ctx, jobId, deviceId) {
|
|
|
16118
16153
|
);
|
|
16119
16154
|
if (error) console.error(`[${jobId}] heartbeat error:`, error.message);
|
|
16120
16155
|
}
|
|
16121
|
-
async function rpcComplete(ctx, jobId, deviceId,
|
|
16156
|
+
async function rpcComplete(ctx, jobId, deviceId, summary2, kind, changed, metrics, logs, advanceMarker = true, affected) {
|
|
16122
16157
|
const extra = {
|
|
16123
16158
|
// `kind` drives lifecycle (squash, marker, retry). `changed` is the
|
|
16124
16159
|
// decoupled what-changed verdict the Check Run / sticky comment render
|
|
16125
16160
|
// (migration 083): a structural-only job is kind=applied but changed=false.
|
|
16126
|
-
result: { summary, kind, changed },
|
|
16161
|
+
result: { summary: summary2, kind, changed },
|
|
16127
16162
|
metrics,
|
|
16128
16163
|
logs,
|
|
16129
16164
|
advance_marker: advanceMarker
|
|
@@ -16657,8 +16692,8 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
16657
16692
|
if (runResult.isError) {
|
|
16658
16693
|
throw new Error(`claude reported is_error in its JSON result (no successful completion)`);
|
|
16659
16694
|
}
|
|
16660
|
-
const
|
|
16661
|
-
if (
|
|
16695
|
+
const summary2 = runResult.summary.trim().slice(0, 4e3);
|
|
16696
|
+
if (summary2) log(`claude> ${summary2.slice(0, 500).replace(/\n/g, " ")}`);
|
|
16662
16697
|
metrics.model_served = runResult.modelServed;
|
|
16663
16698
|
metrics.usage = runResult.usage;
|
|
16664
16699
|
metrics.cost_usd = runResult.costUsd;
|
|
@@ -16685,7 +16720,7 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
16685
16720
|
throw new Error(outcome.reason);
|
|
16686
16721
|
}
|
|
16687
16722
|
const changed = outcome.changed;
|
|
16688
|
-
log(`done (${outcome.kind}, changed=${changed}, ${writes} commit(s) [${diffWrites} diff / ${structuralWrites} structural], ${reads ?? "?"} mcp tool call(s)): ${
|
|
16723
|
+
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
16724
|
const totalMs = Date.now() - startedAt;
|
|
16690
16725
|
markPhase("complete", { kind: outcome.kind, changed, total_ms: totalMs });
|
|
16691
16726
|
metrics.total_ms = totalMs;
|
|
@@ -16693,7 +16728,7 @@ async function runJob(ctx, job, claimDeviceId) {
|
|
|
16693
16728
|
ctx,
|
|
16694
16729
|
job.id,
|
|
16695
16730
|
deviceId,
|
|
16696
|
-
|
|
16731
|
+
summary2,
|
|
16697
16732
|
outcome.kind,
|
|
16698
16733
|
changed,
|
|
16699
16734
|
metrics,
|
|
@@ -18164,14 +18199,14 @@ async function runScan(opts, deps = {}) {
|
|
|
18164
18199
|
log(` supabase: ${supabaseUrl}`);
|
|
18165
18200
|
log(` mcp base: ${mcpUrl}`);
|
|
18166
18201
|
log(` repo: ${opts.repoPath}`);
|
|
18167
|
-
const
|
|
18202
|
+
const authed2 = await createAuthedClient({
|
|
18168
18203
|
supabaseUrl,
|
|
18169
18204
|
supabaseAnonKey: supabaseAnon,
|
|
18170
18205
|
clientId,
|
|
18171
18206
|
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
18172
18207
|
});
|
|
18173
|
-
log(` signed in as ${
|
|
18174
|
-
const { supabase, userId } =
|
|
18208
|
+
log(` signed in as ${authed2.identity}`);
|
|
18209
|
+
const { supabase, userId } = authed2;
|
|
18175
18210
|
let headSha;
|
|
18176
18211
|
try {
|
|
18177
18212
|
headSha = await repoHeadSha(opts.repoPath);
|
|
@@ -18270,7 +18305,7 @@ async function runScan(opts, deps = {}) {
|
|
|
18270
18305
|
const result = await proc;
|
|
18271
18306
|
const exitCode = result.exitCode ?? 0;
|
|
18272
18307
|
const signal = result.signal ?? null;
|
|
18273
|
-
const
|
|
18308
|
+
const summary2 = (finalResultText || (result.stdout ? String(result.stdout) : "")).trim().slice(-4e3);
|
|
18274
18309
|
if (signal) {
|
|
18275
18310
|
log(`claude exited ${exitCode} (signal: ${signal})`);
|
|
18276
18311
|
} else {
|
|
@@ -18281,8 +18316,8 @@ async function runScan(opts, deps = {}) {
|
|
|
18281
18316
|
log(` stories: ${counts.stories}`);
|
|
18282
18317
|
let signals2 = null;
|
|
18283
18318
|
try {
|
|
18284
|
-
await new Promise((
|
|
18285
|
-
transcriptStream.end((err2) => err2 ? reject(err2) :
|
|
18319
|
+
await new Promise((resolve6, reject) => {
|
|
18320
|
+
transcriptStream.end((err2) => err2 ? reject(err2) : resolve6());
|
|
18286
18321
|
});
|
|
18287
18322
|
const reportPath = join19(transcriptDir, "report.json");
|
|
18288
18323
|
const orphansTxtPath = join19(transcriptDir, "orphans.txt");
|
|
@@ -18348,7 +18383,7 @@ async function runScan(opts, deps = {}) {
|
|
|
18348
18383
|
orgSlug: resolved2.orgSlug,
|
|
18349
18384
|
projectSlug: resolved2.projectSlug,
|
|
18350
18385
|
webUrl,
|
|
18351
|
-
summary
|
|
18386
|
+
summary: summary2
|
|
18352
18387
|
};
|
|
18353
18388
|
} finally {
|
|
18354
18389
|
await cleanupMcp().catch(() => {
|
|
@@ -18959,6 +18994,9 @@ var init_githubAppSetup = __esm({
|
|
|
18959
18994
|
|
|
18960
18995
|
// src/daemon/prompt.ts
|
|
18961
18996
|
import { createInterface } from "node:readline/promises";
|
|
18997
|
+
function stdinIsInteractive() {
|
|
18998
|
+
return Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
18999
|
+
}
|
|
18962
19000
|
async function withReadline(fn) {
|
|
18963
19001
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
18964
19002
|
try {
|
|
@@ -19241,9 +19279,9 @@ async function runLocalWatch(opts) {
|
|
|
19241
19279
|
log(` repo: ${target.repo}`);
|
|
19242
19280
|
log(` branch: ${target.repoBranch} \u2192 archme branch ${target.archmeBranch}`);
|
|
19243
19281
|
const cfg = resolveConfig();
|
|
19244
|
-
let
|
|
19282
|
+
let authed2;
|
|
19245
19283
|
try {
|
|
19246
|
-
|
|
19284
|
+
authed2 = await createAuthedClient({
|
|
19247
19285
|
supabaseUrl: cfg.supabaseUrl,
|
|
19248
19286
|
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
19249
19287
|
clientId: cfg.archmeDaemonClientId,
|
|
@@ -19253,8 +19291,8 @@ async function runLocalWatch(opts) {
|
|
|
19253
19291
|
console.error(`archme local-watch: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
19254
19292
|
return 3;
|
|
19255
19293
|
}
|
|
19256
|
-
const { supabase, userId } =
|
|
19257
|
-
const orgId =
|
|
19294
|
+
const { supabase, userId } = authed2;
|
|
19295
|
+
const orgId = authed2.orgId;
|
|
19258
19296
|
if (!orgId) {
|
|
19259
19297
|
console.error("archme local-watch: this login is not paired with an org \u2014 run `archme login`");
|
|
19260
19298
|
return 3;
|
|
@@ -19494,22 +19532,22 @@ async function runInit(opts, deps = {}) {
|
|
|
19494
19532
|
} else {
|
|
19495
19533
|
log("\u2713 already signed in");
|
|
19496
19534
|
}
|
|
19497
|
-
const
|
|
19535
|
+
const authed2 = await authenticate({
|
|
19498
19536
|
supabaseUrl: cfg.supabaseUrl,
|
|
19499
19537
|
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
19500
19538
|
clientId: cfg.archmeDaemonClientId,
|
|
19501
19539
|
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
19502
19540
|
});
|
|
19503
|
-
log(` signed in as ${
|
|
19541
|
+
log(` signed in as ${authed2.identity}`);
|
|
19504
19542
|
let fullSlug = opts.project;
|
|
19505
19543
|
if (!fullSlug) {
|
|
19506
|
-
const orgId =
|
|
19544
|
+
const orgId = authed2.orgId;
|
|
19507
19545
|
if (!orgId) {
|
|
19508
19546
|
throw new Error(
|
|
19509
19547
|
"could not resolve your organization \u2014 run `archme login` again, or pass --project <org>/<slug>"
|
|
19510
19548
|
);
|
|
19511
19549
|
}
|
|
19512
|
-
const orgSlug = await resolveOrgSlug(
|
|
19550
|
+
const orgSlug = await resolveOrgSlug(authed2.supabase, orgId);
|
|
19513
19551
|
if (!orgSlug) {
|
|
19514
19552
|
throw new Error(
|
|
19515
19553
|
`paired org ${orgId} no longer exists \u2014 run \`archme login\` again, or pass --project <org>/<slug>`
|
|
@@ -19518,8 +19556,8 @@ async function runInit(opts, deps = {}) {
|
|
|
19518
19556
|
fullSlug = defaultProjectSlug(orgSlug, repoPath);
|
|
19519
19557
|
}
|
|
19520
19558
|
const project = await resolveProject(
|
|
19521
|
-
|
|
19522
|
-
|
|
19559
|
+
authed2.supabase,
|
|
19560
|
+
authed2.userId,
|
|
19523
19561
|
fullSlug
|
|
19524
19562
|
);
|
|
19525
19563
|
log(
|
|
@@ -19527,7 +19565,7 @@ async function runInit(opts, deps = {}) {
|
|
|
19527
19565
|
);
|
|
19528
19566
|
const scanBranch = initScanBranch(project.defaultBranch);
|
|
19529
19567
|
const detail = await fetchArchitecture(
|
|
19530
|
-
|
|
19568
|
+
authed2.supabase,
|
|
19531
19569
|
project.projectId,
|
|
19532
19570
|
scanBranch
|
|
19533
19571
|
);
|
|
@@ -19541,8 +19579,8 @@ async function runInit(opts, deps = {}) {
|
|
|
19541
19579
|
await runInstallScript(script, repoPath);
|
|
19542
19580
|
ran.push("agent-config");
|
|
19543
19581
|
const watched = await watchRepo({
|
|
19544
|
-
supabase:
|
|
19545
|
-
orgId:
|
|
19582
|
+
supabase: authed2.supabase,
|
|
19583
|
+
orgId: authed2.orgId,
|
|
19546
19584
|
projectId: project.projectId,
|
|
19547
19585
|
repoSlug: git4.repoSlug,
|
|
19548
19586
|
branch: git4.branch,
|
|
@@ -19609,18 +19647,18 @@ Your board: ${webUrl}`);
|
|
|
19609
19647
|
startGitHubAppInstall: () => (deps.buildInstallUrl ?? buildInstallUrl)({
|
|
19610
19648
|
supabaseUrl: cfg.supabaseUrl,
|
|
19611
19649
|
accessToken: getCurrentAccessToken() ?? "",
|
|
19612
|
-
orgId:
|
|
19650
|
+
orgId: authed2.orgId
|
|
19613
19651
|
}),
|
|
19614
19652
|
waitForInstallation: () => (deps.waitForInstallation ?? waitForInstallation)({
|
|
19615
|
-
hasInstallation: () => (deps.orgHasInstallation ?? orgHasInstallation)(
|
|
19653
|
+
hasInstallation: () => (deps.orgHasInstallation ?? orgHasInstallation)(authed2.supabase, authed2.orgId),
|
|
19616
19654
|
sleep: (ms) => new Promise((r) => setTimeout(r, ms)),
|
|
19617
19655
|
now: () => Date.now(),
|
|
19618
19656
|
log
|
|
19619
19657
|
}),
|
|
19620
19658
|
watchRepo: async () => {
|
|
19621
19659
|
const w = await watchRepo({
|
|
19622
|
-
supabase:
|
|
19623
|
-
orgId:
|
|
19660
|
+
supabase: authed2.supabase,
|
|
19661
|
+
orgId: authed2.orgId,
|
|
19624
19662
|
projectId: project.projectId,
|
|
19625
19663
|
repoSlug: git4.repoSlug,
|
|
19626
19664
|
branch: git4.branch,
|
|
@@ -19642,8 +19680,8 @@ Your board: ${webUrl}`);
|
|
|
19642
19680
|
});
|
|
19643
19681
|
if (!target.ok) return { ok: false, reason: target.error };
|
|
19644
19682
|
const res = await (deps.registerLocalWatch ?? registerLocalWatch)({
|
|
19645
|
-
supabase:
|
|
19646
|
-
orgId:
|
|
19683
|
+
supabase: authed2.supabase,
|
|
19684
|
+
orgId: authed2.orgId,
|
|
19647
19685
|
projectId: project.projectId,
|
|
19648
19686
|
repoPath: git4.root,
|
|
19649
19687
|
originUrl: git4.originUrl,
|
|
@@ -19852,12 +19890,575 @@ var init_dispatch4 = __esm({
|
|
|
19852
19890
|
}
|
|
19853
19891
|
});
|
|
19854
19892
|
|
|
19893
|
+
// src/uninstall/edits.ts
|
|
19894
|
+
function stripArchmeClaudeBlock(content) {
|
|
19895
|
+
const start = content.indexOf(START_MARKER);
|
|
19896
|
+
if (start === -1) return UNCHANGED;
|
|
19897
|
+
const end = content.indexOf(END_MARKER, start);
|
|
19898
|
+
if (end === -1) return UNCHANGED;
|
|
19899
|
+
const before = content.slice(0, start).replace(/\n+$/, "");
|
|
19900
|
+
const after = content.slice(end + END_MARKER.length).replace(/^\n+/, "");
|
|
19901
|
+
if (!before.trim() && !after.trim()) return { kind: "delete" };
|
|
19902
|
+
if (!before.trim()) return { kind: "rewrite", content: `${after.replace(/\n*$/, "")}
|
|
19903
|
+
` };
|
|
19904
|
+
if (!after.trim()) return { kind: "rewrite", content: `${before}
|
|
19905
|
+
` };
|
|
19906
|
+
return { kind: "rewrite", content: `${before}
|
|
19907
|
+
|
|
19908
|
+
${after.replace(/\n*$/, "")}
|
|
19909
|
+
` };
|
|
19910
|
+
}
|
|
19911
|
+
function parseObject(raw) {
|
|
19912
|
+
try {
|
|
19913
|
+
const parsed = JSON.parse(raw);
|
|
19914
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
19915
|
+
return parsed;
|
|
19916
|
+
} catch {
|
|
19917
|
+
return null;
|
|
19918
|
+
}
|
|
19919
|
+
}
|
|
19920
|
+
function render(value) {
|
|
19921
|
+
return `${JSON.stringify(value, null, 2)}
|
|
19922
|
+
`;
|
|
19923
|
+
}
|
|
19924
|
+
function stripArchmeMcpServer(content) {
|
|
19925
|
+
const root = parseObject(content);
|
|
19926
|
+
if (!root) return UNCHANGED;
|
|
19927
|
+
const servers = root.mcpServers;
|
|
19928
|
+
if (!servers || typeof servers !== "object" || Array.isArray(servers)) return UNCHANGED;
|
|
19929
|
+
const entries = servers;
|
|
19930
|
+
if (!(MCP_SERVER_NAME2 in entries)) return UNCHANGED;
|
|
19931
|
+
delete entries[MCP_SERVER_NAME2];
|
|
19932
|
+
const onlyKey = Object.keys(root).length === 1 && "mcpServers" in root;
|
|
19933
|
+
if (onlyKey && Object.keys(entries).length === 0) return { kind: "delete" };
|
|
19934
|
+
return { kind: "rewrite", content: render(root) };
|
|
19935
|
+
}
|
|
19936
|
+
function isArchmeHook(hook) {
|
|
19937
|
+
const command = hook?.command;
|
|
19938
|
+
return typeof command === "string" && command.includes(NUDGE_HOOK_FILE);
|
|
19939
|
+
}
|
|
19940
|
+
function stripArchmeHooks(content) {
|
|
19941
|
+
const root = parseObject(content);
|
|
19942
|
+
if (!root) return UNCHANGED;
|
|
19943
|
+
const hooks = root.hooks;
|
|
19944
|
+
if (!hooks || typeof hooks !== "object" || Array.isArray(hooks)) return UNCHANGED;
|
|
19945
|
+
const byEvent = hooks;
|
|
19946
|
+
const pre = byEvent.PreToolUse;
|
|
19947
|
+
if (!Array.isArray(pre)) return UNCHANGED;
|
|
19948
|
+
let changed = false;
|
|
19949
|
+
const kept = [];
|
|
19950
|
+
for (const entry of pre) {
|
|
19951
|
+
const inner = entry?.hooks;
|
|
19952
|
+
if (!Array.isArray(inner) || !inner.some(isArchmeHook)) {
|
|
19953
|
+
kept.push(entry);
|
|
19954
|
+
continue;
|
|
19955
|
+
}
|
|
19956
|
+
changed = true;
|
|
19957
|
+
const survivors = inner.filter((h2) => !isArchmeHook(h2));
|
|
19958
|
+
if (survivors.length > 0) kept.push({ ...entry, hooks: survivors });
|
|
19959
|
+
}
|
|
19960
|
+
if (!changed) return UNCHANGED;
|
|
19961
|
+
if (kept.length > 0) byEvent.PreToolUse = kept;
|
|
19962
|
+
else delete byEvent.PreToolUse;
|
|
19963
|
+
if (Object.keys(byEvent).length === 0) delete root.hooks;
|
|
19964
|
+
if (Object.keys(root).length === 0) return { kind: "delete" };
|
|
19965
|
+
return { kind: "rewrite", content: render(root) };
|
|
19966
|
+
}
|
|
19967
|
+
var UNCHANGED, START_MARKER, END_MARKER, NUDGE_HOOK_FILE, MCP_SERVER_NAME2;
|
|
19968
|
+
var init_edits = __esm({
|
|
19969
|
+
"src/uninstall/edits.ts"() {
|
|
19970
|
+
"use strict";
|
|
19971
|
+
UNCHANGED = { kind: "unchanged" };
|
|
19972
|
+
START_MARKER = "<!-- ARCHME-START -->";
|
|
19973
|
+
END_MARKER = "<!-- ARCHME-END -->";
|
|
19974
|
+
NUDGE_HOOK_FILE = "archme-search-nudge.mjs";
|
|
19975
|
+
MCP_SERVER_NAME2 = "archme";
|
|
19976
|
+
}
|
|
19977
|
+
});
|
|
19978
|
+
|
|
19979
|
+
// src/uninstall/watching.ts
|
|
19980
|
+
function decideDaemonRemoval(input) {
|
|
19981
|
+
if (!input.installed) {
|
|
19982
|
+
return { action: "keep", reason: "no daemon service is installed on this machine" };
|
|
19983
|
+
}
|
|
19984
|
+
if (input.keepWatching) {
|
|
19985
|
+
return { action: "keep", reason: "--keep-watching: this repo is still being watched" };
|
|
19986
|
+
}
|
|
19987
|
+
if (input.remaining.watchedRepos === null) {
|
|
19988
|
+
return {
|
|
19989
|
+
action: "keep",
|
|
19990
|
+
reason: "could not check what else it serves, so it was left running"
|
|
19991
|
+
};
|
|
19992
|
+
}
|
|
19993
|
+
const { watchedRepos, localEntries } = input.remaining;
|
|
19994
|
+
if (watchedRepos > 0 || localEntries > 0) {
|
|
19995
|
+
const parts = [];
|
|
19996
|
+
if (watchedRepos > 0) parts.push(`${watchedRepos} watched repo${watchedRepos === 1 ? "" : "s"}`);
|
|
19997
|
+
if (localEntries > 0) {
|
|
19998
|
+
parts.push(`${localEntries} locally-polled repo${localEntries === 1 ? "" : "s"}`);
|
|
19999
|
+
}
|
|
20000
|
+
return { action: "keep", reason: `still serving ${parts.join(" and ")}` };
|
|
20001
|
+
}
|
|
20002
|
+
return { action: "remove", reason: "nothing is left for it to do" };
|
|
20003
|
+
}
|
|
20004
|
+
function rowsForSlug(rows, slug) {
|
|
20005
|
+
const wanted = slug.toLowerCase();
|
|
20006
|
+
return rows.filter((r) => r.slug.toLowerCase() === wanted);
|
|
20007
|
+
}
|
|
20008
|
+
var init_watching = __esm({
|
|
20009
|
+
"src/uninstall/watching.ts"() {
|
|
20010
|
+
"use strict";
|
|
20011
|
+
}
|
|
20012
|
+
});
|
|
20013
|
+
|
|
20014
|
+
// src/uninstall/backend.ts
|
|
20015
|
+
var backend_exports = {};
|
|
20016
|
+
__export(backend_exports, {
|
|
20017
|
+
createDefaultBackend: () => createDefaultBackend
|
|
20018
|
+
});
|
|
20019
|
+
function labelFor(row) {
|
|
20020
|
+
const org = row.projects?.organizations?.slug;
|
|
20021
|
+
const project = row.projects?.slug;
|
|
20022
|
+
const where = org && project ? `${org}/${project}` : `${row.repo_owner}/${row.repo_name}`;
|
|
20023
|
+
return `${where} @ ${row.branch}`;
|
|
20024
|
+
}
|
|
20025
|
+
function createDefaultBackend() {
|
|
20026
|
+
let clientPromise = null;
|
|
20027
|
+
const supabase = () => clientPromise ??= authed();
|
|
20028
|
+
return {
|
|
20029
|
+
async gitRoot(dir) {
|
|
20030
|
+
try {
|
|
20031
|
+
const { stdout } = await execa("git", ["rev-parse", "--show-toplevel"], { cwd: dir });
|
|
20032
|
+
return stdout.trim() || null;
|
|
20033
|
+
} catch {
|
|
20034
|
+
return null;
|
|
20035
|
+
}
|
|
20036
|
+
},
|
|
20037
|
+
async repoSlug(repoPath) {
|
|
20038
|
+
const origin = await readOriginUrl(repoPath);
|
|
20039
|
+
return origin ? parseGitHubRepo(origin) : null;
|
|
20040
|
+
},
|
|
20041
|
+
async listWatchedRepos() {
|
|
20042
|
+
const { supabase: client } = await supabase();
|
|
20043
|
+
const { data, error } = await client.from("watched_repos").select("id, repo_owner, repo_name, branch, projects(slug, organizations(slug))").is("deleted_at", null);
|
|
20044
|
+
if (error) throw new Error(error.message);
|
|
20045
|
+
return (data ?? []).map((row) => ({
|
|
20046
|
+
id: row.id,
|
|
20047
|
+
slug: `${row.repo_owner}/${row.repo_name}`,
|
|
20048
|
+
label: labelFor(row)
|
|
20049
|
+
}));
|
|
20050
|
+
},
|
|
20051
|
+
async unwatch(ids) {
|
|
20052
|
+
if (ids.length === 0) return;
|
|
20053
|
+
const { supabase: client } = await supabase();
|
|
20054
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
20055
|
+
const { error } = await client.from("watched_repos").update({ deleted_at: now, updated_at: now }).in("id", ids);
|
|
20056
|
+
if (error) throw new Error(error.message);
|
|
20057
|
+
},
|
|
20058
|
+
async localRepoSlugs() {
|
|
20059
|
+
return (await loadLocalRepos()).map((e) => slugOf(e));
|
|
20060
|
+
},
|
|
20061
|
+
async removeLocalRepos(slug) {
|
|
20062
|
+
const repos = await loadLocalRepos();
|
|
20063
|
+
const kept = removeLocalReposBySlug(repos, slug);
|
|
20064
|
+
const dropped = repos.length - kept.length;
|
|
20065
|
+
if (dropped > 0) await saveLocalRepos(kept);
|
|
20066
|
+
return dropped;
|
|
20067
|
+
},
|
|
20068
|
+
async daemonInstalled() {
|
|
20069
|
+
const { pickController: pickController2 } = await Promise.resolve().then(() => (init_serviceController(), serviceController_exports));
|
|
20070
|
+
return await pickController2().unitExists();
|
|
20071
|
+
},
|
|
20072
|
+
async removeDaemon() {
|
|
20073
|
+
const { pickController: pickController2, orchestrateStop: orchestrateStop2 } = await Promise.resolve().then(() => (init_serviceController(), serviceController_exports));
|
|
20074
|
+
const controller = pickController2();
|
|
20075
|
+
await orchestrateStop2(controller);
|
|
20076
|
+
await controller.uninstall();
|
|
20077
|
+
}
|
|
20078
|
+
};
|
|
20079
|
+
}
|
|
20080
|
+
async function authed() {
|
|
20081
|
+
const cfg = resolveConfig();
|
|
20082
|
+
return await createAuthedClient({
|
|
20083
|
+
supabaseUrl: cfg.supabaseUrl,
|
|
20084
|
+
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
20085
|
+
clientId: cfg.archmeDaemonClientId,
|
|
20086
|
+
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
20087
|
+
});
|
|
20088
|
+
}
|
|
20089
|
+
var init_backend = __esm({
|
|
20090
|
+
"src/uninstall/backend.ts"() {
|
|
20091
|
+
"use strict";
|
|
20092
|
+
init_execa();
|
|
20093
|
+
init_authedClient();
|
|
20094
|
+
init_defaults();
|
|
20095
|
+
init_localGit();
|
|
20096
|
+
init_github_remote();
|
|
20097
|
+
init_localRepos();
|
|
20098
|
+
}
|
|
20099
|
+
});
|
|
20100
|
+
|
|
20101
|
+
// src/uninstall/run-uninstall.ts
|
|
20102
|
+
var run_uninstall_exports = {};
|
|
20103
|
+
__export(run_uninstall_exports, {
|
|
20104
|
+
EX_OK: () => EX_OK3,
|
|
20105
|
+
EX_USAGE: () => EX_USAGE3,
|
|
20106
|
+
runUninstall: () => runUninstall
|
|
20107
|
+
});
|
|
20108
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync5, rmSync, writeFileSync as writeFileSync3 } from "node:fs";
|
|
20109
|
+
import { join as join23 } from "node:path";
|
|
20110
|
+
function readIfExists(path7) {
|
|
20111
|
+
try {
|
|
20112
|
+
return existsSync3(path7) ? readFileSync5(path7, "utf8") : null;
|
|
20113
|
+
} catch {
|
|
20114
|
+
return null;
|
|
20115
|
+
}
|
|
20116
|
+
}
|
|
20117
|
+
function isParseableObject(raw) {
|
|
20118
|
+
try {
|
|
20119
|
+
const parsed = JSON.parse(raw);
|
|
20120
|
+
return Boolean(parsed) && typeof parsed === "object" && !Array.isArray(parsed);
|
|
20121
|
+
} catch {
|
|
20122
|
+
return false;
|
|
20123
|
+
}
|
|
20124
|
+
}
|
|
20125
|
+
function applyEdit(absolute, edit) {
|
|
20126
|
+
if (edit.kind === "rewrite") writeFileSync3(absolute, edit.content);
|
|
20127
|
+
else if (edit.kind === "delete") rmSync(absolute, { force: true });
|
|
20128
|
+
}
|
|
20129
|
+
async function runUninstall(opts) {
|
|
20130
|
+
const log = opts.log ?? ((m) => console.log(m));
|
|
20131
|
+
const error = opts.error ?? ((m) => console.error(m));
|
|
20132
|
+
const backend = opts.backend ?? await defaultBackend();
|
|
20133
|
+
const repoPath = await backend.gitRoot(opts.repoPath).catch(() => null) ?? opts.repoPath;
|
|
20134
|
+
if (repoPath !== opts.repoPath) log(`Using the repo root: ${repoPath}`);
|
|
20135
|
+
const abs = (rel) => join23(repoPath, rel);
|
|
20136
|
+
const actions = [];
|
|
20137
|
+
const skipped = [];
|
|
20138
|
+
const mcpRaw = readIfExists(abs(".mcp.json"));
|
|
20139
|
+
if (mcpRaw !== null) {
|
|
20140
|
+
const edit = stripArchmeMcpServer(mcpRaw);
|
|
20141
|
+
if (edit.kind !== "unchanged") {
|
|
20142
|
+
actions.push({
|
|
20143
|
+
path: ".mcp.json",
|
|
20144
|
+
label: edit.kind === "delete" ? `delete (it holds only the ${MCP_SERVER_NAME2} server)` : `remove the ${MCP_SERVER_NAME2} server`,
|
|
20145
|
+
apply: () => applyEdit(abs(".mcp.json"), edit)
|
|
20146
|
+
});
|
|
20147
|
+
} else if (!isParseableObject(mcpRaw)) {
|
|
20148
|
+
skipped.push(
|
|
20149
|
+
`.mcp.json \u2014 could not be parsed as JSON; if it has an ${MCP_SERVER_NAME2} server, remove it by hand`
|
|
20150
|
+
);
|
|
20151
|
+
}
|
|
20152
|
+
}
|
|
20153
|
+
const claudeRaw = readIfExists(abs("CLAUDE.md"));
|
|
20154
|
+
if (claudeRaw !== null) {
|
|
20155
|
+
const edit = stripArchmeClaudeBlock(claudeRaw);
|
|
20156
|
+
if (edit.kind !== "unchanged") {
|
|
20157
|
+
actions.push({
|
|
20158
|
+
path: "CLAUDE.md",
|
|
20159
|
+
label: edit.kind === "delete" ? "delete (it holds only the ArchitectMe block)" : "remove the ArchitectMe block",
|
|
20160
|
+
apply: () => applyEdit(abs("CLAUDE.md"), edit)
|
|
20161
|
+
});
|
|
20162
|
+
} else if (claudeRaw.includes(START_MARKER)) {
|
|
20163
|
+
skipped.push("CLAUDE.md \u2014 the ArchitectMe block has no closing marker; remove it by hand");
|
|
20164
|
+
}
|
|
20165
|
+
}
|
|
20166
|
+
const skillPath = ".claude/skills/architecture/SKILL.md";
|
|
20167
|
+
if (existsSync3(abs(skillPath))) {
|
|
20168
|
+
actions.push({
|
|
20169
|
+
path: skillPath,
|
|
20170
|
+
label: "delete the architecture skill",
|
|
20171
|
+
apply: () => rmSync(abs(skillPath), { force: true })
|
|
20172
|
+
});
|
|
20173
|
+
}
|
|
20174
|
+
const hookPath = `.claude/hooks/${NUDGE_HOOK_FILE}`;
|
|
20175
|
+
if (existsSync3(abs(hookPath))) {
|
|
20176
|
+
actions.push({
|
|
20177
|
+
path: hookPath,
|
|
20178
|
+
label: "delete the retired search-nudge hook",
|
|
20179
|
+
apply: () => rmSync(abs(hookPath), { force: true })
|
|
20180
|
+
});
|
|
20181
|
+
}
|
|
20182
|
+
const settingsRaw = readIfExists(abs(".claude/settings.json"));
|
|
20183
|
+
if (settingsRaw !== null) {
|
|
20184
|
+
const edit = stripArchmeHooks(settingsRaw);
|
|
20185
|
+
if (edit.kind !== "unchanged") {
|
|
20186
|
+
actions.push({
|
|
20187
|
+
path: ".claude/settings.json",
|
|
20188
|
+
label: edit.kind === "delete" ? "delete (it holds only the ArchitectMe hook)" : "unregister the search-nudge hook",
|
|
20189
|
+
apply: () => applyEdit(abs(".claude/settings.json"), edit)
|
|
20190
|
+
});
|
|
20191
|
+
} else if (!isParseableObject(settingsRaw)) {
|
|
20192
|
+
skipped.push(
|
|
20193
|
+
".claude/settings.json \u2014 could not be parsed as JSON; if it registers the ArchMe hook, remove that entry by hand"
|
|
20194
|
+
);
|
|
20195
|
+
}
|
|
20196
|
+
}
|
|
20197
|
+
for (const note of skipped) log(`Left alone: ${note}`);
|
|
20198
|
+
const keepWatching = opts.keepWatching ?? false;
|
|
20199
|
+
const slug = keepWatching ? null : await backend.repoSlug(repoPath).catch(() => null);
|
|
20200
|
+
let allWatched = null;
|
|
20201
|
+
let watchLookupError = null;
|
|
20202
|
+
if (slug) {
|
|
20203
|
+
try {
|
|
20204
|
+
allWatched = await backend.listWatchedRepos();
|
|
20205
|
+
} catch (err2) {
|
|
20206
|
+
watchLookupError = err2 instanceof Error ? err2.message : String(err2);
|
|
20207
|
+
}
|
|
20208
|
+
}
|
|
20209
|
+
const toUnwatch = allWatched ? rowsForSlug(allWatched, slug) : [];
|
|
20210
|
+
let localForSlug = 0;
|
|
20211
|
+
let allLocal = [];
|
|
20212
|
+
if (slug) {
|
|
20213
|
+
allLocal = await backend.localRepoSlugs().catch(() => []);
|
|
20214
|
+
localForSlug = allLocal.filter((s) => s.toLowerCase() === slug.toLowerCase()).length;
|
|
20215
|
+
}
|
|
20216
|
+
if (actions.length === 0 && toUnwatch.length === 0 && localForSlug === 0) {
|
|
20217
|
+
log(`No ArchitectMe agent config found in ${repoPath}. Nothing to remove.`);
|
|
20218
|
+
if (watchLookupError) log(watchingUnknownNote(watchLookupError));
|
|
20219
|
+
return { exitCode: EX_OK3, removed: [], unwatched: [] };
|
|
20220
|
+
}
|
|
20221
|
+
const daemonInstalled = await backend.daemonInstalled().catch(() => false);
|
|
20222
|
+
const plan = [
|
|
20223
|
+
...actions.map((a2) => ` ${a2.path} \u2014 ${a2.label}`),
|
|
20224
|
+
...toUnwatch.map((r) => ` ${r.label} \u2014 stop watching (no more commits will update it)`),
|
|
20225
|
+
...localForSlug > 0 ? [` ${slug} \u2014 stop polling from this machine (${localForSlug} registry entry)`] : [],
|
|
20226
|
+
// Stated up front, not just reported after: a repo-scoped command removing
|
|
20227
|
+
// a machine-wide service is the most surprising thing here. Whether it
|
|
20228
|
+
// actually goes is still decided afterwards, against what really happened.
|
|
20229
|
+
...daemonInstalled && !keepWatching ? [" the daemon service \u2014 removed only if this leaves it nothing to serve"] : []
|
|
20230
|
+
];
|
|
20231
|
+
if (!opts.yes) {
|
|
20232
|
+
const interactive = opts.interactive ?? stdinIsInteractive();
|
|
20233
|
+
if (!interactive && !opts.confirm) {
|
|
20234
|
+
error(
|
|
20235
|
+
"archme uninstall: refusing to edit files without confirmation.\nThere is nobody to ask (stdin is not a terminal). Re-run with --yes."
|
|
20236
|
+
);
|
|
20237
|
+
return { exitCode: EX_USAGE3, removed: [], unwatched: [] };
|
|
20238
|
+
}
|
|
20239
|
+
const confirm = opts.confirm ?? (async (lines) => {
|
|
20240
|
+
log(`This will change, in ${repoPath} and on your ArchMe account:
|
|
20241
|
+
`);
|
|
20242
|
+
lines.forEach((l) => log(l));
|
|
20243
|
+
log("");
|
|
20244
|
+
return await promptYesNo("Uninstall ArchitectMe for this repo?");
|
|
20245
|
+
});
|
|
20246
|
+
if (!await confirm(plan)) {
|
|
20247
|
+
log("Cancelled \u2014 nothing was changed.");
|
|
20248
|
+
return { exitCode: EX_OK3, removed: [], unwatched: [] };
|
|
20249
|
+
}
|
|
20250
|
+
}
|
|
20251
|
+
const removed = [];
|
|
20252
|
+
for (const action of actions) {
|
|
20253
|
+
action.apply();
|
|
20254
|
+
removed.push(action.path);
|
|
20255
|
+
log(`Removed: ${action.path} \u2014 ${action.label}`);
|
|
20256
|
+
}
|
|
20257
|
+
for (const dir of PRUNABLE_DIRS) {
|
|
20258
|
+
const path7 = abs(dir);
|
|
20259
|
+
try {
|
|
20260
|
+
if (existsSync3(path7) && readdirSync(path7).length === 0) rmSync(path7, { recursive: true });
|
|
20261
|
+
} catch {
|
|
20262
|
+
}
|
|
20263
|
+
}
|
|
20264
|
+
const unwatched = [];
|
|
20265
|
+
if (toUnwatch.length > 0) {
|
|
20266
|
+
try {
|
|
20267
|
+
await backend.unwatch(toUnwatch.map((r) => r.id));
|
|
20268
|
+
unwatched.push(...toUnwatch.map((r) => r.id));
|
|
20269
|
+
for (const row of toUnwatch) log(`Stopped watching: ${row.label}`);
|
|
20270
|
+
} catch (err2) {
|
|
20271
|
+
watchLookupError = err2 instanceof Error ? err2.message : String(err2);
|
|
20272
|
+
}
|
|
20273
|
+
}
|
|
20274
|
+
let localRemoved = 0;
|
|
20275
|
+
if (slug && localForSlug > 0) {
|
|
20276
|
+
try {
|
|
20277
|
+
localRemoved = await backend.removeLocalRepos(slug);
|
|
20278
|
+
if (localRemoved > 0) log(`Stopped local polling: ${slug}`);
|
|
20279
|
+
} catch {
|
|
20280
|
+
log(`Could not update the local watch registry \u2014 \`archme local-watch --stop ${slug}\` will.`);
|
|
20281
|
+
}
|
|
20282
|
+
}
|
|
20283
|
+
const unwatchFailed = watchLookupError !== null;
|
|
20284
|
+
const remaining = {
|
|
20285
|
+
watchedRepos: allWatched && !unwatchFailed ? allWatched.length - unwatched.length : null,
|
|
20286
|
+
localEntries: allLocal.length - localRemoved
|
|
20287
|
+
};
|
|
20288
|
+
const daemonPlan = decideDaemonRemoval({ installed: daemonInstalled, keepWatching, remaining });
|
|
20289
|
+
if (daemonPlan.action === "remove") {
|
|
20290
|
+
try {
|
|
20291
|
+
await backend.removeDaemon();
|
|
20292
|
+
log(`Removed the daemon service \u2014 ${daemonPlan.reason}.`);
|
|
20293
|
+
} catch (err2) {
|
|
20294
|
+
log(
|
|
20295
|
+
`Could not remove the daemon service (${err2 instanceof Error ? err2.message : String(err2)}).
|
|
20296
|
+
archme daemon uninstall`
|
|
20297
|
+
);
|
|
20298
|
+
}
|
|
20299
|
+
} else {
|
|
20300
|
+
log(`Daemon left in place \u2014 ${daemonPlan.reason}.`);
|
|
20301
|
+
}
|
|
20302
|
+
log(
|
|
20303
|
+
summary({
|
|
20304
|
+
keepWatching,
|
|
20305
|
+
slug,
|
|
20306
|
+
unwatchFailed,
|
|
20307
|
+
watchLookupError,
|
|
20308
|
+
removedConfig: removed.length > 0,
|
|
20309
|
+
checkedAndNotWatched: Boolean(slug) && !unwatchFailed && toUnwatch.length === 0
|
|
20310
|
+
})
|
|
20311
|
+
);
|
|
20312
|
+
return { exitCode: EX_OK3, removed, unwatched };
|
|
20313
|
+
}
|
|
20314
|
+
function watchingUnknownNote(reason) {
|
|
20315
|
+
return `
|
|
20316
|
+
Could not check whether this repo is still being watched (${reason}).
|
|
20317
|
+
If it is, commits will keep updating the architecture. Sign in and re-run to stop it:
|
|
20318
|
+
archme login && archme uninstall`;
|
|
20319
|
+
}
|
|
20320
|
+
function summary(input) {
|
|
20321
|
+
const lines = input.removedConfig ? ["\nArchitectMe agent config removed from this repo."] : ["\nNo ArchitectMe agent config was here to remove."];
|
|
20322
|
+
if (input.keepWatching) {
|
|
20323
|
+
lines.push(
|
|
20324
|
+
"This repo is STILL being watched (--keep-watching), so commits keep updating its architecture."
|
|
20325
|
+
);
|
|
20326
|
+
} else if (input.unwatchFailed && input.watchLookupError) {
|
|
20327
|
+
lines.push(watchingUnknownNote(input.watchLookupError).trimStart());
|
|
20328
|
+
} else if (!input.slug) {
|
|
20329
|
+
lines.push(
|
|
20330
|
+
"No github.com remote here, so there was no watched repo to stop. Check archme.io \u2192 Settings \u2192 Integrations if you expected one."
|
|
20331
|
+
);
|
|
20332
|
+
} else if (input.checkedAndNotWatched) {
|
|
20333
|
+
lines.push(`${input.slug} was not being watched, so no commits were updating it.`);
|
|
20334
|
+
}
|
|
20335
|
+
lines.push(
|
|
20336
|
+
"\nYour architecture on archme.io and this machine's login are untouched:",
|
|
20337
|
+
" archme logout sign this machine out",
|
|
20338
|
+
"\nThe GitHub App installation is org-wide, so it stays. Remove it on github.com if you want it gone."
|
|
20339
|
+
);
|
|
20340
|
+
return lines.join("\n");
|
|
20341
|
+
}
|
|
20342
|
+
async function defaultBackend() {
|
|
20343
|
+
const { createDefaultBackend: createDefaultBackend2 } = await Promise.resolve().then(() => (init_backend(), backend_exports));
|
|
20344
|
+
return createDefaultBackend2();
|
|
20345
|
+
}
|
|
20346
|
+
var EX_OK3, EX_USAGE3, PRUNABLE_DIRS;
|
|
20347
|
+
var init_run_uninstall = __esm({
|
|
20348
|
+
"src/uninstall/run-uninstall.ts"() {
|
|
20349
|
+
"use strict";
|
|
20350
|
+
init_prompt();
|
|
20351
|
+
init_edits();
|
|
20352
|
+
init_watching();
|
|
20353
|
+
EX_OK3 = 0;
|
|
20354
|
+
EX_USAGE3 = 78;
|
|
20355
|
+
PRUNABLE_DIRS = [
|
|
20356
|
+
".claude/skills/architecture",
|
|
20357
|
+
".claude/skills",
|
|
20358
|
+
".claude/hooks",
|
|
20359
|
+
".claude"
|
|
20360
|
+
];
|
|
20361
|
+
}
|
|
20362
|
+
});
|
|
20363
|
+
|
|
20364
|
+
// src/uninstall/dispatch.ts
|
|
20365
|
+
var dispatch_exports5 = {};
|
|
20366
|
+
__export(dispatch_exports5, {
|
|
20367
|
+
EX_INFRA: () => EX_INFRA3,
|
|
20368
|
+
EX_OK: () => EX_OK4,
|
|
20369
|
+
EX_USAGE: () => EX_USAGE4,
|
|
20370
|
+
dispatchUninstall: () => dispatchUninstall,
|
|
20371
|
+
parseUninstallArgs: () => parseUninstallArgs,
|
|
20372
|
+
printUninstallHelp: () => printUninstallHelp
|
|
20373
|
+
});
|
|
20374
|
+
import { resolve as resolve5 } from "node:path";
|
|
20375
|
+
function parseUninstallArgs(argv) {
|
|
20376
|
+
if (argv.some((a2) => a2 === "--help" || a2 === "-h" || a2 === "help")) return { kind: "help" };
|
|
20377
|
+
let repoPath;
|
|
20378
|
+
let yes = false;
|
|
20379
|
+
let keepWatching = false;
|
|
20380
|
+
for (const arg of argv) {
|
|
20381
|
+
if (arg === "--yes" || arg === "-y") {
|
|
20382
|
+
yes = true;
|
|
20383
|
+
} else if (arg === "--keep-watching") {
|
|
20384
|
+
keepWatching = true;
|
|
20385
|
+
} else if (arg.startsWith("-")) {
|
|
20386
|
+
return { kind: "error", message: `unknown option '${arg}'` };
|
|
20387
|
+
} else if (repoPath === void 0) {
|
|
20388
|
+
repoPath = arg;
|
|
20389
|
+
} else {
|
|
20390
|
+
return { kind: "error", message: `unexpected argument '${arg}'` };
|
|
20391
|
+
}
|
|
20392
|
+
}
|
|
20393
|
+
return { kind: "run", repoPath, yes, keepWatching };
|
|
20394
|
+
}
|
|
20395
|
+
function printUninstallHelp() {
|
|
20396
|
+
console.log(`archme uninstall \u2014 stop ArchitectMe for a repo
|
|
20397
|
+
|
|
20398
|
+
Undoes what \`archme init\` set up, and shows you the whole list before it does:
|
|
20399
|
+
|
|
20400
|
+
1. Agent config \u2014 the archme MCP server in .mcp.json, the ArchitectMe block in
|
|
20401
|
+
CLAUDE.md, the architecture skill, and the retired search-nudge hook older
|
|
20402
|
+
versions registered in .claude/settings.json.
|
|
20403
|
+
2. Watching \u2014 stops this repo producing jobs, both via the GitHub App and via
|
|
20404
|
+
local polling. Without this the board keeps updating while looking removed.
|
|
20405
|
+
3. The daemon \u2014 removed only if it has no other repo left to serve. It is one
|
|
20406
|
+
service per account, so uninstalling one repo never stops the others.
|
|
20407
|
+
|
|
20408
|
+
Your architecture on archme.io and this machine's login survive. The GitHub App
|
|
20409
|
+
installation is org-wide and stays; remove it on github.com if you want it gone.
|
|
20410
|
+
|
|
20411
|
+
Usage:
|
|
20412
|
+
archme uninstall [repo-path] [options]
|
|
20413
|
+
|
|
20414
|
+
repo-path Defaults to the current directory.
|
|
20415
|
+
|
|
20416
|
+
Options:
|
|
20417
|
+
-y, --yes Do not ask for confirmation
|
|
20418
|
+
--keep-watching Remove the agent config only; leave the repo watched
|
|
20419
|
+
and the daemon running
|
|
20420
|
+
-h, --help Show this help`);
|
|
20421
|
+
}
|
|
20422
|
+
async function dispatchUninstall(argv) {
|
|
20423
|
+
const parsed = parseUninstallArgs(argv);
|
|
20424
|
+
if (parsed.kind === "help") {
|
|
20425
|
+
printUninstallHelp();
|
|
20426
|
+
return EX_OK4;
|
|
20427
|
+
}
|
|
20428
|
+
if (parsed.kind === "error") {
|
|
20429
|
+
console.error(`archme uninstall: ${parsed.message}`);
|
|
20430
|
+
printUninstallHelp();
|
|
20431
|
+
return EX_USAGE4;
|
|
20432
|
+
}
|
|
20433
|
+
try {
|
|
20434
|
+
const { runUninstall: runUninstall2 } = await Promise.resolve().then(() => (init_run_uninstall(), run_uninstall_exports));
|
|
20435
|
+
const result = await runUninstall2({
|
|
20436
|
+
repoPath: resolve5(parsed.repoPath ?? process.cwd()),
|
|
20437
|
+
yes: parsed.yes,
|
|
20438
|
+
keepWatching: parsed.keepWatching
|
|
20439
|
+
});
|
|
20440
|
+
return result.exitCode;
|
|
20441
|
+
} catch (err2) {
|
|
20442
|
+
console.error(`archme uninstall: ${err2 instanceof Error ? err2.message : String(err2)}`);
|
|
20443
|
+
return EX_INFRA3;
|
|
20444
|
+
}
|
|
20445
|
+
}
|
|
20446
|
+
var EX_OK4, EX_INFRA3, EX_USAGE4;
|
|
20447
|
+
var init_dispatch5 = __esm({
|
|
20448
|
+
"src/uninstall/dispatch.ts"() {
|
|
20449
|
+
"use strict";
|
|
20450
|
+
EX_OK4 = 0;
|
|
20451
|
+
EX_INFRA3 = 3;
|
|
20452
|
+
EX_USAGE4 = 78;
|
|
20453
|
+
}
|
|
20454
|
+
});
|
|
20455
|
+
|
|
19855
20456
|
// src/daemon/localWatchDispatch.ts
|
|
19856
20457
|
var localWatchDispatch_exports = {};
|
|
19857
20458
|
__export(localWatchDispatch_exports, {
|
|
19858
|
-
EX_INFRA: () =>
|
|
19859
|
-
EX_OK: () =>
|
|
19860
|
-
EX_USAGE: () =>
|
|
20459
|
+
EX_INFRA: () => EX_INFRA4,
|
|
20460
|
+
EX_OK: () => EX_OK5,
|
|
20461
|
+
EX_USAGE: () => EX_USAGE5,
|
|
19861
20462
|
dispatchLocalWatch: () => dispatchLocalWatch,
|
|
19862
20463
|
parseLocalWatchArgs: () => parseLocalWatchArgs,
|
|
19863
20464
|
printLocalWatchHelp: () => printLocalWatchHelp
|
|
@@ -19947,12 +20548,12 @@ async function dispatchLocalWatch(argv) {
|
|
|
19947
20548
|
const parsed = parseLocalWatchArgs(argv);
|
|
19948
20549
|
if (parsed.kind === "help") {
|
|
19949
20550
|
printLocalWatchHelp();
|
|
19950
|
-
return
|
|
20551
|
+
return EX_OK5;
|
|
19951
20552
|
}
|
|
19952
20553
|
if (parsed.kind === "error") {
|
|
19953
20554
|
console.error(parsed.message);
|
|
19954
20555
|
printLocalWatchHelp();
|
|
19955
|
-
return
|
|
20556
|
+
return EX_USAGE5;
|
|
19956
20557
|
}
|
|
19957
20558
|
const mod = await Promise.resolve().then(() => (init_localWatch(), localWatch_exports));
|
|
19958
20559
|
switch (parsed.kind) {
|
|
@@ -19966,13 +20567,13 @@ async function dispatchLocalWatch(argv) {
|
|
|
19966
20567
|
return await mod.runLocalWatch(parsed);
|
|
19967
20568
|
}
|
|
19968
20569
|
}
|
|
19969
|
-
var
|
|
20570
|
+
var EX_OK5, EX_INFRA4, EX_USAGE5, PROJECT_SLUG_RE3, REPO_SLUG_RE;
|
|
19970
20571
|
var init_localWatchDispatch = __esm({
|
|
19971
20572
|
"src/daemon/localWatchDispatch.ts"() {
|
|
19972
20573
|
"use strict";
|
|
19973
|
-
|
|
19974
|
-
|
|
19975
|
-
|
|
20574
|
+
EX_OK5 = 0;
|
|
20575
|
+
EX_INFRA4 = 3;
|
|
20576
|
+
EX_USAGE5 = 78;
|
|
19976
20577
|
PROJECT_SLUG_RE3 = /^[^/\s]+\/[^/\s]+$/;
|
|
19977
20578
|
REPO_SLUG_RE = /^[^/\s]+\/[^/\s]+$/;
|
|
19978
20579
|
}
|
|
@@ -19980,7 +20581,7 @@ var init_localWatchDispatch = __esm({
|
|
|
19980
20581
|
|
|
19981
20582
|
// src/doctor/collect.ts
|
|
19982
20583
|
import { readFile as readFile13 } from "node:fs/promises";
|
|
19983
|
-
import { join as
|
|
20584
|
+
import { join as join24 } from "node:path";
|
|
19984
20585
|
function binaryPresent(name) {
|
|
19985
20586
|
try {
|
|
19986
20587
|
requireBinary(name);
|
|
@@ -19996,7 +20597,7 @@ async function git3(cwd, args) {
|
|
|
19996
20597
|
async function readMcpPin(repoRoot) {
|
|
19997
20598
|
const empty = { installed: false, pin: { project: null, branch: null } };
|
|
19998
20599
|
try {
|
|
19999
|
-
const raw = await readFile13(
|
|
20600
|
+
const raw = await readFile13(join24(repoRoot, ".mcp.json"), "utf8");
|
|
20000
20601
|
const parsed = JSON.parse(raw);
|
|
20001
20602
|
const servers = parsed.mcpServers ?? {};
|
|
20002
20603
|
const entry = Object.entries(servers).find(([name]) => /archme/i.test(name));
|
|
@@ -20064,17 +20665,17 @@ async function collectFacts(cwd) {
|
|
|
20064
20665
|
);
|
|
20065
20666
|
try {
|
|
20066
20667
|
const cfg = resolveConfig();
|
|
20067
|
-
const
|
|
20668
|
+
const authed2 = await createAuthedClient({
|
|
20068
20669
|
supabaseUrl: cfg.supabaseUrl,
|
|
20069
20670
|
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
20070
20671
|
clientId: cfg.archmeDaemonClientId,
|
|
20071
20672
|
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
20072
20673
|
});
|
|
20073
|
-
if (!
|
|
20674
|
+
if (!authed2.orgId) return facts;
|
|
20074
20675
|
facts.signedIn = true;
|
|
20075
|
-
facts.orgId =
|
|
20076
|
-
facts.identity =
|
|
20077
|
-
const supabase =
|
|
20676
|
+
facts.orgId = authed2.orgId;
|
|
20677
|
+
facts.identity = authed2.identity;
|
|
20678
|
+
const supabase = authed2.supabase;
|
|
20078
20679
|
const slug = facts.pinnedProject;
|
|
20079
20680
|
if (slug) {
|
|
20080
20681
|
const [orgSlug, projectSlug] = slug.split("/");
|
|
@@ -20360,18 +20961,18 @@ optional.`;
|
|
|
20360
20961
|
});
|
|
20361
20962
|
|
|
20362
20963
|
// src/doctor/dispatch.ts
|
|
20363
|
-
var
|
|
20364
|
-
__export(
|
|
20365
|
-
EX_INFRA: () =>
|
|
20366
|
-
EX_OK: () =>
|
|
20367
|
-
EX_USAGE: () =>
|
|
20964
|
+
var dispatch_exports6 = {};
|
|
20965
|
+
__export(dispatch_exports6, {
|
|
20966
|
+
EX_INFRA: () => EX_INFRA5,
|
|
20967
|
+
EX_OK: () => EX_OK6,
|
|
20968
|
+
EX_USAGE: () => EX_USAGE6,
|
|
20368
20969
|
dispatchDoctor: () => dispatchDoctor,
|
|
20369
20970
|
parseDoctorArgs: () => parseDoctorArgs,
|
|
20370
20971
|
printDoctorHelp: () => printDoctorHelp
|
|
20371
20972
|
});
|
|
20372
20973
|
import { mkdtemp as mkdtemp5, rm as rm9, writeFile as writeFile13 } from "node:fs/promises";
|
|
20373
20974
|
import { tmpdir as tmpdir5 } from "node:os";
|
|
20374
|
-
import { join as
|
|
20975
|
+
import { join as join25 } from "node:path";
|
|
20375
20976
|
function parseDoctorArgs(argv) {
|
|
20376
20977
|
const args = { plain: false, help: false };
|
|
20377
20978
|
for (const a2 of argv) {
|
|
@@ -20403,11 +21004,11 @@ async function dispatchDoctor(argv) {
|
|
|
20403
21004
|
if (!parsed.ok) {
|
|
20404
21005
|
console.error(parsed.message);
|
|
20405
21006
|
printDoctorHelp();
|
|
20406
|
-
return
|
|
21007
|
+
return EX_USAGE6;
|
|
20407
21008
|
}
|
|
20408
21009
|
if (parsed.args.help) {
|
|
20409
21010
|
printDoctorHelp();
|
|
20410
|
-
return
|
|
21011
|
+
return EX_OK6;
|
|
20411
21012
|
}
|
|
20412
21013
|
const facts = await collectFacts(process.cwd());
|
|
20413
21014
|
const issues = diagnose(facts);
|
|
@@ -20423,17 +21024,17 @@ async function dispatchDoctor(argv) {
|
|
|
20423
21024
|
console.log("");
|
|
20424
21025
|
}
|
|
20425
21026
|
}
|
|
20426
|
-
if (parsed.args.plain) return
|
|
21027
|
+
if (parsed.args.plain) return EX_OK6;
|
|
20427
21028
|
try {
|
|
20428
21029
|
requireBinary("claude");
|
|
20429
21030
|
} catch {
|
|
20430
21031
|
console.log(
|
|
20431
21032
|
"Install Claude Code to ask about any of this: npm i -g @anthropic-ai/claude-code"
|
|
20432
21033
|
);
|
|
20433
|
-
return
|
|
21034
|
+
return EX_OK6;
|
|
20434
21035
|
}
|
|
20435
|
-
const dir = await mkdtemp5(
|
|
20436
|
-
const mcpConfigPath =
|
|
21036
|
+
const dir = await mkdtemp5(join25(tmpdir5(), "archme-doctor-"));
|
|
21037
|
+
const mcpConfigPath = join25(dir, "mcp.json");
|
|
20437
21038
|
await writeFile13(mcpConfigPath, JSON.stringify({ mcpServers: {} }), { mode: 384 });
|
|
20438
21039
|
try {
|
|
20439
21040
|
const result = await spawnClaude({
|
|
@@ -20446,13 +21047,13 @@ async function dispatchDoctor(argv) {
|
|
|
20446
21047
|
// is a much larger surface than the problem justifies.
|
|
20447
21048
|
allowedTools: ["Read", "Glob", "Grep"]
|
|
20448
21049
|
});
|
|
20449
|
-
return result.exitCode === 0 ?
|
|
21050
|
+
return result.exitCode === 0 ? EX_OK6 : EX_INFRA5;
|
|
20450
21051
|
} finally {
|
|
20451
21052
|
await rm9(dir, { recursive: true, force: true });
|
|
20452
21053
|
}
|
|
20453
21054
|
}
|
|
20454
|
-
var
|
|
20455
|
-
var
|
|
21055
|
+
var EX_OK6, EX_INFRA5, EX_USAGE6;
|
|
21056
|
+
var init_dispatch6 = __esm({
|
|
20456
21057
|
"src/doctor/dispatch.ts"() {
|
|
20457
21058
|
"use strict";
|
|
20458
21059
|
init_collect();
|
|
@@ -20460,9 +21061,9 @@ var init_dispatch5 = __esm({
|
|
|
20460
21061
|
init_prompt2();
|
|
20461
21062
|
init_spawn_claude();
|
|
20462
21063
|
init_preflight();
|
|
20463
|
-
|
|
20464
|
-
|
|
20465
|
-
|
|
21064
|
+
EX_OK6 = 0;
|
|
21065
|
+
EX_INFRA5 = 3;
|
|
21066
|
+
EX_USAGE6 = 78;
|
|
20466
21067
|
}
|
|
20467
21068
|
});
|
|
20468
21069
|
|
|
@@ -20520,6 +21121,7 @@ var KNOWN_COMMANDS = /* @__PURE__ */ new Set([
|
|
|
20520
21121
|
"keys",
|
|
20521
21122
|
"scan",
|
|
20522
21123
|
"init",
|
|
21124
|
+
"uninstall",
|
|
20523
21125
|
"local-watch",
|
|
20524
21126
|
"doctor"
|
|
20525
21127
|
]);
|
|
@@ -20618,12 +21220,16 @@ async function main(argv) {
|
|
|
20618
21220
|
const { dispatchInit: dispatchInit2 } = await Promise.resolve().then(() => (init_dispatch4(), dispatch_exports4));
|
|
20619
21221
|
return await dispatchInit2(r.args);
|
|
20620
21222
|
}
|
|
21223
|
+
case "uninstall": {
|
|
21224
|
+
const { dispatchUninstall: dispatchUninstall2 } = await Promise.resolve().then(() => (init_dispatch5(), dispatch_exports5));
|
|
21225
|
+
return await dispatchUninstall2(r.args);
|
|
21226
|
+
}
|
|
20621
21227
|
case "local-watch": {
|
|
20622
21228
|
const { dispatchLocalWatch: dispatchLocalWatch2 } = await Promise.resolve().then(() => (init_localWatchDispatch(), localWatchDispatch_exports));
|
|
20623
21229
|
return await dispatchLocalWatch2(r.args);
|
|
20624
21230
|
}
|
|
20625
21231
|
case "doctor": {
|
|
20626
|
-
const { dispatchDoctor: dispatchDoctor2 } = await Promise.resolve().then(() => (
|
|
21232
|
+
const { dispatchDoctor: dispatchDoctor2 } = await Promise.resolve().then(() => (init_dispatch6(), dispatch_exports6));
|
|
20627
21233
|
return await dispatchDoctor2(r.args);
|
|
20628
21234
|
}
|
|
20629
21235
|
case "logout": {
|
|
@@ -20650,6 +21256,8 @@ Other commands:
|
|
|
20650
21256
|
archme scan <repo-path> Single-pass scan into an explicit project/branch
|
|
20651
21257
|
archme local-watch [repo-path] Watch a repo without a GitHub App
|
|
20652
21258
|
(--list|--stop|--resume)
|
|
21259
|
+
archme uninstall [repo-path] Stop ArchitectMe for a repo: config,
|
|
21260
|
+
watching, and the daemon if it's now idle
|
|
20653
21261
|
archme doctor Check this machine's setup and explain it
|
|
20654
21262
|
archme login | logout Authenticate with archme
|
|
20655
21263
|
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.1",
|
|
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",
|