hivenet 0.4.1 → 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,33 +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-K4DIOYVH.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.0";
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
- if (subcommand === "inbox" || subcommand === "triage" || subcommand === "issues" || subcommand === "issue") {
21
- const owner = await import("./owner-LAL5WKKW.js");
182
+ if (subcommand === "doctor") {
183
+ const { runDoctor } = await import("./doctor-EA2GJRUS.js");
184
+ await runDoctor(process.argv.slice(3), CLI_VERSION);
185
+ return;
186
+ }
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");
22
189
  const run = {
23
190
  inbox: owner.runInbox,
24
191
  triage: owner.runTriage,
25
192
  issues: owner.runIssues,
26
- 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
27
201
  }[subcommand];
28
202
  await run(process.argv.slice(3));
29
203
  return;
30
204
  }
205
+ let inputSeen = false;
31
206
  const args = (0, import_arg.default)(
32
207
  {
33
208
  "--help": Boolean,
@@ -39,7 +214,21 @@ async function main() {
39
214
  "--sentiment": String,
40
215
  "--author": String,
41
216
  "--meta": [String],
217
+ "--task": String,
218
+ "--expected": String,
219
+ "--actual": String,
220
+ "--mistake": String,
221
+ "--attempts": Number,
42
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(),
43
232
  "--json": Boolean,
44
233
  "--dry-run": Boolean,
45
234
  "--flush": Boolean,
@@ -60,49 +249,94 @@ async function main() {
60
249
  return;
61
250
  }
62
251
  if (args["--flush"]) {
252
+ if (args["--input"] !== void 0)
253
+ throw new FeedbackInputError("--input cannot be combined with --flush.", "input_conflict");
63
254
  const result2 = await flushOutbox();
64
- 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
+ );
65
258
  return;
66
259
  }
67
- const feedback = args._.join(" ").trim();
68
- if (!feedback) {
69
- throw new FeedbackError(
70
- `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.
71
285
  Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
72
- );
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
+ };
73
307
  }
74
- const sentiment = parseEnumFlag("--sentiment", args["--sentiment"], sentimentSchema);
75
- const author = parseEnumFlag("--author", args["--author"], authorSchema);
76
- const metadata = parseMetaFlags(args["--meta"]);
77
- const options = {
78
- feedback,
79
- ...args["--to"] !== void 0 ? { to: args["--to"] } : {},
80
- ...args["--dsn"] !== void 0 ? { dsn: args["--dsn"] } : {},
81
- ...args["--category"] !== void 0 ? { category: args["--category"].toLowerCase() } : {},
82
- ...args["--subject"] !== void 0 ? { subject: args["--subject"] } : {},
83
- ...sentiment !== void 0 ? { sentiment } : {},
84
- ...author !== void 0 ? { author } : {},
85
- ...metadata ? { metadata } : {},
86
- ...args["--resume"] !== void 0 ? { resume: args["--resume"] } : {},
87
- clientName: CLI_NAME,
88
- clientVersion: CLI_VERSION
89
- };
90
- flushOutbox({ timeoutMs: 3e3 }).catch(() => {
91
- });
308
+ options.clientName = CLI_NAME;
309
+ options.clientVersion = CLI_VERSION;
92
310
  if (args["--dry-run"]) {
93
311
  const preview = await buildDryRun(options);
94
312
  console.log(JSON.stringify(preview, null, 2));
95
313
  return;
96
314
  }
315
+ flushOutbox({ timeoutMs: 3e3 }).catch(() => {
316
+ });
97
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;
98
323
  if (args["--json"]) {
99
324
  console.log(
100
325
  JSON.stringify(
101
- result.delivered ? {
102
- delivered: true,
103
- ...result.destination.to ? { to: result.destination.to } : {},
104
- ...result.response
105
- } : { delivered: false, queued: result.queued, error: result.error, 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 } : {}
339
+ },
106
340
  null,
107
341
  2
108
342
  )
@@ -112,17 +346,32 @@ Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
112
346
  const toLabel = result.destination.to ?? result.destination.endpoint;
113
347
  if (result.delivered) {
114
348
  console.log(`\u2713 Feedback sent to ${toLabel} (${result.response.id})`);
115
- 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).`);
116
351
  console.log(` Continue this thread: ${resume}`);
352
+ if (attachHint && !result.filesNotAttached) console.log(` ${attachHint}`);
117
353
  if (result.response.guidance) {
118
354
  console.log(` From ${toLabel}: ${result.response.guidance}`);
119
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
+ }
120
365
  } else if (result.queued) {
121
366
  console.log(`\u25CB ${toLabel} is unreachable (${result.error}).`);
122
367
  console.log(` Your feedback was queued locally and will be sent on the next run.`);
123
368
  } else {
124
369
  console.log(`\u2717 Could not deliver feedback to ${toLabel}: ${result.error}`);
125
- 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}`);
126
375
  }
127
376
  }
128
377
  function parseEnumFlag(flag, value, schema) {
@@ -136,19 +385,33 @@ function parseEnumFlag(flag, value, schema) {
136
385
  return parsed.data;
137
386
  }
138
387
  async function buildDryRun(options) {
139
- let captured;
140
- const capture = (async (url, init) => {
141
- if (init?.method !== "POST") {
142
- return fetch(url, init);
388
+ const result = await prepareFeedback(options);
389
+ return {
390
+ destination: { endpoint: result.destination.endpoint, to: result.destination.to },
391
+ event: result.event,
392
+ files: result.files.map(({ manifest, description }) => ({
393
+ ...manifest,
394
+ ...description ? { description } : {}
395
+ }))
396
+ };
397
+ }
398
+ function buildEvalReport(args) {
399
+ const task = args["--task"];
400
+ const details = {
401
+ ...args["--expected"] !== void 0 ? { expected: args["--expected"] } : {},
402
+ ...args["--actual"] !== void 0 ? { actual: args["--actual"] } : {},
403
+ ...args["--mistake"] !== void 0 ? { mistake: args["--mistake"] } : {},
404
+ ...args["--attempts"] !== void 0 ? { attempts: args["--attempts"] } : {}
405
+ };
406
+ if (task === void 0) {
407
+ if (Object.keys(details).length > 0) {
408
+ throw new FeedbackError(
409
+ '--expected/--actual/--mistake/--attempts describe a failed task; add --task "<what you were trying to do>".'
410
+ );
143
411
  }
144
- captured = JSON.parse(String(init.body));
145
- return new Response(JSON.stringify({ id: "evt_dry_run", thread: "aaaaaaaaaaaa" }), {
146
- status: 202,
147
- headers: { "content-type": "application/json" }
148
- });
149
- });
150
- const result = await sendFeedback({ ...options, fetchImpl: capture, outbox: false });
151
- return { destination: { endpoint: result.destination.endpoint, to: result.destination.to }, event: captured };
412
+ return void 0;
413
+ }
414
+ return { task, ...details };
152
415
  }
153
416
  function parseMetaFlags(pairs) {
154
417
  if (!pairs || pairs.length === 0) {
@@ -158,8 +421,7 @@ function parseMetaFlags(pairs) {
158
421
  for (const pair of pairs) {
159
422
  const separator = pair.indexOf("=");
160
423
  if (separator < 1) {
161
- console.error(`--meta expects key=value, got "${pair}".`);
162
- process.exit(2);
424
+ throw new FeedbackError(`--meta expects key=value, got "${pair}".`);
163
425
  }
164
426
  const key = pair.slice(0, separator);
165
427
  const raw = pair.slice(separator + 1);
@@ -171,13 +433,19 @@ function printHelp() {
171
433
  console.log(`
172
434
  Usage
173
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
438
+ $ npx ${CLI_NAME} doctor --to <slug|domain> (vendors: verify your integration end-to-end)
174
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)
175
442
  $ npx ${CLI_NAME} triage --help (owners: set status/notes/links on an event)
176
443
  $ npx ${CLI_NAME} issues --help (owners: clustered issues, regression counts)
177
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)
178
446
 
179
447
  Send feedback about an agentic system's surfaces (CLI, docs, MCP, skills, API)
180
- 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.
181
449
  Feedback can be up to ${MAX_FEEDBACK_LENGTH.toLocaleString("en-US")} characters. Only submit specific, actionable reports.
182
450
 
183
451
  Options
@@ -186,15 +454,34 @@ function printHelp() {
186
454
  --category, -c <cat> ${FEEDBACK_CATEGORIES.join(" | ")} | x-<vendor>
187
455
  --subject, -s <subject> Exact item the feedback is about (see table below)
188
456
  --meta <key=value> Vendor-defined enrichment, repeatable (e.g. --meta sdk_version=1.2.3)
457
+ --task <text> Failed task you are reporting (pair with the surface --category it failed on)
458
+ --expected <text> What should have happened (with --task)
459
+ --actual <text> What actually happened (with --task)
460
+ --mistake <text> The specific mistake made (with --task)
461
+ --attempts <n> How many attempts before giving up (with --task)
189
462
  --sentiment <s> positive | negative | neutral
190
463
  --author <a> agent | human | hybrid (auto-detected when omitted)
191
464
  --resume <threadId> Continue an earlier feedback thread
192
- --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
193
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)
194
472
  --flush Retry locally queued (offline) feedback, then exit
195
473
  --version, -v Version
196
474
  --help, -h This help
197
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
+
198
485
  Subject by category
199
486
  tool Exact tool name, e.g. acme_deploy
200
487
  skill Exact skill name, e.g. eas-simulator
@@ -204,7 +491,6 @@ function printHelp() {
204
491
  cli Full command, e.g. acme deploy --json
205
492
  api Endpoint or SDK method
206
493
  model Model behavior involved
207
- eval Capability the failed task involves (include --category eval details)
208
494
  ux Flow or surface name
209
495
  other Concise topic, or leave empty
210
496
 
@@ -220,10 +506,25 @@ function printHelp() {
220
506
  `);
221
507
  }
222
508
  main().catch((error) => {
223
- if (error instanceof FeedbackError) {
224
- 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
+ );
225
525
  } else {
226
- console.error(error instanceof Error ? error.message : String(error));
526
+ console.error(message);
527
+ if (error instanceof FeedbackInputError) console.error(error.hint);
227
528
  }
228
- process.exit(1);
529
+ process.exitCode = 1;
229
530
  });