auto-mode 0.5.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 CHANGED
@@ -31,14 +31,14 @@ Create the classifier configuration:
31
31
 
32
32
  ```json
33
33
  {
34
- "preset": "jev",
35
- "provider": { "apiKeyEnv": "TYPESAFE_API_KEY" }
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
- `provider.apiKeyCommand` to a command that prints it.
41
- [Configuration](./docs/guides/configuration.md) covers credentials and provider overrides.
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
 
@@ -59,12 +59,14 @@ assessment.
59
59
  ## Decisions and failures
60
60
 
61
61
  Jev evaluates each named rule with a typed choice. auto-mode returns a denial for a confident block,
62
- approval when every rule confidently allows the action, and manual approval for uncertain decisions.
63
- The confidence threshold is configurable and needs evaluation against your actions.
62
+ approval when every rule confidently allows the action, and a denial for uncertain decisions. Every
63
+ denial names its rule, the harm, and what clears it, and the agent continues on another path. The
64
+ confidence threshold is configurable and needs evaluation against your actions.
64
65
 
65
- A missing key, failed API call, malformed response, or oversized request follows `onFailure`. The
66
- default `defer` writes no verdict and keeps the prompt; `deny` fails closed. Failures write a
67
- diagnostic to stderr. auto-mode never truncates a Jev action to make it fit.
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.
68
70
 
69
71
  ## Commands
70
72
 
@@ -75,10 +77,11 @@ diagnostic to stderr. auto-mode never truncates a Jev action to make it fit.
75
77
  | `auto-mode run --local-only` | Skip the model tier |
76
78
  | `auto-mode run --jev-only` | Require Jev with a 5-second API timeout |
77
79
  | `auto-mode print-prompt` | Print the selected provider's base policy |
80
+ | `auto-mode config migrate` | Rewrite an older config file in place |
78
81
 
79
82
  The mod evaluates only ask decisions and preserves existing allow and deny decisions. It runs the
80
- CLI as a bounded child process with `--jev-only`. The `spark`, `claude`, and `glm` presets use the
81
- 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.
82
85
 
83
86
  ## Library
84
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, u as loadConfig } from "./render-verdict-FdG6gahX.js";
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, {
@@ -138,8 +170,10 @@ async function run(explain, localOnly, jevOnly, deadline) {
138
170
  }
139
171
  }
140
172
  function writeVerdict(verdict) {
141
- const rendered = renderVerdict(verdict);
142
- if (rendered !== null) process.stdout.write(rendered);
173
+ process.stdout.write(renderVerdict(verdict));
174
+ }
175
+ function printWarnings(warnings = []) {
176
+ for (const warning of warnings) process.stderr.write(`auto-mode: ${warning}\n`);
143
177
  }
144
178
  function printNote(explain, message) {
145
179
  if (explain) process.stderr.write(`auto-mode: ${message}\n`);
@@ -175,6 +209,7 @@ async function main(argv) {
175
209
  }
176
210
  if (command === "print-prompt") {
177
211
  const config = await loadConfig();
212
+ printWarnings(config.warnings);
178
213
  const framework = config.provider.protocol === "system-one" ? "decision.md" : "classifier.md";
179
214
  const prompt = await loadPolicy({
180
215
  classifierPath: args.values.classifier ?? config.classifierPath,
@@ -183,9 +218,21 @@ async function main(argv) {
183
218
  process.stdout.write(`${prompt}\n`);
184
219
  return 0;
185
220
  }
221
+ if (command === "config" && args.positionals[1] === "migrate") return runConfigMigration();
186
222
  process.stderr.write(`auto-mode: unknown command '${command}'\n\n${USAGE}`);
187
223
  return 2;
188
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
+ }
189
236
  process.exitCode = await main(process.argv.slice(2));
190
237
  //#endregion
191
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>;
@@ -57,8 +68,6 @@ interface ActionRequest {
57
68
  }
58
69
  type Verdict = {
59
70
  readonly kind: 'allow';
60
- } | {
61
- readonly kind: 'ask';
62
71
  } | {
63
72
  readonly kind: 'deny';
64
73
  readonly rule: string;
@@ -80,7 +89,7 @@ interface DecisionAnswer {
80
89
  }
81
90
  type DecisionFailureReason = 'request-too-large' | 'aborted' | 'network' | 'http-status' | 'invalid-response';
82
91
  interface DecisionDiagnostics {
83
- readonly status: 'allow' | 'deny' | 'ask' | 'failure' | 'timeout' | 'cancelled';
92
+ readonly status: 'allow' | 'deny' | 'failure' | 'timeout' | 'cancelled';
84
93
  readonly stage: 'credential' | 'evidence' | 'request' | 'response';
85
94
  readonly keyResolved: boolean;
86
95
  readonly keySource: 'environment' | 'command' | 'none';
@@ -134,7 +143,7 @@ export declare function loadPolicy(paths?: PolicyPaths, framework?: 'classifier.
134
143
  export declare function parseActionRequest(body: unknown): ActionRequest | null;
135
144
  //#endregion
136
145
  //#region src/request/render-verdict.d.ts
137
- export declare function renderVerdict(verdict: Verdict): string | null;
146
+ export declare function renderVerdict(verdict: Verdict): string;
138
147
  //#endregion
139
148
  //#region src/rules/classify-locally.d.ts
140
149
  type LocalVerdict = {
@@ -145,4 +154,4 @@ type LocalVerdict = {
145
154
  };
146
155
  export declare function classifyLocally(payload: ActionRequest): LocalVerdict;
147
156
  //#endregion
148
- 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 resolveApiKey, f as resolveConfigPath, i as classifyLocally, l as PRESETS, n as parseActionRequest, o as parseModelVerdict, r as classifyAction, s as loadPolicy, t as renderVerdict, u as loadConfig } from "./render-verdict-FdG6gahX.js";
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 };