@archme/cli 0.4.0 → 0.4.2
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/LICENSE +64 -0
- package/README.md +64 -277
- package/SKILL.md +3 -4
- package/dist/cli.js +731 -83
- package/package.json +19 -3
package/dist/cli.js
CHANGED
|
@@ -1020,15 +1020,15 @@ var require_windows = __commonJS({
|
|
|
1020
1020
|
}
|
|
1021
1021
|
return false;
|
|
1022
1022
|
}
|
|
1023
|
-
function checkStat(
|
|
1024
|
-
if (!
|
|
1023
|
+
function checkStat(stat10, path7, options) {
|
|
1024
|
+
if (!stat10.isSymbolicLink() && !stat10.isFile()) {
|
|
1025
1025
|
return false;
|
|
1026
1026
|
}
|
|
1027
1027
|
return checkPathExt(path7, options);
|
|
1028
1028
|
}
|
|
1029
1029
|
function isexe(path7, options, cb) {
|
|
1030
|
-
fs.stat(path7, function(er,
|
|
1031
|
-
cb(er, er ? false : checkStat(
|
|
1030
|
+
fs.stat(path7, function(er, stat10) {
|
|
1031
|
+
cb(er, er ? false : checkStat(stat10, path7, options));
|
|
1032
1032
|
});
|
|
1033
1033
|
}
|
|
1034
1034
|
function sync(path7, options) {
|
|
@@ -1044,20 +1044,20 @@ var require_mode = __commonJS({
|
|
|
1044
1044
|
isexe.sync = sync;
|
|
1045
1045
|
var fs = __require("fs");
|
|
1046
1046
|
function isexe(path7, options, cb) {
|
|
1047
|
-
fs.stat(path7, function(er,
|
|
1048
|
-
cb(er, er ? false : checkStat(
|
|
1047
|
+
fs.stat(path7, function(er, stat10) {
|
|
1048
|
+
cb(er, er ? false : checkStat(stat10, options));
|
|
1049
1049
|
});
|
|
1050
1050
|
}
|
|
1051
1051
|
function sync(path7, options) {
|
|
1052
1052
|
return checkStat(fs.statSync(path7), options);
|
|
1053
1053
|
}
|
|
1054
|
-
function checkStat(
|
|
1055
|
-
return
|
|
1054
|
+
function checkStat(stat10, options) {
|
|
1055
|
+
return stat10.isFile() && checkMode(stat10, options);
|
|
1056
1056
|
}
|
|
1057
|
-
function checkMode(
|
|
1058
|
-
var mod =
|
|
1059
|
-
var uid =
|
|
1060
|
-
var gid =
|
|
1057
|
+
function checkMode(stat10, options) {
|
|
1058
|
+
var mod = stat10.mode;
|
|
1059
|
+
var uid = stat10.uid;
|
|
1060
|
+
var gid = stat10.gid;
|
|
1061
1061
|
var myUid = options.uid !== void 0 ? options.uid : process.getuid && process.getuid();
|
|
1062
1062
|
var myGid = options.gid !== void 0 ? options.gid : process.getgid && process.getgid();
|
|
1063
1063
|
var u2 = parseInt("100", 8);
|
|
@@ -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(resolve4, reject) {
|
|
1095
1095
|
isexe(path7, options || {}, function(er, is) {
|
|
1096
1096
|
if (er) {
|
|
1097
1097
|
reject(er);
|
|
1098
1098
|
} else {
|
|
1099
|
-
|
|
1099
|
+
resolve4(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((resolve4, reject) => {
|
|
1166
1166
|
if (i2 === pathEnv.length)
|
|
1167
|
-
return opt.all && found.length ?
|
|
1167
|
+
return opt.all && found.length ? resolve4(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
|
+
resolve4(subStep(p, i2, 0));
|
|
1173
1173
|
});
|
|
1174
|
-
const subStep = (p, i2, ii) => new Promise((
|
|
1174
|
+
const subStep = (p, i2, ii) => new Promise((resolve4, reject) => {
|
|
1175
1175
|
if (ii === pathExt.length)
|
|
1176
|
-
return
|
|
1176
|
+
return resolve4(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 resolve4(p + ext2);
|
|
1184
1184
|
}
|
|
1185
|
-
return
|
|
1185
|
+
return resolve4(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((resolve4, reject) => {
|
|
2266
|
+
Object.assign(methods, { resolve: resolve4, 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 resolve4 = promise.resolve.bind(promise);
|
|
7564
|
+
return { resolve: resolve4, promises };
|
|
7565
7565
|
};
|
|
7566
|
-
waitForConcurrentStreams = async ({ resolve:
|
|
7567
|
-
|
|
7566
|
+
waitForConcurrentStreams = async ({ resolve: resolve4, promises }, subprocess) => {
|
|
7567
|
+
resolve4();
|
|
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((resolve4) => setTimeout(resolve4, intervalMs));
|
|
8640
8640
|
const poll = await postJson("/poll", {
|
|
8641
8641
|
device_code: data.device_code
|
|
8642
8642
|
});
|
|
@@ -8852,7 +8852,7 @@ var init_launchctl = __esm({
|
|
|
8852
8852
|
if (r.exitCode === 0 || r.exitCode === 17) return;
|
|
8853
8853
|
if (r.exitCode === 5) {
|
|
8854
8854
|
await this.opts.spawner.run("launchctl", ["bootout", SERVICE_TARGET(this.opts.uid)]);
|
|
8855
|
-
await new Promise((
|
|
8855
|
+
await new Promise((resolve4) => setTimeout(resolve4, 500));
|
|
8856
8856
|
r = await bootstrap();
|
|
8857
8857
|
if (r.exitCode === 0 || r.exitCode === 17) return;
|
|
8858
8858
|
}
|
|
@@ -9231,7 +9231,7 @@ async function tailLog(opts = {}) {
|
|
|
9231
9231
|
}
|
|
9232
9232
|
}
|
|
9233
9233
|
if (!opts.follow) return;
|
|
9234
|
-
await new Promise((
|
|
9234
|
+
await new Promise((resolve4, reject) => {
|
|
9235
9235
|
let fileWatcher = null;
|
|
9236
9236
|
let dirWatcher = null;
|
|
9237
9237
|
const dir = dirname6(path7);
|
|
@@ -9295,7 +9295,7 @@ async function tailLog(opts = {}) {
|
|
|
9295
9295
|
const handleSigint = () => {
|
|
9296
9296
|
fileWatcher?.close();
|
|
9297
9297
|
dirWatcher?.close();
|
|
9298
|
-
|
|
9298
|
+
resolve4();
|
|
9299
9299
|
};
|
|
9300
9300
|
process.once("SIGINT", handleSigint);
|
|
9301
9301
|
});
|
|
@@ -9425,6 +9425,7 @@ async function writeMcpConfig(opts) {
|
|
|
9425
9425
|
project: opts.projectSlug,
|
|
9426
9426
|
branch: opts.branch
|
|
9427
9427
|
});
|
|
9428
|
+
if (opts.sourceKind) params.set("source_kind", opts.sourceKind);
|
|
9428
9429
|
if (opts.source) {
|
|
9429
9430
|
params.set("source_repo", opts.source.repo);
|
|
9430
9431
|
params.set("source_after_sha", opts.source.after_sha);
|
|
@@ -9506,8 +9507,7 @@ var init_daemonModel = __esm({
|
|
|
9506
9507
|
function buildClaudeArgs(opts) {
|
|
9507
9508
|
const tools = (opts.allowedTools ?? DEFAULT_ALLOWED_TOOLS).join(",");
|
|
9508
9509
|
const args = [
|
|
9509
|
-
"-p",
|
|
9510
|
-
opts.prompt,
|
|
9510
|
+
...opts.interactive ? [opts.prompt] : ["-p", opts.prompt],
|
|
9511
9511
|
"--mcp-config",
|
|
9512
9512
|
opts.mcpConfigPath,
|
|
9513
9513
|
// Load ONLY the server(s) from our --mcp-config file; ignore the repo's
|
|
@@ -9524,7 +9524,7 @@ function buildClaudeArgs(opts) {
|
|
|
9524
9524
|
"--allowedTools",
|
|
9525
9525
|
tools
|
|
9526
9526
|
];
|
|
9527
|
-
if (opts.jsonOutput) {
|
|
9527
|
+
if (opts.jsonOutput && !opts.interactive) {
|
|
9528
9528
|
args.push("--output-format", "json");
|
|
9529
9529
|
}
|
|
9530
9530
|
if (opts.model) {
|
|
@@ -9535,9 +9535,20 @@ function buildClaudeArgs(opts) {
|
|
|
9535
9535
|
function spawnClaude(opts) {
|
|
9536
9536
|
const env = scrubPrivilegedEnv(process.env);
|
|
9537
9537
|
const args = buildClaudeArgs({ ...opts, model: opts.model || DEFAULT_CLAUDE_MODEL });
|
|
9538
|
-
if (opts.streamJson) {
|
|
9538
|
+
if (opts.streamJson && !opts.interactive) {
|
|
9539
9539
|
args.push("--output-format", "stream-json", "--verbose");
|
|
9540
9540
|
}
|
|
9541
|
+
if (opts.interactive) {
|
|
9542
|
+
return execa("claude", args, {
|
|
9543
|
+
reject: false,
|
|
9544
|
+
// No timeout: the session is only as long as the human wants, and killing
|
|
9545
|
+
// it out from under them mid-answer would be worse than any runaway.
|
|
9546
|
+
cwd: opts.cwd,
|
|
9547
|
+
env,
|
|
9548
|
+
// Hand over the terminal — this is the whole point.
|
|
9549
|
+
stdio: "inherit"
|
|
9550
|
+
});
|
|
9551
|
+
}
|
|
9541
9552
|
return execa(
|
|
9542
9553
|
"claude",
|
|
9543
9554
|
args,
|
|
@@ -14086,7 +14097,7 @@ var init_semaphore = __esm({
|
|
|
14086
14097
|
acquire() {
|
|
14087
14098
|
const slot = this.tryAcquire();
|
|
14088
14099
|
if (slot) return Promise.resolve(slot);
|
|
14089
|
-
return new Promise((
|
|
14100
|
+
return new Promise((resolve4) => this.waiters.push(resolve4));
|
|
14090
14101
|
}
|
|
14091
14102
|
makeRelease() {
|
|
14092
14103
|
let released = false;
|
|
@@ -16659,8 +16670,8 @@ async function defaultLoadUptime(pid) {
|
|
|
16659
16670
|
if (process.platform === "linux") {
|
|
16660
16671
|
try {
|
|
16661
16672
|
const { readFile: readFile12 } = await import("node:fs/promises");
|
|
16662
|
-
const
|
|
16663
|
-
const parts =
|
|
16673
|
+
const stat10 = await readFile12(`/proc/${pid}/stat`, "utf8");
|
|
16674
|
+
const parts = stat10.split(") ").pop()?.split(" ") ?? [];
|
|
16664
16675
|
const ticks = Number(parts[19]);
|
|
16665
16676
|
if (!Number.isFinite(ticks)) return null;
|
|
16666
16677
|
const { readFile: rf } = await import("node:fs/promises");
|
|
@@ -16715,6 +16726,45 @@ var init_dispatch2 = __esm({
|
|
|
16715
16726
|
}
|
|
16716
16727
|
});
|
|
16717
16728
|
|
|
16729
|
+
// src/scan/authedClient.ts
|
|
16730
|
+
import { createClient as createClient2 } from "@supabase/supabase-js";
|
|
16731
|
+
async function createAuthedClient(opts) {
|
|
16732
|
+
initBearer({
|
|
16733
|
+
supabaseUrl: opts.supabaseUrl,
|
|
16734
|
+
anonKey: opts.supabaseAnonKey,
|
|
16735
|
+
clientId: opts.clientId,
|
|
16736
|
+
preSpawnMinLifetimeMs: opts.preSpawnMinLifetimeMs
|
|
16737
|
+
});
|
|
16738
|
+
let loaded;
|
|
16739
|
+
try {
|
|
16740
|
+
loaded = await loadStoredAndRefresh();
|
|
16741
|
+
} catch (err) {
|
|
16742
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
16743
|
+
throw new Error(`${msg}
|
|
16744
|
+
\u2192 run \`archme login\` first`);
|
|
16745
|
+
}
|
|
16746
|
+
const userId = getCurrentUserId();
|
|
16747
|
+
if (!userId) throw new Error("could not resolve user id from OAuth tokens");
|
|
16748
|
+
const supabase = createClient2(opts.supabaseUrl, opts.supabaseAnonKey, {
|
|
16749
|
+
accessToken: async () => getCurrentAccessToken()
|
|
16750
|
+
});
|
|
16751
|
+
return {
|
|
16752
|
+
supabase,
|
|
16753
|
+
userId,
|
|
16754
|
+
orgId: getCurrentOrgId(),
|
|
16755
|
+
identity: `${loaded.user.email ?? "OAuth user"} (${loaded.user.sub ?? "unknown"})`
|
|
16756
|
+
};
|
|
16757
|
+
}
|
|
16758
|
+
var SCAN_CLAUDE_TIMEOUT_MS, SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS;
|
|
16759
|
+
var init_authedClient = __esm({
|
|
16760
|
+
"src/scan/authedClient.ts"() {
|
|
16761
|
+
"use strict";
|
|
16762
|
+
init_bearer();
|
|
16763
|
+
SCAN_CLAUDE_TIMEOUT_MS = 45 * 60 * 1e3;
|
|
16764
|
+
SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS = SCAN_CLAUDE_TIMEOUT_MS + 5 * 60 * 1e3;
|
|
16765
|
+
}
|
|
16766
|
+
});
|
|
16767
|
+
|
|
16718
16768
|
// src/scan/web-url.ts
|
|
16719
16769
|
function deriveWebOrigin(apiUrl, onMalformed = "throw") {
|
|
16720
16770
|
let parsed;
|
|
@@ -16995,20 +17045,24 @@ var init_github_remote = __esm({
|
|
|
16995
17045
|
});
|
|
16996
17046
|
|
|
16997
17047
|
// src/scan/run-scan.ts
|
|
16998
|
-
import { createClient as createClient2 } from "@supabase/supabase-js";
|
|
16999
17048
|
import { createWriteStream as createWriteStream2 } from "node:fs";
|
|
17000
17049
|
import { mkdir as mkdir10, readFile as readFile11 } from "node:fs/promises";
|
|
17001
17050
|
import { basename as basename2, join as join16 } from "node:path";
|
|
17002
|
-
function buildBootstrapPrompt(
|
|
17003
|
-
|
|
17051
|
+
function buildBootstrapPrompt(opts = {}) {
|
|
17052
|
+
const { promptVariant, interactive, briefing } = opts;
|
|
17053
|
+
let base = interactive ? briefing ? `${BOOTSTRAP_INTERACTIVE}
|
|
17054
|
+
|
|
17055
|
+
${briefing}` : BOOTSTRAP_INTERACTIVE_NO_BRIEFING : BOOTSTRAP_HEADLESS;
|
|
17056
|
+
if (!promptVariant) return base;
|
|
17004
17057
|
if (!PROMPT_VARIANT_RE.test(promptVariant)) {
|
|
17005
17058
|
throw new Error(
|
|
17006
17059
|
`invalid prompt variant id \`${promptVariant}\` (expected YY-MM-DD-HHMM, optionally followed by -<suffix>)`
|
|
17007
17060
|
);
|
|
17008
17061
|
}
|
|
17009
|
-
|
|
17062
|
+
base += `
|
|
17010
17063
|
|
|
17011
17064
|
Use scan-prompt variant \`${promptVariant}\` \u2014 pass \`variant: "${promptVariant}"\` to \`start_scan\`.`;
|
|
17065
|
+
return base;
|
|
17012
17066
|
}
|
|
17013
17067
|
function todayDate() {
|
|
17014
17068
|
const d = /* @__PURE__ */ new Date();
|
|
@@ -17204,26 +17258,14 @@ async function runScan(opts, deps = {}) {
|
|
|
17204
17258
|
log(` supabase: ${supabaseUrl}`);
|
|
17205
17259
|
log(` mcp base: ${mcpUrl}`);
|
|
17206
17260
|
log(` repo: ${opts.repoPath}`);
|
|
17207
|
-
|
|
17261
|
+
const authed = await createAuthedClient({
|
|
17208
17262
|
supabaseUrl,
|
|
17209
|
-
|
|
17263
|
+
supabaseAnonKey: supabaseAnon,
|
|
17210
17264
|
clientId,
|
|
17211
17265
|
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
17212
17266
|
});
|
|
17213
|
-
|
|
17214
|
-
|
|
17215
|
-
loaded = await loadStoredAndRefresh();
|
|
17216
|
-
} catch (err) {
|
|
17217
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
17218
|
-
throw new Error(`${msg}
|
|
17219
|
-
\u2192 run \`archme login\` first`);
|
|
17220
|
-
}
|
|
17221
|
-
log(` signed in as ${loaded.user.email ?? "OAuth user"} (${loaded.user.sub ?? "unknown"})`);
|
|
17222
|
-
const userId = getCurrentUserId();
|
|
17223
|
-
if (!userId) throw new Error("could not resolve user id from OAuth tokens");
|
|
17224
|
-
const supabase = createClient2(supabaseUrl, supabaseAnon, {
|
|
17225
|
-
accessToken: async () => getCurrentAccessToken()
|
|
17226
|
-
});
|
|
17267
|
+
log(` signed in as ${authed.identity}`);
|
|
17268
|
+
const { supabase, userId } = authed;
|
|
17227
17269
|
let headSha;
|
|
17228
17270
|
try {
|
|
17229
17271
|
headSha = await repoHeadSha(opts.repoPath);
|
|
@@ -17259,24 +17301,38 @@ async function runScan(opts, deps = {}) {
|
|
|
17259
17301
|
baseUrl: mcpUrl,
|
|
17260
17302
|
projectSlug: `${resolved.orgSlug}/${resolved.projectSlug}`,
|
|
17261
17303
|
branch: branchName,
|
|
17262
|
-
bearer
|
|
17263
|
-
// No source
|
|
17304
|
+
bearer,
|
|
17305
|
+
// No source-SHA block (no job id for a local run), but DO tag these as scan
|
|
17306
|
+
// writes: otherwise they are indistinguishable from a manual chat edit, and
|
|
17307
|
+
// the scan-in-progress banner cannot tell a running scan from a human typing.
|
|
17308
|
+
sourceKind: "scan"
|
|
17264
17309
|
});
|
|
17265
17310
|
try {
|
|
17266
17311
|
const model = await resolveModelPin(supabase);
|
|
17267
|
-
|
|
17312
|
+
const interactive = opts.interactive !== false;
|
|
17313
|
+
log(
|
|
17314
|
+
interactive ? `starting claude (mcp config at ${configPath}, model ${model}) \u2014 this session is interactive; answer its questions as they come` : `spawning claude (mcp config at ${configPath}, model ${model}, timeout ${Math.round(SCAN_CLAUDE_TIMEOUT_MS / 6e4)} min)`
|
|
17315
|
+
);
|
|
17268
17316
|
const spawn2 = deps.spawn ?? spawnClaude;
|
|
17269
17317
|
const proc = spawn2({
|
|
17270
|
-
prompt: buildBootstrapPrompt(
|
|
17318
|
+
prompt: buildBootstrapPrompt({
|
|
17319
|
+
promptVariant: opts.promptVariant,
|
|
17320
|
+
interactive,
|
|
17321
|
+
briefing: opts.briefing
|
|
17322
|
+
}),
|
|
17271
17323
|
mcpConfigPath: configPath,
|
|
17272
17324
|
cwd: opts.repoPath,
|
|
17273
|
-
|
|
17325
|
+
// No timeout interactively — a human is present and can Ctrl-C. Killing a
|
|
17326
|
+
// session mid-answer would be worse than any runaway.
|
|
17327
|
+
timeoutMs: interactive ? void 0 : SCAN_CLAUDE_TIMEOUT_MS,
|
|
17274
17328
|
allowedTools: SCAN_ALLOWED_TOOLS,
|
|
17275
17329
|
model,
|
|
17330
|
+
interactive,
|
|
17276
17331
|
// stream-json: required for the transcript regexes in
|
|
17277
17332
|
// transcript-signals.ts to see real tool events. Without it, claude
|
|
17278
17333
|
// -p emits only the model's final text and every signal reports 0.
|
|
17279
|
-
|
|
17334
|
+
// Incompatible with an interactive TTY session.
|
|
17335
|
+
streamJson: !interactive
|
|
17280
17336
|
});
|
|
17281
17337
|
const transcriptDir = join16(opts.repoPath, ".archme", "scan");
|
|
17282
17338
|
const transcriptPath = join16(transcriptDir, "transcript.log");
|
|
@@ -17319,8 +17375,8 @@ async function runScan(opts, deps = {}) {
|
|
|
17319
17375
|
log(` stories: ${counts.stories}`);
|
|
17320
17376
|
let signals2 = null;
|
|
17321
17377
|
try {
|
|
17322
|
-
await new Promise((
|
|
17323
|
-
transcriptStream.end((err) => err ? reject(err) :
|
|
17378
|
+
await new Promise((resolve4, reject) => {
|
|
17379
|
+
transcriptStream.end((err) => err ? reject(err) : resolve4());
|
|
17324
17380
|
});
|
|
17325
17381
|
const reportPath = join16(transcriptDir, "report.json");
|
|
17326
17382
|
const orphansTxtPath = join16(transcriptDir, "orphans.txt");
|
|
@@ -17393,12 +17449,13 @@ async function runScan(opts, deps = {}) {
|
|
|
17393
17449
|
});
|
|
17394
17450
|
}
|
|
17395
17451
|
}
|
|
17396
|
-
var
|
|
17452
|
+
var BOOTSTRAP_HEADLESS, BOOTSTRAP_INTERACTIVE, BOOTSTRAP_INTERACTIVE_NO_BRIEFING, PROMPT_VARIANT_RE;
|
|
17397
17453
|
var init_run_scan = __esm({
|
|
17398
17454
|
"src/scan/run-scan.ts"() {
|
|
17399
17455
|
"use strict";
|
|
17400
17456
|
init_execa();
|
|
17401
17457
|
init_bearer();
|
|
17458
|
+
init_authedClient();
|
|
17402
17459
|
init_mcp_config();
|
|
17403
17460
|
init_spawn_claude();
|
|
17404
17461
|
init_model_pin();
|
|
@@ -17409,9 +17466,11 @@ var init_run_scan = __esm({
|
|
|
17409
17466
|
init_transcript_signals();
|
|
17410
17467
|
init_report();
|
|
17411
17468
|
init_github_remote();
|
|
17412
|
-
|
|
17413
|
-
|
|
17414
|
-
|
|
17469
|
+
BOOTSTRAP_HEADLESS = `Run the \`scan\` MCP prompt now and follow it to completion. This is a non-interactive automated run \u2014 skip any steps that pause for user confirmation (e.g. "does this hierarchy look right?"). Proceed with your best-judgement decisions rather than waiting for human input.`;
|
|
17470
|
+
BOOTSTRAP_INTERACTIVE = `Run the \`scan\` MCP prompt now and follow it to completion. The user is at an interactive terminal \u2014 ask them when the prompt says to, and wait for their answer.
|
|
17471
|
+
|
|
17472
|
+
Before you call \`start_scan\`, show the user this briefing verbatim and ask them to confirm they want to go ahead. Do not start scanning until they say yes.`;
|
|
17473
|
+
BOOTSTRAP_INTERACTIVE_NO_BRIEFING = `Run the \`scan\` MCP prompt now and follow it to completion. The user is at an interactive terminal \u2014 ask them when the prompt says to, and wait for their answer.`;
|
|
17415
17474
|
PROMPT_VARIANT_RE = /^\d{2}-\d{2}-\d{2}-\d{4}(-[A-Za-z0-9_-]+)?$/;
|
|
17416
17475
|
}
|
|
17417
17476
|
});
|
|
@@ -17465,6 +17524,8 @@ async function dispatchScan(args) {
|
|
|
17465
17524
|
} else if (arg === "--mcp-url" && next) {
|
|
17466
17525
|
opts.mcpUrl = next;
|
|
17467
17526
|
i2++;
|
|
17527
|
+
} else if (arg === "--no-interactive") {
|
|
17528
|
+
opts.interactive = false;
|
|
17468
17529
|
} else if (arg && arg.startsWith("--")) {
|
|
17469
17530
|
console.error(`archme scan: unknown flag '${arg}'`);
|
|
17470
17531
|
printScanHelp();
|
|
@@ -17514,6 +17575,7 @@ Usage:
|
|
|
17514
17575
|
Flags:
|
|
17515
17576
|
--project <org/slug> Target project. Defaults to e2e/<basename>.
|
|
17516
17577
|
--branch <name> Target scan branch. Defaults to <date>-<sha7>-scan.
|
|
17578
|
+
--no-interactive Run headless (no questions). For CI/automation.
|
|
17517
17579
|
--prompt-variant <id> Scan prompt variant. Defaults to latest.
|
|
17518
17580
|
--mcp-url <url> MCP base URL. Defaults to $ARCHME_MCP_BASE_URL.
|
|
17519
17581
|
|
|
@@ -17534,6 +17596,584 @@ var init_dispatch3 = __esm({
|
|
|
17534
17596
|
}
|
|
17535
17597
|
});
|
|
17536
17598
|
|
|
17599
|
+
// src/init/repoSize.ts
|
|
17600
|
+
import { stat as stat9 } from "node:fs/promises";
|
|
17601
|
+
import { extname, join as join17 } from "node:path";
|
|
17602
|
+
async function measureRepo(repoPath) {
|
|
17603
|
+
const files = await gitListFiles(repoPath);
|
|
17604
|
+
if (!files) return { sourceFiles: 0, sourceBytes: 0, totalFiles: 0 };
|
|
17605
|
+
let sourceFiles = 0;
|
|
17606
|
+
let sourceBytes = 0;
|
|
17607
|
+
for (const rel of files) {
|
|
17608
|
+
if (!SOURCE_EXTENSIONS.has(extname(rel).toLowerCase())) continue;
|
|
17609
|
+
try {
|
|
17610
|
+
const s = await stat9(join17(repoPath, rel));
|
|
17611
|
+
if (!s.isFile()) continue;
|
|
17612
|
+
sourceFiles++;
|
|
17613
|
+
sourceBytes += s.size;
|
|
17614
|
+
} catch {
|
|
17615
|
+
}
|
|
17616
|
+
}
|
|
17617
|
+
return { sourceFiles, sourceBytes, totalFiles: files.length };
|
|
17618
|
+
}
|
|
17619
|
+
function fullReadTokens(sourceBytes) {
|
|
17620
|
+
return Math.round(sourceBytes / BYTES_PER_TOKEN);
|
|
17621
|
+
}
|
|
17622
|
+
function formatCount(n2) {
|
|
17623
|
+
if (n2 >= 1e6) return `${(n2 / 1e6).toFixed(1)}M`;
|
|
17624
|
+
if (n2 >= 1e3) return `${(n2 / 1e3).toFixed(n2 >= 1e4 ? 0 : 1)}k`;
|
|
17625
|
+
return String(n2);
|
|
17626
|
+
}
|
|
17627
|
+
function formatBytes(bytes) {
|
|
17628
|
+
if (bytes >= 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
17629
|
+
if (bytes >= 1024) return `${Math.round(bytes / 1024)} KB`;
|
|
17630
|
+
return `${bytes} B`;
|
|
17631
|
+
}
|
|
17632
|
+
function expectedDuration(sourceFiles) {
|
|
17633
|
+
if (sourceFiles < 100) return "a few minutes";
|
|
17634
|
+
if (sourceFiles < 1e3) return "5\u201315 minutes";
|
|
17635
|
+
return "15\u201345 minutes";
|
|
17636
|
+
}
|
|
17637
|
+
function formatRepoBriefing(m, repoPath, target) {
|
|
17638
|
+
const lines = [
|
|
17639
|
+
"",
|
|
17640
|
+
"About to scan:",
|
|
17641
|
+
` ${repoPath}`,
|
|
17642
|
+
...target ? [` \u2192 project ${target.project}, branch ${target.branch}`] : [],
|
|
17643
|
+
` ${formatCount(m.sourceFiles)} source files \xB7 ${formatBytes(m.sourceBytes)} (${formatCount(m.totalFiles)} files tracked)`,
|
|
17644
|
+
"",
|
|
17645
|
+
"What this costs:",
|
|
17646
|
+
" The scan runs Claude Code on YOUR machine, against YOUR Claude",
|
|
17647
|
+
" subscription or API key. ArchMe is not billed for it, and you are.",
|
|
17648
|
+
` Reading every source file once would be ~${formatCount(fullReadTokens(m.sourceBytes))} tokens;`,
|
|
17649
|
+
" a scan reads a fraction of that, and reports its actual usage when done.",
|
|
17650
|
+
` Expect it to take ${expectedDuration(m.sourceFiles)}.`,
|
|
17651
|
+
""
|
|
17652
|
+
];
|
|
17653
|
+
if (m.sourceFiles === 0) {
|
|
17654
|
+
lines.push(
|
|
17655
|
+
" No recognised source files found \u2014 is this the right directory?",
|
|
17656
|
+
""
|
|
17657
|
+
);
|
|
17658
|
+
}
|
|
17659
|
+
return lines.join("\n");
|
|
17660
|
+
}
|
|
17661
|
+
var BYTES_PER_TOKEN, SOURCE_EXTENSIONS;
|
|
17662
|
+
var init_repoSize = __esm({
|
|
17663
|
+
"src/init/repoSize.ts"() {
|
|
17664
|
+
"use strict";
|
|
17665
|
+
init_repo_walk();
|
|
17666
|
+
BYTES_PER_TOKEN = 4;
|
|
17667
|
+
SOURCE_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
17668
|
+
".ts",
|
|
17669
|
+
".tsx",
|
|
17670
|
+
".js",
|
|
17671
|
+
".jsx",
|
|
17672
|
+
".mjs",
|
|
17673
|
+
".cjs",
|
|
17674
|
+
".py",
|
|
17675
|
+
".go",
|
|
17676
|
+
".java",
|
|
17677
|
+
".rb",
|
|
17678
|
+
".rs",
|
|
17679
|
+
".swift",
|
|
17680
|
+
".kt",
|
|
17681
|
+
".kts",
|
|
17682
|
+
".c",
|
|
17683
|
+
".h",
|
|
17684
|
+
".cc",
|
|
17685
|
+
".cpp",
|
|
17686
|
+
".hpp",
|
|
17687
|
+
".cs",
|
|
17688
|
+
".php",
|
|
17689
|
+
".scala",
|
|
17690
|
+
".ex",
|
|
17691
|
+
".exs",
|
|
17692
|
+
".sql",
|
|
17693
|
+
".sh",
|
|
17694
|
+
".bash"
|
|
17695
|
+
]);
|
|
17696
|
+
}
|
|
17697
|
+
});
|
|
17698
|
+
|
|
17699
|
+
// src/init/watchRepo.ts
|
|
17700
|
+
async function watchRepoForProject(input) {
|
|
17701
|
+
const { supabase, orgId, projectId, repoSlug, branch } = input;
|
|
17702
|
+
if (!repoSlug) {
|
|
17703
|
+
return {
|
|
17704
|
+
watched: false,
|
|
17705
|
+
reason: "no GitHub remote found, so pushes cannot be linked to this project. Add a remote and re-run `archme init` for continuous updates."
|
|
17706
|
+
};
|
|
17707
|
+
}
|
|
17708
|
+
if (!orgId) {
|
|
17709
|
+
return { watched: false, reason: "could not resolve your organization." };
|
|
17710
|
+
}
|
|
17711
|
+
const [owner, name] = repoSlug.split("/");
|
|
17712
|
+
if (!owner || !name) {
|
|
17713
|
+
return { watched: false, reason: `could not parse repo slug \`${repoSlug}\`.` };
|
|
17714
|
+
}
|
|
17715
|
+
const { data: installs, error: installErr } = await supabase.from("org_github_installations").select("installation_id").eq("org_id", orgId).limit(1);
|
|
17716
|
+
if (installErr) {
|
|
17717
|
+
return {
|
|
17718
|
+
watched: false,
|
|
17719
|
+
reason: `could not check GitHub App installations (${installErr.message}).`
|
|
17720
|
+
};
|
|
17721
|
+
}
|
|
17722
|
+
const installationId = installs?.[0]?.installation_id;
|
|
17723
|
+
if (!installationId) {
|
|
17724
|
+
return {
|
|
17725
|
+
watched: false,
|
|
17726
|
+
reason: "the ArchMe GitHub App is not installed on this org yet \u2014 connect it in the app to get automatic updates on every push."
|
|
17727
|
+
};
|
|
17728
|
+
}
|
|
17729
|
+
const { error } = await supabase.rpc("watch_repo", {
|
|
17730
|
+
p_org_id: orgId,
|
|
17731
|
+
p_project_id: projectId,
|
|
17732
|
+
p_repo_owner: owner,
|
|
17733
|
+
p_repo_name: name,
|
|
17734
|
+
p_branch: branch,
|
|
17735
|
+
p_installation_id: installationId
|
|
17736
|
+
});
|
|
17737
|
+
if (error) {
|
|
17738
|
+
return { watched: false, reason: `could not watch the repo (${error.message}).` };
|
|
17739
|
+
}
|
|
17740
|
+
return { watched: true };
|
|
17741
|
+
}
|
|
17742
|
+
var init_watchRepo = __esm({
|
|
17743
|
+
"src/init/watchRepo.ts"() {
|
|
17744
|
+
"use strict";
|
|
17745
|
+
}
|
|
17746
|
+
});
|
|
17747
|
+
|
|
17748
|
+
// src/init/gitContext.ts
|
|
17749
|
+
async function git(cwd, args) {
|
|
17750
|
+
const { stdout } = await execa("git", args, { cwd });
|
|
17751
|
+
return stdout.trim();
|
|
17752
|
+
}
|
|
17753
|
+
async function resolveGitContext(repoPath) {
|
|
17754
|
+
let root;
|
|
17755
|
+
try {
|
|
17756
|
+
root = await git(repoPath, ["rev-parse", "--show-toplevel"]);
|
|
17757
|
+
} catch {
|
|
17758
|
+
throw new Error(
|
|
17759
|
+
`\`${repoPath}\` is not inside a git repository \u2014 run \`archme init\` from a repo, or pass its path`
|
|
17760
|
+
);
|
|
17761
|
+
}
|
|
17762
|
+
try {
|
|
17763
|
+
await git(root, ["rev-parse", "HEAD"]);
|
|
17764
|
+
} catch {
|
|
17765
|
+
throw new Error(
|
|
17766
|
+
`\`${root}\` is a git repository with no commits yet \u2014 make a commit first, then run \`archme init\``
|
|
17767
|
+
);
|
|
17768
|
+
}
|
|
17769
|
+
let branch = "";
|
|
17770
|
+
try {
|
|
17771
|
+
branch = await git(root, ["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
17772
|
+
} catch {
|
|
17773
|
+
branch = "";
|
|
17774
|
+
}
|
|
17775
|
+
if (!branch || branch === "HEAD") {
|
|
17776
|
+
try {
|
|
17777
|
+
const ref = await git(root, ["symbolic-ref", "refs/remotes/origin/HEAD"]);
|
|
17778
|
+
branch = ref.replace(/^refs\/remotes\/origin\//, "");
|
|
17779
|
+
} catch {
|
|
17780
|
+
branch = "main";
|
|
17781
|
+
}
|
|
17782
|
+
}
|
|
17783
|
+
let repoSlug = null;
|
|
17784
|
+
try {
|
|
17785
|
+
const url = await git(root, ["remote", "get-url", "origin"]);
|
|
17786
|
+
repoSlug = url ? parseGitHubRepo(url) : null;
|
|
17787
|
+
} catch {
|
|
17788
|
+
repoSlug = null;
|
|
17789
|
+
}
|
|
17790
|
+
return { root, branch, repoSlug };
|
|
17791
|
+
}
|
|
17792
|
+
var init_gitContext = __esm({
|
|
17793
|
+
"src/init/gitContext.ts"() {
|
|
17794
|
+
"use strict";
|
|
17795
|
+
init_execa();
|
|
17796
|
+
init_github_remote();
|
|
17797
|
+
}
|
|
17798
|
+
});
|
|
17799
|
+
|
|
17800
|
+
// src/init/plan.ts
|
|
17801
|
+
function shouldRunScan(d) {
|
|
17802
|
+
if (d.skipScan) return false;
|
|
17803
|
+
if (d.force) return true;
|
|
17804
|
+
return !d.architectureExists;
|
|
17805
|
+
}
|
|
17806
|
+
function buildProjectMcpUrl(mcpBaseUrl, project, branch) {
|
|
17807
|
+
const url = new URL(mcpBaseUrl);
|
|
17808
|
+
url.searchParams.set("project", project);
|
|
17809
|
+
url.searchParams.set("branch", branch);
|
|
17810
|
+
return url.toString();
|
|
17811
|
+
}
|
|
17812
|
+
function resolveAppOrigin(mcpBaseUrl, override) {
|
|
17813
|
+
if (override) return override.replace(/\/$/, "");
|
|
17814
|
+
return deriveWebOrigin(mcpBaseUrl, "fallback");
|
|
17815
|
+
}
|
|
17816
|
+
function buildInstallAgentUrl(appOrigin, mcpUrl) {
|
|
17817
|
+
const url = new URL("/api/install-agent", appOrigin);
|
|
17818
|
+
url.searchParams.set("mcp_url", mcpUrl);
|
|
17819
|
+
return url.toString();
|
|
17820
|
+
}
|
|
17821
|
+
var init_plan = __esm({
|
|
17822
|
+
"src/init/plan.ts"() {
|
|
17823
|
+
"use strict";
|
|
17824
|
+
init_web_url();
|
|
17825
|
+
}
|
|
17826
|
+
});
|
|
17827
|
+
|
|
17828
|
+
// src/init/run-init.ts
|
|
17829
|
+
import { mkdtemp as mkdtemp4, rm as rm8, writeFile as writeFile11 } from "node:fs/promises";
|
|
17830
|
+
import { tmpdir as tmpdir4 } from "node:os";
|
|
17831
|
+
import { join as join18 } from "node:path";
|
|
17832
|
+
function initScanBranch(defaultBranch) {
|
|
17833
|
+
return normalizeScanBranchName(defaultBranch);
|
|
17834
|
+
}
|
|
17835
|
+
async function defaultFetchInstallScript(url) {
|
|
17836
|
+
const resp = await fetch(url);
|
|
17837
|
+
if (!resp.ok) {
|
|
17838
|
+
throw new Error(`could not fetch agent installer (${resp.status} ${resp.statusText}) from ${url}`);
|
|
17839
|
+
}
|
|
17840
|
+
return await resp.text();
|
|
17841
|
+
}
|
|
17842
|
+
async function defaultRunInstallScript(script, cwd) {
|
|
17843
|
+
const dir = await mkdtemp4(join18(tmpdir4(), "archme-init-"));
|
|
17844
|
+
const scriptPath = join18(dir, "install-agent.sh");
|
|
17845
|
+
try {
|
|
17846
|
+
await writeFile11(scriptPath, script, { mode: 448 });
|
|
17847
|
+
await execa("sh", [scriptPath], { cwd, stdio: "inherit" });
|
|
17848
|
+
} finally {
|
|
17849
|
+
await rm8(dir, { recursive: true, force: true });
|
|
17850
|
+
}
|
|
17851
|
+
}
|
|
17852
|
+
async function runInit(opts, deps = {}) {
|
|
17853
|
+
const log = opts.log ?? ((msg) => console.log(msg));
|
|
17854
|
+
const login = deps.login ?? runLogin;
|
|
17855
|
+
const fetchInstallScript = deps.fetchInstallScript ?? defaultFetchInstallScript;
|
|
17856
|
+
const runInstallScript = deps.runInstallScript ?? defaultRunInstallScript;
|
|
17857
|
+
const scan = deps.scan ?? runScan;
|
|
17858
|
+
const authenticate = deps.authenticate ?? createAuthedClient;
|
|
17859
|
+
const resolveProject = deps.resolveProject ?? resolveOrCreateProject;
|
|
17860
|
+
const resolveOrgSlug = deps.resolveOrgSlug ?? resolveCurrentOrgSlug;
|
|
17861
|
+
const fetchArchitecture = deps.fetchArchitecture ?? fetchArchitectureDetail;
|
|
17862
|
+
const loadCreds = deps.loadCredentials ?? loadAllCredentials;
|
|
17863
|
+
const gitContext = deps.gitContext ?? resolveGitContext;
|
|
17864
|
+
const measure = deps.measure ?? measureRepo;
|
|
17865
|
+
const watchRepo = deps.watchRepo ?? watchRepoForProject;
|
|
17866
|
+
const cfg = resolveConfig();
|
|
17867
|
+
const git2 = await gitContext(opts.repoPath);
|
|
17868
|
+
const repoPath = git2.root;
|
|
17869
|
+
const { credentials } = await loadCreds(cfg.supabaseUrl);
|
|
17870
|
+
const signedIn = credentials.length > 0;
|
|
17871
|
+
if (!signedIn) {
|
|
17872
|
+
log("\u2192 signing you in\u2026");
|
|
17873
|
+
await login();
|
|
17874
|
+
} else {
|
|
17875
|
+
log("\u2713 already signed in");
|
|
17876
|
+
}
|
|
17877
|
+
const authed = await authenticate({
|
|
17878
|
+
supabaseUrl: cfg.supabaseUrl,
|
|
17879
|
+
supabaseAnonKey: cfg.supabaseAnonKey,
|
|
17880
|
+
clientId: cfg.archmeDaemonClientId,
|
|
17881
|
+
preSpawnMinLifetimeMs: SCAN_PRE_SPAWN_TOKEN_MIN_LIFETIME_MS
|
|
17882
|
+
});
|
|
17883
|
+
log(` signed in as ${authed.identity}`);
|
|
17884
|
+
let fullSlug = opts.project;
|
|
17885
|
+
if (!fullSlug) {
|
|
17886
|
+
const orgId = authed.orgId;
|
|
17887
|
+
if (!orgId) {
|
|
17888
|
+
throw new Error(
|
|
17889
|
+
"could not resolve your organization \u2014 run `archme login` again, or pass --project <org>/<slug>"
|
|
17890
|
+
);
|
|
17891
|
+
}
|
|
17892
|
+
const orgSlug = await resolveOrgSlug(authed.supabase, orgId);
|
|
17893
|
+
if (!orgSlug) {
|
|
17894
|
+
throw new Error(
|
|
17895
|
+
`paired org ${orgId} no longer exists \u2014 run \`archme login\` again, or pass --project <org>/<slug>`
|
|
17896
|
+
);
|
|
17897
|
+
}
|
|
17898
|
+
fullSlug = defaultProjectSlug(orgSlug, repoPath);
|
|
17899
|
+
}
|
|
17900
|
+
const project = await resolveProject(
|
|
17901
|
+
authed.supabase,
|
|
17902
|
+
authed.userId,
|
|
17903
|
+
fullSlug
|
|
17904
|
+
);
|
|
17905
|
+
log(
|
|
17906
|
+
project.created ? `\u2713 created project ${fullSlug}` : `\u2713 using existing project ${fullSlug}`
|
|
17907
|
+
);
|
|
17908
|
+
const scanBranch = initScanBranch(project.defaultBranch);
|
|
17909
|
+
const detail = await fetchArchitecture(
|
|
17910
|
+
authed.supabase,
|
|
17911
|
+
project.projectId,
|
|
17912
|
+
scanBranch
|
|
17913
|
+
);
|
|
17914
|
+
const architectureExists = detail.components.length > 0;
|
|
17915
|
+
const ran = signedIn ? [] : ["login"];
|
|
17916
|
+
ran.push("project");
|
|
17917
|
+
const mcpUrl = buildProjectMcpUrl(cfg.archmeMcpBaseUrl, fullSlug, scanBranch);
|
|
17918
|
+
const appOrigin = resolveAppOrigin(cfg.archmeMcpBaseUrl, opts.appOrigin);
|
|
17919
|
+
log("\u2192 installing agent config (.mcp.json, CLAUDE.md, architecture skill)\u2026");
|
|
17920
|
+
const script = await fetchInstallScript(buildInstallAgentUrl(appOrigin, mcpUrl));
|
|
17921
|
+
await runInstallScript(script, repoPath);
|
|
17922
|
+
ran.push("agent-config");
|
|
17923
|
+
const watched = await watchRepo({
|
|
17924
|
+
supabase: authed.supabase,
|
|
17925
|
+
orgId: authed.orgId,
|
|
17926
|
+
projectId: project.projectId,
|
|
17927
|
+
repoSlug: git2.repoSlug,
|
|
17928
|
+
branch: git2.branch,
|
|
17929
|
+
log
|
|
17930
|
+
});
|
|
17931
|
+
if (!watched.watched && watched.reason) {
|
|
17932
|
+
log(` note: ${watched.reason}`);
|
|
17933
|
+
}
|
|
17934
|
+
const webUrl = buildWebUrl({
|
|
17935
|
+
apiUrl: cfg.archmeMcpBaseUrl,
|
|
17936
|
+
orgSlug: project.orgSlug,
|
|
17937
|
+
projectSlug: project.projectSlug,
|
|
17938
|
+
branchName: scanBranch,
|
|
17939
|
+
defaultBranch: project.defaultBranch,
|
|
17940
|
+
onMalformed: "throw"
|
|
17941
|
+
});
|
|
17942
|
+
const wantsScan = shouldRunScan({
|
|
17943
|
+
architectureExists,
|
|
17944
|
+
force: opts.force,
|
|
17945
|
+
skipScan: opts.skipScan
|
|
17946
|
+
});
|
|
17947
|
+
if (!wantsScan) {
|
|
17948
|
+
if (architectureExists && !opts.force) {
|
|
17949
|
+
log(`\u2713 ${fullSlug} already has an architecture \u2014 skipping the scan (use --force to re-scan)`);
|
|
17950
|
+
}
|
|
17951
|
+
log(`
|
|
17952
|
+
Your board: ${webUrl}`);
|
|
17953
|
+
return { exitCode: 0, project: fullSlug, branch: scanBranch, webUrl, ran };
|
|
17954
|
+
}
|
|
17955
|
+
const measurement = await measure(repoPath);
|
|
17956
|
+
const briefing = formatRepoBriefing(measurement, repoPath, {
|
|
17957
|
+
project: fullSlug,
|
|
17958
|
+
branch: scanBranch
|
|
17959
|
+
});
|
|
17960
|
+
log("\u2192 handing over to Claude Code \u2014 it will confirm before scanning");
|
|
17961
|
+
const result = await scan({
|
|
17962
|
+
repoPath,
|
|
17963
|
+
project: fullSlug,
|
|
17964
|
+
// --yes means unattended: run headless and skip the agent's confirmations.
|
|
17965
|
+
interactive: !opts.yes,
|
|
17966
|
+
briefing,
|
|
17967
|
+
// Explicit: without it runScan derives a dated `<date>-<sha7>-scan` branch
|
|
17968
|
+
// that nothing else in this flow knows about.
|
|
17969
|
+
branch: scanBranch,
|
|
17970
|
+
log
|
|
17971
|
+
});
|
|
17972
|
+
ran.push("scan");
|
|
17973
|
+
const finalUrl = result.webUrl || webUrl;
|
|
17974
|
+
if (result.exitCode === 0) {
|
|
17975
|
+
log(`
|
|
17976
|
+
\u2713 Done. Your architecture: ${finalUrl}`);
|
|
17977
|
+
log(" Restart Claude Code in this repo to pick up the archme MCP server.");
|
|
17978
|
+
if (watched.watched) {
|
|
17979
|
+
log("");
|
|
17980
|
+
log("Keeping it up to date \u2014 one more step:");
|
|
17981
|
+
log(" Pushes to this repo now queue an architecture update, but");
|
|
17982
|
+
log(" something has to process that queue, and nothing does yet.");
|
|
17983
|
+
log("");
|
|
17984
|
+
log(" Run the agent yourself, on a machine that stays on:");
|
|
17985
|
+
log("");
|
|
17986
|
+
log(" archme daemon start");
|
|
17987
|
+
log("");
|
|
17988
|
+
log(" Each update counts tokens against your Claude account, the same");
|
|
17989
|
+
log(" way this scan did. Your code is not sent to our servers \u2014 only");
|
|
17990
|
+
log(" the resulting architecture.");
|
|
17991
|
+
log("");
|
|
17992
|
+
log(" Or get in touch at archme.io and we'll run it for you.");
|
|
17993
|
+
} else {
|
|
17994
|
+
log("");
|
|
17995
|
+
log("Not yet set up for automatic updates:");
|
|
17996
|
+
log(` ${watched.reason ?? "the repo could not be linked to this project."}`);
|
|
17997
|
+
log(" Until that is resolved, this architecture will not track new");
|
|
17998
|
+
log(" commits \u2014 re-run `archme init` once it is.");
|
|
17999
|
+
}
|
|
18000
|
+
}
|
|
18001
|
+
return {
|
|
18002
|
+
exitCode: result.exitCode,
|
|
18003
|
+
project: fullSlug,
|
|
18004
|
+
branch: result.branchName || scanBranch,
|
|
18005
|
+
webUrl: finalUrl,
|
|
18006
|
+
ran
|
|
18007
|
+
};
|
|
18008
|
+
}
|
|
18009
|
+
var init_run_init = __esm({
|
|
18010
|
+
"src/init/run-init.ts"() {
|
|
18011
|
+
"use strict";
|
|
18012
|
+
init_execa();
|
|
18013
|
+
init_defaults();
|
|
18014
|
+
init_enroll();
|
|
18015
|
+
init_credentials();
|
|
18016
|
+
init_authedClient();
|
|
18017
|
+
init_run_scan();
|
|
18018
|
+
init_architecture_detail();
|
|
18019
|
+
init_repoSize();
|
|
18020
|
+
init_watchRepo();
|
|
18021
|
+
init_gitContext();
|
|
18022
|
+
init_web_url();
|
|
18023
|
+
init_plan();
|
|
18024
|
+
}
|
|
18025
|
+
});
|
|
18026
|
+
|
|
18027
|
+
// src/init/args.ts
|
|
18028
|
+
function parseInitArgs(argv) {
|
|
18029
|
+
const args = {
|
|
18030
|
+
force: false,
|
|
18031
|
+
skipScan: false,
|
|
18032
|
+
yes: false,
|
|
18033
|
+
noInteractive: false,
|
|
18034
|
+
help: false
|
|
18035
|
+
};
|
|
18036
|
+
const positionals = [];
|
|
18037
|
+
for (let i2 = 0; i2 < argv.length; i2++) {
|
|
18038
|
+
const arg = argv[i2];
|
|
18039
|
+
if (HELP_FLAGS.has(arg)) {
|
|
18040
|
+
args.help = true;
|
|
18041
|
+
return { ok: true, args };
|
|
18042
|
+
}
|
|
18043
|
+
if (BOOLEAN_FLAGS.has(arg)) {
|
|
18044
|
+
if (arg === "--force") args.force = true;
|
|
18045
|
+
else if (arg === "--yes") args.yes = true;
|
|
18046
|
+
else if (arg === "--no-interactive") args.noInteractive = true;
|
|
18047
|
+
else args.skipScan = true;
|
|
18048
|
+
continue;
|
|
18049
|
+
}
|
|
18050
|
+
const eq = arg.indexOf("=");
|
|
18051
|
+
const name = arg.startsWith("--") && eq > 0 ? arg.slice(0, eq) : arg;
|
|
18052
|
+
if (VALUE_FLAGS.has(name)) {
|
|
18053
|
+
let value;
|
|
18054
|
+
if (eq > 0 && arg.startsWith("--")) {
|
|
18055
|
+
value = arg.slice(eq + 1);
|
|
18056
|
+
} else {
|
|
18057
|
+
value = argv[i2 + 1];
|
|
18058
|
+
if (value !== void 0 && value.startsWith("-")) value = void 0;
|
|
18059
|
+
else i2++;
|
|
18060
|
+
}
|
|
18061
|
+
if (!value) return { ok: false, error: `${name} requires a value` };
|
|
18062
|
+
if (name === "--project") {
|
|
18063
|
+
if (!PROJECT_SLUG_RE2.test(value)) {
|
|
18064
|
+
return {
|
|
18065
|
+
ok: false,
|
|
18066
|
+
error: `--project must be <org>/<slug> (got \`${value}\`)`
|
|
18067
|
+
};
|
|
18068
|
+
}
|
|
18069
|
+
args.project = value;
|
|
18070
|
+
} else {
|
|
18071
|
+
args.appOrigin = value;
|
|
18072
|
+
}
|
|
18073
|
+
continue;
|
|
18074
|
+
}
|
|
18075
|
+
if (arg.startsWith("-")) {
|
|
18076
|
+
return { ok: false, error: `unknown option \`${arg}\`` };
|
|
18077
|
+
}
|
|
18078
|
+
positionals.push(arg);
|
|
18079
|
+
}
|
|
18080
|
+
if (positionals.length > 1) {
|
|
18081
|
+
return {
|
|
18082
|
+
ok: false,
|
|
18083
|
+
error: `expected at most one repo path, got ${positionals.length} (${positionals.join(", ")})`
|
|
18084
|
+
};
|
|
18085
|
+
}
|
|
18086
|
+
args.repoPath = positionals[0];
|
|
18087
|
+
return { ok: true, args };
|
|
18088
|
+
}
|
|
18089
|
+
var BOOLEAN_FLAGS, VALUE_FLAGS, HELP_FLAGS, PROJECT_SLUG_RE2;
|
|
18090
|
+
var init_args = __esm({
|
|
18091
|
+
"src/init/args.ts"() {
|
|
18092
|
+
"use strict";
|
|
18093
|
+
BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
18094
|
+
"--force",
|
|
18095
|
+
"--skip-scan",
|
|
18096
|
+
"--yes",
|
|
18097
|
+
"--no-interactive"
|
|
18098
|
+
]);
|
|
18099
|
+
VALUE_FLAGS = /* @__PURE__ */ new Set(["--project", "--app-origin"]);
|
|
18100
|
+
HELP_FLAGS = /* @__PURE__ */ new Set(["--help", "-h", "help"]);
|
|
18101
|
+
PROJECT_SLUG_RE2 = /^[^/\s]+\/[^/\s]+$/;
|
|
18102
|
+
}
|
|
18103
|
+
});
|
|
18104
|
+
|
|
18105
|
+
// src/init/dispatch.ts
|
|
18106
|
+
var dispatch_exports4 = {};
|
|
18107
|
+
__export(dispatch_exports4, {
|
|
18108
|
+
EX_INFRA: () => EX_INFRA2,
|
|
18109
|
+
EX_OK: () => EX_OK2,
|
|
18110
|
+
EX_USAGE: () => EX_USAGE2,
|
|
18111
|
+
dispatchInit: () => dispatchInit,
|
|
18112
|
+
printInitHelp: () => printInitHelp
|
|
18113
|
+
});
|
|
18114
|
+
import { resolve as resolve3 } from "node:path";
|
|
18115
|
+
function printInitHelp() {
|
|
18116
|
+
console.log(`archme init \u2014 guided first-time setup for this repo
|
|
18117
|
+
|
|
18118
|
+
Signs you in, creates a project named after the repo, installs agent config
|
|
18119
|
+
(.mcp.json, CLAUDE.md block, architecture skill), then hands over to an
|
|
18120
|
+
interactive Claude Code session that confirms the cost and runs the scan.
|
|
18121
|
+
|
|
18122
|
+
Usage:
|
|
18123
|
+
archme init [repo-path] [options]
|
|
18124
|
+
|
|
18125
|
+
repo-path Defaults to the current directory.
|
|
18126
|
+
|
|
18127
|
+
Options:
|
|
18128
|
+
--project <org/slug> Use a specific project instead of <your-org>/<repo-name>
|
|
18129
|
+
--skip-scan Set up agent config but do not scan
|
|
18130
|
+
--force Re-scan even if the project already has an architecture
|
|
18131
|
+
--yes Run unattended (headless scan, no questions)
|
|
18132
|
+
--no-interactive Same as --yes; for automation
|
|
18133
|
+
--app-origin <url> Origin serving /api/install-agent (local development)
|
|
18134
|
+
-h, --help Show this help
|
|
18135
|
+
|
|
18136
|
+
Requires Claude Code to be installed and authenticated \u2014 the scan runs on your
|
|
18137
|
+
own Claude subscription.`);
|
|
18138
|
+
}
|
|
18139
|
+
async function dispatchInit(argv) {
|
|
18140
|
+
const parsed = parseInitArgs(argv);
|
|
18141
|
+
if (!parsed.ok) {
|
|
18142
|
+
console.error(`archme init: ${parsed.error}`);
|
|
18143
|
+
printInitHelp();
|
|
18144
|
+
return EX_USAGE2;
|
|
18145
|
+
}
|
|
18146
|
+
if (parsed.args.help) {
|
|
18147
|
+
printInitHelp();
|
|
18148
|
+
return EX_OK2;
|
|
18149
|
+
}
|
|
18150
|
+
try {
|
|
18151
|
+
const result = await runInit({
|
|
18152
|
+
repoPath: resolve3(parsed.args.repoPath ?? process.cwd()),
|
|
18153
|
+
project: parsed.args.project,
|
|
18154
|
+
appOrigin: parsed.args.appOrigin,
|
|
18155
|
+
force: parsed.args.force,
|
|
18156
|
+
skipScan: parsed.args.skipScan,
|
|
18157
|
+
yes: parsed.args.yes || parsed.args.noInteractive
|
|
18158
|
+
});
|
|
18159
|
+
return result.exitCode;
|
|
18160
|
+
} catch (err) {
|
|
18161
|
+
console.error(`archme init: ${err instanceof Error ? err.message : String(err)}`);
|
|
18162
|
+
return EX_INFRA2;
|
|
18163
|
+
}
|
|
18164
|
+
}
|
|
18165
|
+
var EX_OK2, EX_INFRA2, EX_USAGE2;
|
|
18166
|
+
var init_dispatch4 = __esm({
|
|
18167
|
+
"src/init/dispatch.ts"() {
|
|
18168
|
+
"use strict";
|
|
18169
|
+
init_run_init();
|
|
18170
|
+
init_args();
|
|
18171
|
+
EX_OK2 = 0;
|
|
18172
|
+
EX_INFRA2 = 3;
|
|
18173
|
+
EX_USAGE2 = 78;
|
|
18174
|
+
}
|
|
18175
|
+
});
|
|
18176
|
+
|
|
17537
18177
|
// src/auth/logout.ts
|
|
17538
18178
|
var logout_exports = {};
|
|
17539
18179
|
__export(logout_exports, {
|
|
@@ -17580,7 +18220,7 @@ var init_logout = __esm({
|
|
|
17580
18220
|
|
|
17581
18221
|
// src/cli.ts
|
|
17582
18222
|
init_enroll();
|
|
17583
|
-
var KNOWN_COMMANDS = /* @__PURE__ */ new Set(["daemon", "analyze", "login", "logout", "keys", "scan"]);
|
|
18223
|
+
var KNOWN_COMMANDS = /* @__PURE__ */ new Set(["daemon", "analyze", "login", "logout", "keys", "scan", "init"]);
|
|
17584
18224
|
function route(argv) {
|
|
17585
18225
|
if (argv.length === 0) return { name: "help", args: [] };
|
|
17586
18226
|
const [first, ...rest] = argv;
|
|
@@ -17658,6 +18298,10 @@ async function main(argv) {
|
|
|
17658
18298
|
const { dispatchScan: dispatchScan2 } = await Promise.resolve().then(() => (init_dispatch3(), dispatch_exports3));
|
|
17659
18299
|
return await dispatchScan2(r.args);
|
|
17660
18300
|
}
|
|
18301
|
+
case "init": {
|
|
18302
|
+
const { dispatchInit: dispatchInit2 } = await Promise.resolve().then(() => (init_dispatch4(), dispatch_exports4));
|
|
18303
|
+
return await dispatchInit2(r.args);
|
|
18304
|
+
}
|
|
17661
18305
|
case "logout": {
|
|
17662
18306
|
const { runLogout: runLogout2 } = await Promise.resolve().then(() => (init_logout(), logout_exports));
|
|
17663
18307
|
return await runLogout2(r.args);
|
|
@@ -17668,18 +18312,22 @@ async function main(argv) {
|
|
|
17668
18312
|
}
|
|
17669
18313
|
}
|
|
17670
18314
|
function printHelp() {
|
|
17671
|
-
console.log(`archme \u2014
|
|
17672
|
-
|
|
17673
|
-
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
archme
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
|
|
17682
|
-
|
|
18315
|
+
console.log(`archme \u2014 map your codebase into a living architecture diagram
|
|
18316
|
+
|
|
18317
|
+
Start here:
|
|
18318
|
+
cd <your-repo> && archme init Set up this repo and scan it
|
|
18319
|
+
archme daemon start Run the agent that keeps it current
|
|
18320
|
+
|
|
18321
|
+
Other commands:
|
|
18322
|
+
archme daemon <subcommand> Manage the local daemon as a service
|
|
18323
|
+
(start|stop|status|logs|restart|enable|
|
|
18324
|
+
disable|install|uninstall|run)
|
|
18325
|
+
archme analyze <subcommand> Code analysis (outline|repo|diff|match|prefetch)
|
|
18326
|
+
archme scan <repo-path> Single-pass scan into an explicit project/branch
|
|
18327
|
+
archme login | logout Authenticate with archme
|
|
18328
|
+
|
|
18329
|
+
Run 'archme init --help', 'archme daemon --help', 'archme analyze --help', or
|
|
18330
|
+
'archme scan --help' for subcommand help.`);
|
|
17683
18331
|
}
|
|
17684
18332
|
|
|
17685
18333
|
// src/bin.ts
|