hivenet 0.4.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 ADDED
@@ -0,0 +1,229 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ FEEDBACK_CATEGORIES,
4
+ FeedbackError,
5
+ MAX_FEEDBACK_LENGTH,
6
+ __toESM,
7
+ authorSchema,
8
+ flushOutbox,
9
+ require_arg,
10
+ sendFeedback,
11
+ sentimentSchema
12
+ } from "./chunk-K4DIOYVH.js";
13
+
14
+ // src/cli.ts
15
+ var import_arg = __toESM(require_arg(), 1);
16
+ var CLI_NAME = "hivenet";
17
+ var CLI_VERSION = "0.4.0";
18
+ async function main() {
19
+ const subcommand = process.argv[2];
20
+ if (subcommand === "inbox" || subcommand === "triage" || subcommand === "issues" || subcommand === "issue") {
21
+ const owner = await import("./owner-LAL5WKKW.js");
22
+ const run = {
23
+ inbox: owner.runInbox,
24
+ triage: owner.runTriage,
25
+ issues: owner.runIssues,
26
+ issue: owner.runIssue
27
+ }[subcommand];
28
+ await run(process.argv.slice(3));
29
+ return;
30
+ }
31
+ const args = (0, import_arg.default)(
32
+ {
33
+ "--help": Boolean,
34
+ "--version": Boolean,
35
+ "--to": String,
36
+ "--dsn": String,
37
+ "--category": String,
38
+ "--subject": String,
39
+ "--sentiment": String,
40
+ "--author": String,
41
+ "--meta": [String],
42
+ "--resume": String,
43
+ "--json": Boolean,
44
+ "--dry-run": Boolean,
45
+ "--flush": Boolean,
46
+ "-h": "--help",
47
+ "-v": "--version",
48
+ "-t": "--to",
49
+ "-c": "--category",
50
+ "-s": "--subject"
51
+ },
52
+ { argv: process.argv.slice(2), permissive: false }
53
+ );
54
+ if (args["--help"]) {
55
+ printHelp();
56
+ return;
57
+ }
58
+ if (args["--version"]) {
59
+ console.log(CLI_VERSION);
60
+ return;
61
+ }
62
+ if (args["--flush"]) {
63
+ const result2 = await flushOutbox();
64
+ console.log(`Outbox: delivered ${result2.delivered}, remaining ${result2.remaining}.`);
65
+ return;
66
+ }
67
+ const feedback = args._.join(" ").trim();
68
+ if (!feedback) {
69
+ throw new FeedbackError(
70
+ `A feedback message is required.
71
+ Usage: npx ${CLI_NAME} --to <slug|domain> "<specific, actionable feedback>"`
72
+ );
73
+ }
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
+ });
92
+ if (args["--dry-run"]) {
93
+ const preview = await buildDryRun(options);
94
+ console.log(JSON.stringify(preview, null, 2));
95
+ return;
96
+ }
97
+ const result = await sendFeedback(options);
98
+ if (args["--json"]) {
99
+ console.log(
100
+ 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 },
106
+ null,
107
+ 2
108
+ )
109
+ );
110
+ return;
111
+ }
112
+ const toLabel = result.destination.to ?? result.destination.endpoint;
113
+ if (result.delivered) {
114
+ 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}`;
116
+ console.log(` Continue this thread: ${resume}`);
117
+ if (result.response.guidance) {
118
+ console.log(` From ${toLabel}: ${result.response.guidance}`);
119
+ }
120
+ } else if (result.queued) {
121
+ console.log(`\u25CB ${toLabel} is unreachable (${result.error}).`);
122
+ console.log(` Your feedback was queued locally and will be sent on the next run.`);
123
+ } else {
124
+ console.log(`\u2717 Could not deliver feedback to ${toLabel}: ${result.error}`);
125
+ process.exitCode = 1;
126
+ }
127
+ }
128
+ function parseEnumFlag(flag, value, schema) {
129
+ if (value === void 0) {
130
+ return void 0;
131
+ }
132
+ const parsed = schema.safeParse(value.toLowerCase());
133
+ if (!parsed.success) {
134
+ throw new FeedbackError(`Invalid ${flag} "${value}". Expected: ${schema.options.join(", ")}.`);
135
+ }
136
+ return parsed.data;
137
+ }
138
+ async function buildDryRun(options) {
139
+ let captured;
140
+ const capture = (async (url, init) => {
141
+ if (init?.method !== "POST") {
142
+ return fetch(url, init);
143
+ }
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 };
152
+ }
153
+ function parseMetaFlags(pairs) {
154
+ if (!pairs || pairs.length === 0) {
155
+ return void 0;
156
+ }
157
+ const metadata = {};
158
+ for (const pair of pairs) {
159
+ const separator = pair.indexOf("=");
160
+ if (separator < 1) {
161
+ console.error(`--meta expects key=value, got "${pair}".`);
162
+ process.exit(2);
163
+ }
164
+ const key = pair.slice(0, separator);
165
+ const raw = pair.slice(separator + 1);
166
+ metadata[key] = raw === "true" ? true : raw === "false" ? false : raw !== "" && String(Number(raw)) === raw ? Number(raw) : raw;
167
+ }
168
+ return metadata;
169
+ }
170
+ function printHelp() {
171
+ console.log(`
172
+ Usage
173
+ $ npx ${CLI_NAME} --to <slug|domain> [options] "<feedback>"
174
+ $ npx ${CLI_NAME} inbox --help (owners: list + filter your project's feedback)
175
+ $ npx ${CLI_NAME} triage --help (owners: set status/notes/links on an event)
176
+ $ npx ${CLI_NAME} issues --help (owners: clustered issues, regression counts)
177
+ $ npx ${CLI_NAME} issue --help (owners: manage one issue; cascades to events)
178
+
179
+ 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.
181
+ Feedback can be up to ${MAX_FEEDBACK_LENGTH.toLocaleString("en-US")} characters. Only submit specific, actionable reports.
182
+
183
+ Options
184
+ --to, -t <slug|domain> Destination: registry slug (acme) or domain (acme.dev)
185
+ --dsn <url> Explicit DSN: https://hv_pub_KEY@host/slug
186
+ --category, -c <cat> ${FEEDBACK_CATEGORIES.join(" | ")} | x-<vendor>
187
+ --subject, -s <subject> Exact item the feedback is about (see table below)
188
+ --meta <key=value> Vendor-defined enrichment, repeatable (e.g. --meta sdk_version=1.2.3)
189
+ --sentiment <s> positive | negative | neutral
190
+ --author <a> agent | human | hybrid (auto-detected when omitted)
191
+ --resume <threadId> Continue an earlier feedback thread
192
+ --json Machine-readable result
193
+ --dry-run Print the event and destination without sending
194
+ --flush Retry locally queued (offline) feedback, then exit
195
+ --version, -v Version
196
+ --help, -h This help
197
+
198
+ Subject by category
199
+ tool Exact tool name, e.g. acme_deploy
200
+ skill Exact skill name, e.g. eas-simulator
201
+ prompt Prompt or template name
202
+ docs Full documentation URL
203
+ mcp Exact MCP tool or server name
204
+ cli Full command, e.g. acme deploy --json
205
+ api Endpoint or SDK method
206
+ model Model behavior involved
207
+ eval Capability the failed task involves (include --category eval details)
208
+ ux Flow or surface name
209
+ other Concise topic, or leave empty
210
+
211
+ Data collection
212
+ Submissions include best-effort environment context: agent harness + session ID,
213
+ sandbox, CI, OS/arch/node, and vendor-configured project/session metadata.
214
+ Set DO_NOT_TRACK=1 (or HIVENET_NO_TELEMETRY=1) to omit all of it \u2014
215
+ feedback is still delivered anonymously.
216
+
217
+ Destination resolution
218
+ --dsn \u2192 HIVENET_DSN \u2192 nearest feedback.config.json \u2192 --to slug (registry)
219
+ \u2192 --to domain (https://<domain>/.well-known/agent-feedback.json)
220
+ `);
221
+ }
222
+ main().catch((error) => {
223
+ if (error instanceof FeedbackError) {
224
+ console.error(error.message);
225
+ } else {
226
+ console.error(error instanceof Error ? error.message : String(error));
227
+ }
228
+ process.exit(1);
229
+ });
@@ -0,0 +1,235 @@
1
+ import {
2
+ EVENT_STATUSES,
3
+ FeedbackError,
4
+ ISSUE_STATUSES,
5
+ __toESM,
6
+ ownerApi,
7
+ require_arg,
8
+ resolveApiOrigin
9
+ } from "./chunk-K4DIOYVH.js";
10
+
11
+ // src/owner.ts
12
+ var import_arg = __toESM(require_arg(), 1);
13
+ var SECRET_ENV_VAR = "HIVENET_SECRET_KEY";
14
+ var OWNER_FLAGS = {
15
+ "--to": String,
16
+ "--dsn": String,
17
+ "--api": String,
18
+ "--secret": String,
19
+ "--json": Boolean,
20
+ "--help": Boolean,
21
+ "-h": "--help"
22
+ };
23
+ async function buildApi(args) {
24
+ const secretKey = args["--secret"] ?? process.env[SECRET_ENV_VAR];
25
+ if (!secretKey || !secretKey.startsWith("hv_sec_")) {
26
+ throw new FeedbackError(
27
+ `An owner secret key is required (hv_sec_\u2026, from your project's Settings).
28
+ Pass --secret <key> or set ${SECRET_ENV_VAR}.`
29
+ );
30
+ }
31
+ const apiOrigin = args["--api"] ?? await resolveApiOrigin({
32
+ ...args["--to"] !== void 0 ? { to: args["--to"] } : {},
33
+ ...args["--dsn"] !== void 0 ? { dsn: args["--dsn"] } : {}
34
+ });
35
+ return ownerApi({ secretKey, apiOrigin });
36
+ }
37
+ function statusIcon(status) {
38
+ return { new: "\u25CF", seen: "\u25D0", in_progress: "\u25D2", resolved: "\u2713", wont_fix: "\u2717", spam: "\u2298" }[status];
39
+ }
40
+ function printEvent(event, verbose = false) {
41
+ const subject = event.subject ? ` \xB7 ${event.subject}` : "";
42
+ console.log(
43
+ `${statusIcon(event.status)} ${event.id} [${event.status}] ${event.category}${subject} (${event.agent_id ?? event.author ?? "unknown"}, ${event.received_at.slice(0, 16).replace("T", " ")})`
44
+ );
45
+ console.log(` ${event.feedback}`);
46
+ if (event.triage_note) {
47
+ console.log(` \u21B3 note: ${event.triage_note}`);
48
+ }
49
+ if (verbose) {
50
+ console.log(` thread ${event.thread_id} \xB7 telemetry ${event.consent_telemetry ? "on" : "off"}`);
51
+ }
52
+ }
53
+ async function runInbox(argv) {
54
+ const args = (0, import_arg.default)(
55
+ { ...OWNER_FLAGS, "--status": String, "--category": String, "--thread": String, "--limit": Number },
56
+ { argv }
57
+ );
58
+ if (args["--help"]) {
59
+ console.log(`
60
+ Usage
61
+ $ npx hivenet inbox [--status new] [--category mcp] [--thread <id>] [--limit 50]
62
+
63
+ List feedback for your project (owners only). Auth: --secret <hv_sec_\u2026> or
64
+ ${SECRET_ENV_VAR}. Destination: --to <slug|domain>, --dsn, or --api <origin>
65
+ (defaults to the hosted API). Statuses: ${EVENT_STATUSES.join(" | ")}. --json for raw output.
66
+ `);
67
+ return;
68
+ }
69
+ const status = args["--status"];
70
+ if (status && !EVENT_STATUSES.includes(status)) {
71
+ throw new FeedbackError(`Invalid --status "${status}". Expected: ${EVENT_STATUSES.join(", ")}.`);
72
+ }
73
+ const api = await buildApi(args);
74
+ const result = await api.listFeedback({
75
+ ...status ? { status } : {},
76
+ ...args["--category"] ? { category: args["--category"] } : {},
77
+ ...args["--thread"] ? { thread: args["--thread"] } : {},
78
+ ...args["--limit"] ? { limit: args["--limit"] } : {}
79
+ });
80
+ if (args["--json"]) {
81
+ console.log(JSON.stringify(result, null, 2));
82
+ return;
83
+ }
84
+ if (result.events.length === 0) {
85
+ console.log(`No matching feedback for ${result.project}.`);
86
+ return;
87
+ }
88
+ console.log(`${result.events.length} event(s) for ${result.project}:
89
+ `);
90
+ for (const event of result.events) {
91
+ printEvent(event);
92
+ }
93
+ console.log(`
94
+ Triage: npx hivenet triage <eventId> --status <${EVENT_STATUSES.join("|")}> [--note "\u2026"]`);
95
+ }
96
+ async function runTriage(argv) {
97
+ const args = (0, import_arg.default)({ ...OWNER_FLAGS, "--status": String, "--note": String, "--link": String }, { argv });
98
+ if (args["--help"] || args._.length === 0) {
99
+ console.log(`
100
+ Usage
101
+ $ npx hivenet triage <eventId> [--status <status>] [--note "why"] [--link <url>]
102
+
103
+ Set triage status and/or attach an external link (Slack, Linear, GitHub, PR) to a
104
+ feedback event (owners only). Statuses: ${EVENT_STATUSES.join(" | ")}.
105
+ Auth: --secret <hv_sec_\u2026> or ${SECRET_ENV_VAR}. Destination: --to/--dsn/--api.
106
+ `);
107
+ if (!args["--help"]) {
108
+ throw new FeedbackError("An event ID is required.");
109
+ }
110
+ return;
111
+ }
112
+ const status = args["--status"];
113
+ if (status && !EVENT_STATUSES.includes(status)) {
114
+ throw new FeedbackError(`Invalid --status. Expected: ${EVENT_STATUSES.join(", ")}.`);
115
+ }
116
+ if (!status && !args["--link"]) {
117
+ throw new FeedbackError("Provide --status and/or --link.");
118
+ }
119
+ const api = await buildApi(args);
120
+ const id = args._[0];
121
+ if (args["--link"]) {
122
+ await api.addLink(id, args["--link"]);
123
+ }
124
+ const event = status ? (await api.setFeedbackStatus(id, status, args["--note"] !== void 0 ? args["--note"] : void 0)).event : (await api.getFeedback(id)).event;
125
+ if (args["--json"]) {
126
+ console.log(JSON.stringify({ event }, null, 2));
127
+ return;
128
+ }
129
+ console.log("Updated:");
130
+ printEvent(event, true);
131
+ printLinks(event.links_json);
132
+ }
133
+ function printLinks(linksJson) {
134
+ try {
135
+ const links = JSON.parse(linksJson ?? "[]");
136
+ for (const link of links) {
137
+ console.log(` \u21C4 ${link.kind}: ${link.url}`);
138
+ }
139
+ } catch {
140
+ }
141
+ }
142
+ function issueIcon(status) {
143
+ return { open: "\u25CF", in_progress: "\u25D2", resolved: "\u2713", wont_fix: "\u2717", spam: "\u2298" }[status];
144
+ }
145
+ function printIssue(issue) {
146
+ const regressed = issue.reopened_count > 0 ? ` \u21BB regressed \xD7${issue.reopened_count}` : "";
147
+ console.log(
148
+ `${issueIcon(issue.status)} ${issue.id} [${issue.status}] ${issue.title} (${issue.event_count} event${issue.event_count === 1 ? "" : "s"}, last ${issue.last_seen.slice(0, 16).replace("T", " ")})${regressed}`
149
+ );
150
+ printLinks(issue.links_json);
151
+ }
152
+ async function runIssues(argv) {
153
+ const args = (0, import_arg.default)({ ...OWNER_FLAGS, "--status": String, "--limit": Number }, { argv });
154
+ if (args["--help"]) {
155
+ console.log(`
156
+ Usage
157
+ $ npx hivenet issues [--status open] [--limit 50]
158
+
159
+ List issues \u2014 feedback clustered by thread and (category, subject) fingerprint.
160
+ Statuses: ${ISSUE_STATUSES.join(" | ")}. Auth: --secret or ${SECRET_ENV_VAR}.
161
+ Manage one: npx hivenet issue <issueId> --status resolved --note "\u2026" --link <url>
162
+ `);
163
+ return;
164
+ }
165
+ const status = args["--status"];
166
+ if (status && !ISSUE_STATUSES.includes(status)) {
167
+ throw new FeedbackError(`Invalid --status. Expected: ${ISSUE_STATUSES.join(", ")}.`);
168
+ }
169
+ const api = await buildApi(args);
170
+ const result = await api.listIssues({
171
+ ...status ? { status } : {},
172
+ ...args["--limit"] ? { limit: args["--limit"] } : {}
173
+ });
174
+ if (args["--json"]) {
175
+ console.log(JSON.stringify(result, null, 2));
176
+ return;
177
+ }
178
+ if (result.issues.length === 0) {
179
+ console.log(`No matching issues for ${result.project}.`);
180
+ return;
181
+ }
182
+ console.log(`${result.issues.length} issue(s) for ${result.project}:
183
+ `);
184
+ for (const issue of result.issues) {
185
+ printIssue(issue);
186
+ }
187
+ }
188
+ async function runIssue(argv) {
189
+ const args = (0, import_arg.default)({ ...OWNER_FLAGS, "--status": String, "--note": String, "--link": String }, { argv });
190
+ if (args["--help"] || args._.length === 0) {
191
+ console.log(`
192
+ Usage
193
+ $ npx hivenet issue <issueId> (show with events)
194
+ $ npx hivenet issue <issueId> --status resolved --note "\u2026" --link <url>
195
+
196
+ Statuses: ${ISSUE_STATUSES.join(" | ")}. Resolving cascades to member events; new
197
+ feedback on a resolved issue reopens it. Auth: --secret or ${SECRET_ENV_VAR}.
198
+ `);
199
+ if (!args["--help"]) {
200
+ throw new FeedbackError("An issue ID is required.");
201
+ }
202
+ return;
203
+ }
204
+ const status = args["--status"];
205
+ if (status && !ISSUE_STATUSES.includes(status)) {
206
+ throw new FeedbackError(`Invalid --status. Expected: ${ISSUE_STATUSES.join(", ")}.`);
207
+ }
208
+ const api = await buildApi(args);
209
+ const id = args._[0];
210
+ if (args["--link"]) {
211
+ await api.addLink(id, args["--link"]);
212
+ }
213
+ if (status) {
214
+ await api.setIssueStatus(id, status, args["--note"] !== void 0 ? args["--note"] : void 0);
215
+ }
216
+ const { issue, events } = await api.getIssue(id);
217
+ if (args["--json"]) {
218
+ console.log(JSON.stringify({ issue, events }, null, 2));
219
+ return;
220
+ }
221
+ printIssue(issue);
222
+ if (issue.summary) {
223
+ console.log(` ${issue.summary}`);
224
+ }
225
+ console.log("");
226
+ for (const event of events) {
227
+ printEvent(event);
228
+ }
229
+ }
230
+ export {
231
+ runInbox,
232
+ runIssue,
233
+ runIssues,
234
+ runTriage
235
+ };
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "hivenet",
3
+ "version": "0.4.0",
4
+ "description": "Submit feedback about any agentic system's tools, docs, CLI, or MCP server \u2014 from any AI agent or human",
5
+ "keywords": [
6
+ "agent",
7
+ "feedback",
8
+ "ai",
9
+ "cli",
10
+ "mcp",
11
+ "agent-experience"
12
+ ],
13
+ "license": "MIT",
14
+ "homepage": "https://hivenet.app",
15
+ "repository": {
16
+ "type": "git",
17
+ "url": "git+https://github.com/zvadaadam/hivenet.git",
18
+ "directory": "packages/cli"
19
+ },
20
+ "type": "module",
21
+ "bin": {
22
+ "hivenet": "dist/cli.js"
23
+ },
24
+ "files": [
25
+ "dist"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsup && node -e \"require('node:fs').chmodSync('dist/cli.js', 0o755)\"",
29
+ "typecheck": "tsc --noEmit"
30
+ },
31
+ "devDependencies": {
32
+ "@hivenet/core": "workspace:*",
33
+ "@hivenet/spec": "workspace:*",
34
+ "arg": "^5.0.2"
35
+ },
36
+ "engines": {
37
+ "node": ">=20.10.0"
38
+ }
39
+ }