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/README.md +75 -4
- package/dist/chunk-AK23JXBN.js +32 -0
- package/dist/{chunk-K4DIOYVH.js → chunk-DNR6HNJD.js} +607 -166
- package/dist/cli.js +358 -57
- package/dist/doctor-EA2GJRUS.js +210 -0
- package/dist/index.cjs +16771 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1053 -0
- package/dist/index.d.ts +1053 -0
- package/dist/index.js +16709 -0
- package/dist/index.js.map +1 -0
- package/dist/owner-ZWROA7AU.js +539 -0
- package/package.json +14 -1
- package/dist/owner-LAL5WKKW.js +0 -235
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-
|
|
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.
|
|
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 === "
|
|
21
|
-
const
|
|
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(
|
|
255
|
+
console.log(
|
|
256
|
+
args["--json"] ? JSON.stringify(result2) : `Outbox: delivered ${result2.delivered}, remaining ${result2.remaining}.`
|
|
257
|
+
);
|
|
65
258
|
return;
|
|
66
259
|
}
|
|
67
|
-
|
|
68
|
-
if (
|
|
69
|
-
|
|
70
|
-
|
|
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
|
-
|
|
75
|
-
|
|
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
|
-
|
|
102
|
-
delivered
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
145
|
-
|
|
146
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
--
|
|
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
|
-
|
|
224
|
-
|
|
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(
|
|
526
|
+
console.error(message);
|
|
527
|
+
if (error instanceof FeedbackInputError) console.error(error.hint);
|
|
227
528
|
}
|
|
228
|
-
process.
|
|
529
|
+
process.exitCode = 1;
|
|
229
530
|
});
|