auto-mode 0.21.0 → 1.0.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 +2 -3
- package/dist/cli.js +313 -8
- package/dist/index.d.ts +101 -4637
- package/dist/index.js +2 -7
- package/dist/{render-verdict-CE3ojHJk.js → parse-action-request-BfkmQxEC.js} +1951 -1233
- package/dist/secret-rules.json +1 -0
- package/mods/auto-mode/contract/cli-limits.test.ts +11 -0
- package/mods/auto-mode/contract/cli-limits.ts +17 -0
- package/mods/auto-mode/hooks/register.ts +2 -5
- package/mods/auto-mode/hooks/testing.d.ts +2 -3
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ auto-mode needs node 24 or later, Claude Code, and a TypeSafe API key for Jev.
|
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
20
|
npm i -g auto-mode
|
|
21
|
-
claude --plugin-dir "$(
|
|
21
|
+
claude --plugin-dir "$(auto-mode mod-path)"
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
Export your key as `TYPESAFE_API_KEY`, or point the configuration at a command that prints it.
|
|
@@ -54,8 +54,7 @@ You can call auto-mode's core APIs from your own code. `parseActionRequest` read
|
|
|
54
54
|
mod sends the CLI ([the contract](./docs/architecture/mod.md#the-contract)), and `classifyAction`
|
|
55
55
|
runs it through [the stages](./docs/architecture/overview.md#the-path-of-a-prompted-call) from the
|
|
56
56
|
local rules to the judge. The two options in the example apply the denial budget and write the
|
|
57
|
-
action log, as the CLI does
|
|
58
|
-
session's scope.
|
|
57
|
+
action log, as the CLI does.
|
|
59
58
|
|
|
60
59
|
```ts
|
|
61
60
|
import { classifyAction, loadConfig, parseActionRequest } from 'auto-mode';
|
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { C as
|
|
3
|
-
import { chmod, mkdir, open, realpath, stat } from "node:fs/promises";
|
|
4
|
-
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
2
|
+
import { C as resolveXDGDir, D as string, E as strictObject, S as CLI_LIMITS, T as number, _ as findGitEntry, a as EMPTY_SESSION_SCOPE, b as readHostEnvironment, c as loadPolicy, d as writeActionDiagnostic, f as toRepositorySlug, g as findCheckout, h as isUnresolvedWord, i as loadSessionScope, l as resolveShippedPolicyDir, m as resolveShellPath, n as classifyAction, o as splitGitCommand, p as collectShellCommands, r as resolveSessionScopePath, s as M, t as parseActionRequest, u as loadCheckoutFacts, v as DEFAULT_SCOPE_SOURCES, w as updateJSONFile, x as runCapped, y as loadConfig } from "./parse-action-request-BfkmQxEC.js";
|
|
5
3
|
import { randomUUID } from "node:crypto";
|
|
6
|
-
import {
|
|
4
|
+
import { chmod, mkdir, open, readFile, realpath, stat } from "node:fs/promises";
|
|
5
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
6
|
+
import { constants, existsSync } from "node:fs";
|
|
7
7
|
import { text } from "node:stream/consumers";
|
|
8
8
|
import { parseArgs } from "node:util";
|
|
9
9
|
//#region src/capture/resolve-capture-dir.ts
|
|
@@ -74,15 +74,313 @@ async function writeOwnerFile(path, text, flag) {
|
|
|
74
74
|
//#region src/config/build-jev-only-config.ts
|
|
75
75
|
function buildJevOnlyConfig(config) {
|
|
76
76
|
if (config.provider.protocol !== "system-one") return null;
|
|
77
|
+
const judge = config.judge === void 0 || config.judge === null ? config.judge : {
|
|
78
|
+
...config.judge,
|
|
79
|
+
timeoutMs: Math.min(config.judge.timeoutMs, CLI_LIMITS.judgeMs)
|
|
80
|
+
};
|
|
77
81
|
return {
|
|
78
82
|
...config,
|
|
79
83
|
provider: {
|
|
80
84
|
...config.provider,
|
|
81
|
-
timeoutMs: Math.min(config.provider.timeoutMs,
|
|
82
|
-
}
|
|
85
|
+
timeoutMs: Math.min(config.provider.timeoutMs, CLI_LIMITS.jevMs)
|
|
86
|
+
},
|
|
87
|
+
...judge === void 0 ? {} : { judge }
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
//#endregion
|
|
91
|
+
//#region src/config/resolve-shipped-mod-dir.ts
|
|
92
|
+
function resolveShippedModDir(startDir = import.meta.dirname) {
|
|
93
|
+
const modDir = join(dirname(resolveShippedPolicyDir(startDir)), "mods", "auto-mode");
|
|
94
|
+
if (!existsSync(join(modDir, ".claude-plugin", "plugin.json"))) throw new Error("auto-mode cannot find its shipped mod");
|
|
95
|
+
return modDir;
|
|
96
|
+
}
|
|
97
|
+
//#endregion
|
|
98
|
+
//#region src/scope/read-pull-request.ts
|
|
99
|
+
const LOOKUP_TIMEOUT_MS = 5e3;
|
|
100
|
+
const MAX_LOOKUP_OUTPUT_BYTES = 65536;
|
|
101
|
+
async function readPullRequest(repository, number, options = {}) {
|
|
102
|
+
const [file, ...prefix] = options.command ?? ["gh"];
|
|
103
|
+
const args = [
|
|
104
|
+
"pr",
|
|
105
|
+
"view",
|
|
106
|
+
String(number),
|
|
107
|
+
"--repo",
|
|
108
|
+
repository,
|
|
109
|
+
"--json",
|
|
110
|
+
"headRefName,createdAt"
|
|
111
|
+
];
|
|
112
|
+
const result = await runCapped(file, [
|
|
113
|
+
...prefix,
|
|
114
|
+
...args,
|
|
115
|
+
"--jq",
|
|
116
|
+
"[.headRefName, .createdAt] | @tsv"
|
|
117
|
+
], {
|
|
118
|
+
signal: AbortSignal.timeout(options.timeoutMs ?? LOOKUP_TIMEOUT_MS),
|
|
119
|
+
maxOutputBytes: MAX_LOOKUP_OUTPUT_BYTES
|
|
120
|
+
});
|
|
121
|
+
const [head = "", created = ""] = result.stdout.trim().split(" ");
|
|
122
|
+
const createdAt = Date.parse(created);
|
|
123
|
+
return result.exitCode === 0 && head !== "" && !Number.isNaN(createdAt) ? {
|
|
124
|
+
head,
|
|
125
|
+
createdAt
|
|
126
|
+
} : null;
|
|
127
|
+
}
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region src/git/read-branch-created-at.ts
|
|
130
|
+
async function readBranchCreatedAt(commonDir, branch) {
|
|
131
|
+
if (!/^[\w./-]+$/u.test(branch) || branch.split("/").includes("..")) return null;
|
|
132
|
+
const [first = ""] = (await readFile(join(commonDir, "logs", "refs", "heads", branch), "utf8").catch(() => "")).split("\n");
|
|
133
|
+
const seconds = /^0+ [\da-f]+ .* (?<time>\d+) [+-]\d{4}\t/u.exec(first)?.groups?.["time"];
|
|
134
|
+
return seconds === void 0 ? null : Number(seconds) * 1e3;
|
|
135
|
+
}
|
|
136
|
+
//#endregion
|
|
137
|
+
//#region src/scope/collect-pull-request-addresses.ts
|
|
138
|
+
function collectPullRequestAddresses(text) {
|
|
139
|
+
const addresses = /* @__PURE__ */ new Map();
|
|
140
|
+
for (const match of text.matchAll(PULL_REQUEST_URL)) {
|
|
141
|
+
const repository = toRepositorySlug(`https://${match.groups?.["repository"] ?? ""}`);
|
|
142
|
+
const number = Number(match.groups?.["number"]);
|
|
143
|
+
if (repository !== null) addresses.set(`${repository}#${String(number)}`, {
|
|
144
|
+
repository,
|
|
145
|
+
number
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return [...addresses.values()];
|
|
149
|
+
}
|
|
150
|
+
const PULL_REQUEST_URL = /https:\/\/(?<repository>[^/\s]+\/[^/\s]+\/[^/\s]+)\/pull\/(?<number>[1-9]\d*)\b/gu;
|
|
151
|
+
//#endregion
|
|
152
|
+
//#region mods/auto-mode/contract/scope-commands.ts
|
|
153
|
+
const SCOPE_COMMANDS = {
|
|
154
|
+
git: [
|
|
155
|
+
"worktree add",
|
|
156
|
+
"checkout",
|
|
157
|
+
"switch",
|
|
158
|
+
"branch"
|
|
159
|
+
],
|
|
160
|
+
gh: ["pr create"]
|
|
161
|
+
};
|
|
162
|
+
//#endregion
|
|
163
|
+
//#region src/scope/collect-scope-events.ts
|
|
164
|
+
function collectScopeEvents(command, cwd, home) {
|
|
165
|
+
return collectShellCommands(command, cwd, home).flatMap((entry) => {
|
|
166
|
+
const [name, ...args] = entry.words;
|
|
167
|
+
const directory = entry.directory;
|
|
168
|
+
if (directory === null) return [];
|
|
169
|
+
if (name === "git") return collectGitEvents(args, directory, home);
|
|
170
|
+
return name === "gh" && findScopeCommand(SCOPE_COMMANDS.gh, args) !== null ? [{
|
|
171
|
+
kind: "pull-request",
|
|
172
|
+
directory,
|
|
173
|
+
head: findOptionValue(args, ["--head", "-H"])
|
|
174
|
+
}] : [];
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
function collectGitEvents(args, cwd, home) {
|
|
178
|
+
const git = splitGitCommand(args, cwd, home);
|
|
179
|
+
const found = findScopeCommand(SCOPE_COMMANDS.git, git.args);
|
|
180
|
+
if (git.directory === null || found === null) return [];
|
|
181
|
+
return GIT_EVENT_COLLECTORS[found.command](found.rest, git.directory, home);
|
|
182
|
+
}
|
|
183
|
+
function findScopeCommand(commands, args) {
|
|
184
|
+
for (const command of commands) {
|
|
185
|
+
const words = command.split(" ");
|
|
186
|
+
if (words.every((word, index) => args[index] === word)) return {
|
|
187
|
+
command,
|
|
188
|
+
rest: args.slice(words.length)
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
return null;
|
|
192
|
+
}
|
|
193
|
+
const GIT_EVENT_COLLECTORS = {
|
|
194
|
+
"worktree add": collectWorktreeEvents,
|
|
195
|
+
checkout: collectSwitchEvents,
|
|
196
|
+
switch: collectSwitchEvents,
|
|
197
|
+
branch: collectBranchEvents
|
|
198
|
+
};
|
|
199
|
+
function collectWorktreeEvents(rest, directory, home) {
|
|
200
|
+
const path = findWorktreePath(rest);
|
|
201
|
+
const resolved = path === null ? null : resolveShellPath(directory, path, home);
|
|
202
|
+
return resolved === null ? [] : [{
|
|
203
|
+
kind: "worktree",
|
|
204
|
+
path: resolved
|
|
205
|
+
}];
|
|
206
|
+
}
|
|
207
|
+
const WORKTREE_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
208
|
+
"-b",
|
|
209
|
+
"-B",
|
|
210
|
+
"--reason"
|
|
211
|
+
]);
|
|
212
|
+
function findWorktreePath(args) {
|
|
213
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
214
|
+
const word = args[index] ?? "";
|
|
215
|
+
if (WORKTREE_VALUE_OPTIONS.has(word)) index += 1;
|
|
216
|
+
else if (!word.startsWith("-")) return word;
|
|
217
|
+
}
|
|
218
|
+
return null;
|
|
219
|
+
}
|
|
220
|
+
const BRANCH_CREATE_OPTIONS = /* @__PURE__ */ new Set([
|
|
221
|
+
"-b",
|
|
222
|
+
"-B",
|
|
223
|
+
"-c",
|
|
224
|
+
"-C",
|
|
225
|
+
"--create",
|
|
226
|
+
"--force-create"
|
|
227
|
+
]);
|
|
228
|
+
const BRANCH_OTHER_OPTIONS = /^-(?:d|D|m|M|c|C|l|a|r|v|vv|u)$|^--(?:delete|move|copy|list|all|remotes|verbose|show-current|contains|no-contains|merged|no-merged|points-at|format|sort|set-upstream-to|unset-upstream|edit-description)/u;
|
|
229
|
+
function collectSwitchEvents(rest, directory) {
|
|
230
|
+
const name = findOptionValue(rest, [...BRANCH_CREATE_OPTIONS]);
|
|
231
|
+
return name === null ? [] : [{
|
|
232
|
+
kind: "branch",
|
|
233
|
+
name,
|
|
234
|
+
directory
|
|
235
|
+
}];
|
|
236
|
+
}
|
|
237
|
+
function collectBranchEvents(rest, directory) {
|
|
238
|
+
if (rest.some((word) => BRANCH_OTHER_OPTIONS.test(word))) return [];
|
|
239
|
+
const name = rest.find((word) => !word.startsWith("-"));
|
|
240
|
+
return name === void 0 || isUnresolvedWord(name) ? [] : [{
|
|
241
|
+
kind: "branch",
|
|
242
|
+
name,
|
|
243
|
+
directory
|
|
244
|
+
}];
|
|
245
|
+
}
|
|
246
|
+
function findOptionValue(args, options) {
|
|
247
|
+
const index = args.findIndex((word) => options.includes(word));
|
|
248
|
+
const value = index === -1 ? void 0 : args[index + 1];
|
|
249
|
+
return value === void 0 || isUnresolvedWord(value) ? null : value;
|
|
250
|
+
}
|
|
251
|
+
//#endregion
|
|
252
|
+
//#region src/scope/merge-session-scope.ts
|
|
253
|
+
function mergeSessionScope(all) {
|
|
254
|
+
const branches = /* @__PURE__ */ new Map();
|
|
255
|
+
const pullRequests = /* @__PURE__ */ new Map();
|
|
256
|
+
for (const branch of all.flatMap((scope) => scope.branches)) branches.set(`${branch.commonDir}\0${branch.name}`, branch);
|
|
257
|
+
for (const pull of all.flatMap((scope) => scope.pullRequests)) pullRequests.set(`${pull.repository}#${String(pull.number)}`, pull);
|
|
258
|
+
return {
|
|
259
|
+
worktrees: [...new Set(all.flatMap((scope) => scope.worktrees))],
|
|
260
|
+
branches: [...branches.values()],
|
|
261
|
+
pullRequests: [...pullRequests.values()]
|
|
83
262
|
};
|
|
84
263
|
}
|
|
85
264
|
//#endregion
|
|
265
|
+
//#region src/scope/write-session-scope.ts
|
|
266
|
+
async function writeSessionScope(path, added, clock) {
|
|
267
|
+
await updateJSONFile(path, loadSessionScope, (current) => mergeSessionScope([current, added]), clock);
|
|
268
|
+
}
|
|
269
|
+
//#endregion
|
|
270
|
+
//#region src/scope/update-session-scope.ts
|
|
271
|
+
const MAX_CALL_AGE_MS = 9e5;
|
|
272
|
+
async function updateSessionScope(request, options) {
|
|
273
|
+
const age = options.now - request.startedAt;
|
|
274
|
+
if (age < -6e4 || age > MAX_CALL_AGE_MS) return EMPTY_SESSION_SCOPE;
|
|
275
|
+
const events = collectScopeEvents(request.command, request.cwd, options.home);
|
|
276
|
+
const scope = mergeSessionScope(await Promise.all(events.map((event) => tryVerifyScopeEvent(event, request, options))));
|
|
277
|
+
if (scope.worktrees.length + scope.branches.length + scope.pullRequests.length > 0) await writeSessionScope(resolveSessionScopePath(options.stateDir, request.sessionID), scope, options.lockClock);
|
|
278
|
+
return scope;
|
|
279
|
+
}
|
|
280
|
+
async function tryVerifyScopeEvent(event, request, options) {
|
|
281
|
+
try {
|
|
282
|
+
return await (options.verifyEvent === void 0 ? verifyScopeEvent(event, request, options) : options.verifyEvent(event, request));
|
|
283
|
+
} catch {
|
|
284
|
+
return EMPTY_SESSION_SCOPE;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
const FORGE_CLOCK_SKEW_MS = 6e4;
|
|
288
|
+
const FILE_CLOCK_SLACK_MS = 50;
|
|
289
|
+
function verifyScopeEvent(event, request, options) {
|
|
290
|
+
return M(event).returnType().with({ kind: "worktree" }, (worktree) => verifyWorktreeEvent(worktree.path, request, options)).with({ kind: "branch" }, (branch) => verifyBranchEvent(branch, request, options)).with({ kind: "pull-request" }, (pull) => verifyPullRequestEvent(pull.directory, request, options)).exhaustive();
|
|
291
|
+
}
|
|
292
|
+
async function verifyWorktreeEvent(path, request, options) {
|
|
293
|
+
const link = await stat(join(path, ".git"));
|
|
294
|
+
const facts = await loadCheckoutFacts(path, options.env);
|
|
295
|
+
const checkout = facts.checkout;
|
|
296
|
+
if (!link.isFile() || link.mtimeMs < request.startedAt - FILE_CLOCK_SLACK_MS || checkout?.worktree !== path) return EMPTY_SESSION_SCOPE;
|
|
297
|
+
const branch = facts.head?.branch ?? null;
|
|
298
|
+
if (branch === null) return {
|
|
299
|
+
...EMPTY_SESSION_SCOPE,
|
|
300
|
+
worktrees: [path]
|
|
301
|
+
};
|
|
302
|
+
const isNew = await isBranchCreatedSince(checkout.commonDir, branch, request.startedAt);
|
|
303
|
+
return {
|
|
304
|
+
...EMPTY_SESSION_SCOPE,
|
|
305
|
+
worktrees: [path],
|
|
306
|
+
branches: isNew ? [{
|
|
307
|
+
name: branch,
|
|
308
|
+
commonDir: checkout.commonDir
|
|
309
|
+
}] : []
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
async function verifyBranchEvent(event, request, options) {
|
|
313
|
+
const checkout = await findCheckout(event.directory, options.env);
|
|
314
|
+
if (checkout === null) return EMPTY_SESSION_SCOPE;
|
|
315
|
+
return await isBranchCreatedSince(checkout.commonDir, event.name, request.startedAt) ? {
|
|
316
|
+
...EMPTY_SESSION_SCOPE,
|
|
317
|
+
branches: [{
|
|
318
|
+
name: event.name,
|
|
319
|
+
commonDir: checkout.commonDir
|
|
320
|
+
}]
|
|
321
|
+
} : EMPTY_SESSION_SCOPE;
|
|
322
|
+
}
|
|
323
|
+
async function verifyPullRequestEvent(directory, request, options) {
|
|
324
|
+
const facts = await loadCheckoutFacts(directory, options.env);
|
|
325
|
+
if (facts.checkout === null) return EMPTY_SESSION_SCOPE;
|
|
326
|
+
const repositories = new Set(facts.remotes.map((remote) => toRepositorySlug(remote.url)));
|
|
327
|
+
const addresses = collectPullRequestAddresses(request.resultText).filter((address) => repositories.has(address.repository));
|
|
328
|
+
const pulls = await Promise.all(addresses.map(async (address) => {
|
|
329
|
+
const pull = await options.readPullRequest(address.repository, address.number);
|
|
330
|
+
return pull !== null && pull.createdAt >= request.startedAt - FORGE_CLOCK_SKEW_MS ? [{
|
|
331
|
+
...address,
|
|
332
|
+
head: pull.head
|
|
333
|
+
}] : [];
|
|
334
|
+
}));
|
|
335
|
+
return {
|
|
336
|
+
...EMPTY_SESSION_SCOPE,
|
|
337
|
+
pullRequests: pulls.flat()
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
async function isBranchCreatedSince(commonDir, branch, since) {
|
|
341
|
+
const createdAt = await readBranchCreatedAt(commonDir, branch);
|
|
342
|
+
if (createdAt === null || createdAt < Math.floor(since / 1e3) * 1e3) return false;
|
|
343
|
+
const ref = await stat(join(commonDir, "refs", "heads", branch)).catch(() => null);
|
|
344
|
+
return ref !== null && ref.mtimeMs >= since - FILE_CLOCK_SLACK_MS;
|
|
345
|
+
}
|
|
346
|
+
//#endregion
|
|
347
|
+
//#region src/record-scope.ts
|
|
348
|
+
async function recordScope(request, config, options = {}) {
|
|
349
|
+
const host = options.host ?? readHostEnvironment();
|
|
350
|
+
if (!Object.values(config.scopeSources ?? DEFAULT_SCOPE_SOURCES).some((source) => source.kind === "session")) return EMPTY_SESSION_SCOPE;
|
|
351
|
+
return await updateSessionScope(request, {
|
|
352
|
+
now: options.now ?? Date.now(),
|
|
353
|
+
stateDir: resolveXDGDir(host, "state"),
|
|
354
|
+
home: host.home,
|
|
355
|
+
env: host.env,
|
|
356
|
+
readPullRequest
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
//#endregion
|
|
360
|
+
//#region src/request/scope-record-request-schema.ts
|
|
361
|
+
const scopeRecordRequestSchema = strictObject({
|
|
362
|
+
sessionID: string().min(1),
|
|
363
|
+
cwd: string().min(1),
|
|
364
|
+
startedAt: number().int().positive(),
|
|
365
|
+
command: string().min(1),
|
|
366
|
+
resultText: string()
|
|
367
|
+
});
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region src/request/parse-scope-record-request.ts
|
|
370
|
+
function parseScopeRecordRequest(body) {
|
|
371
|
+
const parsed = scopeRecordRequestSchema.safeParse(body);
|
|
372
|
+
return parsed.success ? parsed.data : null;
|
|
373
|
+
}
|
|
374
|
+
//#endregion
|
|
375
|
+
//#region src/request/render-verdict.ts
|
|
376
|
+
function renderVerdict(verdict) {
|
|
377
|
+
const rendered = M(verdict).returnType().with({ kind: "allow" }, () => ({ decision: "allow" })).with({ kind: "deny" }, (denied) => ({
|
|
378
|
+
decision: "deny",
|
|
379
|
+
reason: `[${denied.rule}] ${denied.reason}`
|
|
380
|
+
})).exhaustive();
|
|
381
|
+
return JSON.stringify(rendered);
|
|
382
|
+
}
|
|
383
|
+
//#endregion
|
|
86
384
|
//#region src/request/to-mod-request.ts
|
|
87
385
|
function toModRequest(request) {
|
|
88
386
|
const context = request.decisionContext;
|
|
@@ -120,6 +418,7 @@ Usage:
|
|
|
120
418
|
auto-mode run Read an action request on stdin, write a verdict on stdout
|
|
121
419
|
auto-mode print-prompt Print the system prompt the classifier receives
|
|
122
420
|
auto-mode record Read a finished Bash call on stdin, add what it created to the session's scope
|
|
421
|
+
auto-mode mod-path Print the directory of the Claude Code mod this package ships
|
|
123
422
|
|
|
124
423
|
Options:
|
|
125
424
|
--classifier <path> Use this framework file instead of the shipped one
|
|
@@ -158,6 +457,13 @@ async function runCLI(argv, io) {
|
|
|
158
457
|
io.stdout.write(`${prompt}\n`);
|
|
159
458
|
return 0;
|
|
160
459
|
}
|
|
460
|
+
if (command === "mod-path") try {
|
|
461
|
+
io.stdout.write(`${resolveShippedModDir()}\n`);
|
|
462
|
+
return 0;
|
|
463
|
+
} catch (error) {
|
|
464
|
+
io.stderr.write(`auto-mode: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
465
|
+
return 1;
|
|
466
|
+
}
|
|
161
467
|
io.stderr.write(`auto-mode: unknown command '${command}'\n\n${USAGE}`);
|
|
162
468
|
return 2;
|
|
163
469
|
}
|
|
@@ -268,8 +574,7 @@ async function runRecord(io) {
|
|
|
268
574
|
const request = parseScopeRecordRequest(body);
|
|
269
575
|
if (request === null) return 0;
|
|
270
576
|
try {
|
|
271
|
-
|
|
272
|
-
await recordScope(request, config, { host: io.host });
|
|
577
|
+
await recordScope(request, await loadConfig(void 0, io.host), { host: io.host });
|
|
273
578
|
} catch {
|
|
274
579
|
io.stderr.write("auto-mode: session scope unavailable\n");
|
|
275
580
|
}
|