@impulselab/hepha 0.2.179 → 0.2.181
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/dist/index.js +1129 -860
- package/dist/index.js.map +1 -1
- package/package.json +5 -4
- package/skills/cli/SKILL.md +1 -1
package/dist/index.js
CHANGED
|
@@ -1,16 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
// src/lib/config.ts
|
|
4
|
-
import {
|
|
5
|
-
chmodSync,
|
|
6
|
-
mkdirSync,
|
|
7
|
-
readFileSync,
|
|
8
|
-
rmSync,
|
|
9
|
-
writeFileSync
|
|
10
|
-
} from "fs";
|
|
11
|
-
import { homedir } from "os";
|
|
12
|
-
import { dirname, join } from "path";
|
|
13
|
-
|
|
14
3
|
// ../../packages/core/src/tagged-error.ts
|
|
15
4
|
var TaggedError = class _TaggedError extends Error {
|
|
16
5
|
_tag;
|
|
@@ -27,7 +16,7 @@ var TaggedError = class _TaggedError extends Error {
|
|
|
27
16
|
}
|
|
28
17
|
};
|
|
29
18
|
|
|
30
|
-
// src/lib/errors.ts
|
|
19
|
+
// src/lib/shared/errors-constants.ts
|
|
31
20
|
var CLI_ERROR_CODES = {
|
|
32
21
|
BAD_USAGE: "BAD_USAGE",
|
|
33
22
|
/** The session needs a person — the exit-3 family. */
|
|
@@ -43,6 +32,8 @@ var CLI_ERROR_CODES = {
|
|
|
43
32
|
NON_JSON_RESPONSE: "NON_JSON_RESPONSE",
|
|
44
33
|
ERROR: "ERROR"
|
|
45
34
|
};
|
|
35
|
+
|
|
36
|
+
// src/lib/cli/cli-error.ts
|
|
46
37
|
var CliError = class extends TaggedError {
|
|
47
38
|
exitCode;
|
|
48
39
|
details;
|
|
@@ -74,91 +65,7 @@ var CliError = class extends TaggedError {
|
|
|
74
65
|
}
|
|
75
66
|
};
|
|
76
67
|
|
|
77
|
-
// src/lib/
|
|
78
|
-
function isHttpUrl(value) {
|
|
79
|
-
try {
|
|
80
|
-
const parsed = new URL(value);
|
|
81
|
-
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
82
|
-
} catch {
|
|
83
|
-
return false;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// src/lib/config.ts
|
|
88
|
-
var CONFIG_PATH = join(homedir(), ".hepha", "config.json");
|
|
89
|
-
var DEFAULT_BASE_URL = "https://hephaistos.impulselab.ai";
|
|
90
|
-
function readFileAsObject() {
|
|
91
|
-
try {
|
|
92
|
-
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
93
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
94
|
-
return {};
|
|
95
|
-
}
|
|
96
|
-
return parsed;
|
|
97
|
-
} catch {
|
|
98
|
-
return {};
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
function readString(value) {
|
|
102
|
-
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
103
|
-
}
|
|
104
|
-
function readUser(value) {
|
|
105
|
-
if (!value || typeof value !== "object") {
|
|
106
|
-
return void 0;
|
|
107
|
-
}
|
|
108
|
-
const candidate = value;
|
|
109
|
-
const user = {
|
|
110
|
-
id: readString(candidate.id),
|
|
111
|
-
name: readString(candidate.name),
|
|
112
|
-
email: readString(candidate.email)
|
|
113
|
-
};
|
|
114
|
-
return user.id && user.name && user.email ? { id: user.id, name: user.name, email: user.email } : void 0;
|
|
115
|
-
}
|
|
116
|
-
function readHttpUrl(value) {
|
|
117
|
-
const raw = readString(value);
|
|
118
|
-
return raw && isHttpUrl(raw) ? raw : void 0;
|
|
119
|
-
}
|
|
120
|
-
function isSameOrigin(a, b) {
|
|
121
|
-
try {
|
|
122
|
-
return new URL(a).origin === new URL(b).origin;
|
|
123
|
-
} catch {
|
|
124
|
-
return false;
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
var config = {
|
|
128
|
-
path: CONFIG_PATH,
|
|
129
|
-
load() {
|
|
130
|
-
const stored = readFileAsObject();
|
|
131
|
-
const storedBaseUrl = readHttpUrl(stored.baseUrl);
|
|
132
|
-
const override = readString(process.env.HEPHA_URL);
|
|
133
|
-
if (override && !readHttpUrl(override)) {
|
|
134
|
-
throw new CliError(
|
|
135
|
-
`HEPHA_URL is not a valid http(s) URL: "${override}".`,
|
|
136
|
-
2
|
|
137
|
-
);
|
|
138
|
-
}
|
|
139
|
-
const baseUrl = readHttpUrl(override) ?? storedBaseUrl ?? DEFAULT_BASE_URL;
|
|
140
|
-
const token = storedBaseUrl && isSameOrigin(storedBaseUrl, baseUrl) ? readString(stored.token) : void 0;
|
|
141
|
-
const user = token ? readUser(stored.user) : void 0;
|
|
142
|
-
return {
|
|
143
|
-
baseUrl,
|
|
144
|
-
...token ? { token } : {},
|
|
145
|
-
...user ? { user } : {}
|
|
146
|
-
};
|
|
147
|
-
},
|
|
148
|
-
save(next) {
|
|
149
|
-
mkdirSync(dirname(CONFIG_PATH), { recursive: true, mode: 448 });
|
|
150
|
-
writeFileSync(CONFIG_PATH, `${JSON.stringify(next, null, 2)}
|
|
151
|
-
`, {
|
|
152
|
-
mode: 384
|
|
153
|
-
});
|
|
154
|
-
chmodSync(CONFIG_PATH, 384);
|
|
155
|
-
},
|
|
156
|
-
clear() {
|
|
157
|
-
rmSync(CONFIG_PATH, { force: true });
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
// src/lib/output.ts
|
|
68
|
+
// src/lib/shared/output-constants.ts
|
|
162
69
|
var COLORS = {
|
|
163
70
|
reset: "\x1B[0m",
|
|
164
71
|
dim: "\x1B[2m",
|
|
@@ -184,59 +91,68 @@ var STATUS_COLORS = {
|
|
|
184
91
|
killed: "red",
|
|
185
92
|
abstained: "yellow"
|
|
186
93
|
};
|
|
187
|
-
|
|
94
|
+
|
|
95
|
+
// src/lib/ansi/strip-ansi.ts
|
|
96
|
+
function stripAnsi(text) {
|
|
97
|
+
return text.replace(/\u001b\[\d+m/g, "");
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// src/lib/supports/supports-color.ts
|
|
101
|
+
function supportsColor(jsonMode) {
|
|
188
102
|
return process.stdout.isTTY === true && process.env.NO_COLOR === void 0 && !jsonMode;
|
|
189
103
|
}
|
|
190
|
-
|
|
191
|
-
|
|
104
|
+
|
|
105
|
+
// src/lib/cli/cli-output.ts
|
|
106
|
+
var CliOutput = class {
|
|
107
|
+
jsonMode = false;
|
|
192
108
|
/** Declared once per run, before anything can fail. */
|
|
193
109
|
setJson(enabled) {
|
|
194
|
-
jsonMode = enabled;
|
|
195
|
-
}
|
|
110
|
+
this.jsonMode = enabled;
|
|
111
|
+
}
|
|
196
112
|
isJson() {
|
|
197
|
-
return jsonMode;
|
|
198
|
-
}
|
|
113
|
+
return this.jsonMode;
|
|
114
|
+
}
|
|
199
115
|
json(value) {
|
|
200
116
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
201
117
|
`);
|
|
202
|
-
}
|
|
118
|
+
}
|
|
203
119
|
line(text = "") {
|
|
204
120
|
process.stdout.write(`${text}
|
|
205
121
|
`);
|
|
206
|
-
}
|
|
122
|
+
}
|
|
207
123
|
error(text) {
|
|
208
|
-
process.stderr.write(`${
|
|
124
|
+
process.stderr.write(`${this.paint(`error: ${this.safe(text)}`, "red")}
|
|
209
125
|
`);
|
|
210
|
-
}
|
|
126
|
+
}
|
|
211
127
|
/**
|
|
212
128
|
* The one place a failure is reported. Under `--json` stdout must be
|
|
213
129
|
* parseable, and it was not: failures went to stderr as prose. Success keeps
|
|
214
130
|
* its bare shape, so nothing that already parses stdout has to change.
|
|
215
131
|
*/
|
|
216
132
|
failure(error) {
|
|
217
|
-
if (!jsonMode) {
|
|
218
|
-
|
|
133
|
+
if (!this.jsonMode) {
|
|
134
|
+
this.error(error.message);
|
|
219
135
|
return;
|
|
220
136
|
}
|
|
221
|
-
|
|
137
|
+
this.json({
|
|
222
138
|
ok: false,
|
|
223
139
|
error: {
|
|
224
140
|
...error.details.status === void 0 ? {} : { status: error.details.status },
|
|
225
141
|
code: error.code,
|
|
226
|
-
message:
|
|
142
|
+
message: this.safe(error.message),
|
|
227
143
|
...error.details.issues ? { issues: error.details.issues } : {},
|
|
228
144
|
...error.details.retryAfterSeconds === void 0 ? {} : { retryAfterSeconds: error.details.retryAfterSeconds }
|
|
229
145
|
},
|
|
230
146
|
// Inside the envelope, so stdout stays one parseable document.
|
|
231
147
|
...error.result === void 0 ? {} : { result: error.result }
|
|
232
148
|
});
|
|
233
|
-
}
|
|
149
|
+
}
|
|
234
150
|
paint(text, color) {
|
|
235
|
-
if (!supportsColor()) {
|
|
151
|
+
if (!supportsColor(this.jsonMode)) {
|
|
236
152
|
return text;
|
|
237
153
|
}
|
|
238
154
|
return `${COLORS[color] ?? ""}${text}${COLORS.reset}`;
|
|
239
|
-
}
|
|
155
|
+
}
|
|
240
156
|
/**
|
|
241
157
|
* Neutralise terminal control sequences in text the platform gives us.
|
|
242
158
|
*
|
|
@@ -249,222 +165,300 @@ var out = {
|
|
|
249
165
|
*/
|
|
250
166
|
safe(text) {
|
|
251
167
|
return text.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f]/g, "");
|
|
252
|
-
}
|
|
168
|
+
}
|
|
253
169
|
/** `safe`, flattened to one line — anything going into a table cell. */
|
|
254
170
|
cell(text) {
|
|
255
|
-
return
|
|
256
|
-
}
|
|
171
|
+
return this.safe(text).replace(/\s+/g, " ").trim();
|
|
172
|
+
}
|
|
257
173
|
status(status) {
|
|
258
|
-
return
|
|
259
|
-
}
|
|
174
|
+
return this.paint(status, STATUS_COLORS[status] ?? "reset");
|
|
175
|
+
}
|
|
260
176
|
/** Left-aligned two-column block — the shape used by every list command. */
|
|
261
177
|
table(rows) {
|
|
262
178
|
if (rows.length === 0) {
|
|
263
179
|
return;
|
|
264
180
|
}
|
|
265
|
-
const
|
|
181
|
+
const firstRow = rows[0];
|
|
182
|
+
if (firstRow === void 0) {
|
|
183
|
+
return;
|
|
184
|
+
}
|
|
185
|
+
const widths = firstRow.map(
|
|
266
186
|
(_, column) => Math.max(...rows.map((row) => stripAnsi(row[column] ?? "").length))
|
|
267
187
|
);
|
|
268
188
|
for (const row of rows) {
|
|
269
189
|
const line = row.map((cell, column) => {
|
|
270
|
-
const
|
|
190
|
+
const width = widths[column];
|
|
191
|
+
if (width === void 0) {
|
|
192
|
+
return cell;
|
|
193
|
+
}
|
|
194
|
+
const padding = width - stripAnsi(cell).length;
|
|
271
195
|
return column === row.length - 1 ? cell : cell + " ".repeat(padding);
|
|
272
196
|
}).join(" ");
|
|
273
|
-
|
|
197
|
+
this.line(line.trimEnd());
|
|
274
198
|
}
|
|
275
199
|
}
|
|
276
200
|
};
|
|
277
|
-
function stripAnsi(text) {
|
|
278
|
-
return text.replace(/\u001b\[\d+m/g, "");
|
|
279
|
-
}
|
|
280
201
|
|
|
281
|
-
// src/lib/
|
|
282
|
-
var
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
202
|
+
// src/lib/output/out.ts
|
|
203
|
+
var out = new CliOutput();
|
|
204
|
+
|
|
205
|
+
// src/lib/shared/config.ts
|
|
206
|
+
import { chmodSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
207
|
+
import { dirname } from "path";
|
|
208
|
+
|
|
209
|
+
// src/lib/shared/config-constants.ts
|
|
210
|
+
var DEFAULT_BASE_URL = "https://hephaistos.impulselab.ai";
|
|
211
|
+
|
|
212
|
+
// src/lib/path/config-path-constants.ts
|
|
213
|
+
import { homedir } from "os";
|
|
214
|
+
import { join } from "path";
|
|
215
|
+
var CONFIG_PATH = join(homedir(), ".hepha", "config.json");
|
|
216
|
+
|
|
217
|
+
// src/lib/file/read-file-as-object.ts
|
|
218
|
+
import { readFileSync } from "fs";
|
|
219
|
+
function readFileAsObject() {
|
|
220
|
+
try {
|
|
221
|
+
const parsed = JSON.parse(readFileSync(CONFIG_PATH, "utf8"));
|
|
222
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
223
|
+
return {};
|
|
224
|
+
}
|
|
225
|
+
return parsed;
|
|
226
|
+
} catch {
|
|
227
|
+
return {};
|
|
297
228
|
}
|
|
298
|
-
return parsed;
|
|
299
229
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
const apiKey = process.env.HEPHA_API_KEY;
|
|
305
|
-
if (apiKey) {
|
|
306
|
-
return { "x-api-key": apiKey };
|
|
307
|
-
}
|
|
308
|
-
const token = process.env.HEPHA_TOKEN ?? config.load().token;
|
|
309
|
-
if (token) {
|
|
310
|
-
return { authorization: `Bearer ${token}` };
|
|
311
|
-
}
|
|
312
|
-
throw new CliError(
|
|
313
|
-
"Not signed in. Run `hepha login`, or set HEPHA_API_KEY to act as an agent.",
|
|
314
|
-
1,
|
|
315
|
-
{ code: CLI_ERROR_CODES.NOT_AUTHENTICATED }
|
|
316
|
-
);
|
|
230
|
+
|
|
231
|
+
// src/lib/string/config-read-string.ts
|
|
232
|
+
function configReadString(value) {
|
|
233
|
+
return typeof value === "string" && value.trim() ? value.trim() : void 0;
|
|
317
234
|
}
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
const data = payload.data;
|
|
323
|
-
if (!data || typeof data !== "object") {
|
|
235
|
+
|
|
236
|
+
// src/lib/user/read-user.ts
|
|
237
|
+
function readUser(value) {
|
|
238
|
+
if (!value || typeof value !== "object") {
|
|
324
239
|
return void 0;
|
|
325
240
|
}
|
|
326
|
-
const
|
|
327
|
-
|
|
241
|
+
const candidate = value;
|
|
242
|
+
const user = {
|
|
243
|
+
id: configReadString(candidate.id),
|
|
244
|
+
name: configReadString(candidate.name),
|
|
245
|
+
email: configReadString(candidate.email)
|
|
246
|
+
};
|
|
247
|
+
return user.id && user.name && user.email ? { id: user.id, name: user.name, email: user.email } : void 0;
|
|
328
248
|
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
249
|
+
|
|
250
|
+
// src/lib/http/is-http-url.ts
|
|
251
|
+
function isHttpUrl(value) {
|
|
252
|
+
try {
|
|
253
|
+
const parsed = new URL(value);
|
|
254
|
+
return parsed.protocol === "http:" || parsed.protocol === "https:";
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
332
257
|
}
|
|
333
|
-
const value = payload[key];
|
|
334
|
-
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
335
258
|
}
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
const seconds = Number(raw.trim());
|
|
342
|
-
return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
|
|
259
|
+
|
|
260
|
+
// src/lib/http/read-http-url.ts
|
|
261
|
+
function readHttpUrl(value) {
|
|
262
|
+
const raw = configReadString(value);
|
|
263
|
+
return raw && isHttpUrl(raw) ? raw : void 0;
|
|
343
264
|
}
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
return
|
|
265
|
+
|
|
266
|
+
// src/lib/same/is-same-origin.ts
|
|
267
|
+
function isSameOrigin(a, b) {
|
|
268
|
+
try {
|
|
269
|
+
return new URL(a).origin === new URL(b).origin;
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
351
272
|
}
|
|
352
|
-
return `Request failed with status ${status}.`;
|
|
353
|
-
}
|
|
354
|
-
function excerpt(text) {
|
|
355
|
-
const flattened = out.cell(text);
|
|
356
|
-
return flattened.length > 200 ? `${flattened.slice(0, 200)}\u2026` : flattened;
|
|
357
273
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
274
|
+
|
|
275
|
+
// src/lib/shared/config.ts
|
|
276
|
+
var config = {
|
|
277
|
+
path: CONFIG_PATH,
|
|
278
|
+
load() {
|
|
279
|
+
const stored = readFileAsObject();
|
|
280
|
+
const storedBaseUrl = readHttpUrl(stored.baseUrl);
|
|
281
|
+
const override = configReadString(process.env.HEPHA_URL);
|
|
282
|
+
if (override && !readHttpUrl(override)) {
|
|
283
|
+
throw new CliError(
|
|
284
|
+
`HEPHA_URL is not a valid http(s) URL: "${override}".`,
|
|
285
|
+
2
|
|
286
|
+
);
|
|
364
287
|
}
|
|
288
|
+
const baseUrl = readHttpUrl(override) ?? storedBaseUrl ?? DEFAULT_BASE_URL;
|
|
289
|
+
const token = storedBaseUrl && isSameOrigin(storedBaseUrl, baseUrl) ? configReadString(stored.token) : void 0;
|
|
290
|
+
const user = token ? readUser(stored.user) : void 0;
|
|
291
|
+
return {
|
|
292
|
+
baseUrl,
|
|
293
|
+
...token ? { token } : {},
|
|
294
|
+
...user ? { user } : {}
|
|
295
|
+
};
|
|
296
|
+
},
|
|
297
|
+
save(next) {
|
|
298
|
+
mkdirSync(dirname(CONFIG_PATH), { recursive: true, mode: 448 });
|
|
299
|
+
writeFileSync(CONFIG_PATH, `${JSON.stringify(next, null, 2)}
|
|
300
|
+
`, {
|
|
301
|
+
mode: 384
|
|
302
|
+
});
|
|
303
|
+
chmodSync(CONFIG_PATH, 384);
|
|
304
|
+
},
|
|
305
|
+
clear() {
|
|
306
|
+
rmSync(CONFIG_PATH, { force: true });
|
|
365
307
|
}
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
let
|
|
374
|
-
|
|
375
|
-
abortedBy ??= "timeout";
|
|
376
|
-
controller.abort();
|
|
377
|
-
}, deadline);
|
|
378
|
-
const onInterrupt = () => {
|
|
379
|
-
abortedBy ??= "interrupt";
|
|
380
|
-
controller.abort();
|
|
381
|
-
};
|
|
382
|
-
process.once("SIGINT", onInterrupt);
|
|
383
|
-
let response;
|
|
384
|
-
let text;
|
|
385
|
-
try {
|
|
386
|
-
response = await fetch(url, {
|
|
387
|
-
method,
|
|
388
|
-
headers,
|
|
389
|
-
signal: controller.signal,
|
|
390
|
-
...options.body === void 0 ? {} : { body: JSON.stringify(options.body) }
|
|
391
|
-
});
|
|
392
|
-
text = await response.text();
|
|
393
|
-
} catch (cause) {
|
|
394
|
-
if (abortedBy === "interrupt") {
|
|
395
|
-
throw new CliError(`${method} ${url.pathname} interrupted.`, 130, {
|
|
396
|
-
code: CLI_ERROR_CODES.INTERRUPTED
|
|
397
|
-
});
|
|
398
|
-
}
|
|
399
|
-
if (abortedBy === "timeout") {
|
|
400
|
-
throw new CliError(
|
|
401
|
-
`${method} ${url.pathname} timed out after ${deadline}ms. Raise HEPHA_TIMEOUT_MS if the platform is simply slow.`,
|
|
402
|
-
1,
|
|
403
|
-
{ code: CLI_ERROR_CODES.TIMEOUT }
|
|
404
|
-
);
|
|
405
|
-
}
|
|
406
|
-
throw new CliError(
|
|
407
|
-
`Cannot reach ${baseUrl}. Set HEPHA_URL if the platform lives elsewhere. (${String(cause)})`,
|
|
408
|
-
1,
|
|
409
|
-
{ code: CLI_ERROR_CODES.NETWORK }
|
|
410
|
-
);
|
|
411
|
-
} finally {
|
|
412
|
-
clearTimeout(timer);
|
|
413
|
-
process.removeListener("SIGINT", onInterrupt);
|
|
414
|
-
}
|
|
415
|
-
const retryAfter = retryAfterSeconds(response);
|
|
416
|
-
let payload = null;
|
|
417
|
-
if (text) {
|
|
308
|
+
};
|
|
309
|
+
|
|
310
|
+
// src/lib/version/read-version.ts
|
|
311
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
312
|
+
import { dirname as dirname2, join as join2 } from "path";
|
|
313
|
+
import { fileURLToPath } from "url";
|
|
314
|
+
function readVersion() {
|
|
315
|
+
let directory = dirname2(fileURLToPath(import.meta.url));
|
|
316
|
+
for (let depth = 0; depth < 5; depth += 1) {
|
|
418
317
|
try {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
const body = excerpt(text);
|
|
422
|
-
const contentType = response.headers.get("content-type") ?? "unknown";
|
|
423
|
-
throw new CliError(
|
|
424
|
-
response.ok ? `${method} ${url.pathname} answered ${response.status} with a non-JSON body (${contentType}). Is HEPHA_URL pointing at a Hephaistos deployment?${body ? ` Body: ${body}` : ""}` : `${method} ${url.pathname} failed with ${response.status} and a non-JSON body (${contentType}).${body ? ` Body: ${body}` : ""}`,
|
|
425
|
-
1,
|
|
426
|
-
{
|
|
427
|
-
status: response.status,
|
|
428
|
-
code: CLI_ERROR_CODES.NON_JSON_RESPONSE,
|
|
429
|
-
...retryAfter === void 0 ? {} : { retryAfterSeconds: retryAfter }
|
|
430
|
-
}
|
|
318
|
+
const manifest = JSON.parse(
|
|
319
|
+
readFileSync2(join2(directory, "package.json"), "utf8")
|
|
431
320
|
);
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
const code = readString2(payload, "code") ?? readString2(payload, "error");
|
|
436
|
-
const issues = readIssues(payload);
|
|
437
|
-
throw new CliError(
|
|
438
|
-
messageFrom(response.status, payload) + (response.status === 429 && retryAfter !== void 0 ? ` Retry in ${retryAfter}s.` : ""),
|
|
439
|
-
1,
|
|
440
|
-
{
|
|
441
|
-
status: response.status,
|
|
442
|
-
...code ? { code } : {},
|
|
443
|
-
...issues ? { issues } : {},
|
|
444
|
-
...retryAfter === void 0 ? {} : { retryAfterSeconds: retryAfter }
|
|
321
|
+
const version = manifest.version;
|
|
322
|
+
if (typeof version === "string" && version.length > 0) {
|
|
323
|
+
return version;
|
|
445
324
|
}
|
|
446
|
-
|
|
325
|
+
} catch {
|
|
326
|
+
}
|
|
327
|
+
const parent = dirname2(directory);
|
|
328
|
+
if (parent === directory) {
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
directory = parent;
|
|
447
332
|
}
|
|
448
|
-
return
|
|
333
|
+
return "unknown";
|
|
449
334
|
}
|
|
450
335
|
|
|
451
|
-
// src/lib/
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
336
|
+
// src/lib/version/version-constants.ts
|
|
337
|
+
var VERSION = readVersion();
|
|
338
|
+
|
|
339
|
+
// src/commands/help/help-constants.ts
|
|
340
|
+
var HELP = `hepha \u2014 orchestrate Hephaistos sessions from a terminal or an agent.
|
|
341
|
+
|
|
342
|
+
USAGE
|
|
343
|
+
hepha <command> [args] [--flags]
|
|
344
|
+
|
|
345
|
+
THE LOOP
|
|
346
|
+
hepha run "<prompt>" --repo owner/repo --follow
|
|
347
|
+
Start a session and follow it until it finishes or asks you something.
|
|
348
|
+
Exit 0 completed \xB7 3 waiting on a human \xB7 1 failed.
|
|
349
|
+
[--title "\u2026"] [--task-type implement|new-project|question|draft|orchestrator]
|
|
350
|
+
[--model <slug>] [--harness claude-code|codex] [--branch <name>]
|
|
351
|
+
[--skill <id>]\u2026
|
|
352
|
+
hepha questions list [--session <id>]
|
|
353
|
+
hepha questions show <batchId>
|
|
354
|
+
hepha questions answer <batchId> --option <questionId>=1,2 --text <questionId>="\u2026"
|
|
355
|
+
[--answers '<json array>']
|
|
356
|
+
hepha questions cancel <batchId>
|
|
357
|
+
hepha approvals list | show <id> | approve <id> | reject <id> --mode rework --feedback "\u2026"
|
|
358
|
+
|
|
359
|
+
SESSIONS
|
|
360
|
+
hepha sessions list [--status active|waiting|done] [--repo owner/repo]
|
|
361
|
+
[--search "\u2026"] [--page N] [--limit N] [--all]
|
|
362
|
+
hepha sessions get <id>
|
|
363
|
+
hepha sessions logs <id> [--after <sequence>] [--limit N] [--follow]
|
|
364
|
+
hepha sessions send <id> "<message>"
|
|
365
|
+
hepha sessions pause|resume|archive <id>
|
|
366
|
+
hepha sessions kill <id> [--reason "\u2026"]
|
|
367
|
+
hepha sessions checks|pr|verify <id>
|
|
368
|
+
|
|
369
|
+
CATALOG
|
|
370
|
+
hepha repos
|
|
371
|
+
hepha branches <owner/repo>
|
|
372
|
+
hepha models
|
|
373
|
+
hepha skills
|
|
374
|
+
|
|
375
|
+
AUTH
|
|
376
|
+
hepha login [--url https://\u2026] Device authorization: approve it in a browser.
|
|
377
|
+
hepha logout
|
|
378
|
+
hepha whoami
|
|
379
|
+
hepha keys list
|
|
380
|
+
hepha keys create <name> [--scope sessions:read]\u2026 [--expires-in-days 90]
|
|
381
|
+
hepha keys revoke <keyId>
|
|
382
|
+
|
|
383
|
+
ENVIRONMENT
|
|
384
|
+
HEPHA_API_KEY Act as an agent. Takes precedence over a stored login, so
|
|
385
|
+
the human-only commands (approvals approve/reject, keys)
|
|
386
|
+
answer 403 while it is set \u2014 unset it to act as yourself.
|
|
387
|
+
HEPHA_TOKEN A device-flow session token, instead of ~/.hepha/config.json.
|
|
388
|
+
HEPHA_URL Platform base URL.
|
|
389
|
+
HEPHA_TIMEOUT_MS Per-request timeout in milliseconds (default 30000).
|
|
390
|
+
|
|
391
|
+
GLOBAL FLAGS
|
|
392
|
+
--json Machine-readable output on stdout. Every command that
|
|
393
|
+
returns data honours it; login is interactive and does not.
|
|
394
|
+
Failures print {"ok":false,"error":{\u2026}} and keep their exit code.
|
|
395
|
+
--quiet Suppress the streamed transcript while following a session.
|
|
396
|
+
--version Print the version and exit.
|
|
397
|
+
--help Print this.
|
|
398
|
+
|
|
399
|
+
Unknown flags and surplus arguments are refused: for an agent, a typo must
|
|
400
|
+
fail rather than run with default options.
|
|
401
|
+
|
|
402
|
+
`;
|
|
403
|
+
|
|
404
|
+
// src/commands/command/help-command.ts
|
|
405
|
+
function helpCommand() {
|
|
406
|
+
const baseUrl = config.load().baseUrl.replace(/\/$/, "");
|
|
407
|
+
out.line(`${HELP}hepha ${VERSION} \xB7 full reference: ${baseUrl}/api/v1/docs`);
|
|
457
408
|
}
|
|
458
409
|
|
|
459
|
-
// src/lib/args.ts
|
|
410
|
+
// src/lib/args/args-constants.ts
|
|
460
411
|
var GLOBAL_BOOLEANS = ["json", "help", "quiet"];
|
|
412
|
+
|
|
413
|
+
// src/lib/boolean/boolean-flags-constants.ts
|
|
461
414
|
var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
|
|
462
415
|
...GLOBAL_BOOLEANS,
|
|
463
416
|
"follow",
|
|
464
417
|
"all",
|
|
465
418
|
"version"
|
|
466
419
|
]);
|
|
467
|
-
|
|
420
|
+
|
|
421
|
+
// src/lib/args/parse-flag.ts
|
|
422
|
+
function parseFlag(argv, index, parsed) {
|
|
423
|
+
const token = argv[index];
|
|
424
|
+
if (token === void 0) {
|
|
425
|
+
return index;
|
|
426
|
+
}
|
|
427
|
+
const withoutDashes = token.slice(2);
|
|
428
|
+
const equalsAt = withoutDashes.indexOf("=");
|
|
429
|
+
let name;
|
|
430
|
+
let value;
|
|
431
|
+
if (equalsAt >= 0) {
|
|
432
|
+
name = withoutDashes.slice(0, equalsAt);
|
|
433
|
+
value = withoutDashes.slice(equalsAt + 1);
|
|
434
|
+
if (BOOLEAN_FLAGS.has(name)) {
|
|
435
|
+
throw new CliError(
|
|
436
|
+
`--${name} is a boolean flag: pass --${name} to enable it, or omit it. "--${name}=${value}" is not a value.`,
|
|
437
|
+
2
|
|
438
|
+
);
|
|
439
|
+
}
|
|
440
|
+
} else {
|
|
441
|
+
name = withoutDashes;
|
|
442
|
+
const next = argv[index + 1];
|
|
443
|
+
if (!BOOLEAN_FLAGS.has(name) && next !== void 0 && !next.startsWith("--")) {
|
|
444
|
+
value = next;
|
|
445
|
+
index += 1;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (value === void 0 || value.trim() === "") {
|
|
449
|
+
if (!BOOLEAN_FLAGS.has(name)) {
|
|
450
|
+
throw new CliError(`--${name} expects a value.`, 2);
|
|
451
|
+
}
|
|
452
|
+
parsed.booleans.add(name);
|
|
453
|
+
return index;
|
|
454
|
+
}
|
|
455
|
+
parsed.flags[name] = value;
|
|
456
|
+
(parsed.repeated[name] ??= []).push(value);
|
|
457
|
+
return index;
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/lib/args/args-parser.ts
|
|
461
|
+
var ArgsParser = class {
|
|
468
462
|
parse(argv) {
|
|
469
463
|
const parsed = {
|
|
470
464
|
positional: [],
|
|
@@ -475,6 +469,9 @@ var args = {
|
|
|
475
469
|
let flagsEnded = false;
|
|
476
470
|
for (let index = 0; index < argv.length; index += 1) {
|
|
477
471
|
const token = argv[index];
|
|
472
|
+
if (token === void 0) {
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
478
475
|
if (!flagsEnded && token === "--") {
|
|
479
476
|
flagsEnded = true;
|
|
480
477
|
continue;
|
|
@@ -483,39 +480,10 @@ var args = {
|
|
|
483
480
|
parsed.positional.push(token);
|
|
484
481
|
continue;
|
|
485
482
|
}
|
|
486
|
-
|
|
487
|
-
const equalsAt = withoutDashes.indexOf("=");
|
|
488
|
-
let name;
|
|
489
|
-
let value;
|
|
490
|
-
if (equalsAt >= 0) {
|
|
491
|
-
name = withoutDashes.slice(0, equalsAt);
|
|
492
|
-
value = withoutDashes.slice(equalsAt + 1);
|
|
493
|
-
if (BOOLEAN_FLAGS.has(name)) {
|
|
494
|
-
throw new CliError(
|
|
495
|
-
`--${name} is a boolean flag: pass --${name} to enable it, or omit it. "--${name}=${value}" is not a value.`,
|
|
496
|
-
2
|
|
497
|
-
);
|
|
498
|
-
}
|
|
499
|
-
} else {
|
|
500
|
-
name = withoutDashes;
|
|
501
|
-
const next = argv[index + 1];
|
|
502
|
-
if (!BOOLEAN_FLAGS.has(name) && next !== void 0 && !next.startsWith("--")) {
|
|
503
|
-
value = next;
|
|
504
|
-
index += 1;
|
|
505
|
-
}
|
|
506
|
-
}
|
|
507
|
-
if (value === void 0 || value.trim() === "") {
|
|
508
|
-
if (!BOOLEAN_FLAGS.has(name)) {
|
|
509
|
-
throw new CliError(`--${name} expects a value.`, 2);
|
|
510
|
-
}
|
|
511
|
-
parsed.booleans.add(name);
|
|
512
|
-
continue;
|
|
513
|
-
}
|
|
514
|
-
parsed.flags[name] = value;
|
|
515
|
-
(parsed.repeated[name] ??= []).push(value);
|
|
483
|
+
index = parseFlag(argv, index, parsed);
|
|
516
484
|
}
|
|
517
485
|
return parsed;
|
|
518
|
-
}
|
|
486
|
+
}
|
|
519
487
|
/**
|
|
520
488
|
* Refuse anything this action does not understand. Called once the action is
|
|
521
489
|
* known, because `sessions list` and `sessions logs` accept different flags.
|
|
@@ -539,7 +507,7 @@ var args = {
|
|
|
539
507
|
2
|
|
540
508
|
);
|
|
541
509
|
}
|
|
542
|
-
}
|
|
510
|
+
}
|
|
543
511
|
/** The action name, validated against what the group actually implements. */
|
|
544
512
|
action(parsed, group, allowed, fallback) {
|
|
545
513
|
const action = parsed.positional[0] ?? fallback;
|
|
@@ -550,14 +518,14 @@ var args = {
|
|
|
550
518
|
);
|
|
551
519
|
}
|
|
552
520
|
return action;
|
|
553
|
-
}
|
|
521
|
+
}
|
|
554
522
|
required(parsed, index, name) {
|
|
555
523
|
const value = parsed.positional[index];
|
|
556
524
|
if (!value) {
|
|
557
525
|
throw new CliError(`Missing <${name}>.`, 2);
|
|
558
526
|
}
|
|
559
527
|
return value;
|
|
560
|
-
}
|
|
528
|
+
}
|
|
561
529
|
/**
|
|
562
530
|
* A whole number inside a range, refused locally: `--page -1` used to come
|
|
563
531
|
* back from the API as a bare "Input validation failed".
|
|
@@ -582,7 +550,7 @@ var args = {
|
|
|
582
550
|
throw new CliError(`--${name} must be at most ${bounds.max}.`, 2);
|
|
583
551
|
}
|
|
584
552
|
return value;
|
|
585
|
-
}
|
|
553
|
+
}
|
|
586
554
|
/** One of a fixed set, named in the error so the fix is obvious. */
|
|
587
555
|
choice(parsed, name, allowed) {
|
|
588
556
|
const raw = parsed.flags[name];
|
|
@@ -599,34 +567,254 @@ var args = {
|
|
|
599
567
|
}
|
|
600
568
|
};
|
|
601
569
|
|
|
602
|
-
// src/lib/
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
function
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
570
|
+
// src/lib/args/args.ts
|
|
571
|
+
var args = new ArgsParser();
|
|
572
|
+
|
|
573
|
+
// src/lib/wants/wants-json.ts
|
|
574
|
+
function wantsJson(argv) {
|
|
575
|
+
for (const token of argv) {
|
|
576
|
+
if (token === "--") {
|
|
577
|
+
return false;
|
|
578
|
+
}
|
|
579
|
+
if (token === "--json") {
|
|
580
|
+
return true;
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
return false;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
// src/lib/api/api-constants.ts
|
|
587
|
+
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
588
|
+
|
|
589
|
+
// src/lib/timeout/resolve-timeout.ts
|
|
590
|
+
function resolveTimeout(override) {
|
|
591
|
+
if (override !== void 0) {
|
|
592
|
+
return override;
|
|
593
|
+
}
|
|
594
|
+
const raw = process.env.HEPHA_TIMEOUT_MS?.trim();
|
|
595
|
+
if (!raw) {
|
|
596
|
+
return DEFAULT_TIMEOUT_MS;
|
|
597
|
+
}
|
|
598
|
+
const parsed = Number(raw);
|
|
599
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
600
|
+
throw new CliError(
|
|
601
|
+
`HEPHA_TIMEOUT_MS expects a positive number of milliseconds, got "${raw}".`,
|
|
602
|
+
2
|
|
603
|
+
);
|
|
604
|
+
}
|
|
605
|
+
return parsed;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
// src/lib/auth/auth-headers.ts
|
|
609
|
+
function authHeaders(as) {
|
|
610
|
+
if (as) {
|
|
611
|
+
return { authorization: `Bearer ${as.token}` };
|
|
612
|
+
}
|
|
613
|
+
const apiKey = process.env.HEPHA_API_KEY;
|
|
614
|
+
if (apiKey) {
|
|
615
|
+
return { "x-api-key": apiKey };
|
|
616
|
+
}
|
|
617
|
+
const token = process.env.HEPHA_TOKEN ?? config.load().token;
|
|
618
|
+
if (token) {
|
|
619
|
+
return { authorization: `Bearer ${token}` };
|
|
620
|
+
}
|
|
621
|
+
throw new CliError(
|
|
622
|
+
"Not signed in. Run `hepha login`, or set HEPHA_API_KEY to act as an agent.",
|
|
623
|
+
1,
|
|
624
|
+
{ code: CLI_ERROR_CODES.NOT_AUTHENTICATED }
|
|
625
|
+
);
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// src/lib/retry/retry-after-seconds.ts
|
|
629
|
+
function retryAfterSeconds(response) {
|
|
630
|
+
const raw = response.headers.get("retry-after") ?? response.headers.get("x-retry-after");
|
|
631
|
+
if (!raw) {
|
|
632
|
+
return void 0;
|
|
633
|
+
}
|
|
634
|
+
const seconds = Number(raw.trim());
|
|
635
|
+
return Number.isFinite(seconds) && seconds >= 0 ? seconds : void 0;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
// src/lib/response/fetch-response.ts
|
|
639
|
+
async function fetchResponse({
|
|
640
|
+
url,
|
|
641
|
+
method,
|
|
642
|
+
headers,
|
|
643
|
+
body,
|
|
644
|
+
deadline,
|
|
645
|
+
baseUrl
|
|
646
|
+
}) {
|
|
647
|
+
const controller = new AbortController();
|
|
648
|
+
let abortedBy = null;
|
|
649
|
+
const timer = setTimeout(() => {
|
|
650
|
+
abortedBy ??= "timeout";
|
|
651
|
+
controller.abort();
|
|
652
|
+
}, deadline);
|
|
653
|
+
const onInterrupt = () => {
|
|
654
|
+
abortedBy ??= "interrupt";
|
|
655
|
+
controller.abort();
|
|
656
|
+
};
|
|
657
|
+
process.once("SIGINT", onInterrupt);
|
|
658
|
+
try {
|
|
659
|
+
const response = await fetch(url, {
|
|
660
|
+
method,
|
|
661
|
+
headers,
|
|
662
|
+
signal: controller.signal,
|
|
663
|
+
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
664
|
+
});
|
|
665
|
+
return [response, await response.text()];
|
|
666
|
+
} catch (cause) {
|
|
667
|
+
if (abortedBy === "interrupt") {
|
|
668
|
+
throw new CliError(`${method} ${url.pathname} interrupted.`, 130, {
|
|
669
|
+
code: CLI_ERROR_CODES.INTERRUPTED
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
if (abortedBy === "timeout") {
|
|
673
|
+
throw new CliError(
|
|
674
|
+
`${method} ${url.pathname} timed out after ${deadline}ms. Raise HEPHA_TIMEOUT_MS if the platform is simply slow.`,
|
|
675
|
+
1,
|
|
676
|
+
{ code: CLI_ERROR_CODES.TIMEOUT }
|
|
612
677
|
);
|
|
613
|
-
const version = manifest.version;
|
|
614
|
-
if (typeof version === "string" && version.length > 0) {
|
|
615
|
-
return version;
|
|
616
|
-
}
|
|
617
|
-
} catch {
|
|
618
678
|
}
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
679
|
+
throw new CliError(
|
|
680
|
+
`Cannot reach ${baseUrl}. Set HEPHA_URL if the platform lives elsewhere. (${String(cause)})`,
|
|
681
|
+
1,
|
|
682
|
+
{ code: CLI_ERROR_CODES.NETWORK }
|
|
683
|
+
);
|
|
684
|
+
} finally {
|
|
685
|
+
clearTimeout(timer);
|
|
686
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
// src/lib/api/add-query-parameters.ts
|
|
691
|
+
function addQueryParameters(url, query) {
|
|
692
|
+
for (const [key, value] of Object.entries(query ?? {})) {
|
|
693
|
+
if (value !== void 0) {
|
|
694
|
+
url.searchParams.set(key, String(value));
|
|
622
695
|
}
|
|
623
|
-
directory = parent;
|
|
624
696
|
}
|
|
625
|
-
return "unknown";
|
|
626
697
|
}
|
|
627
|
-
var VERSION = readVersion();
|
|
628
698
|
|
|
629
|
-
// src/lib/
|
|
699
|
+
// src/lib/excerpt/excerpt.ts
|
|
700
|
+
function excerpt(text) {
|
|
701
|
+
const flattened = out.cell(text);
|
|
702
|
+
return flattened.length > 200 ? `${flattened.slice(0, 200)}\u2026` : flattened;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/lib/api/parse-response-payload.ts
|
|
706
|
+
function parseResponsePayload(response, text, request) {
|
|
707
|
+
const { method, url, retryAfter } = request;
|
|
708
|
+
if (!text) {
|
|
709
|
+
return null;
|
|
710
|
+
}
|
|
711
|
+
try {
|
|
712
|
+
return JSON.parse(text);
|
|
713
|
+
} catch {
|
|
714
|
+
const body = excerpt(text);
|
|
715
|
+
const contentType = response.headers.get("content-type") ?? "unknown";
|
|
716
|
+
throw new CliError(
|
|
717
|
+
response.ok ? `${method} ${url.pathname} answered ${response.status} with a non-JSON body (${contentType}). Is HEPHA_URL pointing at a Hephaistos deployment?${body ? ` Body: ${body}` : ""}` : `${method} ${url.pathname} failed with ${response.status} and a non-JSON body (${contentType}).${body ? ` Body: ${body}` : ""}`,
|
|
718
|
+
1,
|
|
719
|
+
{
|
|
720
|
+
status: response.status,
|
|
721
|
+
code: CLI_ERROR_CODES.NON_JSON_RESPONSE,
|
|
722
|
+
...retryAfter === void 0 ? {} : { retryAfterSeconds: retryAfter }
|
|
723
|
+
}
|
|
724
|
+
);
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// src/lib/issues/read-issues.ts
|
|
729
|
+
function readIssues(payload) {
|
|
730
|
+
if (!payload || typeof payload !== "object") {
|
|
731
|
+
return void 0;
|
|
732
|
+
}
|
|
733
|
+
const data = payload.data;
|
|
734
|
+
if (!data || typeof data !== "object") {
|
|
735
|
+
return void 0;
|
|
736
|
+
}
|
|
737
|
+
const issues = data.issues;
|
|
738
|
+
return Array.isArray(issues) ? issues : void 0;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// src/lib/string/read-string.ts
|
|
742
|
+
function readString(payload, key) {
|
|
743
|
+
if (!payload || typeof payload !== "object") {
|
|
744
|
+
return void 0;
|
|
745
|
+
}
|
|
746
|
+
const value = payload[key];
|
|
747
|
+
return typeof value === "string" && value.length > 0 ? value : void 0;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
// src/lib/message/message-from.ts
|
|
751
|
+
function messageFrom(status, payload) {
|
|
752
|
+
const message = readString(payload, "message");
|
|
753
|
+
if (message) {
|
|
754
|
+
return status === 403 && process.env.HEPHA_API_KEY ? `${message} (HEPHA_API_KEY is set and takes precedence over your \`hepha login\` session \u2014 unset it to act as yourself.)` : message;
|
|
755
|
+
}
|
|
756
|
+
if (status === 401) {
|
|
757
|
+
return "Unauthorized \u2014 your credential is missing, expired or revoked.";
|
|
758
|
+
}
|
|
759
|
+
return `Request failed with status ${status}.`;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// src/lib/api/throw-response-error.ts
|
|
763
|
+
function throwResponseError(response, payload, retryAfter) {
|
|
764
|
+
const code = readString(payload, "code") ?? readString(payload, "error");
|
|
765
|
+
const issues = readIssues(payload);
|
|
766
|
+
throw new CliError(
|
|
767
|
+
messageFrom(response.status, payload) + (response.status === 429 && retryAfter !== void 0 ? ` Retry in ${retryAfter}s.` : ""),
|
|
768
|
+
1,
|
|
769
|
+
{
|
|
770
|
+
status: response.status,
|
|
771
|
+
...code ? { code } : {},
|
|
772
|
+
...issues ? { issues } : {},
|
|
773
|
+
...retryAfter === void 0 ? {} : { retryAfterSeconds: retryAfter }
|
|
774
|
+
}
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
// src/lib/api/api.ts
|
|
779
|
+
async function api(requestPath, options = {}) {
|
|
780
|
+
const baseUrl = options.as?.baseUrl ?? config.load().baseUrl;
|
|
781
|
+
const url = new URL(`${baseUrl.replace(/\/$/, "")}/api/v1${requestPath}`);
|
|
782
|
+
addQueryParameters(url, options.query);
|
|
783
|
+
const headers = {
|
|
784
|
+
...authHeaders(options.as),
|
|
785
|
+
...options.body === void 0 ? {} : { "content-type": "application/json" }
|
|
786
|
+
};
|
|
787
|
+
const method = options.method ?? "GET";
|
|
788
|
+
const deadline = resolveTimeout(options.timeoutMs);
|
|
789
|
+
const [response, text] = await fetchResponse({
|
|
790
|
+
url,
|
|
791
|
+
method,
|
|
792
|
+
headers,
|
|
793
|
+
body: options.body,
|
|
794
|
+
deadline,
|
|
795
|
+
baseUrl
|
|
796
|
+
});
|
|
797
|
+
const retryAfter = retryAfterSeconds(response);
|
|
798
|
+
const payload = parseResponsePayload(response, text, {
|
|
799
|
+
method,
|
|
800
|
+
url,
|
|
801
|
+
retryAfter
|
|
802
|
+
});
|
|
803
|
+
if (!response.ok) {
|
|
804
|
+
throwResponseError(response, payload, retryAfter);
|
|
805
|
+
}
|
|
806
|
+
return payload;
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
// src/lib/path/path.ts
|
|
810
|
+
function path(strings, ...values) {
|
|
811
|
+
return strings.reduce(
|
|
812
|
+
(accumulator, literal, index) => accumulator + (index === 0 ? "" : encodeURIComponent(values[index - 1] ?? "")) + literal,
|
|
813
|
+
""
|
|
814
|
+
);
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/lib/read/read.ts
|
|
630
818
|
function read(payload, dotted) {
|
|
631
819
|
return dotted.split(".").reduce((current, key) => {
|
|
632
820
|
if (!current || typeof current !== "object") {
|
|
@@ -635,6 +823,8 @@ function read(payload, dotted) {
|
|
|
635
823
|
return current[key];
|
|
636
824
|
}, payload);
|
|
637
825
|
}
|
|
826
|
+
|
|
827
|
+
// src/lib/expect/expect-shape.ts
|
|
638
828
|
function expectShape(payload, endpoint, requirements) {
|
|
639
829
|
for (const [field, predicate] of Object.entries(requirements)) {
|
|
640
830
|
if (!predicate(read(payload, field))) {
|
|
@@ -648,7 +838,7 @@ function expectShape(payload, endpoint, requirements) {
|
|
|
648
838
|
return payload;
|
|
649
839
|
}
|
|
650
840
|
|
|
651
|
-
// src/lib/is.ts
|
|
841
|
+
// src/lib/is/is.ts
|
|
652
842
|
var is = {
|
|
653
843
|
string: (value) => typeof value === "string",
|
|
654
844
|
number: (value) => typeof value === "number",
|
|
@@ -656,36 +846,42 @@ var is = {
|
|
|
656
846
|
object: (value) => Boolean(value) && typeof value === "object" && !Array.isArray(value)
|
|
657
847
|
};
|
|
658
848
|
|
|
659
|
-
// src/commands/approvals.ts
|
|
849
|
+
// src/commands/approvals/approvals-constants.ts
|
|
660
850
|
var ACTIONS = ["list", "show", "approve", "reject"];
|
|
661
851
|
var REJECT_MODES = ["rework", "terminate"];
|
|
852
|
+
|
|
853
|
+
// src/commands/approvals/list-approvals.ts
|
|
854
|
+
async function listApprovals(asJson) {
|
|
855
|
+
const result = expectShape(
|
|
856
|
+
await api("/approvals"),
|
|
857
|
+
"GET /approvals",
|
|
858
|
+
{ items: is.array }
|
|
859
|
+
);
|
|
860
|
+
if (asJson) {
|
|
861
|
+
out.json(result);
|
|
862
|
+
return;
|
|
863
|
+
}
|
|
864
|
+
if (result.items.length === 0) {
|
|
865
|
+
out.line("No guardrail waiting on you.");
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
868
|
+
out.table(
|
|
869
|
+
result.items.map((item) => [
|
|
870
|
+
out.cell(item.id),
|
|
871
|
+
out.cell(item.guardrailKind),
|
|
872
|
+
out.cell(item.repoName ?? "\u2014"),
|
|
873
|
+
out.cell(item.summary)
|
|
874
|
+
])
|
|
875
|
+
);
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
// src/commands/command/approvals-command.ts
|
|
662
879
|
async function approvalsCommand(parsed) {
|
|
663
880
|
const action = args.action(parsed, "approvals", ACTIONS, "list");
|
|
664
881
|
const asJson = parsed.booleans.has("json");
|
|
665
882
|
if (action === "list") {
|
|
666
883
|
args.expect(parsed, { positional: 1 });
|
|
667
|
-
|
|
668
|
-
await api("/approvals"),
|
|
669
|
-
"GET /approvals",
|
|
670
|
-
{ items: is.array }
|
|
671
|
-
);
|
|
672
|
-
if (asJson) {
|
|
673
|
-
out.json(result2);
|
|
674
|
-
return;
|
|
675
|
-
}
|
|
676
|
-
if (result2.items.length === 0) {
|
|
677
|
-
out.line("No guardrail waiting on you.");
|
|
678
|
-
return;
|
|
679
|
-
}
|
|
680
|
-
out.table(
|
|
681
|
-
result2.items.map((item) => [
|
|
682
|
-
out.cell(item.id),
|
|
683
|
-
out.cell(item.guardrailKind),
|
|
684
|
-
out.cell(item.repoName ?? "\u2014"),
|
|
685
|
-
out.cell(item.summary)
|
|
686
|
-
])
|
|
687
|
-
);
|
|
688
|
-
return;
|
|
884
|
+
return await listApprovals(asJson);
|
|
689
885
|
}
|
|
690
886
|
args.expect(parsed, {
|
|
691
887
|
positional: 2,
|
|
@@ -748,169 +944,161 @@ async function approvalsCommand(parsed) {
|
|
|
748
944
|
);
|
|
749
945
|
}
|
|
750
946
|
|
|
751
|
-
// src/commands/catalog.ts
|
|
752
|
-
async function
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
947
|
+
// src/commands/catalog/list-catalog-branches.ts
|
|
948
|
+
async function listCatalogBranches(parsed, asJson) {
|
|
949
|
+
args.expect(parsed, { positional: 1 });
|
|
950
|
+
const repoFullName = args.required(parsed, 0, "owner/repo");
|
|
951
|
+
const segments = repoFullName.split("/");
|
|
952
|
+
const [owner, repo] = segments;
|
|
953
|
+
if (segments.length !== 2 || !owner || !repo) {
|
|
954
|
+
throw new CliError(
|
|
955
|
+
`Expected a repository as "owner/repo", got "${repoFullName}".`,
|
|
956
|
+
2
|
|
760
957
|
);
|
|
761
|
-
if (asJson) {
|
|
762
|
-
out.json(result);
|
|
763
|
-
return;
|
|
764
|
-
}
|
|
765
|
-
for (const repo of result.repos) {
|
|
766
|
-
out.line(out.cell(repo));
|
|
767
|
-
}
|
|
768
|
-
return;
|
|
769
958
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
2
|
|
779
|
-
);
|
|
780
|
-
}
|
|
781
|
-
const result = await api(
|
|
782
|
-
path`/repos/${owner}/${repo}/branches`
|
|
783
|
-
);
|
|
784
|
-
if (!result.result) {
|
|
785
|
-
throw new CliError(`No GitHub installation covers ${repoFullName}.`);
|
|
786
|
-
}
|
|
787
|
-
if (asJson) {
|
|
788
|
-
out.json(result);
|
|
789
|
-
return;
|
|
790
|
-
}
|
|
791
|
-
for (const branch of result.result.branches) {
|
|
792
|
-
out.line(
|
|
793
|
-
branch === result.result.defaultBranch ? `${out.cell(branch)} ${out.paint("(default)", "dim")}` : out.cell(branch)
|
|
794
|
-
);
|
|
795
|
-
}
|
|
959
|
+
const result = await api(
|
|
960
|
+
path`/repos/${owner}/${repo}/branches`
|
|
961
|
+
);
|
|
962
|
+
if (!result.result) {
|
|
963
|
+
throw new CliError(`No GitHub installation covers ${repoFullName}.`);
|
|
964
|
+
}
|
|
965
|
+
if (asJson) {
|
|
966
|
+
out.json(result);
|
|
796
967
|
return;
|
|
797
968
|
}
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
await api("/models"),
|
|
802
|
-
"GET /models",
|
|
803
|
-
{ harnesses: is.array, gatewayModels: is.array }
|
|
969
|
+
for (const branch of result.result.branches) {
|
|
970
|
+
out.line(
|
|
971
|
+
branch === result.result.defaultBranch ? `${out.cell(branch)} ${out.paint("(default)", "dim")}` : out.cell(branch)
|
|
804
972
|
);
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
|
|
976
|
+
// src/commands/catalog/list-catalog-models.ts
|
|
977
|
+
async function listCatalogModels(parsed, asJson) {
|
|
978
|
+
args.expect(parsed, {});
|
|
979
|
+
const result = expectShape(
|
|
980
|
+
await api("/models"),
|
|
981
|
+
"GET /models",
|
|
982
|
+
{ harnesses: is.array, gatewayModels: is.array }
|
|
983
|
+
);
|
|
984
|
+
if (asJson) {
|
|
985
|
+
out.json(result);
|
|
818
986
|
return;
|
|
819
987
|
}
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
988
|
+
out.line(out.paint("harnesses", "dim"));
|
|
989
|
+
for (const harness of result.harnesses) {
|
|
990
|
+
out.line(` ${out.cell(harness)}`);
|
|
991
|
+
}
|
|
992
|
+
out.line();
|
|
993
|
+
out.line(out.paint("gateway models", "dim"));
|
|
994
|
+
for (const model of result.gatewayModels) {
|
|
995
|
+
out.line(` ${out.cell(model.id)}`);
|
|
996
|
+
}
|
|
997
|
+
}
|
|
998
|
+
|
|
999
|
+
// src/commands/catalog/read-catalog-list.ts
|
|
1000
|
+
async function readCatalogList(parsed, path2, field) {
|
|
1001
|
+
args.expect(parsed, {});
|
|
1002
|
+
return expectShape(await api(path2), `GET ${path2}`, { [field]: is.array });
|
|
1003
|
+
}
|
|
1004
|
+
|
|
1005
|
+
// src/commands/catalog/list-catalog-repos.ts
|
|
1006
|
+
async function listCatalogRepos(parsed, asJson) {
|
|
1007
|
+
const result = await readCatalogList(
|
|
1008
|
+
parsed,
|
|
1009
|
+
"/repos",
|
|
1010
|
+
"repos"
|
|
1011
|
+
);
|
|
1012
|
+
if (asJson) {
|
|
1013
|
+
out.json(result);
|
|
838
1014
|
return;
|
|
839
1015
|
}
|
|
840
|
-
|
|
1016
|
+
for (const repo of result.repos) {
|
|
1017
|
+
out.line(out.cell(repo));
|
|
1018
|
+
}
|
|
841
1019
|
}
|
|
842
1020
|
|
|
843
|
-
// src/commands/
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
SESSIONS
|
|
864
|
-
hepha sessions list [--status active|waiting|done] [--repo owner/repo]
|
|
865
|
-
[--search "\u2026"] [--page N] [--limit N] [--all]
|
|
866
|
-
hepha sessions get <id>
|
|
867
|
-
hepha sessions logs <id> [--after <sequence>] [--limit N] [--follow]
|
|
868
|
-
hepha sessions send <id> "<message>"
|
|
869
|
-
hepha sessions pause|resume|archive <id>
|
|
870
|
-
hepha sessions kill <id> [--reason "\u2026"]
|
|
871
|
-
hepha sessions checks|pr|verify <id>
|
|
872
|
-
|
|
873
|
-
CATALOG
|
|
874
|
-
hepha repos
|
|
875
|
-
hepha branches <owner/repo>
|
|
876
|
-
hepha models
|
|
877
|
-
hepha skills
|
|
878
|
-
|
|
879
|
-
AUTH
|
|
880
|
-
hepha login [--url https://\u2026] Device authorization: approve it in a browser.
|
|
881
|
-
hepha logout
|
|
882
|
-
hepha whoami
|
|
883
|
-
hepha keys list
|
|
884
|
-
hepha keys create <name> [--scope sessions:read]\u2026 [--expires-in-days 90]
|
|
885
|
-
hepha keys revoke <keyId>
|
|
1021
|
+
// src/commands/catalog/list-catalog-skills.ts
|
|
1022
|
+
async function listCatalogSkills(parsed, asJson) {
|
|
1023
|
+
const result = await readCatalogList(
|
|
1024
|
+
parsed,
|
|
1025
|
+
"/skills",
|
|
1026
|
+
"skills"
|
|
1027
|
+
);
|
|
1028
|
+
if (asJson) {
|
|
1029
|
+
out.json(result);
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
out.table(
|
|
1033
|
+
result.skills.map((skill) => [
|
|
1034
|
+
out.cell(skill.id),
|
|
1035
|
+
out.cell(skill.name),
|
|
1036
|
+
out.cell(skill.description ?? "")
|
|
1037
|
+
])
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
886
1040
|
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
1041
|
+
// src/commands/catalog/catalog-topics.ts
|
|
1042
|
+
var catalogTopics = {
|
|
1043
|
+
branches: listCatalogBranches,
|
|
1044
|
+
models: listCatalogModels,
|
|
1045
|
+
repos: listCatalogRepos,
|
|
1046
|
+
skills: listCatalogSkills
|
|
1047
|
+
};
|
|
894
1048
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
1049
|
+
// src/commands/command/catalog-command.ts
|
|
1050
|
+
async function catalogCommand(topic, parsed) {
|
|
1051
|
+
const list = catalogTopics[topic];
|
|
1052
|
+
if (!list) {
|
|
1053
|
+
throw new CliError(`Unknown catalog topic "${topic}".`, 2);
|
|
1054
|
+
}
|
|
1055
|
+
await list(parsed, parsed.booleans.has("json"));
|
|
1056
|
+
}
|
|
902
1057
|
|
|
903
|
-
|
|
904
|
-
|
|
1058
|
+
// src/commands/keys/keys-constants.ts
|
|
1059
|
+
var ACTIONS2 = ["list", "create", "revoke"];
|
|
905
1060
|
|
|
906
|
-
|
|
907
|
-
function
|
|
908
|
-
|
|
909
|
-
|
|
1061
|
+
// src/commands/key/create-key.ts
|
|
1062
|
+
async function createKey(parsed, asJson) {
|
|
1063
|
+
args.expect(parsed, { flags: ["scope", "expires-in-days"], positional: 2 });
|
|
1064
|
+
const name = args.required(parsed, 1, "name");
|
|
1065
|
+
const scopes = parsed.repeated.scope;
|
|
1066
|
+
const expiresInDays = args.integer(parsed, "expires-in-days", {
|
|
1067
|
+
min: 1,
|
|
1068
|
+
max: 365
|
|
1069
|
+
});
|
|
1070
|
+
const created = expectShape(
|
|
1071
|
+
await api("/keys", {
|
|
1072
|
+
method: "POST",
|
|
1073
|
+
body: {
|
|
1074
|
+
name,
|
|
1075
|
+
...scopes && scopes.length > 0 ? { scopes } : {},
|
|
1076
|
+
...expiresInDays !== void 0 ? { expiresInDays } : {}
|
|
1077
|
+
}
|
|
1078
|
+
}),
|
|
1079
|
+
"POST /keys",
|
|
1080
|
+
{ key: is.string, scopes: is.array }
|
|
1081
|
+
);
|
|
1082
|
+
if (asJson) {
|
|
1083
|
+
out.json(created);
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
out.line(
|
|
1087
|
+
`${out.paint("\u2713", "green")} Created "${out.cell(created.name ?? name)}"`
|
|
1088
|
+
);
|
|
1089
|
+
out.line();
|
|
1090
|
+
out.line(` ${out.paint(created.key, "bold")}`);
|
|
1091
|
+
out.line();
|
|
1092
|
+
out.line(
|
|
1093
|
+
out.paint(
|
|
1094
|
+
" Copy it now \u2014 it is hashed at rest and never shown again.",
|
|
1095
|
+
"dim"
|
|
1096
|
+
)
|
|
1097
|
+
);
|
|
1098
|
+
out.line(out.paint(` Scopes: ${created.scopes.join(" ")}`, "dim"));
|
|
910
1099
|
}
|
|
911
1100
|
|
|
912
|
-
// src/commands/keys.ts
|
|
913
|
-
var ACTIONS2 = ["list", "create", "revoke"];
|
|
1101
|
+
// src/commands/command/keys-command.ts
|
|
914
1102
|
async function keysCommand(parsed) {
|
|
915
1103
|
const action = args.action(parsed, "keys", ACTIONS2, "list");
|
|
916
1104
|
const asJson = parsed.booleans.has("json");
|
|
@@ -946,46 +1134,7 @@ async function keysCommand(parsed) {
|
|
|
946
1134
|
return;
|
|
947
1135
|
}
|
|
948
1136
|
if (action === "create") {
|
|
949
|
-
|
|
950
|
-
flags: ["scope", "expires-in-days"],
|
|
951
|
-
positional: 2
|
|
952
|
-
});
|
|
953
|
-
const name = args.required(parsed, 1, "name");
|
|
954
|
-
const scopes = parsed.repeated.scope;
|
|
955
|
-
const expiresInDays = args.integer(parsed, "expires-in-days", {
|
|
956
|
-
min: 1,
|
|
957
|
-
max: 365
|
|
958
|
-
});
|
|
959
|
-
const created = expectShape(
|
|
960
|
-
await api("/keys", {
|
|
961
|
-
method: "POST",
|
|
962
|
-
body: {
|
|
963
|
-
name,
|
|
964
|
-
...scopes && scopes.length > 0 ? { scopes } : {},
|
|
965
|
-
...expiresInDays !== void 0 ? { expiresInDays } : {}
|
|
966
|
-
}
|
|
967
|
-
}),
|
|
968
|
-
"POST /keys",
|
|
969
|
-
{ key: is.string, scopes: is.array }
|
|
970
|
-
);
|
|
971
|
-
if (asJson) {
|
|
972
|
-
out.json(created);
|
|
973
|
-
return;
|
|
974
|
-
}
|
|
975
|
-
out.line(
|
|
976
|
-
`${out.paint("\u2713", "green")} Created "${out.cell(created.name ?? name)}"`
|
|
977
|
-
);
|
|
978
|
-
out.line();
|
|
979
|
-
out.line(` ${out.paint(created.key, "bold")}`);
|
|
980
|
-
out.line();
|
|
981
|
-
out.line(
|
|
982
|
-
out.paint(
|
|
983
|
-
" Copy it now \u2014 it is hashed at rest and never shown again.",
|
|
984
|
-
"dim"
|
|
985
|
-
)
|
|
986
|
-
);
|
|
987
|
-
out.line(out.paint(` Scopes: ${created.scopes.join(" ")}`, "dim"));
|
|
988
|
-
return;
|
|
1137
|
+
return await createKey(parsed, asJson);
|
|
989
1138
|
}
|
|
990
1139
|
args.expect(parsed, { positional: 2 });
|
|
991
1140
|
const keyId = args.required(parsed, 1, "keyId");
|
|
@@ -999,55 +1148,11 @@ async function keysCommand(parsed) {
|
|
|
999
1148
|
out.line(`${out.paint("\u2713", "green")} Revoked ${out.cell(keyId)}`);
|
|
1000
1149
|
}
|
|
1001
1150
|
|
|
1002
|
-
// src/commands/login.ts
|
|
1151
|
+
// src/commands/command/login-command.ts
|
|
1003
1152
|
import { createAuthClient } from "better-auth/client";
|
|
1004
1153
|
import { deviceAuthorizationClient } from "better-auth/client/plugins";
|
|
1005
1154
|
|
|
1006
|
-
//
|
|
1007
|
-
var USER_ROLES = {
|
|
1008
|
-
ADMIN: "admin",
|
|
1009
|
-
MEMBER: "member"
|
|
1010
|
-
};
|
|
1011
|
-
var ALL_USER_ROLES = Object.values(USER_ROLES);
|
|
1012
|
-
var API_SCOPES = [
|
|
1013
|
-
"sessions:read",
|
|
1014
|
-
"sessions:write",
|
|
1015
|
-
"questions:read",
|
|
1016
|
-
"questions:answer",
|
|
1017
|
-
"approvals:read",
|
|
1018
|
-
"catalog:read",
|
|
1019
|
-
"pull_requests:read"
|
|
1020
|
-
];
|
|
1021
|
-
var DEFAULT_API_SCOPES = API_SCOPES.filter(
|
|
1022
|
-
(scope) => scope.endsWith(":read")
|
|
1023
|
-
);
|
|
1024
|
-
var SESSION = {
|
|
1025
|
-
/** Cookie + session row lifetime: 30 days. */
|
|
1026
|
-
EXPIRES_IN: 60 * 60 * 24 * 30,
|
|
1027
|
-
/** Refresh the expiry at most once a day, to avoid a write per request. */
|
|
1028
|
-
UPDATE_AGE: 60 * 60 * 24,
|
|
1029
|
-
/** Cache the session row in memory for this long, to avoid a read per request. */
|
|
1030
|
-
COOKIE_CACHE_MAX_AGE: 60
|
|
1031
|
-
};
|
|
1032
|
-
var DEVICE_AUTHORIZATION = {
|
|
1033
|
-
/** Only these clients may open a device flow. */
|
|
1034
|
-
CLIENT_IDS: ["hepha-cli"],
|
|
1035
|
-
/** Lifetime of an unapproved user code. */
|
|
1036
|
-
EXPIRES_IN: "15m",
|
|
1037
|
-
/** Minimum delay the CLI must wait between two token polls. */
|
|
1038
|
-
INTERVAL: "5s",
|
|
1039
|
-
/** Page a human visits to approve a pending device. */
|
|
1040
|
-
VERIFICATION_PATH: "/device",
|
|
1041
|
-
/**
|
|
1042
|
-
* Per-IP ceiling on `/device/token`. The window is deliberately SHORTER than
|
|
1043
|
-
* INTERVAL: Better Auth only resets its counter when two requests are further
|
|
1044
|
-
* apart than the window, so a 5s poll under the default 60s window never reset
|
|
1045
|
-
* it and every login 429'd partway through the code's lifetime.
|
|
1046
|
-
*/
|
|
1047
|
-
POLL_RATE_LIMIT: { WINDOW_SECONDS: 4, MAX_REQUESTS: 10 }
|
|
1048
|
-
};
|
|
1049
|
-
|
|
1050
|
-
// src/lib/device-token-error.ts
|
|
1155
|
+
// src/lib/token/device-token-error.ts
|
|
1051
1156
|
var deviceTokenError = {
|
|
1052
1157
|
/** What the server said, when it said anything at all. */
|
|
1053
1158
|
message(error) {
|
|
@@ -1075,7 +1180,7 @@ var deviceTokenError = {
|
|
|
1075
1180
|
}
|
|
1076
1181
|
};
|
|
1077
1182
|
|
|
1078
|
-
// src/commands/
|
|
1183
|
+
// src/commands/next/next-poll-delay.ts
|
|
1079
1184
|
function nextPollDelay(interval, remainingSeconds) {
|
|
1080
1185
|
if (remainingSeconds <= 0) {
|
|
1081
1186
|
return null;
|
|
@@ -1083,7 +1188,7 @@ function nextPollDelay(interval, remainingSeconds) {
|
|
|
1083
1188
|
return Math.min(interval, remainingSeconds);
|
|
1084
1189
|
}
|
|
1085
1190
|
|
|
1086
|
-
// src/lib/open-browser.ts
|
|
1191
|
+
// src/lib/browser/open-browser.ts
|
|
1087
1192
|
import { spawn } from "child_process";
|
|
1088
1193
|
function openBrowser(url) {
|
|
1089
1194
|
const command = process.platform === "darwin" ? "open" : process.platform === "win32" ? "cmd" : "xdg-open";
|
|
@@ -1100,19 +1205,95 @@ function openBrowser(url) {
|
|
|
1100
1205
|
}
|
|
1101
1206
|
}
|
|
1102
1207
|
|
|
1103
|
-
// src/commands/login.ts
|
|
1104
|
-
var CLIENT_ID = DEVICE_AUTHORIZATION.CLIENT_IDS[0];
|
|
1208
|
+
// src/commands/login/login-constants.ts
|
|
1105
1209
|
var MIN_BACKOFF = 10;
|
|
1210
|
+
|
|
1211
|
+
// ../../packages/database/shared/constants.ts
|
|
1212
|
+
var USER_ROLES = {
|
|
1213
|
+
ADMIN: "admin",
|
|
1214
|
+
MEMBER: "member"
|
|
1215
|
+
};
|
|
1216
|
+
var ALL_USER_ROLES = Object.values(USER_ROLES);
|
|
1217
|
+
var API_SCOPES = [
|
|
1218
|
+
"sessions:read",
|
|
1219
|
+
"sessions:write",
|
|
1220
|
+
"questions:read",
|
|
1221
|
+
"questions:answer",
|
|
1222
|
+
"approvals:read",
|
|
1223
|
+
"catalog:read",
|
|
1224
|
+
"pull_requests:read"
|
|
1225
|
+
];
|
|
1226
|
+
var DEFAULT_API_SCOPES = API_SCOPES.filter(
|
|
1227
|
+
(scope) => scope.endsWith(":read")
|
|
1228
|
+
);
|
|
1229
|
+
var SESSION = {
|
|
1230
|
+
/** Cookie + session row lifetime: 30 days. */
|
|
1231
|
+
EXPIRES_IN: 60 * 60 * 24 * 30,
|
|
1232
|
+
/** Refresh the expiry at most once a day, to avoid a write per request. */
|
|
1233
|
+
UPDATE_AGE: 60 * 60 * 24,
|
|
1234
|
+
/** Cache the session row in memory for this long, to avoid a read per request. */
|
|
1235
|
+
COOKIE_CACHE_MAX_AGE: 60
|
|
1236
|
+
};
|
|
1237
|
+
var DEVICE_AUTHORIZATION = {
|
|
1238
|
+
/** Only these clients may open a device flow. */
|
|
1239
|
+
CLIENT_IDS: ["hepha-cli"],
|
|
1240
|
+
/** Lifetime of an unapproved user code. */
|
|
1241
|
+
EXPIRES_IN: "15m",
|
|
1242
|
+
/** Minimum delay the CLI must wait between two token polls. */
|
|
1243
|
+
INTERVAL: "5s",
|
|
1244
|
+
/** Page a human visits to approve a pending device. */
|
|
1245
|
+
VERIFICATION_PATH: "/device",
|
|
1246
|
+
/**
|
|
1247
|
+
* Per-IP ceiling on `/device/token`. The window is deliberately SHORTER than
|
|
1248
|
+
* INTERVAL: Better Auth only resets its counter when two requests are further
|
|
1249
|
+
* apart than the window, so a 5s poll under the default 60s window never reset
|
|
1250
|
+
* it and every login 429'd partway through the code's lifetime.
|
|
1251
|
+
*/
|
|
1252
|
+
POLL_RATE_LIMIT: { WINDOW_SECONDS: 4, MAX_REQUESTS: 10 }
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
// src/commands/client/client-id-constants.ts
|
|
1256
|
+
var CLIENT_ID = DEVICE_AUTHORIZATION.CLIENT_IDS[0];
|
|
1257
|
+
|
|
1258
|
+
// src/commands/sleep/sleep.ts
|
|
1106
1259
|
function sleep(seconds) {
|
|
1107
1260
|
return new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
|
|
1108
1261
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1262
|
+
|
|
1263
|
+
// src/commands/complete/complete-login.ts
|
|
1264
|
+
async function completeLogin(baseUrl, token) {
|
|
1265
|
+
const identity = await api("/me", {
|
|
1266
|
+
as: { baseUrl, token }
|
|
1267
|
+
});
|
|
1268
|
+
config.save({
|
|
1269
|
+
baseUrl,
|
|
1270
|
+
token,
|
|
1271
|
+
user: {
|
|
1272
|
+
id: identity.user.id,
|
|
1273
|
+
name: identity.user.name,
|
|
1274
|
+
email: identity.user.email
|
|
1275
|
+
}
|
|
1276
|
+
});
|
|
1277
|
+
out.line();
|
|
1278
|
+
out.line(
|
|
1279
|
+
`${out.paint("\u2713", "green")} Signed in as ${identity.user.email} on ${baseUrl}`
|
|
1280
|
+
);
|
|
1281
|
+
out.line(out.paint(` Session stored in ${config.path}`, "dim"));
|
|
1282
|
+
}
|
|
1283
|
+
|
|
1284
|
+
// src/commands/login/resolve-login-base-url.ts
|
|
1285
|
+
function resolveLoginBaseUrl(parsed) {
|
|
1111
1286
|
const requested = parsed.flags.url?.trim();
|
|
1112
1287
|
if (requested !== void 0 && !isHttpUrl(requested)) {
|
|
1113
1288
|
throw new CliError(`--url is not a valid http(s) URL: "${requested}".`, 2);
|
|
1114
1289
|
}
|
|
1115
|
-
|
|
1290
|
+
return (requested ?? config.load().baseUrl).replace(/\/$/, "");
|
|
1291
|
+
}
|
|
1292
|
+
|
|
1293
|
+
// src/commands/command/login-command.ts
|
|
1294
|
+
async function loginCommand(parsed) {
|
|
1295
|
+
args.expect(parsed, { flags: ["url"] });
|
|
1296
|
+
const baseUrl = resolveLoginBaseUrl(parsed);
|
|
1116
1297
|
const authClient = createAuthClient({
|
|
1117
1298
|
baseURL: baseUrl,
|
|
1118
1299
|
plugins: [deviceAuthorizationClient()]
|
|
@@ -1154,23 +1335,7 @@ async function loginCommand(parsed) {
|
|
|
1154
1335
|
continue;
|
|
1155
1336
|
}
|
|
1156
1337
|
if (token?.access_token) {
|
|
1157
|
-
|
|
1158
|
-
as: { baseUrl, token: token.access_token }
|
|
1159
|
-
});
|
|
1160
|
-
config.save({
|
|
1161
|
-
baseUrl,
|
|
1162
|
-
token: token.access_token,
|
|
1163
|
-
user: {
|
|
1164
|
-
id: identity.user.id,
|
|
1165
|
-
name: identity.user.name,
|
|
1166
|
-
email: identity.user.email
|
|
1167
|
-
}
|
|
1168
|
-
});
|
|
1169
|
-
out.line();
|
|
1170
|
-
out.line(
|
|
1171
|
-
`${out.paint("\u2713", "green")} Signed in as ${identity.user.email} on ${baseUrl}`
|
|
1172
|
-
);
|
|
1173
|
-
out.line(out.paint(` Session stored in ${config.path}`, "dim"));
|
|
1338
|
+
await completeLogin(baseUrl, token.access_token);
|
|
1174
1339
|
return;
|
|
1175
1340
|
}
|
|
1176
1341
|
if (!error) {
|
|
@@ -1193,7 +1358,7 @@ async function loginCommand(parsed) {
|
|
|
1193
1358
|
throw new CliError("The code expired before it was approved. Try again.");
|
|
1194
1359
|
}
|
|
1195
1360
|
|
|
1196
|
-
// src/commands/logout.ts
|
|
1361
|
+
// src/commands/command/logout-command.ts
|
|
1197
1362
|
function logoutCommand(parsed) {
|
|
1198
1363
|
args.expect(parsed, {});
|
|
1199
1364
|
config.clear();
|
|
@@ -1204,7 +1369,7 @@ function logoutCommand(parsed) {
|
|
|
1204
1369
|
out.line("Signed out.");
|
|
1205
1370
|
}
|
|
1206
1371
|
|
|
1207
|
-
// src/commands/collect-answers.ts
|
|
1372
|
+
// src/commands/answers/collect-answers-constants.ts
|
|
1208
1373
|
var LIMITS = {
|
|
1209
1374
|
QUESTION_ID: 40,
|
|
1210
1375
|
OPTION_ID: 40,
|
|
@@ -1212,48 +1377,62 @@ var LIMITS = {
|
|
|
1212
1377
|
FREE_TEXT: 1e4,
|
|
1213
1378
|
ANSWERS: 50
|
|
1214
1379
|
};
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1380
|
+
|
|
1381
|
+
// src/commands/answer/validate-free-text.ts
|
|
1382
|
+
function validateFreeText(value, label) {
|
|
1383
|
+
if (value === void 0) {
|
|
1384
|
+
return;
|
|
1218
1385
|
}
|
|
1219
|
-
|
|
1386
|
+
const freeText = value.trim();
|
|
1387
|
+
if (freeText.length === 0) {
|
|
1388
|
+
throw new CliError(`${label} has empty free text.`, 2);
|
|
1389
|
+
}
|
|
1390
|
+
if (freeText.length > LIMITS.FREE_TEXT) {
|
|
1220
1391
|
throw new CliError(
|
|
1221
|
-
`${label} has
|
|
1392
|
+
`${label} has free text longer than ${LIMITS.FREE_TEXT} characters.`,
|
|
1222
1393
|
2
|
|
1223
1394
|
);
|
|
1224
1395
|
}
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
);
|
|
1232
|
-
}
|
|
1233
|
-
for (const id of options) {
|
|
1234
|
-
if (id.trim().length === 0) {
|
|
1235
|
-
throw new CliError(`${label} has an empty option id.`, 2);
|
|
1236
|
-
}
|
|
1237
|
-
if (id.trim().length > LIMITS.OPTION_ID) {
|
|
1238
|
-
throw new CliError(
|
|
1239
|
-
`${label} has an option id longer than ${LIMITS.OPTION_ID} characters.`,
|
|
1240
|
-
2
|
|
1241
|
-
);
|
|
1242
|
-
}
|
|
1243
|
-
}
|
|
1396
|
+
}
|
|
1397
|
+
|
|
1398
|
+
// src/commands/answer/validate-selected-option-ids.ts
|
|
1399
|
+
function validateSelectedOptionIds(options, label) {
|
|
1400
|
+
if (options === void 0) {
|
|
1401
|
+
return;
|
|
1244
1402
|
}
|
|
1245
|
-
if (
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1403
|
+
if (options.length > LIMITS.OPTIONS_PER_ANSWER) {
|
|
1404
|
+
throw new CliError(
|
|
1405
|
+
`${label} selects more than ${LIMITS.OPTIONS_PER_ANSWER} options.`,
|
|
1406
|
+
2
|
|
1407
|
+
);
|
|
1408
|
+
}
|
|
1409
|
+
for (const id of options) {
|
|
1410
|
+
if (id.trim().length === 0) {
|
|
1411
|
+
throw new CliError(`${label} has an empty option id.`, 2);
|
|
1249
1412
|
}
|
|
1250
|
-
if (
|
|
1413
|
+
if (id.trim().length > LIMITS.OPTION_ID) {
|
|
1251
1414
|
throw new CliError(
|
|
1252
|
-
`${label} has
|
|
1415
|
+
`${label} has an option id longer than ${LIMITS.OPTION_ID} characters.`,
|
|
1253
1416
|
2
|
|
1254
1417
|
);
|
|
1255
1418
|
}
|
|
1256
1419
|
}
|
|
1420
|
+
}
|
|
1421
|
+
|
|
1422
|
+
// src/commands/answer/validate-answer.ts
|
|
1423
|
+
function validateAnswer(answer, label) {
|
|
1424
|
+
if (answer.questionId.trim().length === 0) {
|
|
1425
|
+
throw new CliError(`${label} needs a non-empty "questionId".`, 2);
|
|
1426
|
+
}
|
|
1427
|
+
if (answer.questionId.length > LIMITS.QUESTION_ID) {
|
|
1428
|
+
throw new CliError(
|
|
1429
|
+
`${label} has a "questionId" longer than ${LIMITS.QUESTION_ID} characters.`,
|
|
1430
|
+
2
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
const options = answer.selectedOptionIds;
|
|
1434
|
+
validateSelectedOptionIds(options, label);
|
|
1435
|
+
validateFreeText(answer.freeText, label);
|
|
1257
1436
|
if ((options === void 0 || options.length === 0) && answer.freeText === void 0) {
|
|
1258
1437
|
throw new CliError(
|
|
1259
1438
|
`${label} answers nothing. Give it at least one option id or some free text.`,
|
|
@@ -1261,6 +1440,31 @@ function validateAnswer(answer, label) {
|
|
|
1261
1440
|
);
|
|
1262
1441
|
}
|
|
1263
1442
|
}
|
|
1443
|
+
|
|
1444
|
+
// src/commands/answers/parse-answer-entry.ts
|
|
1445
|
+
function parseAnswerEntry(entry, label) {
|
|
1446
|
+
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
1447
|
+
throw new CliError(`${label} must be an object.`, 2);
|
|
1448
|
+
}
|
|
1449
|
+
const answer = entry;
|
|
1450
|
+
if (typeof answer.questionId !== "string") {
|
|
1451
|
+
throw new CliError(`${label} needs a non-empty "questionId".`, 2);
|
|
1452
|
+
}
|
|
1453
|
+
if (answer.selectedOptionIds !== void 0 && (!Array.isArray(answer.selectedOptionIds) || answer.selectedOptionIds.some((id) => typeof id !== "string"))) {
|
|
1454
|
+
throw new CliError(
|
|
1455
|
+
`${label}.selectedOptionIds must be an array of strings.`,
|
|
1456
|
+
2
|
|
1457
|
+
);
|
|
1458
|
+
}
|
|
1459
|
+
if (answer.freeText !== void 0 && typeof answer.freeText !== "string") {
|
|
1460
|
+
throw new CliError(`${label}.freeText must be a string.`, 2);
|
|
1461
|
+
}
|
|
1462
|
+
const parsed = entry;
|
|
1463
|
+
validateAnswer(parsed, label);
|
|
1464
|
+
return parsed;
|
|
1465
|
+
}
|
|
1466
|
+
|
|
1467
|
+
// src/commands/answers/parse-answers-flag.ts
|
|
1264
1468
|
function parseAnswersFlag(raw) {
|
|
1265
1469
|
let decoded;
|
|
1266
1470
|
try {
|
|
@@ -1281,27 +1485,12 @@ function parseAnswersFlag(raw) {
|
|
|
1281
1485
|
);
|
|
1282
1486
|
}
|
|
1283
1487
|
for (const [index, entry] of decoded.entries()) {
|
|
1284
|
-
|
|
1285
|
-
if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
|
|
1286
|
-
throw new CliError(`${label} must be an object.`, 2);
|
|
1287
|
-
}
|
|
1288
|
-
const answer = entry;
|
|
1289
|
-
if (typeof answer.questionId !== "string") {
|
|
1290
|
-
throw new CliError(`${label} needs a non-empty "questionId".`, 2);
|
|
1291
|
-
}
|
|
1292
|
-
if (answer.selectedOptionIds !== void 0 && (!Array.isArray(answer.selectedOptionIds) || answer.selectedOptionIds.some((id) => typeof id !== "string"))) {
|
|
1293
|
-
throw new CliError(
|
|
1294
|
-
`${label}.selectedOptionIds must be an array of strings.`,
|
|
1295
|
-
2
|
|
1296
|
-
);
|
|
1297
|
-
}
|
|
1298
|
-
if (answer.freeText !== void 0 && typeof answer.freeText !== "string") {
|
|
1299
|
-
throw new CliError(`${label}.freeText must be a string.`, 2);
|
|
1300
|
-
}
|
|
1301
|
-
validateAnswer(entry, label);
|
|
1488
|
+
parseAnswerEntry(entry, `--answers[${index}]`);
|
|
1302
1489
|
}
|
|
1303
1490
|
return decoded;
|
|
1304
1491
|
}
|
|
1492
|
+
|
|
1493
|
+
// src/commands/answers/collect-answers.ts
|
|
1305
1494
|
function collectAnswers(parsed) {
|
|
1306
1495
|
if (parsed.flags.answers) {
|
|
1307
1496
|
return parseAnswersFlag(parsed.flags.answers);
|
|
@@ -1346,8 +1535,61 @@ function collectAnswers(parsed) {
|
|
|
1346
1535
|
return answers;
|
|
1347
1536
|
}
|
|
1348
1537
|
|
|
1349
|
-
// src/commands/questions.ts
|
|
1538
|
+
// src/commands/questions/questions-constants.ts
|
|
1350
1539
|
var ACTIONS3 = ["list", "show", "answer", "cancel"];
|
|
1540
|
+
|
|
1541
|
+
// src/commands/questions/list-questions.ts
|
|
1542
|
+
async function listQuestions(sessionId, asJson) {
|
|
1543
|
+
const result = expectShape(
|
|
1544
|
+
await api(
|
|
1545
|
+
sessionId ? path`/sessions/${sessionId}/questions` : "/questions"
|
|
1546
|
+
),
|
|
1547
|
+
"GET /questions",
|
|
1548
|
+
{ items: is.array }
|
|
1549
|
+
);
|
|
1550
|
+
if (asJson) {
|
|
1551
|
+
out.json(result);
|
|
1552
|
+
return;
|
|
1553
|
+
}
|
|
1554
|
+
if (result.items.length === 0) {
|
|
1555
|
+
out.line("Nothing waiting on you.");
|
|
1556
|
+
return;
|
|
1557
|
+
}
|
|
1558
|
+
out.table(
|
|
1559
|
+
result.items.map((batch) => [
|
|
1560
|
+
out.cell(batch.id),
|
|
1561
|
+
out.cell(batch.sessionId),
|
|
1562
|
+
`${batch.questions.length} question(s)`,
|
|
1563
|
+
out.cell(batch.sessionTitle)
|
|
1564
|
+
])
|
|
1565
|
+
);
|
|
1566
|
+
}
|
|
1567
|
+
|
|
1568
|
+
// src/commands/batch/print-batch-question.ts
|
|
1569
|
+
function printBatchQuestion(question) {
|
|
1570
|
+
out.line();
|
|
1571
|
+
if (question.header) {
|
|
1572
|
+
out.line(out.paint(out.cell(question.header), "dim"));
|
|
1573
|
+
}
|
|
1574
|
+
out.line(
|
|
1575
|
+
`${out.paint(out.cell(question.id), "dim")} ${out.safe(question.body)}`
|
|
1576
|
+
);
|
|
1577
|
+
if (question.image) {
|
|
1578
|
+
out.line(out.paint(` (image) ${out.cell(question.image)}`, "dim"));
|
|
1579
|
+
}
|
|
1580
|
+
for (const option of question.options) {
|
|
1581
|
+
const recommended = option.id === question.recommendedOptionId;
|
|
1582
|
+
const label = option.label ? out.cell(option.label) : option.image ? out.paint(`(image) ${out.cell(option.image)}`, "dim") : out.paint("(no label)", "dim");
|
|
1583
|
+
out.line(
|
|
1584
|
+
` ${out.cell(option.id)}) ${label}${recommended ? out.paint(" \u2190 recommended", "green") : ""}`
|
|
1585
|
+
);
|
|
1586
|
+
}
|
|
1587
|
+
if (question.allowFreeText) {
|
|
1588
|
+
out.line(out.paint(" free text accepted", "dim"));
|
|
1589
|
+
}
|
|
1590
|
+
}
|
|
1591
|
+
|
|
1592
|
+
// src/commands/batch/print-batch.ts
|
|
1351
1593
|
function printBatch(batch) {
|
|
1352
1594
|
out.line(
|
|
1353
1595
|
`${out.paint(out.cell(batch.sessionTitle), "bold")} ${out.paint(`round ${batch.round}`, "dim")}`
|
|
@@ -1359,56 +1601,17 @@ function printBatch(batch) {
|
|
|
1359
1601
|
)
|
|
1360
1602
|
);
|
|
1361
1603
|
for (const question of batch.questions) {
|
|
1362
|
-
|
|
1363
|
-
if (question.header) {
|
|
1364
|
-
out.line(out.paint(out.cell(question.header), "dim"));
|
|
1365
|
-
}
|
|
1366
|
-
out.line(
|
|
1367
|
-
`${out.paint(out.cell(question.id), "dim")} ${out.safe(question.body)}`
|
|
1368
|
-
);
|
|
1369
|
-
if (question.image) {
|
|
1370
|
-
out.line(out.paint(` (image) ${out.cell(question.image)}`, "dim"));
|
|
1371
|
-
}
|
|
1372
|
-
for (const option of question.options) {
|
|
1373
|
-
const recommended = option.id === question.recommendedOptionId;
|
|
1374
|
-
const label = option.label ? out.cell(option.label) : option.image ? out.paint(`(image) ${out.cell(option.image)}`, "dim") : out.paint("(no label)", "dim");
|
|
1375
|
-
out.line(
|
|
1376
|
-
` ${out.cell(option.id)}) ${label}${recommended ? out.paint(" \u2190 recommended", "green") : ""}`
|
|
1377
|
-
);
|
|
1378
|
-
}
|
|
1379
|
-
if (question.allowFreeText) {
|
|
1380
|
-
out.line(out.paint(" free text accepted", "dim"));
|
|
1381
|
-
}
|
|
1604
|
+
printBatchQuestion(question);
|
|
1382
1605
|
}
|
|
1383
1606
|
}
|
|
1607
|
+
|
|
1608
|
+
// src/commands/command/questions-command.ts
|
|
1384
1609
|
async function questionsCommand(parsed) {
|
|
1385
1610
|
const action = args.action(parsed, "questions", ACTIONS3, "list");
|
|
1386
1611
|
const asJson = parsed.booleans.has("json");
|
|
1387
1612
|
if (action === "list") {
|
|
1388
1613
|
args.expect(parsed, { flags: ["session"], positional: 1 });
|
|
1389
|
-
|
|
1390
|
-
const result2 = expectShape(
|
|
1391
|
-
await api(session ? path`/sessions/${session}/questions` : "/questions"),
|
|
1392
|
-
"GET /questions",
|
|
1393
|
-
{ items: is.array }
|
|
1394
|
-
);
|
|
1395
|
-
if (asJson) {
|
|
1396
|
-
out.json(result2);
|
|
1397
|
-
return;
|
|
1398
|
-
}
|
|
1399
|
-
if (result2.items.length === 0) {
|
|
1400
|
-
out.line("Nothing waiting on you.");
|
|
1401
|
-
return;
|
|
1402
|
-
}
|
|
1403
|
-
out.table(
|
|
1404
|
-
result2.items.map((batch) => [
|
|
1405
|
-
out.cell(batch.id),
|
|
1406
|
-
out.cell(batch.sessionId),
|
|
1407
|
-
`${batch.questions.length} question(s)`,
|
|
1408
|
-
out.cell(batch.sessionTitle)
|
|
1409
|
-
])
|
|
1410
|
-
);
|
|
1411
|
-
return;
|
|
1614
|
+
return await listQuestions(parsed.flags.session, asJson);
|
|
1412
1615
|
}
|
|
1413
1616
|
args.expect(parsed, {
|
|
1414
1617
|
positional: 2,
|
|
@@ -1459,11 +1662,17 @@ async function questionsCommand(parsed) {
|
|
|
1459
1662
|
);
|
|
1460
1663
|
}
|
|
1461
1664
|
|
|
1462
|
-
// src/lib/
|
|
1665
|
+
// src/lib/follow/follow-constants.ts
|
|
1666
|
+
var POLL_SECONDS = 3;
|
|
1667
|
+
var PAGE_SIZE = 200;
|
|
1668
|
+
|
|
1669
|
+
// src/lib/truncate/truncate.ts
|
|
1463
1670
|
function truncate(text, max = 2e3) {
|
|
1464
1671
|
const trimmed = out.safe(text).trim();
|
|
1465
1672
|
return trimmed.length > max ? `${trimmed.slice(0, max)}\u2026` : trimmed;
|
|
1466
1673
|
}
|
|
1674
|
+
|
|
1675
|
+
// src/lib/event/format-event.ts
|
|
1467
1676
|
function formatEvent(event) {
|
|
1468
1677
|
const part = event.part;
|
|
1469
1678
|
switch (part.kind) {
|
|
@@ -1515,14 +1724,33 @@ function formatEvent(event) {
|
|
|
1515
1724
|
}
|
|
1516
1725
|
}
|
|
1517
1726
|
|
|
1518
|
-
// src/lib/
|
|
1727
|
+
// src/lib/session/consume-session-events.ts
|
|
1728
|
+
function consumeSessionEvents(events, cursor, quiet) {
|
|
1729
|
+
for (const event of events) {
|
|
1730
|
+
cursor = Math.max(cursor, event.sequence);
|
|
1731
|
+
if (quiet) {
|
|
1732
|
+
continue;
|
|
1733
|
+
}
|
|
1734
|
+
const line = formatEvent(event);
|
|
1735
|
+
if (line) {
|
|
1736
|
+
out.line(line);
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
return cursor;
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// src/lib/terminal/terminal-constants.ts
|
|
1519
1743
|
var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "killed", "abstained"]);
|
|
1744
|
+
|
|
1745
|
+
// src/lib/blocked/blocked-constants.ts
|
|
1520
1746
|
var BLOCKED = /* @__PURE__ */ new Set(["waiting_human", "awaiting_approval"]);
|
|
1521
|
-
|
|
1522
|
-
|
|
1747
|
+
|
|
1748
|
+
// src/lib/sleep/sleep.ts
|
|
1523
1749
|
function sleep2(seconds) {
|
|
1524
1750
|
return new Promise((resolve) => setTimeout(resolve, seconds * 1e3));
|
|
1525
1751
|
}
|
|
1752
|
+
|
|
1753
|
+
// src/lib/session/follow-session.ts
|
|
1526
1754
|
async function followSession(sessionId, options = {}) {
|
|
1527
1755
|
let cursor = options.after ?? 0;
|
|
1528
1756
|
for (; ; ) {
|
|
@@ -1533,16 +1761,7 @@ async function followSession(sessionId, options = {}) {
|
|
|
1533
1761
|
"GET /sessions/{id}/events",
|
|
1534
1762
|
{ items: is.array }
|
|
1535
1763
|
);
|
|
1536
|
-
|
|
1537
|
-
cursor = Math.max(cursor, event.sequence);
|
|
1538
|
-
if (options.quiet) {
|
|
1539
|
-
continue;
|
|
1540
|
-
}
|
|
1541
|
-
const line = formatEvent(event);
|
|
1542
|
-
if (line) {
|
|
1543
|
-
out.line(line);
|
|
1544
|
-
}
|
|
1545
|
-
}
|
|
1764
|
+
cursor = consumeSessionEvents(page.items, cursor, options.quiet);
|
|
1546
1765
|
if (page.nextCursor !== null) {
|
|
1547
1766
|
cursor = Math.max(cursor, page.nextCursor);
|
|
1548
1767
|
}
|
|
@@ -1557,11 +1776,8 @@ async function followSession(sessionId, options = {}) {
|
|
|
1557
1776
|
}
|
|
1558
1777
|
}
|
|
1559
1778
|
|
|
1560
|
-
// src/lib/
|
|
1561
|
-
function
|
|
1562
|
-
out.line(
|
|
1563
|
-
`${out.paint(out.cell(session.title), "bold")} ${out.status(session.status)}`
|
|
1564
|
-
);
|
|
1779
|
+
// src/lib/session/build-session-detail-rows.ts
|
|
1780
|
+
function buildSessionDetailRows(session) {
|
|
1565
1781
|
const rows = [["id", out.cell(session.id)]];
|
|
1566
1782
|
if (session.statusReason) {
|
|
1567
1783
|
rows.push(["reason", out.cell(session.statusReason)]);
|
|
@@ -1592,23 +1808,39 @@ function printSession(session) {
|
|
|
1592
1808
|
rows.push([out.cell(deliverable.type), out.cell(deliverable.url)]);
|
|
1593
1809
|
}
|
|
1594
1810
|
}
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
)
|
|
1607
|
-
|
|
1811
|
+
return rows;
|
|
1812
|
+
}
|
|
1813
|
+
|
|
1814
|
+
// src/lib/session/print-playbook-steps.ts
|
|
1815
|
+
function printPlaybookSteps(steps) {
|
|
1816
|
+
if (steps.length === 0) {
|
|
1817
|
+
return;
|
|
1818
|
+
}
|
|
1819
|
+
out.line();
|
|
1820
|
+
for (const step of steps) {
|
|
1821
|
+
const done = step.todos.filter(
|
|
1822
|
+
(todo) => todo.status === "completed"
|
|
1823
|
+
).length;
|
|
1824
|
+
const marker = done === step.todos.length && step.todos.length > 0 ? "\u2713" : "\xB7";
|
|
1825
|
+
out.line(
|
|
1826
|
+
` ${marker} ${out.cell(step.title)} ${out.paint(`${done}/${step.todos.length}`, "dim")}`
|
|
1827
|
+
);
|
|
1608
1828
|
}
|
|
1609
1829
|
}
|
|
1610
1830
|
|
|
1611
|
-
// src/lib/session-
|
|
1831
|
+
// src/lib/session/print-session.ts
|
|
1832
|
+
function printSession(session) {
|
|
1833
|
+
out.line(
|
|
1834
|
+
`${out.paint(out.cell(session.title), "bold")} ${out.status(session.status)}`
|
|
1835
|
+
);
|
|
1836
|
+
const rows = buildSessionDetailRows(session);
|
|
1837
|
+
out.table(
|
|
1838
|
+
rows.map(([label, value]) => [out.paint(label ?? "", "dim"), value ?? ""])
|
|
1839
|
+
);
|
|
1840
|
+
printPlaybookSteps(session.playbook?.steps ?? []);
|
|
1841
|
+
}
|
|
1842
|
+
|
|
1843
|
+
// src/lib/session/session-outcome.ts
|
|
1612
1844
|
function sessionOutcome(session) {
|
|
1613
1845
|
switch (session.status) {
|
|
1614
1846
|
case "completed":
|
|
@@ -1637,7 +1869,26 @@ function sessionOutcome(session) {
|
|
|
1637
1869
|
}
|
|
1638
1870
|
}
|
|
1639
1871
|
|
|
1640
|
-
// src/
|
|
1872
|
+
// src/lib/session/write-followed-session-outcome.ts
|
|
1873
|
+
function writeFollowedSessionOutcome(session, asJson) {
|
|
1874
|
+
const outcome = sessionOutcome(session);
|
|
1875
|
+
if (asJson) {
|
|
1876
|
+
if (!outcome) out.json(session);
|
|
1877
|
+
} else {
|
|
1878
|
+
out.line();
|
|
1879
|
+
printSession(session);
|
|
1880
|
+
}
|
|
1881
|
+
if (outcome) throw outcome;
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
// src/commands/harnesses/harnesses-constants.ts
|
|
1885
|
+
var HARNESSES = [
|
|
1886
|
+
"claude-code",
|
|
1887
|
+
"codex",
|
|
1888
|
+
"cursor"
|
|
1889
|
+
];
|
|
1890
|
+
|
|
1891
|
+
// src/commands/task/task-types-constants.ts
|
|
1641
1892
|
var TASK_TYPES = [
|
|
1642
1893
|
"implement",
|
|
1643
1894
|
"new-project",
|
|
@@ -1645,11 +1896,24 @@ var TASK_TYPES = [
|
|
|
1645
1896
|
"draft",
|
|
1646
1897
|
"orchestrator"
|
|
1647
1898
|
];
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
"
|
|
1652
|
-
|
|
1899
|
+
|
|
1900
|
+
// src/commands/command/create-run-input.ts
|
|
1901
|
+
function createRunInput(parsed, prompt) {
|
|
1902
|
+
const taskType = args.choice(parsed, "task-type", TASK_TYPES);
|
|
1903
|
+
const harness = args.choice(parsed, "harness", HARNESSES);
|
|
1904
|
+
return {
|
|
1905
|
+
prompt,
|
|
1906
|
+
...parsed.flags.title ? { title: parsed.flags.title } : {},
|
|
1907
|
+
...taskType ? { taskType } : {},
|
|
1908
|
+
...parsed.flags.model ? { modelSelection: parsed.flags.model } : {},
|
|
1909
|
+
...harness ? { harnessPin: harness } : {},
|
|
1910
|
+
...parsed.flags.repo ? { repoFullName: parsed.flags.repo } : {},
|
|
1911
|
+
...parsed.flags.branch ? { branch: parsed.flags.branch } : {},
|
|
1912
|
+
...parsed.repeated.skill ? { pinnedSkillIds: parsed.repeated.skill } : {}
|
|
1913
|
+
};
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
// src/commands/command/run-command.ts
|
|
1653
1917
|
async function runCommand(parsed) {
|
|
1654
1918
|
args.expect(parsed, {
|
|
1655
1919
|
flags: [
|
|
@@ -1666,18 +1930,7 @@ async function runCommand(parsed) {
|
|
|
1666
1930
|
});
|
|
1667
1931
|
const prompt = args.required(parsed, 0, "prompt");
|
|
1668
1932
|
const asJson = parsed.booleans.has("json");
|
|
1669
|
-
const
|
|
1670
|
-
const harness = args.choice(parsed, "harness", HARNESSES);
|
|
1671
|
-
const body = {
|
|
1672
|
-
prompt,
|
|
1673
|
-
...parsed.flags.title ? { title: parsed.flags.title } : {},
|
|
1674
|
-
...taskType ? { taskType } : {},
|
|
1675
|
-
...parsed.flags.model ? { modelSelection: parsed.flags.model } : {},
|
|
1676
|
-
...harness ? { harnessPin: harness } : {},
|
|
1677
|
-
...parsed.flags.repo ? { repoFullName: parsed.flags.repo } : {},
|
|
1678
|
-
...parsed.flags.branch ? { branch: parsed.flags.branch } : {},
|
|
1679
|
-
...parsed.repeated.skill ? { pinnedSkillIds: parsed.repeated.skill } : {}
|
|
1680
|
-
};
|
|
1933
|
+
const body = createRunInput(parsed, prompt);
|
|
1681
1934
|
const session = expectShape(
|
|
1682
1935
|
await api("/sessions", { method: "POST", body }),
|
|
1683
1936
|
"POST /sessions",
|
|
@@ -1699,21 +1952,10 @@ async function runCommand(parsed) {
|
|
|
1699
1952
|
out.line();
|
|
1700
1953
|
}
|
|
1701
1954
|
const final = await followSession(session.id, { quiet });
|
|
1702
|
-
|
|
1703
|
-
if (asJson) {
|
|
1704
|
-
if (!outcome) {
|
|
1705
|
-
out.json(final);
|
|
1706
|
-
}
|
|
1707
|
-
} else {
|
|
1708
|
-
out.line();
|
|
1709
|
-
printSession(final);
|
|
1710
|
-
}
|
|
1711
|
-
if (outcome) {
|
|
1712
|
-
throw outcome;
|
|
1713
|
-
}
|
|
1955
|
+
writeFollowedSessionOutcome(final, asJson);
|
|
1714
1956
|
}
|
|
1715
1957
|
|
|
1716
|
-
// src/commands/sessions.ts
|
|
1958
|
+
// src/commands/sessions/sessions-constants.ts
|
|
1717
1959
|
var ACTIONS4 = [
|
|
1718
1960
|
"list",
|
|
1719
1961
|
"get",
|
|
@@ -1742,9 +1984,8 @@ var SESSION_FIELDS = {
|
|
|
1742
1984
|
repos: is.array,
|
|
1743
1985
|
deliverables: is.array
|
|
1744
1986
|
};
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
}
|
|
1987
|
+
|
|
1988
|
+
// src/commands/sessions/list-sessions.ts
|
|
1748
1989
|
async function listSessions(parsed) {
|
|
1749
1990
|
args.expect(parsed, {
|
|
1750
1991
|
flags: ["status", "repo", "search", "page", "limit"],
|
|
@@ -1791,6 +2032,39 @@ async function listSessions(parsed) {
|
|
|
1791
2032
|
])
|
|
1792
2033
|
);
|
|
1793
2034
|
}
|
|
2035
|
+
|
|
2036
|
+
// src/commands/session/send-session-message.ts
|
|
2037
|
+
async function sendSessionMessage(parsed, asJson) {
|
|
2038
|
+
args.expect(parsed, { positional: 3 });
|
|
2039
|
+
const sessionId = args.required(parsed, 1, "sessionId");
|
|
2040
|
+
const message = args.required(parsed, 2, "message");
|
|
2041
|
+
const result = expectShape(
|
|
2042
|
+
await api(path`/sessions/${sessionId}/messages`, {
|
|
2043
|
+
method: "POST",
|
|
2044
|
+
body: { message }
|
|
2045
|
+
}),
|
|
2046
|
+
"POST /sessions/{id}/messages",
|
|
2047
|
+
{ delivery: is.string }
|
|
2048
|
+
);
|
|
2049
|
+
if (asJson) {
|
|
2050
|
+
out.json(result);
|
|
2051
|
+
return;
|
|
2052
|
+
}
|
|
2053
|
+
out.line(
|
|
2054
|
+
result.delivery === "interrupt" ? "Delivered to the running agent." : "Queued \u2014 the agent picks it up at the end of its turn."
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
|
|
2058
|
+
// src/commands/show/follow-session-logs.ts
|
|
2059
|
+
async function followSessionLogs(parsed, sessionId, after, asJson) {
|
|
2060
|
+
const session = await followSession(sessionId, {
|
|
2061
|
+
quiet: asJson || parsed.booleans.has("quiet"),
|
|
2062
|
+
...after !== void 0 ? { after } : {}
|
|
2063
|
+
});
|
|
2064
|
+
writeFollowedSessionOutcome(session, asJson);
|
|
2065
|
+
}
|
|
2066
|
+
|
|
2067
|
+
// src/commands/show/show-logs.ts
|
|
1794
2068
|
async function showLogs(parsed) {
|
|
1795
2069
|
args.expect(parsed, {
|
|
1796
2070
|
flags: ["after", "limit"],
|
|
@@ -1801,22 +2075,7 @@ async function showLogs(parsed) {
|
|
|
1801
2075
|
const asJson = parsed.booleans.has("json");
|
|
1802
2076
|
const after = args.integer(parsed, "after", { min: 0 });
|
|
1803
2077
|
if (parsed.booleans.has("follow")) {
|
|
1804
|
-
|
|
1805
|
-
quiet: asJson || parsed.booleans.has("quiet"),
|
|
1806
|
-
...after !== void 0 ? { after } : {}
|
|
1807
|
-
});
|
|
1808
|
-
const outcome = sessionOutcome(session);
|
|
1809
|
-
if (asJson) {
|
|
1810
|
-
if (!outcome) {
|
|
1811
|
-
out.json(session);
|
|
1812
|
-
}
|
|
1813
|
-
} else {
|
|
1814
|
-
out.line();
|
|
1815
|
-
printSession(session);
|
|
1816
|
-
}
|
|
1817
|
-
if (outcome) {
|
|
1818
|
-
throw outcome;
|
|
1819
|
-
}
|
|
2078
|
+
await followSessionLogs(parsed, sessionId, after, asJson);
|
|
1820
2079
|
return;
|
|
1821
2080
|
}
|
|
1822
2081
|
const page = expectShape(
|
|
@@ -1849,36 +2108,41 @@ async function showLogs(parsed) {
|
|
|
1849
2108
|
);
|
|
1850
2109
|
}
|
|
1851
2110
|
}
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
);
|
|
2111
|
+
|
|
2112
|
+
// src/commands/sessions/session-actions.ts
|
|
2113
|
+
var sessionActions = {
|
|
2114
|
+
list: (parsed) => listSessions(parsed),
|
|
2115
|
+
logs: (parsed) => showLogs(parsed),
|
|
2116
|
+
send: sendSessionMessage
|
|
2117
|
+
};
|
|
2118
|
+
|
|
2119
|
+
// src/commands/detail/detail.ts
|
|
2120
|
+
function detail(payload, endpoint) {
|
|
2121
|
+
return expectShape(payload, endpoint, SESSION_FIELDS);
|
|
2122
|
+
}
|
|
2123
|
+
|
|
2124
|
+
// src/commands/command/run-session-control-action.ts
|
|
2125
|
+
async function runSessionControlAction(parsed, action, sessionId, asJson) {
|
|
2126
|
+
const controlPath = CONTROL_PATHS[action];
|
|
2127
|
+
if (controlPath === void 0) {
|
|
2128
|
+
throw new CliError(`Unknown session action: ${action}`, 2);
|
|
2129
|
+
}
|
|
2130
|
+
const session = detail(
|
|
2131
|
+
await api(path`/sessions/${sessionId}/${controlPath}`, {
|
|
2132
|
+
method: "POST",
|
|
2133
|
+
body: action === "kill" && parsed.flags.reason ? { reason: parsed.flags.reason } : {}
|
|
2134
|
+
}),
|
|
2135
|
+
`POST /sessions/{id}/${controlPath}`
|
|
2136
|
+
);
|
|
2137
|
+
if (asJson) {
|
|
2138
|
+
out.json(session);
|
|
1880
2139
|
return;
|
|
1881
2140
|
}
|
|
2141
|
+
printSession(session);
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
// src/commands/command/run-session-operation.ts
|
|
2145
|
+
async function runSessionOperation(parsed, action, asJson) {
|
|
1882
2146
|
args.expect(parsed, {
|
|
1883
2147
|
positional: 2,
|
|
1884
2148
|
...action === "kill" ? { flags: ["reason"] } : {}
|
|
@@ -1897,19 +2161,7 @@ async function sessionsCommand(parsed) {
|
|
|
1897
2161
|
return;
|
|
1898
2162
|
}
|
|
1899
2163
|
if (Object.hasOwn(CONTROL_PATHS, action)) {
|
|
1900
|
-
|
|
1901
|
-
const session = detail(
|
|
1902
|
-
await api(path`/sessions/${sessionId}/${controlPath}`, {
|
|
1903
|
-
method: "POST",
|
|
1904
|
-
body: action === "kill" && parsed.flags.reason ? { reason: parsed.flags.reason } : {}
|
|
1905
|
-
}),
|
|
1906
|
-
`POST /sessions/{id}/${controlPath}`
|
|
1907
|
-
);
|
|
1908
|
-
if (asJson) {
|
|
1909
|
-
out.json(session);
|
|
1910
|
-
return;
|
|
1911
|
-
}
|
|
1912
|
-
printSession(session);
|
|
2164
|
+
await runSessionControlAction(parsed, action, sessionId, asJson);
|
|
1913
2165
|
return;
|
|
1914
2166
|
}
|
|
1915
2167
|
if (action === "checks") {
|
|
@@ -1936,7 +2188,18 @@ async function sessionsCommand(parsed) {
|
|
|
1936
2188
|
);
|
|
1937
2189
|
}
|
|
1938
2190
|
|
|
1939
|
-
// src/commands/
|
|
2191
|
+
// src/commands/command/sessions-command.ts
|
|
2192
|
+
async function sessionsCommand(parsed) {
|
|
2193
|
+
const action = args.action(parsed, "sessions", ACTIONS4, "list");
|
|
2194
|
+
const asJson = parsed.booleans.has("json");
|
|
2195
|
+
const dedicated = sessionActions[action];
|
|
2196
|
+
if (dedicated) {
|
|
2197
|
+
return await dedicated(parsed, asJson);
|
|
2198
|
+
}
|
|
2199
|
+
await runSessionOperation(parsed, action, asJson);
|
|
2200
|
+
}
|
|
2201
|
+
|
|
2202
|
+
// src/commands/command/whoami-command.ts
|
|
1940
2203
|
async function whoamiCommand(parsed) {
|
|
1941
2204
|
args.expect(parsed, {});
|
|
1942
2205
|
const identity = expectShape(
|
|
@@ -1970,21 +2233,15 @@ async function whoamiCommand(parsed) {
|
|
|
1970
2233
|
]);
|
|
1971
2234
|
}
|
|
1972
2235
|
|
|
1973
|
-
// src/
|
|
1974
|
-
|
|
1975
|
-
|
|
1976
|
-
|
|
1977
|
-
|
|
1978
|
-
|
|
1979
|
-
|
|
1980
|
-
return true;
|
|
1981
|
-
}
|
|
1982
|
-
}
|
|
1983
|
-
return false;
|
|
1984
|
-
}
|
|
2236
|
+
// src/catalog-topics-constants.ts
|
|
2237
|
+
var CATALOG_TOPICS = /* @__PURE__ */ new Set([
|
|
2238
|
+
"repos",
|
|
2239
|
+
"branches",
|
|
2240
|
+
"models",
|
|
2241
|
+
"skills"
|
|
2242
|
+
]);
|
|
1985
2243
|
|
|
1986
|
-
// src/
|
|
1987
|
-
var CATALOG_TOPICS = /* @__PURE__ */ new Set(["repos", "branches", "models", "skills"]);
|
|
2244
|
+
// src/dispatch.ts
|
|
1988
2245
|
async function dispatch(command, argv) {
|
|
1989
2246
|
const parsed = args.parse(argv);
|
|
1990
2247
|
if (parsed.booleans.has("help")) {
|
|
@@ -2016,6 +2273,8 @@ async function dispatch(command, argv) {
|
|
|
2016
2273
|
throw new CliError(`Unknown command "${command}".`, 2);
|
|
2017
2274
|
}
|
|
2018
2275
|
}
|
|
2276
|
+
|
|
2277
|
+
// src/main.ts
|
|
2019
2278
|
async function main() {
|
|
2020
2279
|
const argv = process.argv.slice(2);
|
|
2021
2280
|
out.setJson(wantsJson(argv));
|
|
@@ -2035,9 +2294,19 @@ async function main() {
|
|
|
2035
2294
|
}
|
|
2036
2295
|
await dispatch(command, rest);
|
|
2037
2296
|
}
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
|
|
2297
|
+
|
|
2298
|
+
// src/cli-entry.ts
|
|
2299
|
+
async function cliEntry() {
|
|
2300
|
+
try {
|
|
2301
|
+
await main();
|
|
2302
|
+
} catch (error) {
|
|
2303
|
+
const failure = error instanceof CliError ? error : new CliError(error instanceof Error ? error.message : String(error));
|
|
2304
|
+
out.failure(failure);
|
|
2305
|
+
process.exit(failure.exitCode);
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2308
|
+
void cliEntry();
|
|
2309
|
+
export {
|
|
2310
|
+
cliEntry
|
|
2311
|
+
};
|
|
2043
2312
|
//# sourceMappingURL=index.js.map
|