@tokenoftrust/cli 4.2.1 → 4.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/cli",
3
- "version": "4.2.1",
3
+ "version": "4.3.0",
4
4
  "description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Token of Trust",
@@ -29,6 +29,8 @@ import { fail } from "../errors.mjs";
29
29
  import { formatDiagnostics, readDiagnostics, recordDiagnostic } from "../diagnostics.mjs";
30
30
 
31
31
  const DEFAULT_MCP_URL = "https://mcp.tokenoftrust.com";
32
+ /** `feedback_submit` returns this field. Example: `fb-1792647485730-7vguok`. */
33
+ const FEEDBACK_ID = /^fb-[a-z0-9]+(?:-[a-z0-9]+)*$/i;
32
34
  const TYPES = new Set(["bug", "feature", "improvement"]);
33
35
  const SEVERITIES = new Set(["critical", "high", "medium", "low"]);
34
36
  const CATEGORIES = new Set([
@@ -81,6 +83,17 @@ export function buildFeedbackPayload({ type, category, severity, title, descript
81
83
  };
82
84
  }
83
85
 
86
+ /**
87
+ * The fb- id from a `feedback_submit` result. Only `feedbackId` counts.
88
+ * @param {unknown} result
89
+ * @returns {string|null}
90
+ */
91
+ export function feedbackReportId(result) {
92
+ if (result == null || typeof result !== "object" || !("feedbackId" in result)) return null;
93
+ const value = result.feedbackId;
94
+ return typeof value === "string" && FEEDBACK_ID.test(value) ? value : null;
95
+ }
96
+
84
97
  function parseArgs(argv) {
85
98
  const a = {
86
99
  mcp: null, type: "improvement", category: "product-ux", severity: "medium",
@@ -104,8 +117,12 @@ function parseArgs(argv) {
104
117
  return a;
105
118
  }
106
119
 
107
- /** @param {string[]} argv */
108
- export async function run(argv) {
120
+ /**
121
+ * @param {string[]} argv
122
+ * @param {{ createClient?: typeof createMcpClient, establishSession?: typeof establishSession }} [deps]
123
+ * Test seam. Production calls pass only argv.
124
+ */
125
+ export async function run(argv, deps = {}) {
109
126
  const env = process.env;
110
127
  const args = parseArgs(argv);
111
128
  if (args.help) {
@@ -184,12 +201,14 @@ export async function run(argv) {
184
201
  }
185
202
 
186
203
  const mcpUrl = args.mcp || env.MCP_BASE_URL || env.TOT_MCP_URL || DEFAULT_MCP_URL;
187
- const client = createMcpClient(mcpUrl);
204
+ const createClient = deps.createClient || createMcpClient;
205
+ const openSession = deps.establishSession || establishSession;
206
+ const client = createClient(mcpUrl);
188
207
  try {
189
208
  // Attach auth in the right order relative to the handshake (developer bearer
190
209
  // pre-initialize). We keep the feedback-specific clientInfo by injecting our
191
210
  // own initialize.
192
- await establishSession(client, {
211
+ await openSession(client, {
193
212
  env,
194
213
  initialize: () => client.initialize({ name: "tot-cli", version: "feedback" }),
195
214
  });
@@ -204,8 +223,15 @@ export async function run(argv) {
204
223
  traceId,
205
224
  });
206
225
  const res = await client.callTool("feedback_submit", payload);
207
- const id = res?.reportId || res?.id || null;
208
- console.log(`\n+ sent — thank you.${id ? ` (report ${id})` : ""}`);
226
+ const id = feedbackReportId(res);
227
+ if (!id) {
228
+ console.error(fail(
229
+ "the server accepted the note but returned no fb- id.",
230
+ "don't resend yet — the note may already be in the queue.",
231
+ ));
232
+ return 1;
233
+ }
234
+ console.log(`\n+ sent — thank you. ${id}`);
209
235
  return 0;
210
236
  } catch (e) {
211
237
  recordDiagnostic(e, { command: "feedback", operation: "feedback_submit", required: true }, env);