auto-mode 0.6.0 → 0.7.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 +12 -10
- package/dist/cli.js +50 -2
- package/dist/index.d.ts +13 -2
- package/dist/index.js +1 -1
- package/dist/{render-verdict-9pnm4JBS.js → render-verdict-CAg3AKWH.js} +306 -82
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,14 +31,14 @@ Create the classifier configuration:
|
|
|
31
31
|
|
|
32
32
|
```json
|
|
33
33
|
{
|
|
34
|
-
"
|
|
35
|
-
"
|
|
34
|
+
"classifiers": { "jev": { "apiKeyEnv": "TYPESAFE_API_KEY" } },
|
|
35
|
+
"decision": { "classifier": "jev" }
|
|
36
36
|
}
|
|
37
37
|
```
|
|
38
38
|
|
|
39
|
-
Save it as `~/.config/auto-mode/config.json` and export your TypeSafe API key, or set
|
|
40
|
-
`
|
|
41
|
-
|
|
39
|
+
Save it as `~/.config/auto-mode/config.json` and export your TypeSafe API key, or set the entry's
|
|
40
|
+
`apiKeyCommand` to a command that prints it. [Configuration](./docs/guides/configuration.md) covers
|
|
41
|
+
the registries, credentials, and migrating an older file.
|
|
42
42
|
|
|
43
43
|
## Standing permissions
|
|
44
44
|
|
|
@@ -63,9 +63,10 @@ approval when every rule confidently allows the action, and a denial for uncerta
|
|
|
63
63
|
denial names its rule, the harm, and what clears it, and the agent continues on another path. The
|
|
64
64
|
confidence threshold is configurable and needs evaluation against your actions.
|
|
65
65
|
|
|
66
|
-
A missing key, failed API call, malformed response, or oversized request follows
|
|
67
|
-
default `defer` writes no verdict and keeps the prompt; `deny` fails
|
|
68
|
-
diagnostic to stderr. auto-mode never truncates a Jev action to make it
|
|
66
|
+
A missing key, failed API call, malformed response, or oversized request follows
|
|
67
|
+
`decision.onFailure`. The default `defer` writes no verdict and keeps the prompt; `deny` fails
|
|
68
|
+
closed. Failures write a diagnostic to stderr. auto-mode never truncates a Jev action to make it
|
|
69
|
+
fit.
|
|
69
70
|
|
|
70
71
|
## Commands
|
|
71
72
|
|
|
@@ -76,10 +77,11 @@ diagnostic to stderr. auto-mode never truncates a Jev action to make it fit.
|
|
|
76
77
|
| `auto-mode run --local-only` | Skip the model tier |
|
|
77
78
|
| `auto-mode run --jev-only` | Require Jev with a 5-second API timeout |
|
|
78
79
|
| `auto-mode print-prompt` | Print the selected provider's base policy |
|
|
80
|
+
| `auto-mode config migrate` | Rewrite an older config file in place |
|
|
79
81
|
|
|
80
82
|
The mod evaluates only ask decisions and preserves existing allow and deny decisions. It runs the
|
|
81
|
-
CLI as a bounded child process with `--jev-only`. The `spark`, `claude`, and `
|
|
82
|
-
Messages API and serve library callers.
|
|
83
|
+
CLI as a bounded child process with `--jev-only`. The `spark`, `claude`, `glm`, and `messages`
|
|
84
|
+
classifier kinds use the Messages API and serve library callers.
|
|
83
85
|
|
|
84
86
|
## Library
|
|
85
87
|
|
package/dist/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { n as parseActionRequest, r as classifyAction, s as loadPolicy, t as renderVerdict
|
|
3
|
-
import { appendFile, mkdir } from "node:fs/promises";
|
|
2
|
+
import { d as resolveConfigPath, f as normalizeConfigFile, l as loadConfig, n as parseActionRequest, r as classifyAction, s as loadPolicy, t as renderVerdict } from "./render-verdict-CAg3AKWH.js";
|
|
3
|
+
import { appendFile, constants, copyFile, mkdir, readFile, rename, writeFile } from "node:fs/promises";
|
|
4
4
|
import { homedir } from "node:os";
|
|
5
5
|
import { dirname, join } from "node:path";
|
|
6
6
|
import { createHash, randomUUID } from "node:crypto";
|
|
@@ -18,6 +18,36 @@ function buildJevOnlyConfig(config) {
|
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
20
|
//#endregion
|
|
21
|
+
//#region src/config/write-migrated-config.ts
|
|
22
|
+
async function writeMigratedConfig(path) {
|
|
23
|
+
let raw;
|
|
24
|
+
try {
|
|
25
|
+
raw = await readFile(path, "utf8");
|
|
26
|
+
} catch (error) {
|
|
27
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") return `${path} does not exist; nothing to migrate`;
|
|
28
|
+
throw new Error(`${path} is unreadable`, { cause: error });
|
|
29
|
+
}
|
|
30
|
+
let json;
|
|
31
|
+
try {
|
|
32
|
+
json = JSON.parse(raw);
|
|
33
|
+
} catch {
|
|
34
|
+
throw new Error(`${path} is not valid JSON`);
|
|
35
|
+
}
|
|
36
|
+
const normalized = normalizeConfigFile(json, path);
|
|
37
|
+
if (!normalized.isLegacy) return `${path} already uses the current shape`;
|
|
38
|
+
const backup = `${path}.bak`;
|
|
39
|
+
const staged = `${path}.migrating`;
|
|
40
|
+
try {
|
|
41
|
+
await copyFile(path, backup, constants.COPYFILE_EXCL);
|
|
42
|
+
} catch (error) {
|
|
43
|
+
if (error instanceof Error && "code" in error && error.code === "EEXIST") throw new Error(`${backup} already exists; move it aside and run the migration again`, { cause: error });
|
|
44
|
+
throw error;
|
|
45
|
+
}
|
|
46
|
+
await writeFile(staged, `${JSON.stringify(normalized.file, null, 2)}\n`, { mode: 384 });
|
|
47
|
+
await rename(staged, path);
|
|
48
|
+
return `rewrote ${path} in the current shape; the old file is at ${backup}`;
|
|
49
|
+
}
|
|
50
|
+
//#endregion
|
|
21
51
|
//#region src/diagnostics/write-action-diagnostic.ts
|
|
22
52
|
async function writeActionDiagnostic(payload, entry) {
|
|
23
53
|
const stateHome = process.env["XDG_STATE_HOME"];
|
|
@@ -54,6 +84,7 @@ const USAGE = `auto-mode — a permission classifier for the auto-mode Claude Co
|
|
|
54
84
|
Usage:
|
|
55
85
|
auto-mode run Read an action request on stdin, write a verdict on stdout
|
|
56
86
|
auto-mode print-prompt Print the system prompt the classifier receives
|
|
87
|
+
auto-mode config migrate Rewrite the config file from the old keys into the current shape
|
|
57
88
|
|
|
58
89
|
Options:
|
|
59
90
|
--classifier <path> Use this framework file instead of the shipped one
|
|
@@ -92,6 +123,7 @@ async function run(explain, localOnly, jevOnly, deadline) {
|
|
|
92
123
|
});
|
|
93
124
|
return printNote(true, "configuration unreadable; no verdict");
|
|
94
125
|
}
|
|
126
|
+
printWarnings(loaded.warnings);
|
|
95
127
|
const config = jevOnly ? buildJevOnlyConfig(loaded) : loaded;
|
|
96
128
|
if (config === null) {
|
|
97
129
|
await writeActionDiagnostic(request, {
|
|
@@ -140,6 +172,9 @@ async function run(explain, localOnly, jevOnly, deadline) {
|
|
|
140
172
|
function writeVerdict(verdict) {
|
|
141
173
|
process.stdout.write(renderVerdict(verdict));
|
|
142
174
|
}
|
|
175
|
+
function printWarnings(warnings = []) {
|
|
176
|
+
for (const warning of warnings) process.stderr.write(`auto-mode: ${warning}\n`);
|
|
177
|
+
}
|
|
143
178
|
function printNote(explain, message) {
|
|
144
179
|
if (explain) process.stderr.write(`auto-mode: ${message}\n`);
|
|
145
180
|
return 0;
|
|
@@ -174,6 +209,7 @@ async function main(argv) {
|
|
|
174
209
|
}
|
|
175
210
|
if (command === "print-prompt") {
|
|
176
211
|
const config = await loadConfig();
|
|
212
|
+
printWarnings(config.warnings);
|
|
177
213
|
const framework = config.provider.protocol === "system-one" ? "decision.md" : "classifier.md";
|
|
178
214
|
const prompt = await loadPolicy({
|
|
179
215
|
classifierPath: args.values.classifier ?? config.classifierPath,
|
|
@@ -182,9 +218,21 @@ async function main(argv) {
|
|
|
182
218
|
process.stdout.write(`${prompt}\n`);
|
|
183
219
|
return 0;
|
|
184
220
|
}
|
|
221
|
+
if (command === "config" && args.positionals[1] === "migrate") return runConfigMigration();
|
|
185
222
|
process.stderr.write(`auto-mode: unknown command '${command}'\n\n${USAGE}`);
|
|
186
223
|
return 2;
|
|
187
224
|
}
|
|
225
|
+
async function runConfigMigration() {
|
|
226
|
+
const path = resolveConfigPath();
|
|
227
|
+
try {
|
|
228
|
+
const result = await writeMigratedConfig(path);
|
|
229
|
+
process.stdout.write(`${result}\n`);
|
|
230
|
+
return 0;
|
|
231
|
+
} catch (error) {
|
|
232
|
+
process.stderr.write(`auto-mode: ${error instanceof Error ? error.message : "config migration failed"}\n`);
|
|
233
|
+
return 1;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
188
236
|
process.exitCode = await main(process.argv.slice(2));
|
|
189
237
|
//#endregion
|
|
190
238
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -4,6 +4,9 @@ interface EvaluationOptions {
|
|
|
4
4
|
readonly signal?: Readonly<AbortSignal> | undefined;
|
|
5
5
|
}
|
|
6
6
|
//#endregion
|
|
7
|
+
//#region src/config/presets.d.ts
|
|
8
|
+
export declare const PRESETS: Readonly<Record<string, ProviderConfig>>;
|
|
9
|
+
//#endregion
|
|
7
10
|
//#region src/config/config.d.ts
|
|
8
11
|
interface ProviderConfig {
|
|
9
12
|
readonly protocol?: 'messages' | 'system-one';
|
|
@@ -15,16 +18,24 @@ interface ProviderConfig {
|
|
|
15
18
|
readonly maxTokens: number;
|
|
16
19
|
readonly timeoutMs: number;
|
|
17
20
|
}
|
|
21
|
+
interface ScopeSource {
|
|
22
|
+
readonly kind: ScopeSourceKind;
|
|
23
|
+
readonly paths?: readonly string[];
|
|
24
|
+
}
|
|
25
|
+
type ScopeSourceKind = (typeof SCOPE_SOURCE_KINDS)[number];
|
|
26
|
+
declare const SCOPE_SOURCE_KINDS: readonly ['cwd', 'session', 'globs', 'atc'];
|
|
18
27
|
interface Config {
|
|
19
28
|
readonly provider: ProviderConfig;
|
|
29
|
+
readonly judge?: ProviderConfig | null;
|
|
30
|
+
readonly scopeSources?: Readonly<Record<string, ScopeSource>>;
|
|
20
31
|
readonly classifierPath?: string | undefined;
|
|
21
32
|
readonly rulesPath?: string | undefined;
|
|
22
33
|
readonly onFailure: 'defer' | 'deny';
|
|
23
34
|
readonly claudeSettingsPath?: string | null | undefined;
|
|
24
35
|
readonly minConfidence?: number | undefined;
|
|
36
|
+
readonly warnings?: readonly string[];
|
|
25
37
|
}
|
|
26
38
|
export declare const DEFAULT_CONFIG: Config;
|
|
27
|
-
export declare const PRESETS: Readonly<Record<string, ProviderConfig>>;
|
|
28
39
|
export declare function resolveConfigPath(): string;
|
|
29
40
|
export declare function loadConfig(path?: string): Promise<Config>;
|
|
30
41
|
export declare function resolveApiKey(provider: ProviderConfig, options?: EvaluationOptions): Promise<string | null>;
|
|
@@ -143,4 +154,4 @@ type LocalVerdict = {
|
|
|
143
154
|
};
|
|
144
155
|
export declare function classifyLocally(payload: ActionRequest): LocalVerdict;
|
|
145
156
|
//#endregion
|
|
146
|
-
export type { ActionOutcome, ActionRequest, ClassifyOptions, Config, DecisionContext, LocalVerdict, ModelOutcome, PolicyPaths, ProviderConfig, Verdict };
|
|
157
|
+
export type { ActionOutcome, ActionRequest, ClassifyOptions, Config, DecisionContext, LocalVerdict, ModelOutcome, PolicyPaths, ProviderConfig, ScopeSource, Verdict };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as classifyWithModel, c as DEFAULT_CONFIG, d as
|
|
1
|
+
import { a as classifyWithModel, c as DEFAULT_CONFIG, d as resolveConfigPath, i as classifyLocally, l as loadConfig, n as parseActionRequest, o as parseModelVerdict, p as PRESETS, r as classifyAction, s as loadPolicy, t as renderVerdict, u as resolveApiKey } from "./render-verdict-CAg3AKWH.js";
|
|
2
2
|
export { DEFAULT_CONFIG, PRESETS, classifyAction, classifyLocally, classifyWithModel, loadConfig, loadPolicy, parseActionRequest, parseModelVerdict, renderVerdict, resolveApiKey, resolveConfigPath };
|
|
@@ -252,7 +252,7 @@ function safeExtend(schema, shape) {
|
|
|
252
252
|
if (!isPlainObject(shape)) throw new Error("Invalid input to safeExtend: expected a plain object");
|
|
253
253
|
return clone(schema, mergeDefs(schema._zod.def, { shape: extended(schema, shape) }));
|
|
254
254
|
}
|
|
255
|
-
function merge
|
|
255
|
+
function merge(a, b) {
|
|
256
256
|
if (!b?._zod?.def) throw new Error("Invalid input to merge: expected an object schema. To merge a plain shape, use `.extend()`.");
|
|
257
257
|
if (a._zod.def.checks?.length) throw new Error(".merge() cannot be used on object schemas containing refinements. Use .safeExtend() instead.");
|
|
258
258
|
const newShape = {};
|
|
@@ -5218,7 +5218,7 @@ const ZodObject = /*@__PURE__*/ $constructor("ZodObject", (inst, def) => {
|
|
|
5218
5218
|
return safeExtend(this, incoming);
|
|
5219
5219
|
},
|
|
5220
5220
|
merge(other) {
|
|
5221
|
-
return merge
|
|
5221
|
+
return merge(this, other);
|
|
5222
5222
|
},
|
|
5223
5223
|
pick(mask) {
|
|
5224
5224
|
return pick(this, mask);
|
|
@@ -5569,6 +5569,171 @@ async function loadClaudeRules(path) {
|
|
|
5569
5569
|
hard_deny: (rules.autoMode?.hard_deny ?? []).filter((entry) => entry !== "$defaults")
|
|
5570
5570
|
};
|
|
5571
5571
|
}
|
|
5572
|
+
const PRESETS = {
|
|
5573
|
+
jev: {
|
|
5574
|
+
protocol: "system-one",
|
|
5575
|
+
baseURL: "https://api.typesafe.ai",
|
|
5576
|
+
model: "jev-1.13.0",
|
|
5577
|
+
apiKeyEnv: "TYPESAFE_API_KEY",
|
|
5578
|
+
reasoning: false,
|
|
5579
|
+
maxTokens: 3e3,
|
|
5580
|
+
timeoutMs: 5e3
|
|
5581
|
+
},
|
|
5582
|
+
spark: {
|
|
5583
|
+
baseURL: "https://api.meta.ai",
|
|
5584
|
+
model: "muse-spark-1.3-contributor",
|
|
5585
|
+
apiKeyEnv: "META_API_KEY",
|
|
5586
|
+
reasoning: true,
|
|
5587
|
+
maxTokens: 3e3,
|
|
5588
|
+
timeoutMs: 45e3
|
|
5589
|
+
},
|
|
5590
|
+
claude: {
|
|
5591
|
+
baseURL: "https://api.anthropic.com",
|
|
5592
|
+
model: "claude-haiku-4-5-20251001",
|
|
5593
|
+
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
5594
|
+
reasoning: true,
|
|
5595
|
+
maxTokens: 3e3,
|
|
5596
|
+
timeoutMs: 45e3
|
|
5597
|
+
},
|
|
5598
|
+
glm: {
|
|
5599
|
+
baseURL: "https://api.z.ai/api/anthropic",
|
|
5600
|
+
model: "glm-5.3-flash",
|
|
5601
|
+
apiKeyEnv: "ZAI_API_KEY",
|
|
5602
|
+
reasoning: true,
|
|
5603
|
+
maxTokens: 3e3,
|
|
5604
|
+
timeoutMs: 6e4
|
|
5605
|
+
}
|
|
5606
|
+
};
|
|
5607
|
+
const MESSAGES_DEFAULTS = {
|
|
5608
|
+
protocol: "messages",
|
|
5609
|
+
baseURL: "https://api.anthropic.com",
|
|
5610
|
+
reasoning: true,
|
|
5611
|
+
maxTokens: 3e3,
|
|
5612
|
+
timeoutMs: 45e3
|
|
5613
|
+
};
|
|
5614
|
+
function findPreset(id) {
|
|
5615
|
+
return Object.hasOwn(PRESETS, id) ? PRESETS[id] : void 0;
|
|
5616
|
+
}
|
|
5617
|
+
//#endregion
|
|
5618
|
+
//#region src/config/normalize-config-file.ts
|
|
5619
|
+
function normalizeConfigFile(json, path) {
|
|
5620
|
+
if (!isRecord(json)) return {
|
|
5621
|
+
file: json,
|
|
5622
|
+
isLegacy: false,
|
|
5623
|
+
warnings: []
|
|
5624
|
+
};
|
|
5625
|
+
const legacyKeys = Object.keys(json).filter((key) => LEGACY_KEYS.has(key));
|
|
5626
|
+
if (legacyKeys.length === 0) return {
|
|
5627
|
+
file: json,
|
|
5628
|
+
isLegacy: false,
|
|
5629
|
+
warnings: []
|
|
5630
|
+
};
|
|
5631
|
+
const currentKeys = Object.keys(json).filter((key) => !LEGACY_KEYS.has(key));
|
|
5632
|
+
if (currentKeys.length > 0) throw new Error(`${path} mixes the old keys (${legacyKeys.join(", ")}) with the new ones (${currentKeys.join(", ")}); run \`auto-mode config migrate\` on a copy without the new keys`);
|
|
5633
|
+
const parsed = legacyConfigSchema.safeParse(json);
|
|
5634
|
+
if (!parsed.success) throw new Error(`${path} is not a valid config: ${prettifyError(parsed.error)}`);
|
|
5635
|
+
const legacy = parsed.data;
|
|
5636
|
+
const warnings = [`${path} uses the old config keys (${legacyKeys.join(", ")}); run \`auto-mode config migrate\` to rewrite it`, ...legacy.transcriptEntries === void 0 ? [] : [`${path} sets transcriptEntries, which has no effect; migration drops it`]];
|
|
5637
|
+
return {
|
|
5638
|
+
file: buildFileFromLegacy(legacy, path),
|
|
5639
|
+
isLegacy: true,
|
|
5640
|
+
warnings
|
|
5641
|
+
};
|
|
5642
|
+
}
|
|
5643
|
+
function isRecord(value) {
|
|
5644
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
5645
|
+
}
|
|
5646
|
+
const LEGACY_KEYS = /* @__PURE__ */ new Set([
|
|
5647
|
+
"preset",
|
|
5648
|
+
"provider",
|
|
5649
|
+
"classifierPath",
|
|
5650
|
+
"rulesPath",
|
|
5651
|
+
"transcriptEntries",
|
|
5652
|
+
"onFailure",
|
|
5653
|
+
"claudeSettingsPath",
|
|
5654
|
+
"minConfidence"
|
|
5655
|
+
]);
|
|
5656
|
+
const text$1 = string().min(1);
|
|
5657
|
+
const positive$1 = number().positive();
|
|
5658
|
+
const legacyConfigSchema = strictObject({
|
|
5659
|
+
preset: text$1.optional(),
|
|
5660
|
+
provider: strictObject({
|
|
5661
|
+
protocol: _enum(["messages", "system-one"]).optional(),
|
|
5662
|
+
baseURL: text$1.optional(),
|
|
5663
|
+
model: text$1.optional(),
|
|
5664
|
+
apiKeyEnv: text$1.optional(),
|
|
5665
|
+
apiKeyCommand: text$1.optional(),
|
|
5666
|
+
reasoning: boolean().optional(),
|
|
5667
|
+
maxTokens: positive$1.optional(),
|
|
5668
|
+
timeoutMs: positive$1.optional()
|
|
5669
|
+
}).optional(),
|
|
5670
|
+
classifierPath: text$1.optional(),
|
|
5671
|
+
rulesPath: text$1.optional(),
|
|
5672
|
+
transcriptEntries: number().nonnegative().optional(),
|
|
5673
|
+
onFailure: _enum(["defer", "deny"]).optional(),
|
|
5674
|
+
claudeSettingsPath: text$1.nullable().optional(),
|
|
5675
|
+
minConfidence: number().min(.5).max(1).optional()
|
|
5676
|
+
});
|
|
5677
|
+
function buildFileFromLegacy(legacy, path) {
|
|
5678
|
+
const hasClassifier = legacy.preset !== void 0 || legacy.provider !== void 0;
|
|
5679
|
+
const decision = {
|
|
5680
|
+
...hasClassifier ? { classifier: getLegacyClassifierID(legacy) } : {},
|
|
5681
|
+
...legacy.minConfidence === void 0 ? {} : { minConfidence: legacy.minConfidence },
|
|
5682
|
+
...legacy.onFailure === void 0 ? {} : { onFailure: legacy.onFailure }
|
|
5683
|
+
};
|
|
5684
|
+
const policy = {
|
|
5685
|
+
...legacy.rulesPath === void 0 ? {} : { rulesPath: legacy.rulesPath },
|
|
5686
|
+
...legacy.classifierPath === void 0 ? {} : { frameworkPath: legacy.classifierPath },
|
|
5687
|
+
...legacy.claudeSettingsPath === void 0 ? {} : { claudeSettingsPath: legacy.claudeSettingsPath }
|
|
5688
|
+
};
|
|
5689
|
+
return {
|
|
5690
|
+
...hasClassifier ? { classifiers: { [getLegacyClassifierID(legacy)]: buildClassifierEntry(getLegacyClassifierID(legacy), buildLegacyProvider(legacy, path)) } } : {},
|
|
5691
|
+
...Object.keys(decision).length === 0 ? {} : { decision },
|
|
5692
|
+
...Object.keys(policy).length === 0 ? {} : { policy }
|
|
5693
|
+
};
|
|
5694
|
+
}
|
|
5695
|
+
function getLegacyClassifierID(legacy) {
|
|
5696
|
+
if (legacy.preset !== void 0) return legacy.preset;
|
|
5697
|
+
return legacy.provider !== void 0 && legacy.provider.protocol !== "system-one" ? "spark" : "jev";
|
|
5698
|
+
}
|
|
5699
|
+
function buildLegacyProvider(legacy, path) {
|
|
5700
|
+
const preset = findPreset(getLegacyClassifierID(legacy));
|
|
5701
|
+
if (preset === void 0) throw new Error(`${path} names an unknown preset '${legacy.preset}'; known presets are ${Object.keys(PRESETS).join(", ")}`);
|
|
5702
|
+
const override = legacy.provider ?? {};
|
|
5703
|
+
return {
|
|
5704
|
+
protocol: override.protocol ?? preset.protocol ?? "messages",
|
|
5705
|
+
baseURL: override.baseURL ?? preset.baseURL,
|
|
5706
|
+
model: override.model ?? preset.model,
|
|
5707
|
+
apiKeyEnv: override.apiKeyEnv ?? preset.apiKeyEnv,
|
|
5708
|
+
apiKeyCommand: override.apiKeyCommand ?? preset.apiKeyCommand,
|
|
5709
|
+
reasoning: override.reasoning ?? preset.reasoning,
|
|
5710
|
+
maxTokens: override.maxTokens ?? preset.maxTokens,
|
|
5711
|
+
timeoutMs: override.timeoutMs ?? preset.timeoutMs
|
|
5712
|
+
};
|
|
5713
|
+
}
|
|
5714
|
+
const PROVIDER_FIELDS = [
|
|
5715
|
+
"baseURL",
|
|
5716
|
+
"model",
|
|
5717
|
+
"apiKeyEnv",
|
|
5718
|
+
"apiKeyCommand",
|
|
5719
|
+
"reasoning",
|
|
5720
|
+
"maxTokens",
|
|
5721
|
+
"timeoutMs"
|
|
5722
|
+
];
|
|
5723
|
+
function buildClassifierEntry(id, provider) {
|
|
5724
|
+
const kind = pickEntryKind(id, provider);
|
|
5725
|
+
const base = kind === "messages" ? MESSAGES_DEFAULTS : findPreset(kind) ?? {};
|
|
5726
|
+
const fields = Object.fromEntries(PROVIDER_FIELDS.filter((field) => provider[field] !== void 0 && provider[field] !== base[field]).map((field) => [field, provider[field]]));
|
|
5727
|
+
return {
|
|
5728
|
+
...kind === id ? {} : { kind },
|
|
5729
|
+
...fields
|
|
5730
|
+
};
|
|
5731
|
+
}
|
|
5732
|
+
function pickEntryKind(id, provider) {
|
|
5733
|
+
const preset = findPreset(id);
|
|
5734
|
+
if (preset !== void 0 && (preset.protocol ?? "messages") === provider.protocol) return id;
|
|
5735
|
+
return provider.protocol === "system-one" ? "jev" : "messages";
|
|
5736
|
+
}
|
|
5572
5737
|
//#endregion
|
|
5573
5738
|
//#region src/config/read-api-key-from-command.ts
|
|
5574
5739
|
function readApiKeyFromCommand(command, options = {}) {
|
|
@@ -5624,73 +5789,24 @@ function readApiKeyFromCommand(command, options = {}) {
|
|
|
5624
5789
|
}
|
|
5625
5790
|
//#endregion
|
|
5626
5791
|
//#region src/config/config.ts
|
|
5792
|
+
const SCOPE_SOURCE_KINDS = [
|
|
5793
|
+
"cwd",
|
|
5794
|
+
"session",
|
|
5795
|
+
"globs",
|
|
5796
|
+
"atc"
|
|
5797
|
+
];
|
|
5798
|
+
const DEFAULT_PROVIDER = findPreset("jev");
|
|
5799
|
+
if (DEFAULT_PROVIDER === void 0) throw new Error("the jev preset is missing");
|
|
5627
5800
|
const DEFAULT_CONFIG = {
|
|
5628
|
-
provider:
|
|
5629
|
-
protocol: "system-one",
|
|
5630
|
-
baseURL: "https://api.typesafe.ai",
|
|
5631
|
-
model: "jev-1.13.0",
|
|
5632
|
-
apiKeyEnv: "TYPESAFE_API_KEY",
|
|
5633
|
-
reasoning: false,
|
|
5634
|
-
maxTokens: 3e3,
|
|
5635
|
-
timeoutMs: 5e3
|
|
5636
|
-
},
|
|
5801
|
+
provider: DEFAULT_PROVIDER,
|
|
5637
5802
|
onFailure: "defer",
|
|
5638
5803
|
minConfidence: .8
|
|
5639
5804
|
};
|
|
5640
|
-
const SPARK_PROVIDER = {
|
|
5641
|
-
baseURL: "https://api.meta.ai",
|
|
5642
|
-
model: "muse-spark-1.3-contributor",
|
|
5643
|
-
apiKeyEnv: "META_API_KEY",
|
|
5644
|
-
reasoning: true,
|
|
5645
|
-
maxTokens: 3e3,
|
|
5646
|
-
timeoutMs: 45e3
|
|
5647
|
-
};
|
|
5648
|
-
const PRESETS = {
|
|
5649
|
-
jev: DEFAULT_CONFIG.provider,
|
|
5650
|
-
spark: SPARK_PROVIDER,
|
|
5651
|
-
claude: {
|
|
5652
|
-
baseURL: "https://api.anthropic.com",
|
|
5653
|
-
model: "claude-haiku-4-5-20251001",
|
|
5654
|
-
apiKeyEnv: "ANTHROPIC_API_KEY",
|
|
5655
|
-
reasoning: true,
|
|
5656
|
-
maxTokens: 3e3,
|
|
5657
|
-
timeoutMs: 45e3
|
|
5658
|
-
},
|
|
5659
|
-
glm: {
|
|
5660
|
-
baseURL: "https://api.z.ai/api/anthropic",
|
|
5661
|
-
model: "glm-5.3-flash",
|
|
5662
|
-
apiKeyEnv: "ZAI_API_KEY",
|
|
5663
|
-
reasoning: true,
|
|
5664
|
-
maxTokens: 3e3,
|
|
5665
|
-
timeoutMs: 6e4
|
|
5666
|
-
}
|
|
5667
|
-
};
|
|
5668
5805
|
function resolveConfigPath() {
|
|
5669
5806
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
5670
5807
|
const base = xdg !== void 0 && xdg !== "" ? xdg : join(homedir(), ".config");
|
|
5671
5808
|
return join(base, "auto-mode", "config.json");
|
|
5672
5809
|
}
|
|
5673
|
-
const text = string().min(1);
|
|
5674
|
-
const positive = number().positive();
|
|
5675
|
-
const configFileSchema = strictObject({
|
|
5676
|
-
preset: text.optional(),
|
|
5677
|
-
provider: strictObject({
|
|
5678
|
-
protocol: _enum(["messages", "system-one"]).optional(),
|
|
5679
|
-
baseURL: text.optional(),
|
|
5680
|
-
model: text.optional(),
|
|
5681
|
-
apiKeyEnv: text.optional(),
|
|
5682
|
-
apiKeyCommand: text.optional(),
|
|
5683
|
-
reasoning: boolean().optional(),
|
|
5684
|
-
maxTokens: positive.optional(),
|
|
5685
|
-
timeoutMs: positive.optional()
|
|
5686
|
-
}).optional(),
|
|
5687
|
-
classifierPath: text.optional(),
|
|
5688
|
-
rulesPath: text.optional(),
|
|
5689
|
-
transcriptEntries: number().nonnegative().optional(),
|
|
5690
|
-
onFailure: _enum(["defer", "deny"]).optional(),
|
|
5691
|
-
claudeSettingsPath: text.nullable().optional(),
|
|
5692
|
-
minConfidence: number().min(.5).max(1).optional()
|
|
5693
|
-
});
|
|
5694
5810
|
async function loadConfig(path = resolveConfigPath()) {
|
|
5695
5811
|
let raw;
|
|
5696
5812
|
try {
|
|
@@ -5705,33 +5821,141 @@ async function loadConfig(path = resolveConfigPath()) {
|
|
|
5705
5821
|
} catch {
|
|
5706
5822
|
throw new Error(`${path} is not valid JSON`);
|
|
5707
5823
|
}
|
|
5824
|
+
const normalized = normalizeConfigFile(json, path);
|
|
5825
|
+
return buildConfig(normalized.file, path, normalized.warnings);
|
|
5826
|
+
}
|
|
5827
|
+
const text = string().min(1);
|
|
5828
|
+
const positive = number().positive();
|
|
5829
|
+
const configFileSchema = strictObject({
|
|
5830
|
+
classifiers: record(text, unknown()).optional(),
|
|
5831
|
+
scopeSources: record(text, unknown()).optional(),
|
|
5832
|
+
decision: strictObject({
|
|
5833
|
+
classifier: text.optional(),
|
|
5834
|
+
judge: text.nullable().optional(),
|
|
5835
|
+
minConfidence: number().min(.5).max(1).optional(),
|
|
5836
|
+
onFailure: _enum(["defer", "deny"]).optional()
|
|
5837
|
+
}).optional(),
|
|
5838
|
+
policy: strictObject({
|
|
5839
|
+
rulesPath: text.nullable().optional(),
|
|
5840
|
+
frameworkPath: text.nullable().optional(),
|
|
5841
|
+
claudeSettingsPath: text.nullable().optional()
|
|
5842
|
+
}).optional()
|
|
5843
|
+
});
|
|
5844
|
+
function buildConfig(json, path, legacyWarnings) {
|
|
5708
5845
|
const parsed = configFileSchema.safeParse(json);
|
|
5709
5846
|
if (!parsed.success) throw new Error(`${path} is not a valid config: ${prettifyError(parsed.error)}`);
|
|
5710
|
-
|
|
5847
|
+
const file = parsed.data;
|
|
5848
|
+
const warnings = [...legacyWarnings];
|
|
5849
|
+
const classifiers = buildRegistry(file.classifiers ?? {}, (id, entry) => parseClassifierEntry(id, entry));
|
|
5850
|
+
const scopeSources = buildRegistry(file.scopeSources ?? {}, (id, entry) => parseScopeSourceEntry(id, entry));
|
|
5851
|
+
warnings.push(...classifiers.dropped.map((line) => `${path}: classifiers.${line}`), ...scopeSources.dropped.map((line) => `${path}: scopeSources.${line}`));
|
|
5852
|
+
const decision = file.decision ?? {};
|
|
5853
|
+
const policy = file.policy ?? {};
|
|
5854
|
+
const judgeID = decision.judge ?? null;
|
|
5855
|
+
return {
|
|
5856
|
+
provider: resolveRole("classifier", decision.classifier ?? "jev", classifiers.entries, classifiers.droppedIDs, path),
|
|
5857
|
+
judge: judgeID === null ? null : resolveRole("judge", judgeID, classifiers.entries, classifiers.droppedIDs, path),
|
|
5858
|
+
scopeSources: scopeSources.entries,
|
|
5859
|
+
classifierPath: expandHomePath(policy.frameworkPath ?? void 0),
|
|
5860
|
+
rulesPath: expandHomePath(policy.rulesPath ?? void 0),
|
|
5861
|
+
onFailure: decision.onFailure ?? DEFAULT_CONFIG.onFailure,
|
|
5862
|
+
claudeSettingsPath: policy.claudeSettingsPath === null ? null : expandHomePath(policy.claudeSettingsPath),
|
|
5863
|
+
minConfidence: decision.minConfidence ?? DEFAULT_CONFIG.minConfidence,
|
|
5864
|
+
warnings
|
|
5865
|
+
};
|
|
5711
5866
|
}
|
|
5712
|
-
function
|
|
5713
|
-
const
|
|
5714
|
-
const
|
|
5715
|
-
|
|
5716
|
-
const
|
|
5867
|
+
function buildRegistry(raw, parseEntry) {
|
|
5868
|
+
const entries = /* @__PURE__ */ new Map();
|
|
5869
|
+
const dropped = [];
|
|
5870
|
+
const droppedIDs = [];
|
|
5871
|
+
for (const [id, entry] of Object.entries(raw)) {
|
|
5872
|
+
const result = parseEntry(id, entry);
|
|
5873
|
+
if (typeof result === "string") {
|
|
5874
|
+
dropped.push(`${id} dropped: ${result}`);
|
|
5875
|
+
droppedIDs.push(id);
|
|
5876
|
+
} else entries.set(id, result);
|
|
5877
|
+
}
|
|
5717
5878
|
return {
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
5730
|
-
|
|
5731
|
-
|
|
5732
|
-
|
|
5879
|
+
entries: Object.fromEntries(entries),
|
|
5880
|
+
dropped,
|
|
5881
|
+
droppedIDs
|
|
5882
|
+
};
|
|
5883
|
+
}
|
|
5884
|
+
const CLASSIFIER_KINDS = [...Object.keys(PRESETS), "messages"];
|
|
5885
|
+
const classifierEntrySchema = strictObject({
|
|
5886
|
+
kind: text.optional(),
|
|
5887
|
+
baseURL: text.optional(),
|
|
5888
|
+
model: text.optional(),
|
|
5889
|
+
apiKeyEnv: text.optional(),
|
|
5890
|
+
apiKeyCommand: text.optional(),
|
|
5891
|
+
reasoning: boolean().optional(),
|
|
5892
|
+
maxTokens: positive.optional(),
|
|
5893
|
+
timeoutMs: positive.optional()
|
|
5894
|
+
});
|
|
5895
|
+
const CREDENTIAL_KEYS = /* @__PURE__ */ new Set([
|
|
5896
|
+
"apiKey",
|
|
5897
|
+
"key",
|
|
5898
|
+
"token",
|
|
5899
|
+
"secret"
|
|
5900
|
+
]);
|
|
5901
|
+
function parseClassifierEntry(id, entry) {
|
|
5902
|
+
if (typeof entry === "object" && entry !== null) {
|
|
5903
|
+
const credential = Object.keys(entry).find((key) => CREDENTIAL_KEYS.has(key));
|
|
5904
|
+
if (credential !== void 0) return `holds a literal ${credential}; name the key with apiKeyEnv or apiKeyCommand`;
|
|
5905
|
+
}
|
|
5906
|
+
const parsed = classifierEntrySchema.safeParse(entry);
|
|
5907
|
+
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
5908
|
+
const kind = parsed.data.kind ?? id;
|
|
5909
|
+
if (!CLASSIFIER_KINDS.includes(kind)) return `unknown kind '${kind}'; known kinds are ${CLASSIFIER_KINDS.join(", ")}`;
|
|
5910
|
+
const base = kind === "messages" ? MESSAGES_DEFAULTS : findPreset(kind);
|
|
5911
|
+
const model = parsed.data.model ?? base?.model;
|
|
5912
|
+
if (base === void 0 || model === void 0) return `kind '${kind}' needs a model`;
|
|
5913
|
+
return {
|
|
5914
|
+
protocol: base.protocol ?? "messages",
|
|
5915
|
+
baseURL: parsed.data.baseURL ?? base.baseURL,
|
|
5916
|
+
model,
|
|
5917
|
+
apiKeyEnv: parsed.data.apiKeyEnv ?? base.apiKeyEnv,
|
|
5918
|
+
apiKeyCommand: parsed.data.apiKeyCommand ?? base.apiKeyCommand,
|
|
5919
|
+
reasoning: parsed.data.reasoning ?? base.reasoning,
|
|
5920
|
+
maxTokens: parsed.data.maxTokens ?? base.maxTokens,
|
|
5921
|
+
timeoutMs: parsed.data.timeoutMs ?? base.timeoutMs
|
|
5733
5922
|
};
|
|
5734
5923
|
}
|
|
5924
|
+
const scopeSourceEntrySchema = strictObject({
|
|
5925
|
+
kind: text.optional(),
|
|
5926
|
+
paths: array(text).min(1).optional()
|
|
5927
|
+
});
|
|
5928
|
+
function parseScopeSourceEntry(id, entry) {
|
|
5929
|
+
const parsed = scopeSourceEntrySchema.safeParse(entry);
|
|
5930
|
+
if (!parsed.success) return formatFirstIssue(parsed.error.issues);
|
|
5931
|
+
const kind = SCOPE_SOURCE_KINDS.find((known) => known === (parsed.data.kind ?? id));
|
|
5932
|
+
if (kind === void 0) return `unknown kind '${parsed.data.kind ?? id}'; known kinds are ${SCOPE_SOURCE_KINDS.join(", ")}`;
|
|
5933
|
+
if (kind === "globs") return parsed.data.paths === void 0 ? "kind 'globs' needs paths" : {
|
|
5934
|
+
kind,
|
|
5935
|
+
paths: parsed.data.paths
|
|
5936
|
+
};
|
|
5937
|
+
return parsed.data.paths === void 0 ? { kind } : `kind '${kind}' takes no paths`;
|
|
5938
|
+
}
|
|
5939
|
+
function formatFirstIssue(issues) {
|
|
5940
|
+
const [issue] = issues;
|
|
5941
|
+
if (issue === void 0) return "invalid entry";
|
|
5942
|
+
return issue.path.length === 0 ? issue.message : `${issue.path.map(String).join(".")}: ${issue.message}`;
|
|
5943
|
+
}
|
|
5944
|
+
function resolveRole(role, id, entries, droppedIDs, path) {
|
|
5945
|
+
const entry = Object.hasOwn(entries, id) ? entries[id] : void 0;
|
|
5946
|
+
if (entry !== void 0) return entry;
|
|
5947
|
+
if (droppedIDs.includes(id)) throw new Error(`${path}: decision.${role} names '${id}', whose entry was dropped`);
|
|
5948
|
+
if (findPreset(id) === void 0) throw new Error(`${path}: decision.${role} names '${id}', which is neither a classifiers entry nor a built-in kind (${Object.keys(PRESETS).join(", ")})`);
|
|
5949
|
+
return parseBuiltIn(id);
|
|
5950
|
+
}
|
|
5951
|
+
function parseBuiltIn(id) {
|
|
5952
|
+
const resolved = parseClassifierEntry(id, {});
|
|
5953
|
+
if (typeof resolved === "string") throw new TypeError(`built-in kind ${id} does not resolve: ${resolved}`);
|
|
5954
|
+
return resolved;
|
|
5955
|
+
}
|
|
5956
|
+
function expandHomePath(path) {
|
|
5957
|
+
return path?.startsWith("~/") === true ? join(homedir(), path.slice(2)) : path;
|
|
5958
|
+
}
|
|
5735
5959
|
function resolveApiKey(provider, options = {}) {
|
|
5736
5960
|
const fromEnv = provider.apiKeyEnv === void 0 ? void 0 : process.env[provider.apiKeyEnv];
|
|
5737
5961
|
if (fromEnv !== void 0 && fromEnv !== "") return Promise.resolve(fromEnv);
|
|
@@ -7076,4 +7300,4 @@ function renderVerdict(verdict) {
|
|
|
7076
7300
|
})).exhaustive();
|
|
7077
7301
|
}
|
|
7078
7302
|
//#endregion
|
|
7079
|
-
export { classifyWithModel as a, DEFAULT_CONFIG as c,
|
|
7303
|
+
export { classifyWithModel as a, DEFAULT_CONFIG as c, resolveConfigPath as d, normalizeConfigFile as f, classifyLocally as i, loadConfig as l, parseActionRequest as n, parseModelVerdict as o, PRESETS as p, classifyAction as r, loadPolicy as s, renderVerdict as t, resolveApiKey as u };
|