@tokenfactory/acc-runner 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -4
- package/package.json +1 -1
- package/dist/anthropic-auth.d.ts +0 -53
- package/dist/anthropic-auth.d.ts.map +0 -1
- package/dist/anthropic-auth.js +0 -89
- package/dist/anthropic-auth.js.map +0 -1
- package/dist/bin-resolve.d.ts +0 -16
- package/dist/bin-resolve.d.ts.map +0 -1
- package/dist/bin-resolve.js +0 -35
- package/dist/bin-resolve.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -128
- package/dist/cli.js.map +0 -1
- package/dist/config.d.ts +0 -121
- package/dist/config.d.ts.map +0 -1
- package/dist/config.js +0 -396
- package/dist/config.js.map +0 -1
- package/dist/cost-pricing.d.ts +0 -130
- package/dist/cost-pricing.d.ts.map +0 -1
- package/dist/cost-pricing.js +0 -191
- package/dist/cost-pricing.js.map +0 -1
- package/dist/doctor.d.ts +0 -37
- package/dist/doctor.d.ts.map +0 -1
- package/dist/doctor.js +0 -658
- package/dist/doctor.js.map +0 -1
- package/dist/failure-classifier.d.ts +0 -112
- package/dist/failure-classifier.d.ts.map +0 -1
- package/dist/failure-classifier.js +0 -353
- package/dist/failure-classifier.js.map +0 -1
- package/dist/gh.d.ts +0 -22
- package/dist/gh.d.ts.map +0 -1
- package/dist/gh.js +0 -48
- package/dist/gh.js.map +0 -1
- package/dist/git.d.ts +0 -50
- package/dist/git.d.ts.map +0 -1
- package/dist/git.js +0 -127
- package/dist/git.js.map +0 -1
- package/dist/github-client.d.ts +0 -133
- package/dist/github-client.d.ts.map +0 -1
- package/dist/github-client.js +0 -234
- package/dist/github-client.js.map +0 -1
- package/dist/keychain.d.ts +0 -21
- package/dist/keychain.d.ts.map +0 -1
- package/dist/keychain.js +0 -45
- package/dist/keychain.js.map +0 -1
- package/dist/login.d.ts +0 -12
- package/dist/login.d.ts.map +0 -1
- package/dist/login.js +0 -133
- package/dist/login.js.map +0 -1
- package/dist/logout.d.ts +0 -2
- package/dist/logout.d.ts.map +0 -1
- package/dist/logout.js +0 -31
- package/dist/logout.js.map +0 -1
- package/dist/mcp-spawn.d.ts +0 -30
- package/dist/mcp-spawn.d.ts.map +0 -1
- package/dist/mcp-spawn.js +0 -145
- package/dist/mcp-spawn.js.map +0 -1
- package/dist/messaging.d.ts +0 -49
- package/dist/messaging.d.ts.map +0 -1
- package/dist/messaging.js +0 -36
- package/dist/messaging.js.map +0 -1
- package/dist/pkg-version.d.ts +0 -3
- package/dist/pkg-version.d.ts.map +0 -1
- package/dist/pkg-version.js +0 -20
- package/dist/pkg-version.js.map +0 -1
- package/dist/profiles/designer-prompt.d.ts +0 -18
- package/dist/profiles/designer-prompt.d.ts.map +0 -1
- package/dist/profiles/designer-prompt.js +0 -172
- package/dist/profiles/designer-prompt.js.map +0 -1
- package/dist/profiles/developer-prompt.d.ts +0 -24
- package/dist/profiles/developer-prompt.d.ts.map +0 -1
- package/dist/profiles/developer-prompt.js +0 -24
- package/dist/profiles/developer-prompt.js.map +0 -1
- package/dist/profiles/manager-prompt.d.ts +0 -34
- package/dist/profiles/manager-prompt.d.ts.map +0 -1
- package/dist/profiles/manager-prompt.js +0 -93
- package/dist/profiles/manager-prompt.js.map +0 -1
- package/dist/profiles/tester-prompt.d.ts +0 -28
- package/dist/profiles/tester-prompt.d.ts.map +0 -1
- package/dist/profiles/tester-prompt.js +0 -165
- package/dist/profiles/tester-prompt.js.map +0 -1
- package/dist/prompt.d.ts +0 -38
- package/dist/prompt.d.ts.map +0 -1
- package/dist/prompt.js +0 -78
- package/dist/prompt.js.map +0 -1
- package/dist/runtime/cache-dir.d.ts +0 -2
- package/dist/runtime/cache-dir.d.ts.map +0 -1
- package/dist/runtime/cache-dir.js +0 -15
- package/dist/runtime/cache-dir.js.map +0 -1
- package/dist/runtime/conflict-resolver.d.ts +0 -65
- package/dist/runtime/conflict-resolver.d.ts.map +0 -1
- package/dist/runtime/conflict-resolver.js +0 -477
- package/dist/runtime/conflict-resolver.js.map +0 -1
- package/dist/runtime/expand-args.d.ts +0 -28
- package/dist/runtime/expand-args.d.ts.map +0 -1
- package/dist/runtime/expand-args.js +0 -50
- package/dist/runtime/expand-args.js.map +0 -1
- package/dist/runtime/locks.d.ts +0 -21
- package/dist/runtime/locks.d.ts.map +0 -1
- package/dist/runtime/locks.js +0 -97
- package/dist/runtime/locks.js.map +0 -1
- package/dist/runtime/provision-mutex.d.ts +0 -37
- package/dist/runtime/provision-mutex.d.ts.map +0 -1
- package/dist/runtime/provision-mutex.js +0 -67
- package/dist/runtime/provision-mutex.js.map +0 -1
- package/dist/runtime/quarantine.d.ts +0 -26
- package/dist/runtime/quarantine.d.ts.map +0 -1
- package/dist/runtime/quarantine.js +0 -50
- package/dist/runtime/quarantine.js.map +0 -1
- package/dist/runtime/resolution-integrity.d.ts +0 -86
- package/dist/runtime/resolution-integrity.d.ts.map +0 -1
- package/dist/runtime/resolution-integrity.js +0 -248
- package/dist/runtime/resolution-integrity.js.map +0 -1
- package/dist/runtime/reviewer.d.ts +0 -81
- package/dist/runtime/reviewer.d.ts.map +0 -1
- package/dist/runtime/reviewer.js +0 -374
- package/dist/runtime/reviewer.js.map +0 -1
- package/dist/runtime/rework.d.ts +0 -48
- package/dist/runtime/rework.d.ts.map +0 -1
- package/dist/runtime/rework.js +0 -136
- package/dist/runtime/rework.js.map +0 -1
- package/dist/runtime/singleton.d.ts +0 -47
- package/dist/runtime/singleton.d.ts.map +0 -1
- package/dist/runtime/singleton.js +0 -200
- package/dist/runtime/singleton.js.map +0 -1
- package/dist/runtime/version-drift.d.ts +0 -31
- package/dist/runtime/version-drift.d.ts.map +0 -1
- package/dist/runtime/version-drift.js +0 -114
- package/dist/runtime/version-drift.js.map +0 -1
- package/dist/runtime/worktree.d.ts +0 -74
- package/dist/runtime/worktree.d.ts.map +0 -1
- package/dist/runtime/worktree.js +0 -206
- package/dist/runtime/worktree.js.map +0 -1
- package/dist/secrets/inject.d.ts +0 -70
- package/dist/secrets/inject.d.ts.map +0 -1
- package/dist/secrets/inject.js +0 -102
- package/dist/secrets/inject.js.map +0 -1
- package/dist/supabase.d.ts +0 -4
- package/dist/supabase.d.ts.map +0 -1
- package/dist/supabase.js +0 -36
- package/dist/supabase.js.map +0 -1
- package/dist/task-runner.d.ts +0 -313
- package/dist/task-runner.d.ts.map +0 -1
- package/dist/task-runner.js +0 -1766
- package/dist/task-runner.js.map +0 -1
- package/dist/token-provider.d.ts +0 -50
- package/dist/token-provider.d.ts.map +0 -1
- package/dist/token-provider.js +0 -177
- package/dist/token-provider.js.map +0 -1
- package/dist/types.d.ts +0 -120
- package/dist/types.d.ts.map +0 -1
- package/dist/types.js +0 -16
- package/dist/types.js.map +0 -1
- package/dist/version-check.d.ts +0 -17
- package/dist/version-check.d.ts.map +0 -1
- package/dist/version-check.js +0 -56
- package/dist/version-check.js.map +0 -1
- package/dist/watch.d.ts +0 -295
- package/dist/watch.d.ts.map +0 -1
- package/dist/watch.js +0 -1532
- package/dist/watch.js.map +0 -1
package/dist/doctor.js
DELETED
|
@@ -1,658 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `acc-runner doctor` — environment checks. Returns 0 if all green, 1 if
|
|
3
|
-
* any check failed. Each check carries a remediation hint so users can
|
|
4
|
-
* self-serve.
|
|
5
|
-
*/
|
|
6
|
-
import fs from "node:fs/promises";
|
|
7
|
-
import os from "node:os";
|
|
8
|
-
import path from "node:path";
|
|
9
|
-
import { execa } from "execa";
|
|
10
|
-
import chalk from "chalk";
|
|
11
|
-
import { buildEnv } from "./bin-resolve.js";
|
|
12
|
-
import { resolveAuthMode, describeAuthMode, authPrecedenceHint } from "./anthropic-auth.js";
|
|
13
|
-
import { configSources, loadConfigOrNull } from "./config.js";
|
|
14
|
-
import { getTokenProvider, resolveTokenMode } from "./token-provider.js";
|
|
15
|
-
import { createRunnerClient } from "./supabase.js";
|
|
16
|
-
import { checkVersion } from "./version-check.js";
|
|
17
|
-
import { PROTOCOL_VERSION } from "./types.js";
|
|
18
|
-
import { locksDir, listStaleLocks } from "./runtime/locks.js";
|
|
19
|
-
import { workDir, listOrphanWorktrees } from "./runtime/worktree.js";
|
|
20
|
-
import { getQuarantine, quarantineFile } from "./runtime/quarantine.js";
|
|
21
|
-
import { findLiveLockHolder, listForeignRunnerPids, singletonLockPath, } from "./runtime/singleton.js";
|
|
22
|
-
import { getClaudeVersion, npmLatestVersion, readTaskEnv, } from "./runtime/version-drift.js";
|
|
23
|
-
import { compareSemver as compareSemverVer } from "./version-check.js";
|
|
24
|
-
import { PACKAGE_VERSION } from "./pkg-version.js";
|
|
25
|
-
export const LAUNCHD_LABEL = "com.tokenfactory.acc-runner";
|
|
26
|
-
export function launchdPlistPath() {
|
|
27
|
-
return path.join(os.homedir(), "Library", "LaunchAgents", `${LAUNCHD_LABEL}.plist`);
|
|
28
|
-
}
|
|
29
|
-
export const ENV_NAMES = {
|
|
30
|
-
publicUrl: "ACC_PUBLIC_URL",
|
|
31
|
-
supabaseUrl: "ACC_SUPABASE_URL",
|
|
32
|
-
supabaseAnonKey: "ACC_SUPABASE_ANON_KEY",
|
|
33
|
-
repoPath: "ACC_REPO_PATH",
|
|
34
|
-
targetRepo: "ACC_TARGET_REPO",
|
|
35
|
-
integrationBranch: "ACC_INTEGRATION_BRANCH",
|
|
36
|
-
capabilities: "ACC_RUNNER_CAPABILITIES",
|
|
37
|
-
worktreeRepoPath: "ACC_WORKTREE_REPO_PATH",
|
|
38
|
-
};
|
|
39
|
-
export const CONFIG_LABELS = {
|
|
40
|
-
publicUrl: "ACC public URL:",
|
|
41
|
-
supabaseUrl: "Supabase URL:",
|
|
42
|
-
supabaseAnonKey: "Supabase anon key:",
|
|
43
|
-
repoPath: "ACC repo path:",
|
|
44
|
-
targetRepo: "Target repo:",
|
|
45
|
-
integrationBranch: "Integration branch:",
|
|
46
|
-
capabilities: "Capabilities:",
|
|
47
|
-
worktreeRepoPath: "Worktree repo path:",
|
|
48
|
-
};
|
|
49
|
-
function maskKey(s) {
|
|
50
|
-
if (s.length <= 8)
|
|
51
|
-
return "<short>";
|
|
52
|
-
return `${s.slice(0, 4)}…${s.slice(-4)} (len ${s.length})`;
|
|
53
|
-
}
|
|
54
|
-
function formatConfig(cfg) {
|
|
55
|
-
const src = configSources();
|
|
56
|
-
const lines = Object.keys(CONFIG_LABELS).map((k) => {
|
|
57
|
-
// v0.41-B: worktreeRepoPath is optional at the config layer
|
|
58
|
-
// (undefined means "use repoPath"). Render "<not set>" rather than
|
|
59
|
-
// the literal string "undefined" so the doctor output is clean.
|
|
60
|
-
const raw = cfg[k];
|
|
61
|
-
const value = k === "supabaseAnonKey"
|
|
62
|
-
? maskKey(cfg[k])
|
|
63
|
-
: raw === undefined
|
|
64
|
-
? "<not set>"
|
|
65
|
-
: raw;
|
|
66
|
-
const tag = src[k] === "env"
|
|
67
|
-
? chalk.gray(`(env: ${ENV_NAMES[k]})`)
|
|
68
|
-
: chalk.gray("(default)");
|
|
69
|
-
return ` ${CONFIG_LABELS[k].padEnd(20, " ")}${value} ${tag}`;
|
|
70
|
-
});
|
|
71
|
-
return [chalk.bold("Config:"), ...lines].join("\n");
|
|
72
|
-
}
|
|
73
|
-
async function checkBinary(bin, args) {
|
|
74
|
-
try {
|
|
75
|
-
const { stdout } = await execa(bin, args, { env: buildEnv() });
|
|
76
|
-
const first = stdout.split(/\r?\n/)[0]?.trim() ?? bin;
|
|
77
|
-
return { ok: true, detail: first };
|
|
78
|
-
}
|
|
79
|
-
catch (err) {
|
|
80
|
-
return { ok: false, detail: err.message.split("\n")[0] };
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
async function checkNode() {
|
|
84
|
-
const major = Number(process.versions.node.split(".")[0]);
|
|
85
|
-
return {
|
|
86
|
-
name: "node 20+",
|
|
87
|
-
ok: major >= 20,
|
|
88
|
-
detail: `node ${process.versions.node}`,
|
|
89
|
-
fix: "Install Node 20.6+ (https://nodejs.org).",
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
async function checkClaude() {
|
|
93
|
-
const r = await checkBinary("claude", ["--version"]);
|
|
94
|
-
return {
|
|
95
|
-
name: "claude CLI in PATH",
|
|
96
|
-
ok: r.ok,
|
|
97
|
-
detail: r.detail,
|
|
98
|
-
fix: "Install Claude Code (https://claude.com/claude-code) and re-run.",
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
/**
|
|
102
|
-
* v0.74-B (T-1780800000740201): advisory check that ANTHROPIC_API_KEY is set.
|
|
103
|
-
* When present, spawned claude sessions authenticate via the API account,
|
|
104
|
-
* which has no per-session cap. When absent claude falls back to the
|
|
105
|
-
* operator's interactive OAuth session, which is subject to 429 session caps
|
|
106
|
-
* during long autonomous runs — worth flagging but not a hard environment
|
|
107
|
-
* failure, so this is a WARN (advisory, never fails the doctor exit code).
|
|
108
|
-
*/
|
|
109
|
-
async function checkAnthropicKey() {
|
|
110
|
-
const mode = resolveAuthMode();
|
|
111
|
-
if (mode === "api-key") {
|
|
112
|
-
const hint = authPrecedenceHint();
|
|
113
|
-
return {
|
|
114
|
-
name: "ANTHROPIC_API_KEY set",
|
|
115
|
-
ok: true,
|
|
116
|
-
detail: hint ? `${describeAuthMode(mode)} — ${hint}` : describeAuthMode(mode),
|
|
117
|
-
};
|
|
118
|
-
}
|
|
119
|
-
return {
|
|
120
|
-
name: "ANTHROPIC_API_KEY set",
|
|
121
|
-
ok: false,
|
|
122
|
-
warn: true,
|
|
123
|
-
detail: describeAuthMode(mode),
|
|
124
|
-
fix: "export ANTHROPIC_API_KEY=sk-ant-… so spawned claude sessions bill against the API account and avoid 429 session caps.",
|
|
125
|
-
};
|
|
126
|
-
}
|
|
127
|
-
async function checkGh() {
|
|
128
|
-
const ghOk = await checkBinary("gh", ["--version"]);
|
|
129
|
-
if (!ghOk.ok) {
|
|
130
|
-
return {
|
|
131
|
-
name: "gh CLI in PATH",
|
|
132
|
-
ok: false,
|
|
133
|
-
detail: ghOk.detail,
|
|
134
|
-
fix: "Install GitHub CLI (https://cli.github.com).",
|
|
135
|
-
};
|
|
136
|
-
}
|
|
137
|
-
try {
|
|
138
|
-
await execa("gh", ["auth", "status"], { env: buildEnv() });
|
|
139
|
-
return { name: "gh CLI in PATH", ok: true, detail: ghOk.detail };
|
|
140
|
-
}
|
|
141
|
-
catch {
|
|
142
|
-
return {
|
|
143
|
-
name: "gh CLI in PATH",
|
|
144
|
-
ok: false,
|
|
145
|
-
detail: "gh installed but not authenticated",
|
|
146
|
-
fix: "Run `gh auth login`.",
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
async function checkGit() {
|
|
151
|
-
const r = await checkBinary("git", ["--version"]);
|
|
152
|
-
if (!r.ok) {
|
|
153
|
-
return {
|
|
154
|
-
name: "git installed",
|
|
155
|
-
ok: false,
|
|
156
|
-
detail: r.detail,
|
|
157
|
-
fix: "Install git (https://git-scm.com).",
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
// Soft-check that user.email is set; agents can't push commits without it.
|
|
161
|
-
try {
|
|
162
|
-
const { stdout } = await execa("git", ["config", "user.email"], { env: buildEnv() });
|
|
163
|
-
if (!stdout.trim())
|
|
164
|
-
throw new Error("user.email empty");
|
|
165
|
-
return { name: "git installed and configured", ok: true, detail: r.detail };
|
|
166
|
-
}
|
|
167
|
-
catch {
|
|
168
|
-
return {
|
|
169
|
-
name: "git installed and configured",
|
|
170
|
-
ok: false,
|
|
171
|
-
detail: "git.user.email is not set",
|
|
172
|
-
fix: "Run `git config --global user.email <you@example.com>`.",
|
|
173
|
-
};
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
async function checkKeychain() {
|
|
177
|
-
// v0.12.0 (T-52-7): env-token runners (containers) have no OS keychain;
|
|
178
|
-
// the check is inapplicable rather than broken.
|
|
179
|
-
if (resolveTokenMode() === "env") {
|
|
180
|
-
return {
|
|
181
|
-
name: "keychain read/write",
|
|
182
|
-
ok: true,
|
|
183
|
-
detail: "skipped (token mode: env)",
|
|
184
|
-
};
|
|
185
|
-
}
|
|
186
|
-
try {
|
|
187
|
-
const keytar = (await import("keytar"));
|
|
188
|
-
const k = keytar.default ?? keytar;
|
|
189
|
-
await k.setPassword("acc-runner-doctor", "sentinel", "ok");
|
|
190
|
-
const v = await k.getPassword("acc-runner-doctor", "sentinel");
|
|
191
|
-
await k.deletePassword("acc-runner-doctor", "sentinel");
|
|
192
|
-
return {
|
|
193
|
-
name: "keychain read/write",
|
|
194
|
-
ok: v === "ok",
|
|
195
|
-
detail: v === "ok" ? "ok" : `unexpected: ${String(v)}`,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
catch (err) {
|
|
199
|
-
return {
|
|
200
|
-
name: "keychain read/write",
|
|
201
|
-
ok: false,
|
|
202
|
-
detail: err.message.split("\n")[0],
|
|
203
|
-
fix: "On Linux: install libsecret. On macOS/Windows: ensure the keychain service is unlocked.",
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
async function checkServer(publicUrl) {
|
|
208
|
-
try {
|
|
209
|
-
const res = await fetch(`${publicUrl.replace(/\/+$/, "")}/api/health`, {
|
|
210
|
-
headers: { "User-Agent": `acc-runner/${PROTOCOL_VERSION}` },
|
|
211
|
-
});
|
|
212
|
-
if (!res.ok) {
|
|
213
|
-
return {
|
|
214
|
-
name: "/api/health reachable",
|
|
215
|
-
ok: false,
|
|
216
|
-
detail: `HTTP ${res.status}`,
|
|
217
|
-
fix: "Check ACC_PUBLIC_URL points to a deployment that is up.",
|
|
218
|
-
};
|
|
219
|
-
}
|
|
220
|
-
return { name: "/api/health reachable", ok: true, detail: "200 OK" };
|
|
221
|
-
}
|
|
222
|
-
catch (err) {
|
|
223
|
-
return {
|
|
224
|
-
name: "/api/health reachable",
|
|
225
|
-
ok: false,
|
|
226
|
-
detail: err.message.split("\n")[0],
|
|
227
|
-
fix: "Check ACC_PUBLIC_URL and your network connection.",
|
|
228
|
-
};
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
async function checkProtocolVersion(publicUrl) {
|
|
232
|
-
const r = await checkVersion(publicUrl);
|
|
233
|
-
if (r.ok) {
|
|
234
|
-
return {
|
|
235
|
-
name: "protocol version",
|
|
236
|
-
ok: true,
|
|
237
|
-
detail: `server min v${r.serverMin}, you have v${PROTOCOL_VERSION}`,
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
if (r.reason === "outdated") {
|
|
241
|
-
return {
|
|
242
|
-
name: "protocol version",
|
|
243
|
-
ok: false,
|
|
244
|
-
detail: `server min v${r.serverMin}, you have v${PROTOCOL_VERSION}`,
|
|
245
|
-
fix: "Run `pnpm add -g @tokenfactory/acc-runner@latest`.",
|
|
246
|
-
};
|
|
247
|
-
}
|
|
248
|
-
return {
|
|
249
|
-
name: "protocol version",
|
|
250
|
-
ok: false,
|
|
251
|
-
detail: r.detail ?? "unreachable",
|
|
252
|
-
fix: "Check ACC_PUBLIC_URL and connectivity.",
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
async function dirEntryCount(dir) {
|
|
256
|
-
try {
|
|
257
|
-
const entries = await fs.readdir(dir);
|
|
258
|
-
return entries.length;
|
|
259
|
-
}
|
|
260
|
-
catch (err) {
|
|
261
|
-
if (err.code === "ENOENT")
|
|
262
|
-
return null;
|
|
263
|
-
throw err;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
async function checkWorktreeDir() {
|
|
267
|
-
const count = await dirEntryCount(workDir());
|
|
268
|
-
if (count === null) {
|
|
269
|
-
return {
|
|
270
|
-
name: "worktree cache present",
|
|
271
|
-
ok: true,
|
|
272
|
-
detail: `not yet created (${workDir()})`,
|
|
273
|
-
};
|
|
274
|
-
}
|
|
275
|
-
return {
|
|
276
|
-
name: "worktree cache present",
|
|
277
|
-
ok: true,
|
|
278
|
-
detail: `${count} entr${count === 1 ? "y" : "ies"} under ${workDir()}`,
|
|
279
|
-
};
|
|
280
|
-
}
|
|
281
|
-
async function checkOrphanWorktrees(cfg) {
|
|
282
|
-
if (!cfg) {
|
|
283
|
-
return {
|
|
284
|
-
name: "no orphan worktrees",
|
|
285
|
-
ok: false,
|
|
286
|
-
detail: "config unavailable",
|
|
287
|
-
fix: "Re-run `acc-runner doctor` once config loads.",
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
try {
|
|
291
|
-
const orphans = await listOrphanWorktrees(cfg.repoPath);
|
|
292
|
-
if (orphans.length === 0) {
|
|
293
|
-
return { name: "no orphan worktrees", ok: true, detail: "clean" };
|
|
294
|
-
}
|
|
295
|
-
const first = orphans.slice(0, 3).map((o) => o.taskId).join(", ");
|
|
296
|
-
const more = orphans.length > 3 ? ` (+${orphans.length - 3} more)` : "";
|
|
297
|
-
return {
|
|
298
|
-
name: "no orphan worktrees",
|
|
299
|
-
ok: false,
|
|
300
|
-
detail: `${orphans.length} stale dir(s): ${first}${more}`,
|
|
301
|
-
fix: `Run \`git -C ${cfg.repoPath} worktree prune\` and \`rm -rf ${workDir()}/<task_id>\` for the listed ids.`,
|
|
302
|
-
};
|
|
303
|
-
}
|
|
304
|
-
catch (err) {
|
|
305
|
-
return {
|
|
306
|
-
name: "no orphan worktrees",
|
|
307
|
-
ok: false,
|
|
308
|
-
detail: err.message.split("\n")[0],
|
|
309
|
-
fix: "Check that ACC_REPO_PATH is a valid git repo.",
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
async function checkQuarantine() {
|
|
314
|
-
try {
|
|
315
|
-
const q = await getQuarantine();
|
|
316
|
-
if (!q) {
|
|
317
|
-
return { name: "runner quarantine", ok: true, detail: "not quarantined" };
|
|
318
|
-
}
|
|
319
|
-
// v0.53 T-53-4: surface the consecutive instant-empty count when present
|
|
320
|
-
// so an operator can see whether this was a confirmed env_broken (2) or
|
|
321
|
-
// a definitive failure (1).
|
|
322
|
-
const consec = q.consecutive !== undefined ? `, consecutive: ${q.consecutive}` : "";
|
|
323
|
-
return {
|
|
324
|
-
name: "runner quarantine",
|
|
325
|
-
ok: false,
|
|
326
|
-
detail: `QUARANTINED (${q.cause}): ${q.detail} — task: ${q.taskId}, at: ${q.classifiedAt}${consec}`,
|
|
327
|
-
fix: `Resolve the underlying ${q.cause} issue then delete ${quarantineFile()} or run \`acc-runner quarantine clear\`.`,
|
|
328
|
-
};
|
|
329
|
-
}
|
|
330
|
-
catch (err) {
|
|
331
|
-
return {
|
|
332
|
-
name: "runner quarantine",
|
|
333
|
-
ok: false,
|
|
334
|
-
detail: err.message.split("\n")[0],
|
|
335
|
-
fix: `Check permissions on ${quarantineFile()}.`,
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
async function checkStaleLocks() {
|
|
340
|
-
try {
|
|
341
|
-
const stale = await listStaleLocks();
|
|
342
|
-
if (stale.length === 0) {
|
|
343
|
-
return { name: "no stale task locks", ok: true, detail: "clean" };
|
|
344
|
-
}
|
|
345
|
-
const first = stale.slice(0, 3).map((l) => l.taskId).join(", ");
|
|
346
|
-
const more = stale.length > 3 ? ` (+${stale.length - 3} more)` : "";
|
|
347
|
-
return {
|
|
348
|
-
name: "no stale task locks",
|
|
349
|
-
ok: false,
|
|
350
|
-
detail: `${stale.length} stale lock(s): ${first}${more}`,
|
|
351
|
-
fix: `Delete dead pid files under ${locksDir()}/ — the runner ignores live-pid entries.`,
|
|
352
|
-
};
|
|
353
|
-
}
|
|
354
|
-
catch (err) {
|
|
355
|
-
return {
|
|
356
|
-
name: "no stale task locks",
|
|
357
|
-
ok: false,
|
|
358
|
-
detail: err.message.split("\n")[0],
|
|
359
|
-
fix: `Check permissions on ${locksDir()}.`,
|
|
360
|
-
};
|
|
361
|
-
}
|
|
362
|
-
}
|
|
363
|
-
export async function checkSingleton(cfg, deps = {}) {
|
|
364
|
-
if (!cfg) {
|
|
365
|
-
return {
|
|
366
|
-
name: "no duplicate runner",
|
|
367
|
-
ok: true,
|
|
368
|
-
detail: "config unavailable (skipped)",
|
|
369
|
-
};
|
|
370
|
-
}
|
|
371
|
-
const scan = deps.scan ?? listForeignRunnerPids;
|
|
372
|
-
try {
|
|
373
|
-
const holder = await findLiveLockHolder(cfg.repoPath, process.pid);
|
|
374
|
-
if (holder) {
|
|
375
|
-
return {
|
|
376
|
-
name: "no duplicate runner",
|
|
377
|
-
ok: false,
|
|
378
|
-
detail: `pid ${holder.pid} already serves ${holder.repoPath} (since ${holder.acquiredAt})`,
|
|
379
|
-
fix: `Stop it: kill ${holder.pid} — or remove ${singletonLockPath(cfg.repoPath)} if that pid is dead.`,
|
|
380
|
-
};
|
|
381
|
-
}
|
|
382
|
-
// No lock holder for our repo path. Scan for any other acc-runner watch
|
|
383
|
-
// process that predates the singleton lock (no lock file written).
|
|
384
|
-
const foreign = await scan(process.pid);
|
|
385
|
-
if (foreign.length > 0) {
|
|
386
|
-
return {
|
|
387
|
-
name: "no duplicate runner",
|
|
388
|
-
ok: false,
|
|
389
|
-
warn: true,
|
|
390
|
-
detail: `another acc-runner watch process is running (pid ${foreign.join(", ")}); cannot confirm its repo path`,
|
|
391
|
-
fix: "Confirm it is not serving this repo path; if it is, stop it before running watch here.",
|
|
392
|
-
};
|
|
393
|
-
}
|
|
394
|
-
return { name: "no duplicate runner", ok: true, detail: "no other runner on this repo path" };
|
|
395
|
-
}
|
|
396
|
-
catch (err) {
|
|
397
|
-
return {
|
|
398
|
-
name: "no duplicate runner",
|
|
399
|
-
ok: false,
|
|
400
|
-
detail: err.message.split("\n")[0],
|
|
401
|
-
fix: `Check permissions on ${singletonLockPath(cfg.repoPath)}.`,
|
|
402
|
-
};
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
// v0.14.0 (T-54-2, GA-6) ------------------------------------------------
|
|
406
|
-
// Version-drift WARNs. Both are advisory (never fail the run): a stale
|
|
407
|
-
// binary or a swapped-out claude CLI is worth surfacing but is not a hard
|
|
408
|
-
// environment break. The npm lookup is cached + offline-safe (skips
|
|
409
|
-
// silently when the registry is unreachable and no cache exists).
|
|
410
|
-
export async function checkUpdateAvailable(deps = {}) {
|
|
411
|
-
let latest;
|
|
412
|
-
try {
|
|
413
|
-
latest = await npmLatestVersion(deps);
|
|
414
|
-
}
|
|
415
|
-
catch {
|
|
416
|
-
latest = null;
|
|
417
|
-
}
|
|
418
|
-
if (!latest) {
|
|
419
|
-
return {
|
|
420
|
-
name: "acc-runner up to date",
|
|
421
|
-
ok: true,
|
|
422
|
-
detail: "skipped (registry unreachable)",
|
|
423
|
-
};
|
|
424
|
-
}
|
|
425
|
-
if (compareSemverVer(PACKAGE_VERSION, latest) < 0) {
|
|
426
|
-
return {
|
|
427
|
-
name: "acc-runner up to date",
|
|
428
|
-
ok: false,
|
|
429
|
-
warn: true,
|
|
430
|
-
detail: `running v${PACKAGE_VERSION}, npm latest is v${latest}`,
|
|
431
|
-
fix: "Run `npm install -g @tokenfactory/acc-runner@latest` then reload launchd.",
|
|
432
|
-
};
|
|
433
|
-
}
|
|
434
|
-
return {
|
|
435
|
-
name: "acc-runner up to date",
|
|
436
|
-
ok: true,
|
|
437
|
-
detail: `v${PACKAGE_VERSION} (npm latest v${latest})`,
|
|
438
|
-
};
|
|
439
|
-
}
|
|
440
|
-
export async function checkClaudeDrift(deps = {}) {
|
|
441
|
-
const record = await readTaskEnv();
|
|
442
|
-
if (!record || !record.claudeVersion) {
|
|
443
|
-
return {
|
|
444
|
-
name: "claude CLI stable",
|
|
445
|
-
ok: true,
|
|
446
|
-
detail: "no successful task recorded yet (skipped)",
|
|
447
|
-
};
|
|
448
|
-
}
|
|
449
|
-
const current = await (deps.currentVersion ?? getClaudeVersion)();
|
|
450
|
-
if (!current) {
|
|
451
|
-
// claude missing/erroring is already covered by checkClaude — don't
|
|
452
|
-
// double-report it here.
|
|
453
|
-
return { name: "claude CLI stable", ok: true, detail: "current version unavailable (skipped)" };
|
|
454
|
-
}
|
|
455
|
-
if (current !== record.claudeVersion) {
|
|
456
|
-
return {
|
|
457
|
-
name: "claude CLI stable",
|
|
458
|
-
ok: false,
|
|
459
|
-
warn: true,
|
|
460
|
-
detail: `claude changed since last green task: was "${record.claudeVersion}", now "${current}"`,
|
|
461
|
-
fix: "Verify the new claude works (`acc-runner doctor` after a task), or pin the prior version if the update regressed.",
|
|
462
|
-
};
|
|
463
|
-
}
|
|
464
|
-
return {
|
|
465
|
-
name: "claude CLI stable",
|
|
466
|
-
ok: true,
|
|
467
|
-
detail: `unchanged since last green task (${current})`,
|
|
468
|
-
};
|
|
469
|
-
}
|
|
470
|
-
async function checkRegistration() {
|
|
471
|
-
const cfg = loadConfigOrNull();
|
|
472
|
-
if (!cfg) {
|
|
473
|
-
// With defaults baked in this branch is unreachable, but keep a
|
|
474
|
-
// defensive guard so a future config refactor that re-enables
|
|
475
|
-
// throws still produces a meaningful error.
|
|
476
|
-
return {
|
|
477
|
-
name: "runner registered",
|
|
478
|
-
ok: false,
|
|
479
|
-
detail: "config could not be loaded",
|
|
480
|
-
fix: "Run `acc-runner doctor` and report the failing config line.",
|
|
481
|
-
};
|
|
482
|
-
}
|
|
483
|
-
// v0.12.0 (T-52-7): session may come from the keychain (desktop) or
|
|
484
|
-
// env vars (ephemeral container) depending on ACC_RUNNER_TOKEN_MODE.
|
|
485
|
-
const provider = getTokenProvider();
|
|
486
|
-
let session;
|
|
487
|
-
try {
|
|
488
|
-
session = await provider.load();
|
|
489
|
-
}
|
|
490
|
-
catch (err) {
|
|
491
|
-
return {
|
|
492
|
-
name: "runner registered",
|
|
493
|
-
ok: false,
|
|
494
|
-
detail: err.message.split("\n")[0],
|
|
495
|
-
fix: "Set ACC_RUNNER_ACCESS_TOKEN (token mode: env).",
|
|
496
|
-
};
|
|
497
|
-
}
|
|
498
|
-
if (!session) {
|
|
499
|
-
return {
|
|
500
|
-
name: "runner registered",
|
|
501
|
-
ok: false,
|
|
502
|
-
detail: provider.mode === "env" ? "no session in env" : "no session in keychain",
|
|
503
|
-
fix: provider.mode === "env"
|
|
504
|
-
? "Set ACC_RUNNER_ACCESS_TOKEN."
|
|
505
|
-
: "Run `acc-runner login`.",
|
|
506
|
-
};
|
|
507
|
-
}
|
|
508
|
-
try {
|
|
509
|
-
const supabase = createRunnerClient(cfg, session.access_token);
|
|
510
|
-
const { data, error } = await supabase
|
|
511
|
-
.from("runners")
|
|
512
|
-
.select("id,bound_user_id,status")
|
|
513
|
-
.eq("id", session.runner_id)
|
|
514
|
-
.maybeSingle();
|
|
515
|
-
if (error) {
|
|
516
|
-
return {
|
|
517
|
-
name: "runner registered",
|
|
518
|
-
ok: false,
|
|
519
|
-
detail: error.message,
|
|
520
|
-
fix: "Re-run `acc-runner login`.",
|
|
521
|
-
};
|
|
522
|
-
}
|
|
523
|
-
if (!data) {
|
|
524
|
-
return {
|
|
525
|
-
name: "runner registered",
|
|
526
|
-
ok: false,
|
|
527
|
-
detail: `runner row ${session.runner_id} not found`,
|
|
528
|
-
fix: "Re-run `acc-runner login`.",
|
|
529
|
-
};
|
|
530
|
-
}
|
|
531
|
-
if (data.bound_user_id !== session.user_id) {
|
|
532
|
-
return {
|
|
533
|
-
name: "runner registered",
|
|
534
|
-
ok: false,
|
|
535
|
-
detail: "runner bound to a different user",
|
|
536
|
-
fix: "Run `acc-runner logout` then `acc-runner login` again.",
|
|
537
|
-
};
|
|
538
|
-
}
|
|
539
|
-
return { name: "runner registered", ok: true, detail: `${session.runner_id} (status=${data.status})` };
|
|
540
|
-
}
|
|
541
|
-
catch (err) {
|
|
542
|
-
return {
|
|
543
|
-
name: "runner registered",
|
|
544
|
-
ok: false,
|
|
545
|
-
detail: err.message.split("\n")[0],
|
|
546
|
-
fix: "Re-run `acc-runner login`.",
|
|
547
|
-
};
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
// v0.12-RUNNER-HEALTH ----------------------------------------------------
|
|
551
|
-
// macOS LaunchAgent + caffeinate are how the operator side stays up:
|
|
552
|
-
// launchd auto-restarts the watch process on crash, caffeinate keeps
|
|
553
|
-
// the Mac from sleeping mid-task. Both are no-ops outside Darwin so
|
|
554
|
-
// the checks report as "skipped" and stay green.
|
|
555
|
-
async function checkCaffeinate() {
|
|
556
|
-
if (process.platform !== "darwin") {
|
|
557
|
-
return { name: "caffeinate available", ok: true, detail: "skipped (non-darwin)" };
|
|
558
|
-
}
|
|
559
|
-
const r = await checkBinary("caffeinate", ["-h"]);
|
|
560
|
-
// caffeinate -h exits non-zero (prints usage to stderr) but the
|
|
561
|
-
// binary is present. Treat ENOENT as the only failure mode.
|
|
562
|
-
const present = r.ok || !/not found|ENOENT/i.test(r.detail);
|
|
563
|
-
if (present) {
|
|
564
|
-
return { name: "caffeinate available", ok: true, detail: "/usr/bin/caffeinate" };
|
|
565
|
-
}
|
|
566
|
-
return {
|
|
567
|
-
name: "caffeinate available",
|
|
568
|
-
ok: false,
|
|
569
|
-
detail: r.detail,
|
|
570
|
-
fix: "caffeinate ships with macOS at /usr/bin/caffeinate; ensure $PATH includes /usr/bin.",
|
|
571
|
-
};
|
|
572
|
-
}
|
|
573
|
-
async function checkLaunchd() {
|
|
574
|
-
if (process.platform !== "darwin") {
|
|
575
|
-
return { name: "launchd agent loaded", ok: true, detail: "skipped (non-darwin)" };
|
|
576
|
-
}
|
|
577
|
-
const plist = launchdPlistPath();
|
|
578
|
-
try {
|
|
579
|
-
await fs.access(plist);
|
|
580
|
-
}
|
|
581
|
-
catch {
|
|
582
|
-
return {
|
|
583
|
-
name: "launchd agent loaded",
|
|
584
|
-
ok: false,
|
|
585
|
-
detail: `plist missing at ${plist}`,
|
|
586
|
-
fix: "Run packages/acc-runner/scripts/install-launchd.sh from the repo clone.",
|
|
587
|
-
};
|
|
588
|
-
}
|
|
589
|
-
try {
|
|
590
|
-
const { stdout } = await execa("launchctl", ["list"], { env: process.env });
|
|
591
|
-
const loaded = stdout
|
|
592
|
-
.split(/\r?\n/)
|
|
593
|
-
.some((line) => line.trim().split(/\s+/).pop() === LAUNCHD_LABEL);
|
|
594
|
-
if (!loaded) {
|
|
595
|
-
return {
|
|
596
|
-
name: "launchd agent loaded",
|
|
597
|
-
ok: false,
|
|
598
|
-
detail: `${LAUNCHD_LABEL} not in launchctl list`,
|
|
599
|
-
fix: `launchctl load ${plist}`,
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
return { name: "launchd agent loaded", ok: true, detail: LAUNCHD_LABEL };
|
|
603
|
-
}
|
|
604
|
-
catch (err) {
|
|
605
|
-
return {
|
|
606
|
-
name: "launchd agent loaded",
|
|
607
|
-
ok: false,
|
|
608
|
-
detail: err.message.split("\n")[0],
|
|
609
|
-
fix: "Ensure launchctl is on PATH (default on macOS).",
|
|
610
|
-
};
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
function format(outcome) {
|
|
614
|
-
const tag = outcome.ok
|
|
615
|
-
? chalk.green("[OK ]")
|
|
616
|
-
: outcome.warn
|
|
617
|
-
? chalk.yellow("[WARN]")
|
|
618
|
-
: chalk.red("[FAIL]");
|
|
619
|
-
const fix = !outcome.ok && outcome.fix ? chalk.gray(`\n → ${outcome.fix}`) : "";
|
|
620
|
-
return `${tag} ${outcome.name.padEnd(28, " ")} ${chalk.gray(outcome.detail)}${fix}`;
|
|
621
|
-
}
|
|
622
|
-
export async function doctorCommand(options = {}) {
|
|
623
|
-
const cfg = loadConfigOrNull();
|
|
624
|
-
const checks = options.checks ?? [
|
|
625
|
-
checkNode,
|
|
626
|
-
checkClaude,
|
|
627
|
-
checkAnthropicKey,
|
|
628
|
-
checkGh,
|
|
629
|
-
checkGit,
|
|
630
|
-
checkKeychain,
|
|
631
|
-
...(cfg ? [() => checkServer(cfg.publicUrl)] : []),
|
|
632
|
-
...(cfg ? [() => checkProtocolVersion(cfg.publicUrl)] : []),
|
|
633
|
-
checkRegistration,
|
|
634
|
-
checkQuarantine,
|
|
635
|
-
checkWorktreeDir,
|
|
636
|
-
() => checkOrphanWorktrees(cfg),
|
|
637
|
-
checkStaleLocks,
|
|
638
|
-
() => checkSingleton(cfg),
|
|
639
|
-
checkUpdateAvailable,
|
|
640
|
-
checkClaudeDrift,
|
|
641
|
-
checkCaffeinate,
|
|
642
|
-
checkLaunchd,
|
|
643
|
-
];
|
|
644
|
-
if (cfg && !options.checks) {
|
|
645
|
-
console.log(formatConfig(cfg));
|
|
646
|
-
console.log("");
|
|
647
|
-
}
|
|
648
|
-
let failed = 0;
|
|
649
|
-
for (const fn of checks) {
|
|
650
|
-
const outcome = await fn();
|
|
651
|
-
// v0.14.0 (T-54-2): WARN outcomes are advisory and never fail the run.
|
|
652
|
-
if (!outcome.ok && !outcome.warn)
|
|
653
|
-
failed++;
|
|
654
|
-
console.log(format(outcome));
|
|
655
|
-
}
|
|
656
|
-
return failed === 0 ? 0 : 1;
|
|
657
|
-
}
|
|
658
|
-
//# sourceMappingURL=doctor.js.map
|