aptly 0.1.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.
Files changed (54) hide show
  1. package/LICENSE +21 -0
  2. package/README.en.md +45 -0
  3. package/README.ja.md +45 -0
  4. package/README.md +45 -0
  5. package/README.zh-CN.md +45 -0
  6. package/dist/bin/aptly.d.ts +2 -0
  7. package/dist/bin/aptly.js +122 -0
  8. package/dist/src/automatic-hook.d.ts +42 -0
  9. package/dist/src/automatic-hook.js +232 -0
  10. package/dist/src/catalog.d.ts +19 -0
  11. package/dist/src/catalog.js +135 -0
  12. package/dist/src/config.d.ts +24 -0
  13. package/dist/src/config.js +75 -0
  14. package/dist/src/core.d.ts +50 -0
  15. package/dist/src/core.js +324 -0
  16. package/dist/src/diagnostics.d.ts +11 -0
  17. package/dist/src/diagnostics.js +68 -0
  18. package/dist/src/files.d.ts +10 -0
  19. package/dist/src/files.js +70 -0
  20. package/dist/src/hook.d.ts +20 -0
  21. package/dist/src/hook.js +102 -0
  22. package/dist/src/inspect.d.ts +31 -0
  23. package/dist/src/inspect.js +219 -0
  24. package/dist/src/model-info.d.ts +32 -0
  25. package/dist/src/model-info.js +223 -0
  26. package/dist/src/provenance.d.ts +62 -0
  27. package/dist/src/provenance.js +266 -0
  28. package/dist/src/provider.d.ts +9 -0
  29. package/dist/src/provider.js +80 -0
  30. package/dist/src/report.d.ts +51 -0
  31. package/dist/src/report.js +177 -0
  32. package/dist/src/runtime.d.ts +8 -0
  33. package/dist/src/runtime.js +22 -0
  34. package/dist/src/server.d.ts +7 -0
  35. package/dist/src/server.js +80 -0
  36. package/dist/src/setup.d.ts +22 -0
  37. package/dist/src/setup.js +408 -0
  38. package/dist/src/types.d.ts +76 -0
  39. package/dist/src/types.js +2 -0
  40. package/dist/src/version.d.ts +1 -0
  41. package/dist/src/version.js +1 -0
  42. package/docs/ADVISORY_USAGE.md +196 -0
  43. package/docs/DRAFT_0_1_0.md +55 -0
  44. package/docs/guide/en.md +190 -0
  45. package/docs/guide/ja.md +190 -0
  46. package/docs/guide/ko.md +190 -0
  47. package/docs/guide/zh-CN.md +190 -0
  48. package/examples/claude-mcp.json +15 -0
  49. package/examples/codex-mcp.toml +9 -0
  50. package/examples/recommend.claude.json +9 -0
  51. package/examples/recommend.codex.json +9 -0
  52. package/examples/router.shadow.json +5 -0
  53. package/examples/settings-inventory.json +5 -0
  54. package/package.json +75 -0
@@ -0,0 +1,408 @@
1
+ import { lstatSync, readFileSync, mkdirSync, chmodSync, openSync, writeFileSync, closeSync, renameSync, unlinkSync, rmdirSync } from 'node:fs';
2
+ import { homedir } from 'node:os';
3
+ import { dirname, isAbsolute, join, resolve } from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { randomUUID } from 'node:crypto';
6
+ import { isDeepStrictEqual } from 'node:util';
7
+ import { parse } from 'smol-toml';
8
+ const object = (value) => !!value && typeof value === 'object' && !Array.isArray(value);
9
+ const stringify = (value) => JSON.stringify(value, null, 2) + '\n';
10
+ const fail = (code) => { throw new Error(code); };
11
+ const checkedDirectory = (path) => { const s = lstatSync(path); if (!s.isDirectory() || s.isSymbolicLink())
12
+ fail('unsafe_directory'); };
13
+ function noSymlinks(path) {
14
+ let current = resolve(path);
15
+ for (;;) {
16
+ try {
17
+ if (lstatSync(current).isSymbolicLink())
18
+ fail('unsafe_symlink');
19
+ }
20
+ catch (e) {
21
+ if (e.code !== 'ENOENT')
22
+ throw e;
23
+ }
24
+ const parent = dirname(current);
25
+ if (parent === current)
26
+ break;
27
+ current = parent;
28
+ }
29
+ }
30
+ function read(path) {
31
+ noSymlinks(path);
32
+ try {
33
+ const s = lstatSync(path);
34
+ if (!s.isFile() || s.size > 4 * 1024 * 1024)
35
+ fail('unsafe_configuration_file');
36
+ return { text: readFileSync(path, 'utf8'), mode: s.mode & 0o777 };
37
+ }
38
+ catch (e) {
39
+ if (e.code === 'ENOENT')
40
+ return { text: null, mode: 0o600 };
41
+ throw e;
42
+ }
43
+ }
44
+ function json(text) {
45
+ if (text === null)
46
+ return {};
47
+ let value;
48
+ try {
49
+ value = JSON.parse(text);
50
+ }
51
+ catch {
52
+ return fail('invalid_json');
53
+ }
54
+ if (!object(value))
55
+ fail('invalid_json_object');
56
+ return value;
57
+ }
58
+ function map(parent, key) {
59
+ if (parent[key] === undefined)
60
+ parent[key] = {};
61
+ if (!object(parent[key]))
62
+ fail('configuration_shape_conflict');
63
+ return parent[key];
64
+ }
65
+ function hook(client) {
66
+ return { matcher: client === 'codex' ? '^(spawn_agent|collaborationspawn_agent|Agent)$' : '^Agent$', hooks: [{ type: 'mcp_tool', server: 'aptly', tool: 'route_spawn_hook', input: { client, event: { hook_event_name: '${hook_event_name}', tool_name: '${tool_name}', tool_input: '${tool_input}', tool_use_id: '${tool_use_id}', session_id: '${session_id}', cwd: '${cwd}' } }, timeout: 20 }] };
67
+ }
68
+ function hooksArray(settings) {
69
+ const hooks = map(settings, 'hooks');
70
+ if (hooks.PreToolUse === undefined)
71
+ hooks.PreToolUse = [];
72
+ if (!Array.isArray(hooks.PreToolUse))
73
+ fail('hook_shape_conflict');
74
+ return hooks.PreToolUse;
75
+ }
76
+ function routeHook(entry) { return object(entry) && Array.isArray(entry.hooks) && entry.hooks.some((h) => object(h) && h.type === 'mcp_tool' && h.server === 'aptly' && h.tool === 'route_spawn_hook'); }
77
+ function toml(text) { try {
78
+ return structuredClone(parse(text));
79
+ }
80
+ catch {
81
+ return fail('invalid_toml');
82
+ } }
83
+ const q = (value) => JSON.stringify(value);
84
+ const blockStart = '# aptly managed MCP start v1';
85
+ const blockEnd = '# aptly managed MCP end v1';
86
+ function newTomlBlock(entry) {
87
+ const lines = [blockStart, '[mcp_servers.aptly]', `command = ${q(entry.command)}`, `args = ${JSON.stringify(entry.args)}`, 'env_vars = ["OPENAI_API_KEY"]', '[mcp_servers.aptly.env]'];
88
+ for (const [key, value] of Object.entries(entry.env))
89
+ lines.push(`${key} = ${q(value)}`);
90
+ return lines.concat(blockEnd, '').join('\n');
91
+ }
92
+ /** Append only the owned env table/keys; validation rejects inline/ambiguous layouts. */
93
+ function addTomlEnv(text, additions) {
94
+ if (!Object.keys(additions).length)
95
+ return text;
96
+ const target = structuredClone(toml(text));
97
+ Object.assign(map(map(map(target, 'mcp_servers'), 'aptly'), 'env'), additions);
98
+ const lines = Object.entries(additions).map(([key, value]) => `${key} = ${q(value)}\n`).join('');
99
+ const headers = [...text.matchAll(/^\s*\[([^\n]+)\][ \t]*(?:#[^\n]*)?\r?$/gm)];
100
+ let found;
101
+ for (const h of headers) {
102
+ try {
103
+ const probe = toml(`${h[0]}\naptly_probe = true\n`);
104
+ if (probe.mcp_servers?.aptly?.env?.aptly_probe === true)
105
+ found = h;
106
+ }
107
+ catch { /* Not the target table. */ }
108
+ }
109
+ let result;
110
+ if (found) {
111
+ const next = headers.find(h => h.index > found.index);
112
+ const offset = next?.index ?? text.length;
113
+ result = text.slice(0, offset) + (offset && text[offset - 1] !== '\n' ? '\n' : '') + lines + text.slice(offset);
114
+ }
115
+ else
116
+ result = text + (text.endsWith('\n') ? '\n' : '\n\n') + '[mcp_servers.aptly.env]\n' + lines;
117
+ try {
118
+ if (!isDeepStrictEqual(toml(result), target))
119
+ fail('unsupported_toml_layout');
120
+ }
121
+ catch {
122
+ fail('unsupported_toml_layout');
123
+ }
124
+ return result;
125
+ }
126
+ function removeTomlEnv(text, additions) {
127
+ const target = structuredClone(toml(text));
128
+ for (const [key, value] of Object.entries(additions)) {
129
+ if (target.mcp_servers?.aptly?.env?.[key] !== value)
130
+ fail('managed_entry_changed');
131
+ delete target.mcp_servers.aptly.env[key];
132
+ const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
133
+ const line = new RegExp(`^${escaped} = ${q(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&')}\\r?\\n`, 'gm');
134
+ const matches = [...text.matchAll(line)];
135
+ if (matches.length !== 1)
136
+ fail('managed_entry_changed');
137
+ text = text.replace(line, '');
138
+ }
139
+ if (!isDeepStrictEqual(toml(text), target))
140
+ fail('managed_entry_changed');
141
+ return text;
142
+ }
143
+ function resolved(options, client) {
144
+ const home = resolve(options.home ?? homedir()), env = options.env ?? process.env;
145
+ const override = client === 'codex' ? env.CODEX_HOME : env.CLAUDE_CONFIG_DIR;
146
+ if (override && !isAbsolute(override))
147
+ fail('relative_configuration_directory');
148
+ const dir = override || join(home, client === 'codex' ? '.codex' : '.claude');
149
+ const mcpPath = client === 'codex' ? join(dir, 'config.toml') : override ? join(dir, '.claude.json') : join(home, '.claude.json');
150
+ return { dir, mcpPath, hooksPath: join(dir, client === 'codex' ? 'hooks.json' : 'settings.json'), privateDir: join(dir, 'aptly') };
151
+ }
152
+ function ownership(value, client) {
153
+ if (value.schema_version !== 1 || value.kind !== 'aptly-setup-ownership' || value.client !== client || !object(value.hook) || typeof value.mcp_created !== 'boolean' || !object(value.env_added) || !object(value.policy) || typeof value.policy_created !== 'boolean')
154
+ fail('invalid_ownership');
155
+ return value;
156
+ }
157
+ function prepare(options, client, removing) {
158
+ const env = options.env ?? process.env, paths = resolved(options, client), { dir, mcpPath, hooksPath, privateDir } = paths;
159
+ const changes = [], dirs = [];
160
+ noSymlinks(dir);
161
+ noSymlinks(privateDir);
162
+ for (const path of [privateDir, join(privateDir, 'state')]) {
163
+ try {
164
+ checkedDirectory(path);
165
+ if ((lstatSync(path).mode & 0o077) !== 0)
166
+ fail('private_directory_permissions');
167
+ }
168
+ catch (e) {
169
+ if (e.code !== 'ENOENT')
170
+ throw e;
171
+ }
172
+ }
173
+ const hasDir = (() => { try {
174
+ checkedDirectory(dir);
175
+ return true;
176
+ }
177
+ catch (e) {
178
+ if (e.code === 'ENOENT')
179
+ return false;
180
+ throw e;
181
+ } })();
182
+ const result = { client, status: 'unchanged', codes: [] };
183
+ if (!hasDir && (options.automatic || removing))
184
+ return { changes, dirs, result: { client, status: 'skipped', codes: ['host_not_configured'] } };
185
+ const policyPath = join(privateDir, 'auto-policy.json'), manifestPath = join(privateDir, 'setup-ownership.json'), stateDir = join(privateDir, 'state');
186
+ const snap = (path) => { const source = read(path); return { path, before: source.text, after: source.text, mode: source.mode }; };
187
+ const mcp = snap(mcpPath), hooks = snap(hooksPath), policy = snap(policyPath), manifest = snap(manifestPath);
188
+ for (const privateFile of [policy, manifest])
189
+ if (privateFile.before !== null && (privateFile.mode & 0o077) !== 0)
190
+ fail('private_file_permissions');
191
+ const oldOwner = manifest.before === null ? undefined : ownership(json(manifest.before), client);
192
+ if (removing && !oldOwner)
193
+ return { changes, dirs, result: { client, status: 'skipped', codes: ['no_owned_setup'] } };
194
+ const hookValue = hook(client), hooksObject = json(hooks.before), entries = hooksArray(hooksObject);
195
+ const config = client === 'codex' ? toml(mcp.before ?? '') : json(mcp.before), servers = map(config, client === 'codex' ? 'mcp_servers' : 'mcpServers');
196
+ if (removing) {
197
+ const owned = oldOwner;
198
+ const index = entries.findIndex(entry => isDeepStrictEqual(entry, owned.hook));
199
+ if (index < 0)
200
+ fail('managed_hook_changed');
201
+ for (let i = entries.length - 1; i >= 0; i--)
202
+ if (isDeepStrictEqual(entries[i], owned.hook))
203
+ entries.splice(i, 1);
204
+ hooks.after = stringify(hooksObject);
205
+ if (owned.mcp_created) {
206
+ if (!isDeepStrictEqual(servers.aptly, owned.mcp))
207
+ fail('managed_mcp_changed');
208
+ if (client === 'codex') {
209
+ if (!owned.toml_block || !(mcp.before ?? '').includes(owned.toml_block))
210
+ fail('managed_mcp_changed');
211
+ mcp.after = (mcp.before ?? '').replace(owned.toml_block, '');
212
+ const expected = structuredClone(config);
213
+ delete expected.mcp_servers.aptly;
214
+ // An empty mcp_servers parent is semantically harmless after removing our section.
215
+ const actual = toml(mcp.after);
216
+ if (!Object.keys(expected.mcp_servers).length && !actual.mcp_servers)
217
+ delete expected.mcp_servers;
218
+ if (!isDeepStrictEqual(actual, expected))
219
+ fail('managed_mcp_changed');
220
+ }
221
+ else {
222
+ delete servers.aptly;
223
+ mcp.after = stringify(config);
224
+ }
225
+ }
226
+ else {
227
+ if (!object(servers.aptly))
228
+ fail('managed_mcp_changed');
229
+ for (const [key, value] of Object.entries(owned.env_added)) {
230
+ if (servers.aptly.env?.[key] !== value)
231
+ fail('managed_entry_changed');
232
+ delete servers.aptly.env[key];
233
+ }
234
+ mcp.after = client === 'codex' ? removeTomlEnv(mcp.before ?? '', owned.env_added) : stringify(config);
235
+ }
236
+ const p = json(policy.before);
237
+ if (!isDeepStrictEqual(p, owned.policy))
238
+ fail('managed_policy_changed');
239
+ if (owned.policy_created)
240
+ policy.after = null;
241
+ manifest.after = null;
242
+ result.status = 'removed';
243
+ }
244
+ else {
245
+ const root = resolve(options.packageRoot ?? join(dirname(fileURLToPath(import.meta.url)), '..', ...(import.meta.url.includes('/dist/src/') ? ['..'] : [])));
246
+ const additions = { APTLY_HOOK_POLICY_PATH: policyPath, ROUTER_LEDGER_PATH: join(stateDir, 'ledger.json') };
247
+ let created = false, tomlBlock;
248
+ if (servers.aptly === undefined) {
249
+ created = true;
250
+ servers.aptly = { ...(client === 'claude' ? { type: 'stdio' } : {}), command: process.execPath, args: [join(root, 'dist', 'src', 'server.js')], ...(client === 'codex' ? { env_vars: ['OPENAI_API_KEY'] } : {}), env: { ROUTER_PROVIDER: env.ROUTER_PROVIDER === 'fixture' ? 'fixture' : 'decisions', ...additions } };
251
+ if (client === 'codex') {
252
+ tomlBlock = newTomlBlock(servers.aptly);
253
+ mcp.after = (mcp.before ?? '') + ((mcp.before ?? '').endsWith('\n') || !mcp.before ? '\n' : '\n\n') + tomlBlock;
254
+ }
255
+ else
256
+ mcp.after = stringify(config);
257
+ }
258
+ else {
259
+ if (!object(servers.aptly))
260
+ fail('mcp_entry_conflict');
261
+ const currentEnv = map(servers.aptly, 'env');
262
+ if (currentEnv.APTLY_HOOK_POLICY_PATH !== undefined && currentEnv.APTLY_HOOK_POLICY_PATH !== policyPath)
263
+ fail('mcp_environment_conflict');
264
+ const missing = Object.fromEntries(Object.entries(additions).filter(([key]) => currentEnv[key] === undefined));
265
+ Object.assign(currentEnv, missing);
266
+ mcp.after = client === 'codex' ? addTomlEnv(mcp.before ?? '', missing) : stringify(config);
267
+ }
268
+ const existingRoute = entries.filter(routeHook);
269
+ if (existingRoute.some(entry => !isDeepStrictEqual(entry, hookValue)))
270
+ fail('existing_routing_hook_conflict');
271
+ if (existingRoute.length === 0)
272
+ entries.push(hookValue);
273
+ hooks.after = stringify(hooksObject);
274
+ const policyValue = { schema_version: 1, kind: 'aptly-auto-hook', mode: options.mode ?? 'defaults', issuer: 'operator_installation', enabled: true, state_directory: stateDir };
275
+ if (policy.before !== null) {
276
+ const p = json(policy.before);
277
+ if (!isDeepStrictEqual(p, policyValue))
278
+ fail('policy_conflict');
279
+ }
280
+ else
281
+ policy.after = stringify(policyValue);
282
+ const envAdded = oldOwner?.env_added ?? (created ? {} : Object.fromEntries(Object.entries(additions).filter(([key]) => {
283
+ const original = client === 'codex' ? toml(mcp.before ?? '') : json(mcp.before);
284
+ return original[client === 'codex' ? 'mcp_servers' : 'mcpServers']?.aptly?.env?.[key] === undefined;
285
+ })));
286
+ if (existingRoute.length && !oldOwner)
287
+ fail('existing_hook_not_owned');
288
+ const owner = oldOwner ?? { schema_version: 1, kind: 'aptly-setup-ownership', client, hook: hookValue, policy: policyValue, policy_created: policy.before === null, mcp_created: created, ...(created ? { mcp: servers.aptly } : {}), env_added: envAdded, ...(tomlBlock ? { toml_block: tomlBlock } : {}) };
289
+ manifest.after = stringify(owner);
290
+ if (client === 'codex')
291
+ result.requires_hook_review = true;
292
+ result.codes = client === 'codex' ? ['requires_hook_review'] : [];
293
+ dirs.push(dir, privateDir, stateDir);
294
+ }
295
+ // Validate complete desired data before any host file is changed.
296
+ if (client === 'codex' && mcp.after !== null)
297
+ toml(mcp.after);
298
+ for (const snapshot of [mcp, hooks, policy, manifest])
299
+ if (snapshot.before !== snapshot.after)
300
+ changes.push(snapshot);
301
+ if (!removing && changes.length)
302
+ result.status = 'configured';
303
+ return { changes, dirs, result };
304
+ }
305
+ /** Secure sibling staging is short-lived and never a backup; original settings stay in memory. */
306
+ function replace(snapshot, forward) {
307
+ const expected = forward ? snapshot.before : snapshot.after, next = forward ? snapshot.after : snapshot.before;
308
+ if (read(snapshot.path).text !== expected)
309
+ fail('concurrent_configuration_change');
310
+ if (next === null) {
311
+ if (expected !== null)
312
+ unlinkSync(snapshot.path);
313
+ return;
314
+ }
315
+ const stage = join(dirname(snapshot.path), `.aptly-stage-${randomUUID()}`);
316
+ let fd;
317
+ try {
318
+ fd = openSync(stage, 'wx', 0o600);
319
+ writeFileSync(fd, next);
320
+ closeSync(fd);
321
+ fd = undefined;
322
+ if (read(snapshot.path).text !== expected)
323
+ fail('concurrent_configuration_change');
324
+ renameSync(stage, snapshot.path);
325
+ if (snapshot.mode !== 0o600)
326
+ chmodSync(snapshot.path, snapshot.mode);
327
+ }
328
+ finally {
329
+ if (fd !== undefined)
330
+ closeSync(fd);
331
+ try {
332
+ unlinkSync(stage);
333
+ }
334
+ catch (e) {
335
+ if (e.code !== 'ENOENT')
336
+ throw e;
337
+ }
338
+ }
339
+ }
340
+ function execute(options, removing) {
341
+ const clients = [...new Set(options.clients ?? ['codex', 'claude'])];
342
+ if (clients.some(client => client !== 'codex' && client !== 'claude'))
343
+ return { status: 'partial', clients: [] };
344
+ const env = options.env ?? process.env;
345
+ if (!removing && options.automatic && (env.APTLY_SKIP_SETUP === '1' || !!env.CI))
346
+ return { status: 'skipped', clients: clients.map(client => ({ client, status: 'skipped', codes: [env.APTLY_SKIP_SETUP === '1' ? 'setup_disabled' : 'ci_environment'] })) };
347
+ const prepared = clients.map(client => {
348
+ try {
349
+ return prepare(options, client, removing);
350
+ }
351
+ catch (e) {
352
+ return { changes: [], dirs: [], result: { client, status: 'error', codes: [safeCode(e)] } };
353
+ }
354
+ });
355
+ const results = prepared.map(plan => plan.result);
356
+ // Each host is independent; prepare every host before committing any host.
357
+ for (const plan of prepared) {
358
+ if (!plan.changes.length)
359
+ continue;
360
+ const written = [], madeDirs = [];
361
+ try {
362
+ for (const path of plan.dirs) {
363
+ noSymlinks(path);
364
+ try {
365
+ checkedDirectory(path);
366
+ }
367
+ catch (e) {
368
+ if (e.code !== 'ENOENT')
369
+ throw e;
370
+ mkdirSync(path, { mode: 0o700 });
371
+ madeDirs.push(path);
372
+ }
373
+ if (path.endsWith('/aptly') || path.endsWith('/aptly/state')) {
374
+ if ((lstatSync(path).mode & 0o077) !== 0)
375
+ fail('private_directory_permissions');
376
+ }
377
+ }
378
+ for (const snapshot of plan.changes) {
379
+ replace(snapshot, true);
380
+ written.push(snapshot);
381
+ }
382
+ }
383
+ catch (e) {
384
+ let rollbackConflict = false;
385
+ for (const snapshot of written.reverse()) {
386
+ try {
387
+ replace(snapshot, false);
388
+ }
389
+ catch {
390
+ rollbackConflict = true;
391
+ }
392
+ }
393
+ for (const path of madeDirs.reverse()) {
394
+ try {
395
+ rmdirSync(path);
396
+ }
397
+ catch { /* Never remove another writer's files. */ }
398
+ }
399
+ plan.result.status = 'error';
400
+ plan.result.codes = [safeCode(e), ...(rollbackConflict ? ['rollback_concurrent_change'] : [])];
401
+ }
402
+ }
403
+ return { status: results.some(result => result.status === 'error') ? 'partial' : results.every(result => result.status === 'skipped') ? 'skipped' : 'complete', clients: results };
404
+ }
405
+ const codes = new Set(['unsafe_directory', 'unsafe_symlink', 'unsafe_configuration_file', 'invalid_json', 'invalid_json_object', 'configuration_shape_conflict', 'hook_shape_conflict', 'invalid_toml', 'unsupported_toml_layout', 'relative_configuration_directory', 'invalid_ownership', 'managed_hook_changed', 'managed_mcp_changed', 'managed_entry_changed', 'managed_policy_changed', 'mcp_entry_conflict', 'mcp_environment_conflict', 'existing_routing_hook_conflict', 'policy_conflict', 'existing_hook_not_owned', 'concurrent_configuration_change', 'private_directory_permissions', 'private_file_permissions']);
406
+ function safeCode(error) { return error instanceof Error && codes.has(error.message) ? error.message : 'configuration_io_error'; }
407
+ export function setupHosts(options = {}) { return execute(options, false); }
408
+ export function removeSetup(options = {}) { return execute(options, true); }
@@ -0,0 +1,76 @@
1
+ export type Client = 'codex' | 'claude';
2
+ export type UnknownRecord = Record<string, unknown>;
3
+ export type JsonValue = null | boolean | number | string | JsonValue[] | {
4
+ [key: string]: JsonValue;
5
+ };
6
+ export interface Profile {
7
+ id: string;
8
+ client: Client;
9
+ model: string;
10
+ reasoning_effort?: string;
11
+ }
12
+ export interface RoutingConstraints {
13
+ model_pinned?: boolean;
14
+ effort_pinned?: boolean;
15
+ role_pinned?: boolean;
16
+ fork_inherits?: boolean;
17
+ provenance_verified?: boolean;
18
+ }
19
+ export interface RoutingTask {
20
+ summary: string;
21
+ operation: 'read_only' | 'write' | 'review' | 'analysis';
22
+ context_sufficient: boolean;
23
+ }
24
+ export interface RoutingInput {
25
+ request_id: string;
26
+ client: Client;
27
+ task: RoutingTask;
28
+ eligible_profile_ids: string[];
29
+ constraints: RoutingConstraints;
30
+ }
31
+ export interface Usage {
32
+ input_tokens: number;
33
+ output_tokens: number;
34
+ total_tokens: number;
35
+ }
36
+ export type RoutingStatus = 'recommended' | 'keep_current' | 'needs_context' | 'unavailable';
37
+ export interface RoutingResult {
38
+ status: RoutingStatus;
39
+ reason: string;
40
+ application: 'advisory_only';
41
+ policy_version: string;
42
+ catalog_version: string;
43
+ profile_id?: string;
44
+ profile?: Profile;
45
+ usage?: Usage;
46
+ latency_ms?: number;
47
+ confidence?: number;
48
+ cached?: boolean;
49
+ }
50
+ export interface RoutingPolicy {
51
+ version: string;
52
+ catalog_version: string;
53
+ max_summary_bytes: number;
54
+ max_concurrency: number;
55
+ max_daily_calls: number;
56
+ max_total_calls: number;
57
+ estimated_call_usd: number;
58
+ max_estimated_usd: number;
59
+ timeout_ms: number;
60
+ profiles: readonly Profile[];
61
+ provider_id?: string;
62
+ profile_descriptions?: Record<string, string>;
63
+ }
64
+ export interface ProviderArguments {
65
+ input: RoutingInput;
66
+ profiles: Profile[];
67
+ signal: AbortSignal;
68
+ }
69
+ export interface ProviderAnswer {
70
+ choice: string;
71
+ confidence?: number;
72
+ usage?: unknown;
73
+ }
74
+ export type RoutingProvider = (args: ProviderArguments) => Promise<ProviderAnswer>;
75
+ export declare function isRecord(value: unknown): value is UnknownRecord;
76
+ export declare function errorCode(error: unknown): string | undefined;
@@ -0,0 +1,2 @@
1
+ export function isRecord(value) { return value !== null && typeof value === 'object' && !Array.isArray(value); }
2
+ export function errorCode(error) { return isRecord(error) && typeof error.code === 'string' ? error.code : undefined; }
@@ -0,0 +1 @@
1
+ export declare const VERSION = "0.1.0";
@@ -0,0 +1 @@
1
+ export const VERSION = '0.1.0';