@oneharness/sdk 0.6.13 → 0.6.15
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 +1 -1
- package/dist/generated/capabilities.d.ts +1010 -0
- package/dist/generated/capabilities.js +1162 -0
- package/dist/generated/config-options.d.ts +8 -0
- package/dist/generated/config-options.js +2 -0
- package/dist/generated/config-report.d.ts +283 -0
- package/dist/generated/config-report.js +2 -0
- package/dist/generated/detect-options.d.ts +19 -0
- package/dist/generated/detect-options.js +2 -0
- package/dist/generated/detection.d.ts +6 -0
- package/dist/generated/gate-options.d.ts +17 -0
- package/dist/generated/gate-options.js +2 -0
- package/dist/generated/history-clear-options.d.ts +15 -0
- package/dist/generated/history-clear-options.js +2 -0
- package/dist/generated/history-clear-report.d.ts +39 -0
- package/dist/generated/history-clear-report.js +2 -0
- package/dist/generated/history-list-options.d.ts +13 -0
- package/dist/generated/history-lookup.d.ts +26 -0
- package/dist/generated/history-migrate-options.d.ts +8 -0
- package/dist/generated/history-migrate-options.js +2 -0
- package/dist/generated/history-migrate-report.d.ts +31 -0
- package/dist/generated/history-migrate-report.js +2 -0
- package/dist/generated/history-watch-options.d.ts +13 -0
- package/dist/generated/init-options.d.ts +11 -0
- package/dist/generated/init-options.js +2 -0
- package/dist/generated/interrupt-options.d.ts +12 -0
- package/dist/generated/interrupt-options.js +2 -0
- package/dist/generated/interrupt-response.d.ts +40 -0
- package/dist/generated/interrupt-response.js +2 -0
- package/dist/generated/mock-options.d.ts +12 -0
- package/dist/generated/mock-options.js +2 -0
- package/dist/generated/options.d.ts +108 -0
- package/dist/generated/registry.d.ts +14 -4
- package/dist/generated/schemas.json +3245 -15
- package/dist/generated/sync-options.d.ts +14 -0
- package/dist/generated/sync-options.js +2 -0
- package/dist/generated/sync-report.d.ts +64 -0
- package/dist/generated/sync-report.js +2 -0
- package/dist/generated/usage-options.d.ts +15 -0
- package/dist/generated/usage-options.js +2 -0
- package/dist/generated/usage-report.d.ts +241 -0
- package/dist/generated/usage-report.js +2 -0
- package/dist/generated/zod.d.ts +70 -2
- package/dist/generated/zod.js +444 -0
- package/dist/index.d.ts +91 -1
- package/dist/index.js +271 -102
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,26 +1,60 @@
|
|
|
1
|
+
import type { ConfigOptions } from "./generated/config-options.js";
|
|
2
|
+
import type { ConfigReport } from "./generated/config-report.js";
|
|
1
3
|
import type { RunReport } from "./generated/contracts.js";
|
|
4
|
+
import type { DetectOptions } from "./generated/detect-options.js";
|
|
2
5
|
import type { DetectInfo } from "./generated/detection.js";
|
|
6
|
+
import type { GateOptions } from "./generated/gate-options.js";
|
|
3
7
|
import type { HistoryRecord } from "./generated/history.js";
|
|
8
|
+
import type { HistoryClearOptions } from "./generated/history-clear-options.js";
|
|
9
|
+
import type { HistoryClearReport } from "./generated/history-clear-report.js";
|
|
4
10
|
import type { HistorySessionSummary } from "./generated/history-list.js";
|
|
5
11
|
import type { HistoryListOptions } from "./generated/history-list-options.js";
|
|
6
12
|
import type { HistoryLookup } from "./generated/history-lookup.js";
|
|
13
|
+
import type { HistoryMigrateOptions } from "./generated/history-migrate-options.js";
|
|
14
|
+
import type { HistoryMigrateReport } from "./generated/history-migrate-report.js";
|
|
7
15
|
import type { HistoryStreamEnvelope } from "./generated/history-stream-envelope.js";
|
|
8
16
|
import type { HistoryWatchOptions } from "./generated/history-watch-options.js";
|
|
17
|
+
import type { InitOptions } from "./generated/init-options.js";
|
|
18
|
+
import type { InterruptOptions } from "./generated/interrupt-options.js";
|
|
19
|
+
import type { InterruptResponse } from "./generated/interrupt-response.js";
|
|
20
|
+
import type { MockOptions } from "./generated/mock-options.js";
|
|
9
21
|
import type { RunOptions } from "./generated/options.js";
|
|
10
22
|
import type { HarnessInfo } from "./generated/registry.js";
|
|
11
23
|
import type { RunStreamEnvelope } from "./generated/run-stream-envelope.js";
|
|
24
|
+
import type { SyncOptions } from "./generated/sync-options.js";
|
|
25
|
+
import type { SyncReport } from "./generated/sync-report.js";
|
|
26
|
+
import type { UsageOptions } from "./generated/usage-options.js";
|
|
27
|
+
import type { UsageReport } from "./generated/usage-report.js";
|
|
28
|
+
export type { Capability, CapabilityMethod, FlagKind, OptionBinding, StdoutShape, } from "./generated/capabilities.js";
|
|
29
|
+
export { CAPABILITIES } from "./generated/capabilities.js";
|
|
30
|
+
export type { ConfigOptions } from "./generated/config-options.js";
|
|
31
|
+
export type { ConfigReport } from "./generated/config-report.js";
|
|
12
32
|
export type { ActionEvent, BatchReport, FailureKind, FallbackReport, FallThrough, OutputFormat, RunReport, RunResult, SessionReport, Status, Usage, } from "./generated/contracts.js";
|
|
33
|
+
export type { DetectOptions } from "./generated/detect-options.js";
|
|
13
34
|
export type { DetectInfo, DetectReport } from "./generated/detection.js";
|
|
35
|
+
export type { GateOptions } from "./generated/gate-options.js";
|
|
14
36
|
export type { HistoryRecord } from "./generated/history.js";
|
|
37
|
+
export type { HistoryClearOptions } from "./generated/history-clear-options.js";
|
|
38
|
+
export type { HistoryClearDryRun, HistoryClearRemoved, HistoryClearReport, } from "./generated/history-clear-report.js";
|
|
15
39
|
export type { HistoryLine } from "./generated/history-line.js";
|
|
16
40
|
export type { HistoryList, HistorySessionSummary, } from "./generated/history-list.js";
|
|
17
41
|
export type { HistoryListOptions } from "./generated/history-list-options.js";
|
|
18
42
|
export type { HistoryLookup, HistoryLookupByLast, HistoryLookupBySession, } from "./generated/history-lookup.js";
|
|
43
|
+
export type { HistoryMigrateOptions } from "./generated/history-migrate-options.js";
|
|
44
|
+
export type { HistoryMigrateReport } from "./generated/history-migrate-report.js";
|
|
19
45
|
export type { HistoryRecords } from "./generated/history-records.js";
|
|
20
46
|
export type { HistoryStreamEnvelope } from "./generated/history-stream-envelope.js";
|
|
21
47
|
export type { HistoryWatchOptions } from "./generated/history-watch-options.js";
|
|
48
|
+
export type { InitOptions } from "./generated/init-options.js";
|
|
49
|
+
export type { InterruptOptions } from "./generated/interrupt-options.js";
|
|
50
|
+
export type { InterruptResponse } from "./generated/interrupt-response.js";
|
|
51
|
+
export type { MockOptions } from "./generated/mock-options.js";
|
|
22
52
|
export type { PermissionMode, RunOptions } from "./generated/options.js";
|
|
23
53
|
export type { RunStreamEnvelope } from "./generated/run-stream-envelope.js";
|
|
54
|
+
export type { SyncOptions } from "./generated/sync-options.js";
|
|
55
|
+
export type { SyncReport } from "./generated/sync-report.js";
|
|
56
|
+
export type { UsageOptions } from "./generated/usage-options.js";
|
|
57
|
+
export type { UsageReport } from "./generated/usage-report.js";
|
|
24
58
|
export type Detection = DetectInfo;
|
|
25
59
|
export type { HarnessInfo, ListReport, ModeInfo, } from "./generated/registry.js";
|
|
26
60
|
export * from "./generated/zod.js";
|
|
@@ -49,12 +83,68 @@ export declare class HistoryNotFoundError extends OneHarnessProcessError {
|
|
|
49
83
|
export declare class OneHarness {
|
|
50
84
|
private readonly options;
|
|
51
85
|
constructor(options?: OneHarnessOptions);
|
|
86
|
+
/**
|
|
87
|
+
* Validate options, render the capability's argv, invoke, validate stdout.
|
|
88
|
+
*
|
|
89
|
+
* Every JSON-returning method is this call with two schemas, so none of them
|
|
90
|
+
* can drift from the manifest in how it builds a command or in what it
|
|
91
|
+
* promises back.
|
|
92
|
+
*/
|
|
93
|
+
private call;
|
|
52
94
|
run(options: RunOptions): Promise<RunReport>;
|
|
53
95
|
runMock(harness: string, options: RunOptions, script?: MockHarnessScript): Promise<RunReport>;
|
|
54
96
|
runStream(options: RunOptions): AsyncGenerator<RunStreamEnvelope>;
|
|
55
97
|
list(): Promise<HarnessInfo[]>;
|
|
56
|
-
|
|
98
|
+
/**
|
|
99
|
+
* Probe harness binaries.
|
|
100
|
+
*
|
|
101
|
+
* The array overload is the long-standing shape and still means "these
|
|
102
|
+
* harnesses"; the options object is what reaches the rest of the verb's
|
|
103
|
+
* flags (`--all`, `--exclude`, `--bin`, the config layer).
|
|
104
|
+
*/
|
|
105
|
+
detect(harnessesOrOptions?: readonly string[] | DetectOptions): Promise<Detection[]>;
|
|
106
|
+
config(options?: ConfigOptions): Promise<ConfigReport>;
|
|
107
|
+
sync(options?: SyncOptions): Promise<SyncReport>;
|
|
108
|
+
usage(options?: UsageOptions): Promise<UsageReport>;
|
|
109
|
+
/**
|
|
110
|
+
* Write a starter `oneharness.toml` and return where it landed.
|
|
111
|
+
*
|
|
112
|
+
* The one verb whose stdout is a human confirmation line rather than a JSON
|
|
113
|
+
* contract — the deliverable is the file — so the return is the resolved
|
|
114
|
+
* path the caller asked for, not a parsed document.
|
|
115
|
+
*/
|
|
116
|
+
init(options?: InitOptions): Promise<string>;
|
|
117
|
+
/**
|
|
118
|
+
* Run the pre-tool gate over one harness hook event, as that harness's own
|
|
119
|
+
* runtime would.
|
|
120
|
+
*
|
|
121
|
+
* Returns the harness's native deny verdict, or `null` when the call is
|
|
122
|
+
* allowed through — which the CLI says by printing nothing at all, so an
|
|
123
|
+
* empty answer is the allow rather than a missing one.
|
|
124
|
+
*/
|
|
125
|
+
gate(options: GateOptions): Promise<string | null>;
|
|
126
|
+
/** The read-write sibling of {@link gate}, driven by a rules file. */
|
|
127
|
+
mock(options: MockOptions): Promise<string | null>;
|
|
128
|
+
/**
|
|
129
|
+
* Abort the in-flight turn of a `run --control` session, optionally
|
|
130
|
+
* redirecting it with `input`.
|
|
131
|
+
*
|
|
132
|
+
* A refusal is an answer, not a throw: the response says whether the abort
|
|
133
|
+
* was served and, when it was not, why — which is what a supervisor branches
|
|
134
|
+
* on. So a non-zero exit still yields the frame.
|
|
135
|
+
*/
|
|
136
|
+
interrupt(options: InterruptOptions): Promise<InterruptResponse>;
|
|
57
137
|
history(lookup: HistoryLookup): Promise<HistoryRecord[]>;
|
|
58
138
|
historyList(options?: HistoryListOptions): Promise<HistorySessionSummary[]>;
|
|
139
|
+
/**
|
|
140
|
+
* Remove history sessions.
|
|
141
|
+
*
|
|
142
|
+
* A dry run until `yes` is set, and the two answers are different documents:
|
|
143
|
+
* `dry_run` discriminates them, so a caller reads `removed` only from a run
|
|
144
|
+
* that removed something.
|
|
145
|
+
*/
|
|
146
|
+
historyClear(options?: HistoryClearOptions): Promise<HistoryClearReport>;
|
|
147
|
+
/** Rewrite legacy session files to the current record version. */
|
|
148
|
+
historyMigrate(options?: HistoryMigrateOptions): Promise<HistoryMigrateReport>;
|
|
59
149
|
historyWatch(options?: HistoryWatchOptions): AsyncGenerator<HistoryStreamEnvelope>;
|
|
60
150
|
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { spawn } from "node:child_process";
|
|
2
2
|
import { createRequire } from "node:module";
|
|
3
3
|
import { createInterface } from "node:readline";
|
|
4
|
-
import {
|
|
4
|
+
import { CAPABILITIES, } from "./generated/capabilities.js";
|
|
5
|
+
import { ConfigOptionsSchema, ConfigReportSchema, DetectOptionsSchema, DetectReportSchema, GateOptionsSchema, HistoryClearOptionsSchema, HistoryClearReportSchema, HistoryListOptionsSchema, HistoryListSchema, HistoryLookupSchema, HistoryMigrateOptionsSchema, HistoryMigrateReportSchema, HistoryRecordsSchema, HistoryStreamEnvelopeSchema, HistoryWatchOptionsSchema, InitOptionsSchema, InterruptOptionsSchema, InterruptResponseSchema, ListReportSchema, MockOptionsSchema, RunOptionsSchema, RunReportSchema, RunStreamEnvelopeSchema, SyncOptionsSchema, SyncReportSchema, UsageOptionsSchema, UsageReportSchema, } from "./generated/zod.js";
|
|
6
|
+
export { CAPABILITIES } from "./generated/capabilities.js";
|
|
5
7
|
export * from "./generated/zod.js";
|
|
6
8
|
/** A non-zero exit from the oneharness subprocess. */
|
|
7
9
|
export class OneHarnessProcessError extends Error {
|
|
@@ -75,6 +77,43 @@ async function invokeWith(options, args, cwd, acceptJsonOnNonzero = false) {
|
|
|
75
77
|
});
|
|
76
78
|
});
|
|
77
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Invoke a capability whose stdout is text rather than a JSON contract, and
|
|
82
|
+
* return it verbatim.
|
|
83
|
+
*
|
|
84
|
+
* `init` prints a confirmation line; `gate` and `mock` answer with a harness's
|
|
85
|
+
* own native verdict, or with nothing when the call is allowed. None of the
|
|
86
|
+
* three has a document to validate, so parsing one would be inventing a shape
|
|
87
|
+
* the CLI does not promise.
|
|
88
|
+
*
|
|
89
|
+
* `stdin` carries the hook event for the two gate verbs, and closing it is what
|
|
90
|
+
* tells them the event is complete.
|
|
91
|
+
*/
|
|
92
|
+
async function invokeText(options, args, stdin) {
|
|
93
|
+
const bin = executable(options);
|
|
94
|
+
return await new Promise((resolve, reject) => {
|
|
95
|
+
const child = spawn(bin.command, [...bin.prefix, ...args], {
|
|
96
|
+
env: { ...process.env, ...options.env },
|
|
97
|
+
windowsHide: true,
|
|
98
|
+
});
|
|
99
|
+
let stdout = "";
|
|
100
|
+
let stderr = "";
|
|
101
|
+
child.stdout
|
|
102
|
+
.setEncoding("utf8")
|
|
103
|
+
.on("data", (chunk) => (stdout += chunk));
|
|
104
|
+
child.stderr
|
|
105
|
+
.setEncoding("utf8")
|
|
106
|
+
.on("data", (chunk) => (stderr += chunk));
|
|
107
|
+
child.on("error", reject);
|
|
108
|
+
child.on("close", (code) => {
|
|
109
|
+
if (code !== 0)
|
|
110
|
+
return reject(processError(code, stderr));
|
|
111
|
+
resolve(stdout);
|
|
112
|
+
});
|
|
113
|
+
child.stdin.on("error", reject);
|
|
114
|
+
child.stdin.end(stdin ?? "");
|
|
115
|
+
});
|
|
116
|
+
}
|
|
78
117
|
function processError(code, stderr) {
|
|
79
118
|
return new OneHarnessProcessError(`oneharness exited ${code}: ${stderr.trim()}`, code, stderr);
|
|
80
119
|
}
|
|
@@ -147,40 +186,92 @@ function pushMany(args, flag, values) {
|
|
|
147
186
|
for (const value of values ?? [])
|
|
148
187
|
args.push(flag, value);
|
|
149
188
|
}
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
189
|
+
/**
|
|
190
|
+
* Does this binding put anything on the argv for `value`?
|
|
191
|
+
*
|
|
192
|
+
* This is the question `unless` asks, and asking it any other way is a bug: the
|
|
193
|
+
* conflict a suppression encodes is clap's, and clap conflicts on a flag being
|
|
194
|
+
* *present*. JavaScript truthiness is not that test — `{all: true, harnesses:
|
|
195
|
+
* []}`, the shape a caller assembling options programmatically produces, sends
|
|
196
|
+
* no `--harness` at all, yet an empty array is truthy, so reading it as truth
|
|
197
|
+
* dropped `--all` and left the call with no selection to make.
|
|
198
|
+
*/
|
|
199
|
+
function rendersArgument(binding, value) {
|
|
200
|
+
if (value === undefined || value === null)
|
|
201
|
+
return false;
|
|
202
|
+
switch (binding.kind) {
|
|
203
|
+
case "repeated":
|
|
204
|
+
case "trailing":
|
|
205
|
+
return value.length > 0;
|
|
206
|
+
case "key-value":
|
|
207
|
+
return Object.keys(value).length > 0;
|
|
208
|
+
case "switch":
|
|
209
|
+
return Boolean(value);
|
|
210
|
+
default:
|
|
211
|
+
// A `positional` or `value` binding renders whatever it carries, an
|
|
212
|
+
// empty string included — which is a present flag to clap.
|
|
213
|
+
return true;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Render one capability's argv from its declared bindings.
|
|
218
|
+
*
|
|
219
|
+
* The client lists no flags of its own: `CAPABILITIES` says which option
|
|
220
|
+
* renders which flag and how, so a flag added to the CLI reaches every method
|
|
221
|
+
* as soon as the manifest binds it, and a method cannot quietly omit one. The
|
|
222
|
+
* Rust-side gates hold that manifest to clap and to the option contracts, which
|
|
223
|
+
* is what makes this table trustworthy enough to build calls from.
|
|
224
|
+
*/
|
|
225
|
+
function capabilityArguments(method, options) {
|
|
226
|
+
// The options contracts are closed object types with no index signature, so
|
|
227
|
+
// reading a declared binding's key needs this one widening. It is safe
|
|
228
|
+
// because `every_bound_option_is_a_field_of_its_options_contract` proves in
|
|
229
|
+
// Rust that each key named here is a field of the very contract Zod just
|
|
230
|
+
// validated the value against.
|
|
231
|
+
const input = options;
|
|
232
|
+
const capability = CAPABILITIES[method];
|
|
233
|
+
const args = [...capability.argv, ...capability.always];
|
|
234
|
+
const positional = [];
|
|
235
|
+
const trailing = [];
|
|
236
|
+
const bound = new Map(capability.bindings.map((binding) => [binding.option, binding]));
|
|
237
|
+
for (const binding of capability.bindings) {
|
|
238
|
+
// `a_suppressing_option_is_one_the_same_capability_binds` proves in Rust
|
|
239
|
+
// that an `unless` always resolves here, so a missing entry is unreachable
|
|
240
|
+
// rather than a case to render through.
|
|
241
|
+
const suppressor = binding.unless === null ? null : bound.get(binding.unless);
|
|
242
|
+
if (suppressor && rendersArgument(suppressor, input[suppressor.option]))
|
|
243
|
+
continue;
|
|
244
|
+
const value = input[binding.option];
|
|
245
|
+
if (value === undefined || value === null)
|
|
246
|
+
continue;
|
|
247
|
+
switch (binding.kind) {
|
|
248
|
+
case "positional":
|
|
249
|
+
positional.push(String(value));
|
|
250
|
+
break;
|
|
251
|
+
case "value":
|
|
252
|
+
args.push(binding.flag, String(value));
|
|
253
|
+
break;
|
|
254
|
+
case "repeated":
|
|
255
|
+
pushMany(args, binding.flag, value.map(String));
|
|
256
|
+
break;
|
|
257
|
+
case "switch":
|
|
258
|
+
if (value)
|
|
259
|
+
args.push(binding.flag);
|
|
260
|
+
break;
|
|
261
|
+
case "key-value":
|
|
262
|
+
for (const [key, item] of Object.entries(value))
|
|
263
|
+
args.push(binding.flag, `${key}=${String(item)}`);
|
|
264
|
+
break;
|
|
265
|
+
case "trailing":
|
|
266
|
+
trailing.push(...value.map(String));
|
|
267
|
+
break;
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
args.push(...positional);
|
|
271
|
+
// After a `--` separator, so a passthrough argument that looks like a flag
|
|
272
|
+
// reaches the harness instead of being read by oneharness.
|
|
273
|
+
if (trailing.length > 0)
|
|
274
|
+
args.push("--", ...trailing);
|
|
184
275
|
return args;
|
|
185
276
|
}
|
|
186
277
|
export class OneHarness {
|
|
@@ -188,11 +279,42 @@ export class OneHarness {
|
|
|
188
279
|
constructor(options = {}) {
|
|
189
280
|
this.options = options;
|
|
190
281
|
}
|
|
282
|
+
/**
|
|
283
|
+
* Validate options, render the capability's argv, invoke, validate stdout.
|
|
284
|
+
*
|
|
285
|
+
* Every JSON-returning method is this call with two schemas, so none of them
|
|
286
|
+
* can drift from the manifest in how it builds a command or in what it
|
|
287
|
+
* promises back.
|
|
288
|
+
*/
|
|
289
|
+
async call(method, options, optionsSchema, reportSchema, { acceptJsonOnNonzero = false, history = false,
|
|
290
|
+
// The two labels a caller sees on a rejection. They default to the
|
|
291
|
+
// verb, and are given explicitly where a method's wording predates
|
|
292
|
+
// this shared path — those strings are what a consumer's error
|
|
293
|
+
// handling already reads, so they are contract rather than prose.
|
|
294
|
+
optionsLabel = `invalid oneharness ${CAPABILITIES[method].argv.join(" ")} options`, contractLabel = `invalid oneharness ${CAPABILITIES[method].argv.join(" ")} contract`, } = {}) {
|
|
295
|
+
const input = parseContract(optionsSchema, options, optionsLabel);
|
|
296
|
+
const args = capabilityArguments(method, input);
|
|
297
|
+
const cwd = input.cwd;
|
|
298
|
+
let value;
|
|
299
|
+
try {
|
|
300
|
+
value = await invokeWith(this.options, args, cwd, acceptJsonOnNonzero);
|
|
301
|
+
}
|
|
302
|
+
catch (error) {
|
|
303
|
+
if (history && historyNotFound(error))
|
|
304
|
+
throw asHistoryNotFound(error);
|
|
305
|
+
throw error;
|
|
306
|
+
}
|
|
307
|
+
return parseContract(reportSchema, value, contractLabel);
|
|
308
|
+
}
|
|
191
309
|
async run(options) {
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
return
|
|
310
|
+
// `acceptJsonOnNonzero`, because a harness that fails is data in the
|
|
311
|
+
// report rather than a failure of the call: the CLI still prints a whole
|
|
312
|
+
// contract and exits non-zero to say some result was not `ok`.
|
|
313
|
+
return await this.call("run", options, RunOptionsSchema, RunReportSchema, {
|
|
314
|
+
acceptJsonOnNonzero: true,
|
|
315
|
+
optionsLabel: "invalid oneharness run options",
|
|
316
|
+
contractLabel: "invalid oneharness run contract",
|
|
317
|
+
});
|
|
196
318
|
}
|
|
197
319
|
async runMock(harness, options, script = {}) {
|
|
198
320
|
if (harness.length === 0)
|
|
@@ -207,82 +329,129 @@ export class OneHarness {
|
|
|
207
329
|
env.MOCK_EXIT = String(script.exitCode);
|
|
208
330
|
if (script.latencyMs !== undefined)
|
|
209
331
|
env.MOCK_SLEEP_MS = String(script.latencyMs);
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
332
|
+
return await this.call("run", {
|
|
333
|
+
...input,
|
|
334
|
+
harnesses: [harness],
|
|
335
|
+
mockHarnesses: [harness],
|
|
336
|
+
env,
|
|
337
|
+
}, RunOptionsSchema, RunReportSchema, {
|
|
338
|
+
acceptJsonOnNonzero: true,
|
|
339
|
+
optionsLabel: "invalid oneharness run options",
|
|
340
|
+
contractLabel: "invalid oneharness run contract",
|
|
341
|
+
});
|
|
214
342
|
}
|
|
215
343
|
runStream(options) {
|
|
216
344
|
const input = parseContract(RunOptionsSchema, options, "invalid oneharness run options");
|
|
217
|
-
return invokeStreamWith(this.options,
|
|
345
|
+
return invokeStreamWith(this.options, capabilityArguments("runStream", input), RunStreamEnvelopeSchema, "invalid oneharness run stream contract", input.cwd);
|
|
218
346
|
}
|
|
219
347
|
async list() {
|
|
220
|
-
const value = await invokeWith(this.options,
|
|
348
|
+
const value = await invokeWith(this.options, capabilityArguments("list", {}));
|
|
221
349
|
return parseContract(ListReportSchema, value, "invalid oneharness list contract").harnesses;
|
|
222
350
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
351
|
+
/**
|
|
352
|
+
* Probe harness binaries.
|
|
353
|
+
*
|
|
354
|
+
* The array overload is the long-standing shape and still means "these
|
|
355
|
+
* harnesses"; the options object is what reaches the rest of the verb's
|
|
356
|
+
* flags (`--all`, `--exclude`, `--bin`, the config layer).
|
|
357
|
+
*/
|
|
358
|
+
async detect(harnessesOrOptions = []) {
|
|
359
|
+
const options = Array.isArray(harnessesOrOptions)
|
|
360
|
+
? { harnesses: [...harnessesOrOptions] }
|
|
361
|
+
: harnessesOrOptions;
|
|
362
|
+
const report = await this.call("detect", options, DetectOptionsSchema, DetectReportSchema, {
|
|
363
|
+
optionsLabel: "invalid oneharness detect options",
|
|
364
|
+
contractLabel: "invalid oneharness detect contract",
|
|
365
|
+
});
|
|
366
|
+
return report.detected;
|
|
367
|
+
}
|
|
368
|
+
async config(options = {}) {
|
|
369
|
+
return await this.call("config", options, ConfigOptionsSchema, ConfigReportSchema);
|
|
370
|
+
}
|
|
371
|
+
async sync(options = {}) {
|
|
372
|
+
// `--check` exits non-zero when a file would change, which is the answer
|
|
373
|
+
// rather than a failure, and the report says which files those are.
|
|
374
|
+
return await this.call("sync", options, SyncOptionsSchema, SyncReportSchema, { acceptJsonOnNonzero: true });
|
|
375
|
+
}
|
|
376
|
+
async usage(options = {}) {
|
|
377
|
+
return await this.call("usage", options, UsageOptionsSchema, UsageReportSchema);
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* Write a starter `oneharness.toml` and return where it landed.
|
|
381
|
+
*
|
|
382
|
+
* The one verb whose stdout is a human confirmation line rather than a JSON
|
|
383
|
+
* contract — the deliverable is the file — so the return is the resolved
|
|
384
|
+
* path the caller asked for, not a parsed document.
|
|
385
|
+
*/
|
|
386
|
+
async init(options = {}) {
|
|
387
|
+
const input = parseContract(InitOptionsSchema, options, "invalid oneharness init options");
|
|
388
|
+
await invokeText(this.options, capabilityArguments("init", input));
|
|
389
|
+
return input.path ?? "oneharness.toml";
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Run the pre-tool gate over one harness hook event, as that harness's own
|
|
393
|
+
* runtime would.
|
|
394
|
+
*
|
|
395
|
+
* Returns the harness's native deny verdict, or `null` when the call is
|
|
396
|
+
* allowed through — which the CLI says by printing nothing at all, so an
|
|
397
|
+
* empty answer is the allow rather than a missing one.
|
|
398
|
+
*/
|
|
399
|
+
async gate(options) {
|
|
400
|
+
const input = parseContract(GateOptionsSchema, options, "invalid oneharness gate options");
|
|
401
|
+
const verdict = await invokeText(this.options, capabilityArguments("gate", input), input.event);
|
|
402
|
+
return verdict.trim() === "" ? null : verdict;
|
|
403
|
+
}
|
|
404
|
+
/** The read-write sibling of {@link gate}, driven by a rules file. */
|
|
405
|
+
async mock(options) {
|
|
406
|
+
const input = parseContract(MockOptionsSchema, options, "invalid oneharness mock options");
|
|
407
|
+
const verdict = await invokeText(this.options, capabilityArguments("mock", input), input.event);
|
|
408
|
+
return verdict.trim() === "" ? null : verdict;
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* Abort the in-flight turn of a `run --control` session, optionally
|
|
412
|
+
* redirecting it with `input`.
|
|
413
|
+
*
|
|
414
|
+
* A refusal is an answer, not a throw: the response says whether the abort
|
|
415
|
+
* was served and, when it was not, why — which is what a supervisor branches
|
|
416
|
+
* on. So a non-zero exit still yields the frame.
|
|
417
|
+
*/
|
|
418
|
+
async interrupt(options) {
|
|
419
|
+
return await this.call("interrupt", options, InterruptOptionsSchema, InterruptResponseSchema, { acceptJsonOnNonzero: true });
|
|
228
420
|
}
|
|
229
421
|
async history(lookup) {
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
//
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
else
|
|
241
|
-
args.push(input.session);
|
|
242
|
-
if (input.project)
|
|
243
|
-
args.push("--project", input.project);
|
|
244
|
-
if (input.allProjects)
|
|
245
|
-
args.push("--all-projects");
|
|
246
|
-
if (input.historyDir)
|
|
247
|
-
args.push("--history-dir", input.historyDir);
|
|
248
|
-
let value;
|
|
249
|
-
try {
|
|
250
|
-
value = await invokeWith(this.options, args);
|
|
251
|
-
}
|
|
252
|
-
catch (error) {
|
|
253
|
-
if (historyNotFound(error))
|
|
254
|
-
throw asHistoryNotFound(error);
|
|
255
|
-
throw error;
|
|
256
|
-
}
|
|
257
|
-
return parseContract(HistoryRecordsSchema, value, "invalid history contract");
|
|
422
|
+
// The `--last`-suppresses-a-name rule is declared, not re-derived here:
|
|
423
|
+
// the union deliberately accepts `{session, last: true}` and resolves it
|
|
424
|
+
// to "the most recent", and the manifest binds `session` with
|
|
425
|
+
// `unless: "last"` so the builder renders only `--last`. Rust, the
|
|
426
|
+
// generated Zod, and this call therefore agree by construction.
|
|
427
|
+
return await this.call("history", lookup, HistoryLookupSchema, HistoryRecordsSchema, {
|
|
428
|
+
history: true,
|
|
429
|
+
optionsLabel: "invalid oneharness history options",
|
|
430
|
+
contractLabel: "invalid history contract",
|
|
431
|
+
});
|
|
258
432
|
}
|
|
259
433
|
async historyList(options = {}) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
434
|
+
return await this.call("historyList", options, HistoryListOptionsSchema, HistoryListSchema, {
|
|
435
|
+
optionsLabel: "invalid oneharness history list options",
|
|
436
|
+
contractLabel: "invalid history list contract",
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/**
|
|
440
|
+
* Remove history sessions.
|
|
441
|
+
*
|
|
442
|
+
* A dry run until `yes` is set, and the two answers are different documents:
|
|
443
|
+
* `dry_run` discriminates them, so a caller reads `removed` only from a run
|
|
444
|
+
* that removed something.
|
|
445
|
+
*/
|
|
446
|
+
async historyClear(options = {}) {
|
|
447
|
+
return await this.call("historyClear", options, HistoryClearOptionsSchema, HistoryClearReportSchema);
|
|
448
|
+
}
|
|
449
|
+
/** Rewrite legacy session files to the current record version. */
|
|
450
|
+
async historyMigrate(options = {}) {
|
|
451
|
+
return await this.call("historyMigrate", options, HistoryMigrateOptionsSchema, HistoryMigrateReportSchema);
|
|
270
452
|
}
|
|
271
453
|
historyWatch(options = {}) {
|
|
272
454
|
const input = parseContract(HistoryWatchOptionsSchema, options, "invalid oneharness history watch options");
|
|
273
|
-
|
|
274
|
-
if (input.after)
|
|
275
|
-
args.push("--after", input.after);
|
|
276
|
-
for (const [key, value] of Object.entries(input.labels ?? {}))
|
|
277
|
-
args.push("--label", `${key}=${value}`);
|
|
278
|
-
if (input.project)
|
|
279
|
-
args.push("--project", input.project);
|
|
280
|
-
if (input.allProjects)
|
|
281
|
-
args.push("--all-projects");
|
|
282
|
-
if (input.historyDir)
|
|
283
|
-
args.push("--history-dir", input.historyDir);
|
|
284
|
-
if (input.events)
|
|
285
|
-
args.push("--events");
|
|
286
|
-
return invokeStreamWith(this.options, args, HistoryStreamEnvelopeSchema, "invalid oneharness history watch contract", undefined, true);
|
|
455
|
+
return invokeStreamWith(this.options, capabilityArguments("historyWatch", input), HistoryStreamEnvelopeSchema, "invalid oneharness history watch contract", undefined, true);
|
|
287
456
|
}
|
|
288
457
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oneharness/sdk",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.15",
|
|
4
4
|
"description": "Typed Node.js SDK for oneharness",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -19,7 +19,7 @@
|
|
|
19
19
|
"node": ">=20"
|
|
20
20
|
},
|
|
21
21
|
"dependencies": {
|
|
22
|
-
"oneharness-cli": "0.6.
|
|
22
|
+
"oneharness-cli": "0.6.15",
|
|
23
23
|
"zod": "^4.4.3"
|
|
24
24
|
}
|
|
25
25
|
}
|