hivenet 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -1,38 +1,208 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ attachmentFlags
4
+ } from "./chunk-AK23JXBN.js";
2
5
  import {
3
6
  FEEDBACK_CATEGORIES,
4
7
  FeedbackError,
8
+ MAX_ATTACHMENTS,
5
9
  MAX_FEEDBACK_LENGTH,
6
10
  __toESM,
11
+ attachmentDescriptionSchema,
7
12
  authorSchema,
13
+ evalReportSchema,
14
+ external_exports,
15
+ feedbackEventSchema,
8
16
  flushOutbox,
17
+ prepareFeedback,
9
18
  require_arg,
10
19
  sendFeedback,
11
20
  sentimentSchema
12
- } from "./chunk-WWPF6V2Y.js";
21
+ } from "./chunk-DNR6HNJD.js";
13
22
 
14
23
  // src/cli.ts
15
24
  var import_arg = __toESM(require_arg(), 1);
25
+
26
+ // src/feedback-input.ts
27
+ import { constants } from "fs";
28
+ import { open } from "fs/promises";
29
+ import path from "path";
30
+ var MAX_INPUT_BYTES = 64 * 1024;
31
+ var inputSchema = feedbackEventSchema.pick({
32
+ feedback: true,
33
+ to: true,
34
+ category: true,
35
+ subject: true,
36
+ sentiment: true,
37
+ author: true,
38
+ metadata: true,
39
+ idempotencyKey: true
40
+ }).extend({
41
+ feedback: feedbackEventSchema.shape.feedback.trim().min(1),
42
+ to: feedbackEventSchema.shape.to.unwrap().trim().min(1).optional(),
43
+ dsn: external_exports.url().optional(),
44
+ eval: evalReportSchema.strict().optional(),
45
+ resume: feedbackEventSchema.shape.thread.shape.id.optional(),
46
+ question: feedbackEventSchema.shape.question.unwrap().min(1).optional(),
47
+ attachments: external_exports.array(
48
+ external_exports.preprocess(
49
+ (value) => typeof value === "string" ? { path: value } : value,
50
+ external_exports.strictObject({
51
+ path: external_exports.string().min(1),
52
+ description: attachmentDescriptionSchema.optional()
53
+ })
54
+ )
55
+ ).max(MAX_ATTACHMENTS).optional()
56
+ }).strict().refine((input) => !(input.to && input.dsn), {
57
+ path: ["dsn"],
58
+ message: "Choose either to or dsn, not both."
59
+ });
60
+ var FeedbackInputError = class extends FeedbackError {
61
+ constructor(message, code, issues = []) {
62
+ super(message);
63
+ this.code = code;
64
+ this.issues = issues;
65
+ }
66
+ code;
67
+ issues;
68
+ hint = "Fix the input using the error details, then rerun the command with corrected JSON.";
69
+ };
70
+ async function readFeedbackInput(source) {
71
+ let bytes;
72
+ if (source === "-") {
73
+ if (process.stdin.isTTY)
74
+ throw new FeedbackInputError(
75
+ "--input - needs piped JSON. Use --input report.json or pipe one JSON object to stdin.",
76
+ "input_read_error"
77
+ );
78
+ const chunks = [];
79
+ let size = 0;
80
+ for await (const chunk of process.stdin) {
81
+ const buffer = Buffer.from(chunk);
82
+ size += buffer.length;
83
+ if (size > MAX_INPUT_BYTES) throw inputTooLarge();
84
+ chunks.push(buffer);
85
+ }
86
+ bytes = Buffer.concat(chunks);
87
+ } else {
88
+ let handle;
89
+ try {
90
+ handle = await open(source, constants.O_RDONLY | constants.O_NONBLOCK);
91
+ const stat = await handle.stat();
92
+ if (!stat.isFile())
93
+ throw new FeedbackInputError("--input requires a regular JSON file.", "input_read_error");
94
+ if (stat.size > MAX_INPUT_BYTES) throw inputTooLarge();
95
+ const buffer = Buffer.alloc(MAX_INPUT_BYTES + 1);
96
+ let size = 0;
97
+ while (size < buffer.length) {
98
+ const { bytesRead } = await handle.read(buffer, size, buffer.length - size);
99
+ if (!bytesRead) break;
100
+ size += bytesRead;
101
+ }
102
+ if (size > MAX_INPUT_BYTES) throw inputTooLarge();
103
+ bytes = buffer.subarray(0, size);
104
+ } catch (error) {
105
+ if (error instanceof FeedbackInputError) throw error;
106
+ throw new FeedbackInputError(
107
+ `Cannot read --input file "${source}". Choose an existing readable JSON file.`,
108
+ "input_read_error"
109
+ );
110
+ } finally {
111
+ await handle?.close();
112
+ }
113
+ }
114
+ try {
115
+ return {
116
+ raw: new TextDecoder("utf-8", { fatal: true }).decode(bytes),
117
+ baseDir: source === "-" ? process.cwd() : path.dirname(path.resolve(source))
118
+ };
119
+ } catch {
120
+ throw new FeedbackInputError("JSON input must be UTF-8 text.", "invalid_json");
121
+ }
122
+ }
123
+ function parseFeedbackInput(document) {
124
+ let value;
125
+ try {
126
+ value = JSON.parse(document.raw);
127
+ } catch (error) {
128
+ const location = error instanceof Error ? /\(line \d+ column \d+\)/.exec(error.message)?.[0] : "";
129
+ throw new FeedbackInputError(
130
+ `Invalid JSON${location ? ` ${location}` : ""}. Supply one JSON object with double-quoted keys and no trailing commas.`,
131
+ "invalid_json"
132
+ );
133
+ }
134
+ const parsed = inputSchema.safeParse(value);
135
+ if (!parsed.success) {
136
+ const issues = parsed.error.issues.flatMap((issue) => {
137
+ if (issue.code !== "unrecognized_keys")
138
+ return [{ path: fieldPath(issue.path), message: issue.message }];
139
+ const fields = issue.path[0] === "attachments" ? ["path", "description"] : issue.path[0] === "eval" ? Object.keys(evalReportSchema.shape) : Object.keys(inputSchema.shape);
140
+ return issue.keys.map((key) => ({
141
+ path: fieldPath([...issue.path, key]),
142
+ message: `Unknown field. Use: ${fields.join(", ")}.`
143
+ }));
144
+ });
145
+ throw new FeedbackInputError(
146
+ `Invalid feedback input:
147
+ ${issues.map((issue) => ` ${issue.path}: ${issue.message}`).join("\n")}`,
148
+ "invalid_input",
149
+ issues
150
+ );
151
+ }
152
+ const { attachments, ...input } = parsed.data;
153
+ return {
154
+ ...input,
155
+ ...attachments ? {
156
+ attachments: attachments.map((file) => ({
157
+ ...file,
158
+ path: path.resolve(document.baseDir, file.path)
159
+ }))
160
+ } : {}
161
+ };
162
+ }
163
+ function fieldPath(segments) {
164
+ return segments.map(
165
+ (segment, index) => typeof segment === "number" ? `[${segment}]` : `${index ? "." : ""}${String(segment)}`
166
+ ).join("") || "input";
167
+ }
168
+ function inputTooLarge() {
169
+ return new FeedbackInputError(
170
+ "JSON input exceeds 64 KiB. Attach large files by path instead of embedding their contents.",
171
+ "input_too_large"
172
+ );
173
+ }
174
+
175
+ // src/cli.ts
16
176
  var CLI_NAME = "hivenet";
17
- var CLI_VERSION = "0.4.2";
177
+ var CLI_VERSION = "0.6.0";
178
+ var rawArgs = process.argv.slice(2);
179
+ var flagArgs = rawArgs.slice(0, rawArgs.includes("--") ? rawArgs.indexOf("--") : void 0);
18
180
  async function main() {
19
181
  const subcommand = process.argv[2];
20
182
  if (subcommand === "doctor") {
21
- const { runDoctor } = await import("./doctor-CVWF7J3P.js");
183
+ const { runDoctor } = await import("./doctor-EA2GJRUS.js");
22
184
  await runDoctor(process.argv.slice(3), CLI_VERSION);
23
185
  return;
24
186
  }
25
- if (subcommand === "inbox" || subcommand === "triage" || subcommand === "issues" || subcommand === "issue") {
26
- const owner = await import("./owner-N2MSYLV7.js");
187
+ if (subcommand === "inbox" || subcommand === "triage" || subcommand === "issues" || subcommand === "issue" || subcommand === "questions" || subcommand === "ask" || subcommand === "question" || subcommand === "evals" || subcommand === "eval" || subcommand === "promote" || subcommand === "attach") {
188
+ const owner = await import("./owner-ZWROA7AU.js");
27
189
  const run = {
28
190
  inbox: owner.runInbox,
29
191
  triage: owner.runTriage,
30
192
  issues: owner.runIssues,
31
- issue: owner.runIssue
193
+ issue: owner.runIssue,
194
+ questions: owner.runQuestions,
195
+ ask: owner.runAsk,
196
+ question: owner.runQuestion,
197
+ evals: owner.runEvals,
198
+ eval: owner.runEval,
199
+ promote: owner.runPromote,
200
+ attach: owner.runAttach
32
201
  }[subcommand];
33
202
  await run(process.argv.slice(3));
34
203
  return;
35
204
  }
205
+ let inputSeen = false;
36
206
  const args = (0, import_arg.default)(
37
207
  {
38
208
  "--help": Boolean,
@@ -50,6 +220,15 @@ async function main() {
50
220
  "--mistake": String,
51
221
  "--attempts": Number,
52
222
  "--resume": String,
223
+ "--answer": String,
224
+ "--idempotency-key": String,
225
+ "--input": (value) => {
226
+ if (inputSeen)
227
+ throw new FeedbackInputError("Use --input once for one JSON report.", "input_conflict");
228
+ inputSeen = true;
229
+ return value;
230
+ },
231
+ ...attachmentFlags(),
53
232
  "--json": Boolean,
54
233
  "--dry-run": Boolean,
55
234
  "--flush": Boolean,
@@ -70,55 +249,93 @@ async function main() {
70
249
  return;
71
250
  }
72
251
  if (args["--flush"]) {
252
+ if (args["--input"] !== void 0)
253
+ throw new FeedbackInputError("--input cannot be combined with --flush.", "input_conflict");
73
254
  const result2 = await flushOutbox();
74
- console.log(`Outbox: delivered ${result2.delivered}, remaining ${result2.remaining}.`);
255
+ console.log(
256
+ args["--json"] ? JSON.stringify(result2) : `Outbox: delivered ${result2.delivered}, remaining ${result2.remaining}.`
257
+ );
75
258
  return;
76
259
  }
77
- const feedback = args._.join(" ").trim();
78
- if (!feedback) {
79
- throw new FeedbackError(
80
- `A feedback message is required.
260
+ let options;
261
+ if (args["--input"] !== void 0) {
262
+ const allowed = /* @__PURE__ */ new Set(["_", "--input", "--json", "--dry-run", "--to", "--dsn"]);
263
+ const conflicts = Object.keys(args).filter((flag) => !allowed.has(flag));
264
+ if (args._.length || conflicts.length)
265
+ throw new FeedbackInputError(
266
+ `--input cannot be combined with positional feedback or report flags${conflicts.length ? ` (${conflicts.join(", ")})` : ""}. Put report fields in the JSON object.`,
267
+ "input_conflict"
268
+ );
269
+ const inputDocument = await readFeedbackInput(args["--input"]);
270
+ options = parseFeedbackInput(inputDocument);
271
+ if ((args["--to"] !== void 0 || args["--dsn"] !== void 0) && (options.to !== void 0 || options.dsn !== void 0))
272
+ throw new FeedbackInputError(
273
+ "Set the destination in JSON or with --to/--dsn, not both.",
274
+ "input_conflict"
275
+ );
276
+ if (args["--to"] !== void 0 && args["--dsn"] !== void 0)
277
+ throw new FeedbackInputError("Choose either --to or --dsn.", "input_conflict");
278
+ if (args["--to"] !== void 0) options.to = args["--to"];
279
+ if (args["--dsn"] !== void 0) options.dsn = args["--dsn"];
280
+ } else {
281
+ const feedback = args._.join(" ").trim();
282
+ if (!feedback) {
283
+ throw new FeedbackError(
284
+ `A feedback message is required.
81
285
  Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
82
- );
286
+ );
287
+ }
288
+ const sentiment = parseEnumFlag("--sentiment", args["--sentiment"], sentimentSchema);
289
+ const author = parseEnumFlag("--author", args["--author"], authorSchema);
290
+ const metadata = parseMetaFlags(args["--meta"]);
291
+ const evalReport = buildEvalReport(args);
292
+ options = {
293
+ feedback,
294
+ ...args["--to"] !== void 0 ? { to: args["--to"] } : {},
295
+ ...args["--dsn"] !== void 0 ? { dsn: args["--dsn"] } : {},
296
+ ...args["--category"] !== void 0 ? { category: args["--category"].toLowerCase() } : {},
297
+ ...args["--subject"] !== void 0 ? { subject: args["--subject"] } : {},
298
+ ...evalReport ? { eval: evalReport } : {},
299
+ ...sentiment !== void 0 ? { sentiment } : {},
300
+ ...author !== void 0 ? { author } : {},
301
+ ...metadata ? { metadata } : {},
302
+ ...args["--resume"] !== void 0 ? { resume: args["--resume"] } : {},
303
+ ...args["--answer"] !== void 0 ? { question: args["--answer"] } : {},
304
+ ...args["--idempotency-key"] !== void 0 ? { idempotencyKey: args["--idempotency-key"] } : {},
305
+ ...args["--attach"] ? { attachments: args["--attach"] } : {}
306
+ };
83
307
  }
84
- const sentiment = parseEnumFlag("--sentiment", args["--sentiment"], sentimentSchema);
85
- const author = parseEnumFlag("--author", args["--author"], authorSchema);
86
- const metadata = parseMetaFlags(args["--meta"]);
87
- const evalReport = buildEvalReport(args);
88
- const options = {
89
- feedback,
90
- ...args["--to"] !== void 0 ? { to: args["--to"] } : {},
91
- ...args["--dsn"] !== void 0 ? { dsn: args["--dsn"] } : {},
92
- ...args["--category"] !== void 0 ? { category: args["--category"].toLowerCase() } : evalReport ? { category: "eval" } : {},
93
- ...args["--subject"] !== void 0 ? { subject: args["--subject"] } : {},
94
- ...evalReport ? { eval: evalReport } : {},
95
- ...sentiment !== void 0 ? { sentiment } : {},
96
- ...author !== void 0 ? { author } : {},
97
- ...metadata ? { metadata } : {},
98
- ...args["--resume"] !== void 0 ? { resume: args["--resume"] } : {},
99
- clientName: CLI_NAME,
100
- clientVersion: CLI_VERSION
101
- };
102
- flushOutbox({ timeoutMs: 3e3 }).catch(() => {
103
- });
308
+ options.clientName = CLI_NAME;
309
+ options.clientVersion = CLI_VERSION;
104
310
  if (args["--dry-run"]) {
105
311
  const preview = await buildDryRun(options);
106
312
  console.log(JSON.stringify(preview, null, 2));
107
313
  return;
108
314
  }
315
+ flushOutbox({ timeoutMs: 3e3 }).catch(() => {
316
+ });
109
317
  const result = await sendFeedback(options);
318
+ if (!result.delivered && !result.queued) process.exitCode = 1;
319
+ const resume = (result.delivered ? result.response.resume : void 0) ?? `npx ${CLI_NAME}${result.destination.to ? ` --to ${result.destination.to}` : ""} --resume ${result.event.thread.id}`;
320
+ const answer = result.event.question;
321
+ const attach = `${resume}${answer ? ` --answer ${answer}` : ""} --attach <path> "<what it shows>"`;
322
+ const attachHint = result.filesNotAttached ? `Add them later: ${attach}` : result.delivered && !result.event.attachments?.length && !answer ? `Have a screenshot or log that shows this? Attach it: ${attach}` : void 0;
110
323
  if (args["--json"]) {
111
324
  console.log(
112
325
  JSON.stringify(
113
- result.delivered ? {
114
- delivered: true,
115
- ...result.destination.to ? { to: result.destination.to } : {},
116
- ...result.response
117
- } : {
118
- delivered: false,
119
- queued: result.queued,
120
- error: result.error,
121
- thread: result.event.thread.id
326
+ {
327
+ ...result.delivered ? {
328
+ delivered: true,
329
+ ...result.destination.to ? { to: result.destination.to } : {},
330
+ ...result.response
331
+ } : {
332
+ delivered: false,
333
+ queued: result.queued,
334
+ error: result.error,
335
+ thread: result.event.thread.id
336
+ },
337
+ ...result.filesNotAttached ? { filesNotAttached: result.filesNotAttached } : {},
338
+ ...attachHint ? { attach_hint: attachHint } : {}
122
339
  },
123
340
  null,
124
341
  2
@@ -129,17 +346,32 @@ Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
129
346
  const toLabel = result.destination.to ?? result.destination.endpoint;
130
347
  if (result.delivered) {
131
348
  console.log(`\u2713 Feedback sent to ${toLabel} (${result.response.id})`);
132
- const resume = result.response.resume ?? `npx ${CLI_NAME}${result.destination.to ? ` --to ${result.destination.to}` : ""} --resume ${result.response.thread}`;
349
+ if (result.event.attachments?.length)
350
+ console.log(` Attached ${result.event.attachments.length} file(s).`);
133
351
  console.log(` Continue this thread: ${resume}`);
352
+ if (attachHint && !result.filesNotAttached) console.log(` ${attachHint}`);
134
353
  if (result.response.guidance) {
135
354
  console.log(` From ${toLabel}: ${result.response.guidance}`);
136
355
  }
356
+ if (result.response.ask) {
357
+ console.log(` ? ${toLabel} asks: ${result.response.ask.prompt}`);
358
+ console.log(` Answer (only if you can ground it): ${result.response.ask.command}`);
359
+ for (const file of result.response.ask.attachments ?? [])
360
+ console.log(
361
+ ` File: ${file.filename} (${file.mediaType}, ${file.size} bytes) ${file.url} (expires ${file.expiresAt})${file.description ? `
362
+ Description: ${file.description}` : ""}`
363
+ );
364
+ }
137
365
  } else if (result.queued) {
138
366
  console.log(`\u25CB ${toLabel} is unreachable (${result.error}).`);
139
367
  console.log(` Your feedback was queued locally and will be sent on the next run.`);
140
368
  } else {
141
369
  console.log(`\u2717 Could not deliver feedback to ${toLabel}: ${result.error}`);
142
- process.exitCode = 1;
370
+ }
371
+ if (result.filesNotAttached) {
372
+ const { files, reason } = result.filesNotAttached;
373
+ console.log(` ! Not attached (${reason}): ${files.join(", ")}`);
374
+ console.log(` ${attachHint}`);
143
375
  }
144
376
  }
145
377
  function parseEnumFlag(flag, value, schema) {
@@ -153,21 +385,14 @@ function parseEnumFlag(flag, value, schema) {
153
385
  return parsed.data;
154
386
  }
155
387
  async function buildDryRun(options) {
156
- let captured;
157
- const capture = (async (url, init) => {
158
- if (init?.method !== "POST") {
159
- return fetch(url, init);
160
- }
161
- captured = JSON.parse(String(init.body));
162
- return new Response(JSON.stringify({ id: "evt_dry_run", thread: "aaaaaaaaaaaa" }), {
163
- status: 202,
164
- headers: { "content-type": "application/json" }
165
- });
166
- });
167
- const result = await sendFeedback({ ...options, fetchImpl: capture, outbox: false });
388
+ const result = await prepareFeedback(options);
168
389
  return {
169
390
  destination: { endpoint: result.destination.endpoint, to: result.destination.to },
170
- event: captured
391
+ event: result.event,
392
+ files: result.files.map(({ manifest, description }) => ({
393
+ ...manifest,
394
+ ...description ? { description } : {}
395
+ }))
171
396
  };
172
397
  }
173
398
  function buildEvalReport(args) {
@@ -196,8 +421,7 @@ function parseMetaFlags(pairs) {
196
421
  for (const pair of pairs) {
197
422
  const separator = pair.indexOf("=");
198
423
  if (separator < 1) {
199
- console.error(`--meta expects key=value, got "${pair}".`);
200
- process.exit(2);
424
+ throw new FeedbackError(`--meta expects key=value, got "${pair}".`);
201
425
  }
202
426
  const key = pair.slice(0, separator);
203
427
  const raw = pair.slice(separator + 1);
@@ -209,14 +433,19 @@ function printHelp() {
209
433
  console.log(`
210
434
  Usage
211
435
  $ npx ${CLI_NAME} --to <slug|domain> [options] "<feedback>"
436
+ $ npx ${CLI_NAME} --input report.json [--json] [--dry-run]
437
+ $ cat report.json | npx ${CLI_NAME} --input - --json
212
438
  $ npx ${CLI_NAME} doctor --to <slug|domain> (vendors: verify your integration end-to-end)
213
439
  $ npx ${CLI_NAME} inbox --help (owners: list + filter your project's feedback)
440
+ $ npx ${CLI_NAME} questions --help (owners: ask your agent users, read the answers)
441
+ $ npx ${CLI_NAME} evals --help (owners: curate failed tasks into a replayable corpus)
214
442
  $ npx ${CLI_NAME} triage --help (owners: set status/notes/links on an event)
215
443
  $ npx ${CLI_NAME} issues --help (owners: clustered issues, regression counts)
216
444
  $ npx ${CLI_NAME} issue --help (owners: manage one issue; cascades to events)
445
+ $ npx ${CLI_NAME} attach --help (owners: add files to a record)
217
446
 
218
447
  Send feedback about an agentic system's surfaces (CLI, docs, MCP, skills, API)
219
- to its maintainers. Works for AI agents and humans; never prompts, never blocks.
448
+ to its maintainers. Works for AI agents and humans; never opens an interactive prompt.
220
449
  Feedback can be up to ${MAX_FEEDBACK_LENGTH.toLocaleString("en-US")} characters. Only submit specific, actionable reports.
221
450
 
222
451
  Options
@@ -225,7 +454,7 @@ function printHelp() {
225
454
  --category, -c <cat> ${FEEDBACK_CATEGORIES.join(" | ")} | x-<vendor>
226
455
  --subject, -s <subject> Exact item the feedback is about (see table below)
227
456
  --meta <key=value> Vendor-defined enrichment, repeatable (e.g. --meta sdk_version=1.2.3)
228
- --task <text> Failed task you are reporting (sets --category eval when omitted)
457
+ --task <text> Failed task you are reporting (pair with the surface --category it failed on)
229
458
  --expected <text> What should have happened (with --task)
230
459
  --actual <text> What actually happened (with --task)
231
460
  --mistake <text> The specific mistake made (with --task)
@@ -233,12 +462,26 @@ function printHelp() {
233
462
  --sentiment <s> positive | negative | neutral
234
463
  --author <a> agent | human | hybrid (auto-detected when omitted)
235
464
  --resume <threadId> Continue an earlier feedback thread
236
- --json Machine-readable result
465
+ --answer <questionId> Answer a question a previous response asked (only if grounded)
466
+ --idempotency-key <key> Stable retry key (8\u201364 letters, digits, _ or -)
467
+ --input <file|-> Read one JSON report from a UTF-8 file or stdin (64 KiB maximum)
468
+ --json Machine-readable result or error; does not read JSON input
237
469
  --dry-run Print the event and destination without sending
470
+ --attach <path> Attach a local file (repeatable; 10 files, 20 MiB each)
471
+ --describe <text> What the preceding --attach shows (optional, up to 1000 characters)
238
472
  --flush Retry locally queued (offline) feedback, then exit
239
473
  --version, -v Version
240
474
  --help, -h This help
241
475
 
476
+ JSON input (--input report.json, or --input - to read stdin)
477
+ { "to": "acme", "feedback": "Export failed",
478
+ "attachments": [{ "path": "./screen.png", "description": "The error after export." }] }
479
+ Fields: feedback, to or dsn, category, subject, sentiment, author, metadata,
480
+ eval { task, expected?, actual?, mistake?, attempts? }, resume, question, attachments,
481
+ idempotencyKey. Paths are relative to the JSON file (stdin: the current directory).
482
+ Don't combine --input with positional feedback or report flags. Invalid input exits 1
483
+ with the fields to fix; nothing is sent or saved.
484
+
242
485
  Subject by category
243
486
  tool Exact tool name, e.g. acme_deploy
244
487
  skill Exact skill name, e.g. eas-simulator
@@ -248,7 +491,6 @@ function printHelp() {
248
491
  cli Full command, e.g. acme deploy --json
249
492
  api Endpoint or SDK method
250
493
  model Model behavior involved
251
- eval Capability the failed task involves (add --task, --expected, --actual, \u2026)
252
494
  ux Flow or surface name
253
495
  other Concise topic, or leave empty
254
496
 
@@ -264,10 +506,25 @@ function printHelp() {
264
506
  `);
265
507
  }
266
508
  main().catch((error) => {
267
- if (error instanceof FeedbackError) {
268
- console.error(error.message);
509
+ const message = error instanceof Error ? error.message : String(error);
510
+ if (flagArgs.includes("--json")) {
511
+ console.log(
512
+ JSON.stringify(
513
+ {
514
+ delivered: false,
515
+ queued: false,
516
+ code: error instanceof FeedbackInputError ? error.code : "feedback_error",
517
+ error: message,
518
+ ...error instanceof FeedbackInputError && error.issues.length ? { issues: error.issues } : {},
519
+ ...error instanceof FeedbackInputError ? { hint: error.hint } : {}
520
+ },
521
+ null,
522
+ 2
523
+ )
524
+ );
269
525
  } else {
270
- console.error(error instanceof Error ? error.message : String(error));
526
+ console.error(message);
527
+ if (error instanceof FeedbackInputError) console.error(error.hint);
271
528
  }
272
- process.exit(1);
529
+ process.exitCode = 1;
273
530
  });
@@ -10,7 +10,7 @@ import {
10
10
  require_arg,
11
11
  validateDescriptor,
12
12
  validateFeedbackEvent
13
- } from "./chunk-WWPF6V2Y.js";
13
+ } from "./chunk-DNR6HNJD.js";
14
14
 
15
15
  // src/doctor.ts
16
16
  var import_arg = __toESM(require_arg(), 1);