assent-cli 0.0.0-stage → 0.6.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.
@@ -0,0 +1,224 @@
1
+ import { CliConfig } from "./config";
2
+ export interface FetchInit {
3
+ method: string;
4
+ headers: Record<string, string>;
5
+ body?: string | Uint8Array;
6
+ signal: AbortSignal;
7
+ redirect: "error";
8
+ }
9
+ export interface FetchResponse {
10
+ status: number;
11
+ text(): Promise<string>;
12
+ }
13
+ export type FetchLike = (url: string, init: FetchInit) => Promise<FetchResponse>;
14
+ export interface HttpOptions {
15
+ apiUrl: string;
16
+ timeoutMs: number;
17
+ headers: Record<string, string>;
18
+ }
19
+ export declare const defaultFetch: FetchLike;
20
+ export declare class HttpClient {
21
+ private readonly options;
22
+ private readonly fetchImpl;
23
+ constructor(options: HttpOptions, fetchImpl?: FetchLike);
24
+ request<T>(method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE", path: string, body?: unknown, extraHeaders?: Record<string, string>): Promise<T>;
25
+ upload<T>(path: string, bytes: Uint8Array): Promise<T>;
26
+ private exchange;
27
+ }
28
+ export interface TaskView {
29
+ uuid: string;
30
+ status: string;
31
+ kind: string;
32
+ title: string;
33
+ answer: unknown;
34
+ answerExpiresAt: string | null;
35
+ heldItemIds?: string[];
36
+ withdrawnItemIds?: string[];
37
+ itemOutcomes?: Array<{
38
+ itemId: string;
39
+ outcome: string;
40
+ }> | null;
41
+ [key: string]: unknown;
42
+ }
43
+ export interface AttachmentView {
44
+ uuid: string;
45
+ name: string;
46
+ mime: string;
47
+ sizeBytes: number;
48
+ pages?: number;
49
+ }
50
+ export type LockResult = {
51
+ acquired: true;
52
+ token: string;
53
+ expiresAt: string;
54
+ } | {
55
+ acquired: false;
56
+ heldByAgentUuid: string | null;
57
+ expiresAt: string;
58
+ };
59
+ export interface AskBody {
60
+ kind: string;
61
+ title: string;
62
+ detail?: string;
63
+ draft?: string;
64
+ url?: string;
65
+ actions?: Array<{
66
+ id: string;
67
+ label: string;
68
+ style?: string;
69
+ }>;
70
+ items?: Array<Record<string, unknown>>;
71
+ why?: string;
72
+ fields?: Array<Record<string, unknown>>;
73
+ group?: {
74
+ key: string;
75
+ title: string;
76
+ };
77
+ rules?: {
78
+ conflictOn?: string[];
79
+ totalOf?: string;
80
+ declineAction?: string;
81
+ };
82
+ dueAt?: string;
83
+ remindAt?: string;
84
+ dedupeKey?: string;
85
+ runUuid?: string;
86
+ question?: string;
87
+ answerType?: string;
88
+ effortMinutes?: number;
89
+ seriesKey?: string;
90
+ allowDuplicate?: boolean;
91
+ action?: string;
92
+ target?: string;
93
+ parentTaskUuid?: string;
94
+ parentItemId?: string;
95
+ }
96
+ export interface RuleCheck {
97
+ decision: string;
98
+ ruleUuid: string | null;
99
+ label: string | null;
100
+ reason: string | null;
101
+ }
102
+ export interface RuleView {
103
+ uuid: string;
104
+ effect: string;
105
+ action: string;
106
+ target: string | null;
107
+ label: string;
108
+ [key: string]: unknown;
109
+ }
110
+ export declare class AgentApi {
111
+ private readonly http;
112
+ constructor(http: HttpClient);
113
+ static fromConfig(config: CliConfig, fetchImpl?: FetchLike): AgentApi;
114
+ me(): Promise<Record<string, unknown>>;
115
+ runStart(body: {
116
+ summary?: string;
117
+ maxMinutes?: number;
118
+ }): Promise<{
119
+ uuid: string;
120
+ }>;
121
+ runFinish(uuid: string, body: {
122
+ status: string;
123
+ retryAt?: string;
124
+ issue?: {
125
+ reason: string;
126
+ impact: string;
127
+ action: string;
128
+ };
129
+ exitCode?: number;
130
+ summary?: string;
131
+ logTail?: string;
132
+ taskUuid?: string;
133
+ results?: Record<string, number>;
134
+ }): Promise<Record<string, unknown>>;
135
+ runDefer(uuid: string, body: {
136
+ retryAt: string;
137
+ }): Promise<Record<string, unknown>>;
138
+ runProgress(uuid: string, body: {
139
+ done: number;
140
+ total: number;
141
+ label?: string;
142
+ }): Promise<Record<string, unknown>>;
143
+ taskUpdate(uuid: string, body: {
144
+ dueAt?: string | null;
145
+ remindAt?: string | null;
146
+ }): Promise<TaskView>;
147
+ ask(body: AskBody, idempotencyKey?: string): Promise<TaskView>;
148
+ resolve(uuid: string, body: {
149
+ outcome: "resolved" | "withdrawn";
150
+ note?: string;
151
+ }): Promise<TaskView>;
152
+ task(uuid: string): Promise<TaskView>;
153
+ itemResult(uuid: string, itemId: string, body: {
154
+ outcome: string;
155
+ reason?: string;
156
+ url?: string;
157
+ }): Promise<Record<string, unknown>>;
158
+ answers(): Promise<TaskView[]>;
159
+ pickedUp(uuid: string): Promise<TaskView>;
160
+ note(body: {
161
+ title: string;
162
+ content: string;
163
+ tags?: string[];
164
+ runUuid?: string;
165
+ }): Promise<Record<string, unknown>>;
166
+ noteUpdate(uuid: string, body: {
167
+ title?: string;
168
+ content?: string;
169
+ tags?: string[];
170
+ }): Promise<Record<string, unknown>>;
171
+ link(body: {
172
+ from: {
173
+ type: string;
174
+ uuid: string;
175
+ };
176
+ to: {
177
+ type: string;
178
+ uuid: string;
179
+ };
180
+ relation: string;
181
+ }): Promise<Record<string, unknown>>;
182
+ memory(): Promise<Record<string, unknown>[]>;
183
+ lockAcquire(body: {
184
+ resource: string;
185
+ ttlMinutes: number;
186
+ }): Promise<LockResult>;
187
+ lockRelease(body: {
188
+ resource: string;
189
+ token: string;
190
+ }): Promise<{
191
+ released: boolean;
192
+ }>;
193
+ handoff(body: {
194
+ toAgentName?: string;
195
+ toAgentUuid?: string;
196
+ title: string;
197
+ detail?: string;
198
+ runUuid?: string;
199
+ }): Promise<TaskView>;
200
+ handoffs(): Promise<TaskView[]>;
201
+ planCreate(body: Record<string, unknown>, idempotencyKey: string): Promise<Record<string, unknown>>;
202
+ projectGet(uuid: string): Promise<Record<string, unknown>>;
203
+ projectOverview(uuid: string): Promise<Record<string, unknown>>;
204
+ planGet(uuid: string): Promise<Record<string, unknown>>;
205
+ planSessions(uuid: string, body: Record<string, unknown>): Promise<{
206
+ updated: number;
207
+ }>;
208
+ attach(name: string, bytes: Uint8Array): Promise<AttachmentView>;
209
+ rulesCheck(body: {
210
+ action: string;
211
+ target?: string;
212
+ summary?: string;
213
+ }): Promise<RuleCheck>;
214
+ rulesList(): Promise<RuleView[]>;
215
+ rulesAdd(body: {
216
+ effect: "deny";
217
+ action: string;
218
+ target?: string;
219
+ reason?: string;
220
+ expiresIn: string;
221
+ }): Promise<RuleView>;
222
+ rulesRemove(uuid: string): Promise<RuleView>;
223
+ completeTask(uuid: string): Promise<TaskView>;
224
+ }
package/dist/client.js ADDED
@@ -0,0 +1,112 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentApi = exports.HttpClient = exports.defaultFetch = void 0;
4
+ const exit_codes_1 = require("./exit-codes");
5
+ const API_VERSION_SUFFIX = /\/api\/v1$/;
6
+ const defaultFetch = (url, init) => fetch(url, init);
7
+ exports.defaultFetch = defaultFetch;
8
+ function parseBody(text) {
9
+ if (!text)
10
+ return null;
11
+ try {
12
+ return JSON.parse(text);
13
+ }
14
+ catch {
15
+ return text;
16
+ }
17
+ }
18
+ function errorMessage(body, status) {
19
+ if (body && typeof body === "object" && "message" in body) {
20
+ const message = body.message;
21
+ if (Array.isArray(message))
22
+ return message.join("; ");
23
+ if (typeof message === "string")
24
+ return message;
25
+ }
26
+ return typeof body === "string" ? body.slice(0, 300) : `HTTP ${status}`;
27
+ }
28
+ class HttpClient {
29
+ options;
30
+ fetchImpl;
31
+ constructor(options, fetchImpl = exports.defaultFetch) {
32
+ this.options = options;
33
+ this.fetchImpl = fetchImpl;
34
+ }
35
+ request(method, path, body, extraHeaders = {}) {
36
+ return this.exchange(method, path, body === undefined ? undefined : JSON.stringify(body), extraHeaders);
37
+ }
38
+ upload(path, bytes) {
39
+ return this.exchange("POST", path, bytes, { "content-type": "application/octet-stream" });
40
+ }
41
+ async exchange(method, path, body, extraHeaders) {
42
+ const controller = new AbortController();
43
+ const timer = setTimeout(() => controller.abort(), this.options.timeoutMs);
44
+ let response;
45
+ let text;
46
+ try {
47
+ response = await this.fetchImpl(`${this.options.apiUrl.replace(API_VERSION_SUFFIX, "")}/api/v1${path}`, {
48
+ method,
49
+ headers: { accept: "application/json", "content-type": "application/json", ...this.options.headers, ...extraHeaders },
50
+ body,
51
+ signal: controller.signal,
52
+ redirect: "error",
53
+ });
54
+ text = await response.text();
55
+ }
56
+ catch (err) {
57
+ throw new exit_codes_1.CliError(exit_codes_1.EXIT.unreachable, `Assent is unreachable at ${this.options.apiUrl}: ${err.message}`);
58
+ }
59
+ finally {
60
+ clearTimeout(timer);
61
+ }
62
+ const parsed = parseBody(text);
63
+ if (response.status >= 500 || response.status === 429)
64
+ throw new exit_codes_1.CliError(exit_codes_1.EXIT.unreachable, `Assent returned ${response.status}: ${errorMessage(parsed, response.status)}`, response.status);
65
+ if (response.status >= 400)
66
+ throw new exit_codes_1.CliError((0, exit_codes_1.exitCodeForStatus)(response.status), errorMessage(parsed, response.status), response.status);
67
+ return parsed;
68
+ }
69
+ }
70
+ exports.HttpClient = HttpClient;
71
+ const segment = (value) => encodeURIComponent(value);
72
+ class AgentApi {
73
+ http;
74
+ constructor(http) {
75
+ this.http = http;
76
+ }
77
+ static fromConfig(config, fetchImpl) {
78
+ return new AgentApi(new HttpClient({ apiUrl: config.apiUrl, timeoutMs: config.timeoutMs, headers: { "x-ordia-agent-key": config.agentKey } }, fetchImpl));
79
+ }
80
+ me() { return this.http.request("GET", "/agent/me"); }
81
+ runStart(body) { return this.http.request("POST", "/agent/runs", body); }
82
+ runFinish(uuid, body) { return this.http.request("POST", `/agent/runs/${segment(uuid)}/finish`, body); }
83
+ runDefer(uuid, body) { return this.http.request("POST", `/agent/runs/${segment(uuid)}/defer`, body); }
84
+ runProgress(uuid, body) { return this.http.request("POST", `/agent/runs/${segment(uuid)}/progress`, body); }
85
+ taskUpdate(uuid, body) { return this.http.request("PATCH", `/agent/tasks/${segment(uuid)}`, body); }
86
+ ask(body, idempotencyKey) { return this.http.request("POST", "/agent/ask", body, idempotencyKey ? { "Idempotency-Key": idempotencyKey } : {}); }
87
+ resolve(uuid, body) { return this.http.request("POST", `/agent/tasks/${segment(uuid)}/resolve`, body); }
88
+ task(uuid) { return this.http.request("GET", `/agent/tasks/${segment(uuid)}`); }
89
+ itemResult(uuid, itemId, body) { return this.http.request("POST", `/agent/tasks/${segment(uuid)}/items/${segment(itemId)}/result`, body); }
90
+ answers() { return this.http.request("GET", "/agent/answers"); }
91
+ pickedUp(uuid) { return this.http.request("POST", `/agent/tasks/${segment(uuid)}/picked-up`); }
92
+ note(body) { return this.http.request("POST", "/agent/notes", body); }
93
+ noteUpdate(uuid, body) { return this.http.request("PATCH", `/agent/notes/${segment(uuid)}`, body); }
94
+ link(body) { return this.http.request("POST", "/agent/links", body); }
95
+ memory() { return this.http.request("GET", "/agent/memory"); }
96
+ lockAcquire(body) { return this.http.request("POST", "/agent/locks/acquire", body); }
97
+ lockRelease(body) { return this.http.request("POST", "/agent/locks/release", body); }
98
+ handoff(body) { return this.http.request("POST", "/agent/handoff", body); }
99
+ handoffs() { return this.http.request("GET", "/agent/handoffs"); }
100
+ planCreate(body, idempotencyKey) { return this.http.request("POST", "/agent/plans", body, { "Idempotency-Key": idempotencyKey }); }
101
+ projectGet(uuid) { return this.http.request("GET", `/agent/projects/${segment(uuid)}`); }
102
+ projectOverview(uuid) { return this.http.request("GET", `/agent/projects/${segment(uuid)}/overview`); }
103
+ planGet(uuid) { return this.http.request("GET", `/agent/plans/${segment(uuid)}`); }
104
+ planSessions(uuid, body) { return this.http.request("PATCH", `/agent/plans/${segment(uuid)}/sessions`, body); }
105
+ attach(name, bytes) { return this.http.upload(`/agent/attachments?name=${encodeURIComponent(name)}`, bytes); }
106
+ rulesCheck(body) { return this.http.request("POST", "/agent/rules/check", body); }
107
+ rulesList() { return this.http.request("GET", "/agent/rules"); }
108
+ rulesAdd(body) { return this.http.request("POST", "/agent/rules", body); }
109
+ rulesRemove(uuid) { return this.http.request("DELETE", `/agent/rules/${segment(uuid)}`); }
110
+ completeTask(uuid) { return this.http.request("POST", `/agent/tasks/${segment(uuid)}/complete`); }
111
+ }
112
+ exports.AgentApi = AgentApi;
@@ -0,0 +1,20 @@
1
+ export interface CliConfig {
2
+ apiUrl: string;
3
+ agentKey: string;
4
+ timeoutMs: number;
5
+ }
6
+ export interface ConfigSources {
7
+ env: NodeJS.ProcessEnv;
8
+ home: string;
9
+ readFile(path: string): string | null;
10
+ exists(path: string): boolean;
11
+ }
12
+ export declare const DEFAULT_API_URL = "https://ordia-api.specvista.com/api/v1";
13
+ export declare function assentEnv(env: NodeJS.ProcessEnv, name: string): string | undefined;
14
+ export declare function configPath(env: NodeJS.ProcessEnv, home: string, exists: (path: string) => boolean): string;
15
+ export declare function readConfigFile(src: ConfigSources): Record<string, unknown>;
16
+ export declare function assertSecureApiUrl(apiUrl: string): void;
17
+ export declare function loadCliConfig(src: ConfigSources): CliConfig;
18
+ export declare function readFileOrNull(path: string): string | null;
19
+ export declare function readBytesOrNull(path: string): Uint8Array | null;
20
+ export declare function writeFileAtomic(path: string, content: string): void;
package/dist/config.js ADDED
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DEFAULT_API_URL = void 0;
4
+ exports.assentEnv = assentEnv;
5
+ exports.configPath = configPath;
6
+ exports.readConfigFile = readConfigFile;
7
+ exports.assertSecureApiUrl = assertSecureApiUrl;
8
+ exports.loadCliConfig = loadCliConfig;
9
+ exports.readFileOrNull = readFileOrNull;
10
+ exports.readBytesOrNull = readBytesOrNull;
11
+ exports.writeFileAtomic = writeFileAtomic;
12
+ const node_crypto_1 = require("node:crypto");
13
+ const node_fs_1 = require("node:fs");
14
+ const node_path_1 = require("node:path");
15
+ const exit_codes_1 = require("./exit-codes");
16
+ exports.DEFAULT_API_URL = "https://ordia-api.specvista.com/api/v1";
17
+ const DEFAULT_TIMEOUT_MS = 10000;
18
+ function assentEnv(env, name) {
19
+ return env[`ASSENT_${name}`] || env[`ORDIA_${name}`] || undefined;
20
+ }
21
+ function configPath(env, home, exists) {
22
+ const named = assentEnv(env, "CONFIG");
23
+ if (named)
24
+ return named;
25
+ const assentFile = (0, node_path_1.join)(home, ".assent", "config.json");
26
+ return exists(assentFile) ? assentFile : (0, node_path_1.join)(home, ".ordia", "config.json");
27
+ }
28
+ function readConfigFile(src) {
29
+ const path = configPath(src.env, src.home, src.exists);
30
+ const raw = src.readFile(path);
31
+ if (raw === null)
32
+ return {};
33
+ let parsed;
34
+ try {
35
+ parsed = JSON.parse(raw);
36
+ }
37
+ catch {
38
+ throw new exit_codes_1.CliError(exit_codes_1.EXIT.auth, `${path} is not valid JSON`);
39
+ }
40
+ return parsed !== null && typeof parsed === "object" ? parsed : {};
41
+ }
42
+ function stringField(file, name) {
43
+ const value = file[name];
44
+ return typeof value === "string" ? value : "";
45
+ }
46
+ const LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
47
+ function assertSecureApiUrl(apiUrl) {
48
+ let url;
49
+ try {
50
+ url = new URL(apiUrl);
51
+ }
52
+ catch {
53
+ throw new exit_codes_1.CliError(exit_codes_1.EXIT.auth, `The Assent API URL is not a valid URL: ${apiUrl}`);
54
+ }
55
+ if (url.protocol === "https:" || (url.protocol === "http:" && LOCAL_HOSTS.has(url.hostname)))
56
+ return;
57
+ throw new exit_codes_1.CliError(exit_codes_1.EXIT.auth, `The Assent API URL must use https (plain http only for localhost), so the agent key is never sent in the clear: ${apiUrl}`);
58
+ }
59
+ function loadCliConfig(src) {
60
+ const file = readConfigFile(src);
61
+ const apiUrl = (assentEnv(src.env, "API_URL") || stringField(file, "apiUrl") || exports.DEFAULT_API_URL).replace(/\/+$/, "");
62
+ assertSecureApiUrl(apiUrl);
63
+ const agentKey = assentEnv(src.env, "AGENT_KEY") || stringField(file, "agentKey");
64
+ if (!agentKey.startsWith("oak_"))
65
+ throw new exit_codes_1.CliError(exit_codes_1.EXIT.auth, "Not configured: set ASSENT_AGENT_KEY to an oak_ key, or agentKey in ~/.assent/config.json");
66
+ const timeoutMs = Number(assentEnv(src.env, "TIMEOUT_MS"));
67
+ return { apiUrl, agentKey, timeoutMs: Number.isInteger(timeoutMs) && timeoutMs > 0 ? timeoutMs : DEFAULT_TIMEOUT_MS };
68
+ }
69
+ function readFileOrNull(path) {
70
+ try {
71
+ return (0, node_fs_1.readFileSync)(path, "utf8");
72
+ }
73
+ catch {
74
+ return null;
75
+ }
76
+ }
77
+ function readBytesOrNull(path) {
78
+ try {
79
+ return (0, node_fs_1.readFileSync)(path);
80
+ }
81
+ catch {
82
+ return null;
83
+ }
84
+ }
85
+ function writeFileAtomic(path, content) {
86
+ const target = (0, node_fs_1.existsSync)(path) ? (0, node_fs_1.realpathSync)(path) : path;
87
+ const mode = (0, node_fs_1.existsSync)(target) ? (0, node_fs_1.statSync)(target).mode & 0o777 : 0o600;
88
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(target), { recursive: true, mode: 0o700 });
89
+ const temporary = `${target}.${process.pid}.${(0, node_crypto_1.randomUUID)()}.tmp`;
90
+ try {
91
+ (0, node_fs_1.writeFileSync)(temporary, content, { mode, flag: "wx" });
92
+ (0, node_fs_1.renameSync)(temporary, target);
93
+ }
94
+ catch (err) {
95
+ (0, node_fs_1.rmSync)(temporary, { force: true });
96
+ throw err;
97
+ }
98
+ }
package/dist/diff.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function lineDiff(before: string, after: string): string[];
package/dist/diff.js ADDED
@@ -0,0 +1,46 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.lineDiff = lineDiff;
4
+ const CONTEXT_LINES = 3;
5
+ function lines(text) {
6
+ const split = text.split("\n");
7
+ if (split[split.length - 1] === "")
8
+ split.pop();
9
+ return split;
10
+ }
11
+ function fullDiff(a, b) {
12
+ const table = Array.from({ length: a.length + 1 }, () => new Array(b.length + 1).fill(0));
13
+ for (let i = a.length - 1; i >= 0; i--) {
14
+ for (let j = b.length - 1; j >= 0; j--)
15
+ table[i][j] = a[i] === b[j] ? table[i + 1][j + 1] + 1 : Math.max(table[i + 1][j], table[i][j + 1]);
16
+ }
17
+ const out = [];
18
+ let i = 0;
19
+ let j = 0;
20
+ while (i < a.length || j < b.length) {
21
+ if (i < a.length && j < b.length && a[i] === b[j]) {
22
+ out.push(` ${a[i++]}`);
23
+ j++;
24
+ }
25
+ else if (i < a.length && (j >= b.length || table[i + 1][j] >= table[i][j + 1])) {
26
+ out.push(`- ${a[i++]}`);
27
+ }
28
+ else {
29
+ out.push(`+ ${b[j++]}`);
30
+ }
31
+ }
32
+ return out;
33
+ }
34
+ function lineDiff(before, after) {
35
+ const all = fullDiff(lines(before), lines(after));
36
+ const changed = all.map((line, index) => (line.startsWith(" ") ? -1 : index)).filter((index) => index >= 0);
37
+ const near = (index) => changed.some((c) => Math.abs(c - index) <= CONTEXT_LINES);
38
+ const out = [];
39
+ all.forEach((line, index) => {
40
+ if (near(index))
41
+ out.push(line);
42
+ else if (out[out.length - 1] !== " ...")
43
+ out.push(" ...");
44
+ });
45
+ return out;
46
+ }
@@ -0,0 +1,19 @@
1
+ export declare const EXIT: {
2
+ readonly ok: 0;
3
+ readonly failure: 1;
4
+ readonly usage: 2;
5
+ readonly conflict: 3;
6
+ readonly notFound: 4;
7
+ readonly auth: 5;
8
+ readonly timeout: 6;
9
+ readonly ruleDeny: 10;
10
+ readonly ruleAsk: 11;
11
+ readonly unreachable: 69;
12
+ };
13
+ export type ExitCode = (typeof EXIT)[keyof typeof EXIT];
14
+ export declare class CliError extends Error {
15
+ readonly exitCode: ExitCode;
16
+ readonly status?: number | undefined;
17
+ constructor(exitCode: ExitCode, message: string, status?: number | undefined);
18
+ }
19
+ export declare function exitCodeForStatus(status: number): ExitCode;
@@ -0,0 +1,27 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CliError = exports.EXIT = void 0;
4
+ exports.exitCodeForStatus = exitCodeForStatus;
5
+ exports.EXIT = { ok: 0, failure: 1, usage: 2, conflict: 3, notFound: 4, auth: 5, timeout: 6, ruleDeny: 10, ruleAsk: 11, unreachable: 69 };
6
+ class CliError extends Error {
7
+ exitCode;
8
+ status;
9
+ constructor(exitCode, message, status) {
10
+ super(message);
11
+ this.exitCode = exitCode;
12
+ this.status = status;
13
+ this.name = "CliError";
14
+ }
15
+ }
16
+ exports.CliError = CliError;
17
+ function exitCodeForStatus(status) {
18
+ if (status === 401 || status === 403)
19
+ return exports.EXIT.auth;
20
+ if (status === 404)
21
+ return exports.EXIT.notFound;
22
+ if (status === 409 || status === 410)
23
+ return exports.EXIT.conflict;
24
+ if (status === 400)
25
+ return exports.EXIT.usage;
26
+ return exports.EXIT.failure;
27
+ }
@@ -0,0 +1,39 @@
1
+ import { AgentApi } from "./client";
2
+ export interface AskPattern {
3
+ tool: string;
4
+ target?: string;
5
+ }
6
+ export interface HookAskSettings {
7
+ patterns: AskPattern[];
8
+ waitSeconds: number;
9
+ }
10
+ export interface ToolCall {
11
+ tool: string;
12
+ raw: string;
13
+ target: string;
14
+ matched: string;
15
+ summary: string;
16
+ exact: boolean;
17
+ cwd?: string;
18
+ }
19
+ export interface Waiting {
20
+ now(): number;
21
+ sleep(ms: number): Promise<void>;
22
+ }
23
+ export interface Verdict {
24
+ decision: "allow" | "deny";
25
+ reason: string;
26
+ }
27
+ export declare const DEFAULT_ASK_PATTERNS: AskPattern[];
28
+ export declare const DEFAULT_WAIT_SECONDS = 120;
29
+ export declare const MAX_WAIT_SECONDS = 3600;
30
+ export declare function globMatches(pattern: string, value: string): boolean;
31
+ export declare function wordTailMatcher(pattern: string): RegExp;
32
+ export declare function commandSegments(command: string): string[];
33
+ export declare function canonicalWords(words: string[]): string[];
34
+ export declare function shellCandidates(command: string): string[];
35
+ export declare function askMatch(patterns: AskPattern[], tool: string, raw: string): string | null;
36
+ export declare function needsAssent(patterns: AskPattern[], tool: string, raw: string): boolean;
37
+ export declare function hookAskSettings(file: Record<string, unknown>, env: NodeJS.ProcessEnv): HookAskSettings;
38
+ export declare function visible(text: string): string;
39
+ export declare function askAssent(api: AgentApi, clock: Waiting, call: ToolCall, waitSeconds: number): Promise<Verdict>;