pinsay-cli 0.13.0 → 0.14.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.
Files changed (3) hide show
  1. package/README.md +5 -0
  2. package/dist/cli.js +179 -107
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -62,6 +62,11 @@ Full guides: https://pinsay.dev/docs/ · Issues: https://github.com/moamen-ui/pi
62
62
 
63
63
  ## Changelog
64
64
 
65
+ ### 0.14.0
66
+
67
+ - Custom statuses: `list --status`, `apply --status` and `status <number> <status>` (and the MCP `pinsay_list_comments` / `pinsay_set_status` tools) take a custom status by label or key (`c<id>`), case-insensitive. Output shows the status label; `--json` adds `statusKey` and `statusLabel` next to `status`.
68
+ - An unknown status now exits 2 with the workspace's statuses (MCP: `invalid_input`) instead of listing everything. `apply` alone still means Ready; `applied` stays Completed and is set only by `apply --mark`.
69
+
65
70
  ### 0.13.0
66
71
 
67
72
  - Comment media in `get` (human output and `--json`) and in the MCP `pinsay_get_comment` / `pinsay_get_queue` tools: recording steps (each with its screenshot), annotated screenshots, voice messages and the files on replies. Each file is named by its saved path (`media[].path`, `element.screenshotPath`; MCP `untrusted.media[].path`).
package/dist/cli.js CHANGED
@@ -392,7 +392,7 @@ var init_build_constants = __esm({
392
392
  "src/build-constants.ts"() {
393
393
  "use strict";
394
394
  BUILD_DEFAULT_SERVER = true ? "https://app.pinsay.dev" : "https://app.pinsay.dev";
395
- BUILD_CLI_VERSION = true ? "0.13.0" : "0.0.0-dev";
395
+ BUILD_CLI_VERSION = true ? "0.14.0" : "0.0.0-dev";
396
396
  }
397
397
  });
398
398
 
@@ -3010,23 +3010,84 @@ var init_shared = __esm({
3010
3010
  }
3011
3011
  });
3012
3012
 
3013
- // src/apply/queue.ts
3014
- function mapStatusToNumber(status) {
3015
- if (status === void 0 || status === null)
3016
- return void 0;
3017
- if (typeof status === "number")
3018
- return status;
3019
- const s = String(status).toLowerCase();
3020
- if (s === "open" || s === "1")
3021
- return 1;
3022
- if (s === "ready" || s === "readytoapply" || s === "2")
3023
- return 2;
3024
- if (s === "applied" || s === "3")
3025
- return 3;
3026
- if (s === "archived" || s === "4")
3027
- return 4;
3013
+ // src/statuses.ts
3014
+ function fetchStatuses(client) {
3015
+ const id = `${client.server}|${client.token ?? ""}`;
3016
+ let hit = cache.get(id);
3017
+ if (!hit) {
3018
+ hit = api(client.server, "/api/statuses", { token: client.token }).then((res) => Array.isArray(res) ? res : []).catch(() => []);
3019
+ cache.set(id, hit);
3020
+ }
3021
+ return hit;
3022
+ }
3023
+ function builtinKeyFor(norm) {
3024
+ if (norm in BUILTIN_KEYS)
3025
+ return norm;
3026
+ if (norm in BUILTIN_ALIASES)
3027
+ return BUILTIN_ALIASES[norm];
3028
+ for (const [k, n] of Object.entries(BUILTIN_KEYS))
3029
+ if (String(n) === norm)
3030
+ return k;
3028
3031
  return void 0;
3029
3032
  }
3033
+ function resolveStatus(input, catalog = []) {
3034
+ const norm = String(input ?? "").trim().toLowerCase();
3035
+ if (!norm)
3036
+ return void 0;
3037
+ const builtin = builtinKeyFor(norm);
3038
+ if (builtin) {
3039
+ const status = BUILTIN_KEYS[builtin];
3040
+ return { key: builtin, status, label: BUILTIN_LABELS[status] };
3041
+ }
3042
+ const custom2 = catalog.filter((c) => c.isCustom !== false && typeof c.key === "string" && !(c.key in BUILTIN_KEYS));
3043
+ const hit = custom2.find((c) => c.key.toLowerCase() === norm) ?? custom2.find((c) => typeof c.label === "string" && c.label.trim().toLowerCase() === norm);
3044
+ if (!hit)
3045
+ return void 0;
3046
+ return { key: hit.key, status: CUSTOM_STATUS, label: hit.label ?? hit.key };
3047
+ }
3048
+ function statusLabel(comment, catalog = []) {
3049
+ if (comment.customStatus?.label)
3050
+ return comment.customStatus.label;
3051
+ if (comment.statusKey) {
3052
+ const hit = catalog.find((c) => c.key === comment.statusKey && c.label);
3053
+ if (hit)
3054
+ return hit.label;
3055
+ }
3056
+ const n = Number(comment.status);
3057
+ if (BUILTIN_NAMES[n])
3058
+ return BUILTIN_NAMES[n];
3059
+ if (n === CUSTOM_STATUS)
3060
+ return "custom";
3061
+ return String(comment.status);
3062
+ }
3063
+ function unknownStatusError(input, catalog = []) {
3064
+ const names = Object.keys(BUILTIN_KEYS);
3065
+ for (const c of catalog) {
3066
+ if (c.isCustom && c.key)
3067
+ names.push(`"${c.label ?? c.key}" (${c.key})`);
3068
+ }
3069
+ return `Unknown status "${input}". This workspace's statuses: ${names.join(", ")}`;
3070
+ }
3071
+ function statusJson(comment, catalog) {
3072
+ const n = Number(comment.status);
3073
+ const key = comment.statusKey ?? Object.entries(BUILTIN_KEYS).find(([, v]) => v === n)?.[0] ?? null;
3074
+ return { statusKey: key, statusLabel: statusLabel(comment, catalog) };
3075
+ }
3076
+ var BUILTIN_KEYS, BUILTIN_ALIASES, BUILTIN_NAMES, BUILTIN_LABELS, CUSTOM_STATUS, cache;
3077
+ var init_statuses = __esm({
3078
+ "src/statuses.ts"() {
3079
+ "use strict";
3080
+ init_api();
3081
+ BUILTIN_KEYS = { open: 1, ready: 2, applied: 3, archived: 4 };
3082
+ BUILTIN_ALIASES = { readytoapply: "ready", completed: "applied" };
3083
+ BUILTIN_NAMES = { 1: "Open", 2: "ReadyToApply", 3: "Applied", 4: "Archived" };
3084
+ BUILTIN_LABELS = { 1: "Open", 2: "Ready", 3: "Applied", 4: "Archived" };
3085
+ CUSTOM_STATUS = 5;
3086
+ cache = /* @__PURE__ */ new Map();
3087
+ }
3088
+ });
3089
+
3090
+ // src/apply/queue.ts
3030
3091
  function mapEnvironmentToNumber(env) {
3031
3092
  if (env === void 0 || env === null)
3032
3093
  return void 0;
@@ -3045,11 +3106,16 @@ async function fetchQueue(ctx, filter) {
3045
3106
  return (await fetchQueueShared(ctx, filter)).items;
3046
3107
  }
3047
3108
  async function fetchQueueShared(ctx, filter) {
3048
- const statusNum = filter?.status !== void 0 ? mapStatusToNumber(filter.status) : 2;
3049
3109
  const envNum = mapEnvironmentToNumber(filter?.environment);
3050
3110
  const queryParts = [];
3051
- if (statusNum !== void 0)
3052
- queryParts.push(`status=${statusNum}`);
3111
+ if (filter?.status === void 0) {
3112
+ queryParts.push("status=2");
3113
+ } else {
3114
+ const resolved = resolveStatus(filter.status, await fetchStatuses(ctx));
3115
+ if (!resolved)
3116
+ throw new Error(unknownStatusError(String(filter.status), await fetchStatuses(ctx)));
3117
+ queryParts.push(`statusKey=${encodeURIComponent(resolved.key)}`);
3118
+ }
3053
3119
  if (envNum !== void 0)
3054
3120
  queryParts.push(`environment=${envNum}`);
3055
3121
  const qs = queryParts.length > 0 ? `?${queryParts.join("&")}` : "";
@@ -3072,6 +3138,8 @@ async function fetchQueueShared(ctx, filter) {
3072
3138
  id: item.id,
3073
3139
  number: item.number,
3074
3140
  status: item.status,
3141
+ statusKey: item.statusKey ?? null,
3142
+ customStatus: item.customStatus ?? null,
3075
3143
  environment: item.environment,
3076
3144
  body: item.body ?? "",
3077
3145
  authorName: item.authorName ?? null,
@@ -3157,6 +3225,8 @@ async function fetchQueueShared(ctx, filter) {
3157
3225
  id: item.id,
3158
3226
  number: item.number,
3159
3227
  status: item.status,
3228
+ statusKey: item.statusKey ?? null,
3229
+ customStatus: item.customStatus ?? null,
3160
3230
  environment: item.environment,
3161
3231
  body: item.body ?? "",
3162
3232
  authorName: item.authorName ?? null,
@@ -3203,6 +3273,7 @@ var init_queue = __esm({
3203
3273
  init_member_rules();
3204
3274
  init_media();
3205
3275
  init_shared();
3276
+ init_statuses();
3206
3277
  warnedNonAdminFallback = false;
3207
3278
  }
3208
3279
  });
@@ -3294,6 +3365,7 @@ function toAiCommentView(raw, page, saved) {
3294
3365
  aiRules,
3295
3366
  replies,
3296
3367
  status: raw?.status,
3368
+ ...statusJson(raw ?? {}),
3297
3369
  media: toMediaViews(raw?.media, saved?.media),
3298
3370
  textSource: typeof raw?.textSource === "string" ? raw.textSource : null
3299
3371
  };
@@ -3310,36 +3382,12 @@ var init_projection = __esm({
3310
3382
  "src/apply/projection.ts"() {
3311
3383
  "use strict";
3312
3384
  init_media();
3385
+ init_statuses();
3313
3386
  init_shared();
3314
3387
  }
3315
3388
  });
3316
3389
 
3317
3390
  // src/commands/comments.ts
3318
- function mapStatusToNumber2(status) {
3319
- if (!status)
3320
- return void 0;
3321
- const s = status.toLowerCase();
3322
- if (s === "open" || s === "1")
3323
- return 1;
3324
- if (s === "ready" || s === "readytoapply" || s === "2")
3325
- return 2;
3326
- if (s === "applied" || s === "3")
3327
- return 3;
3328
- if (s === "archived" || s === "4")
3329
- return 4;
3330
- return void 0;
3331
- }
3332
- function mapStatusToString(status) {
3333
- if (status === 1 || status === "1")
3334
- return "Open";
3335
- if (status === 2 || status === "2")
3336
- return "ReadyToApply";
3337
- if (status === 3 || status === "3")
3338
- return "Applied";
3339
- if (status === 4 || status === "4")
3340
- return "Archived";
3341
- return String(status);
3342
- }
3343
3391
  function mapEnvironmentToNumber2(env) {
3344
3392
  if (!env)
3345
3393
  return void 0;
@@ -3395,18 +3443,18 @@ function exitOnUnresolvedProject(resolved, flag) {
3395
3443
  }
3396
3444
  process.exit(2);
3397
3445
  }
3398
- async function fetchCommentsFor(server, token, project, statusNum, envNum) {
3446
+ async function fetchCommentsFor(server, token, project, statusKey, envNum) {
3399
3447
  const queryParts = ["view=summary"];
3400
- if (statusNum !== void 0)
3401
- queryParts.push(`status=${statusNum}`);
3448
+ if (statusKey !== void 0)
3449
+ queryParts.push(`statusKey=${encodeURIComponent(statusKey)}`);
3402
3450
  if (envNum !== void 0)
3403
3451
  queryParts.push(`environment=${envNum}`);
3404
3452
  const url = `/api/projects/${encodeURIComponent(project)}/comments?${queryParts.join("&")}`;
3405
3453
  const res = await api(server, url, { token });
3406
3454
  return res?.items ?? [];
3407
3455
  }
3408
- function printCommentLine(item) {
3409
- const st = mapStatusToString(item.status);
3456
+ function printCommentLine(item, catalog) {
3457
+ const st = statusLabel(item, catalog);
3410
3458
  const env = mapEnvironmentToString(item.environment);
3411
3459
  const author = item.authorName || "Anonymous";
3412
3460
  const loc = item.route || item.sourcePath || "";
@@ -3416,14 +3464,23 @@ async function listCommand(cwd2, parsed, positionals = []) {
3416
3464
  const { server, token, root, config } = await getClient(cwd2, parsed, { requireProject: false });
3417
3465
  const statusArg = (typeof parsed["status"] === "string" ? parsed["status"] : positionals[1]) || void 0;
3418
3466
  const envArg = (typeof parsed["env"] === "string" ? parsed["env"] : positionals[2]) || void 0;
3419
- const statusNum = mapStatusToNumber2(statusArg);
3467
+ const catalog = await fetchStatuses({ server, token });
3468
+ let statusKey;
3469
+ if (statusArg !== void 0) {
3470
+ const resolvedStatus = resolveStatus(statusArg, catalog);
3471
+ if (!resolvedStatus) {
3472
+ console.error(unknownStatusError(statusArg, catalog));
3473
+ process.exit(2);
3474
+ }
3475
+ statusKey = resolvedStatus.key;
3476
+ }
3420
3477
  const envNum = mapEnvironmentToNumber2(envArg);
3421
3478
  const flag = typeof parsed["project"] === "string" ? parsed["project"] : void 0;
3422
3479
  const resolved = resolveProject(config, cwd2, root, flag);
3423
3480
  if (resolved.ok) {
3424
- const items = await fetchCommentsFor(server, token, resolved.project.key, statusNum, envNum);
3481
+ const items = await fetchCommentsFor(server, token, resolved.project.key, statusKey, envNum);
3425
3482
  if (parsed["json"] === true) {
3426
- console.log(JSON.stringify(items.map(({ id, ...rest }) => ({ number: rest.number, internalId: id, ...rest })), null, 2));
3483
+ console.log(JSON.stringify(items.map(({ id, ...rest }) => ({ number: rest.number, internalId: id, ...rest, ...statusJson(rest, catalog) })), null, 2));
3427
3484
  process.exit(0);
3428
3485
  }
3429
3486
  if (items.length === 0) {
@@ -3431,7 +3488,7 @@ async function listCommand(cwd2, parsed, positionals = []) {
3431
3488
  process.exit(0);
3432
3489
  }
3433
3490
  for (const item of items)
3434
- printCommentLine(item);
3491
+ printCommentLine(item, catalog);
3435
3492
  process.exit(0);
3436
3493
  }
3437
3494
  if (resolved.reason === "not-found") {
@@ -3444,11 +3501,11 @@ async function listCommand(cwd2, parsed, positionals = []) {
3444
3501
  const projects = listProjects(config);
3445
3502
  const grouped = [];
3446
3503
  for (const p of projects) {
3447
- const comments = await fetchCommentsFor(server, token, p.key, statusNum, envNum);
3504
+ const comments = await fetchCommentsFor(server, token, p.key, statusKey, envNum);
3448
3505
  grouped.push({ key: p.key, path: p.path, comments });
3449
3506
  }
3450
3507
  if (parsed["json"] === true) {
3451
- console.log(JSON.stringify(grouped.map((g) => ({ project: g.key, comments: g.comments.map(({ id, ...rest }) => ({ number: rest.number, internalId: id, ...rest })) })), null, 2));
3508
+ console.log(JSON.stringify(grouped.map((g) => ({ project: g.key, comments: g.comments.map(({ id, ...rest }) => ({ number: rest.number, internalId: id, ...rest, ...statusJson(rest, catalog) })) })), null, 2));
3452
3509
  process.exit(0);
3453
3510
  }
3454
3511
  for (const g of grouped) {
@@ -3457,7 +3514,7 @@ async function listCommand(cwd2, parsed, positionals = []) {
3457
3514
  console.log("No comments found.");
3458
3515
  } else {
3459
3516
  for (const item of g.comments)
3460
- printCommentLine(item);
3517
+ printCommentLine(item, catalog);
3461
3518
  }
3462
3519
  console.log("");
3463
3520
  }
@@ -3492,12 +3549,13 @@ async function getCommand(cwd2, parsed, positionals = []) {
3492
3549
  await pruneMedia(root);
3493
3550
  const saved = await saveIfRooted(raw, { root, projectKey: project, server, token });
3494
3551
  const view = toAiCommentView(raw, void 0, saved);
3552
+ const catalog = await fetchStatuses({ server, token });
3495
3553
  if (parsed["json"] === true) {
3496
3554
  const resolved = resolveSource(cwd2, view.element?.sourcePath);
3497
- console.log(JSON.stringify({ ...view, resolvedSource: resolved }, null, 2));
3555
+ console.log(JSON.stringify({ ...view, ...statusJson(raw, catalog), resolvedSource: resolved }, null, 2));
3498
3556
  process.exit(0);
3499
3557
  }
3500
- console.log(`Comment #${n} [${mapStatusToString(view.status)}] [${mapEnvironmentToString(view.environment)}]`);
3558
+ console.log(`Comment #${n} [${statusLabel(raw, catalog)}] [${mapEnvironmentToString(view.environment)}]`);
3501
3559
  console.log(`Author: ${view.authorName || "Anonymous"} | Created: ${view.createdAt}`);
3502
3560
  if (view.element.route || view.element.sourcePath) {
3503
3561
  console.log(`Location: ${view.element.route || ""} ${view.element.sourcePath ? `(${view.element.sourcePath})` : ""}`);
@@ -3588,7 +3646,7 @@ async function statusCommand(cwd2, parsed, positionals = []) {
3588
3646
  const numStr = positionals[1];
3589
3647
  const newStatusStr = positionals[2];
3590
3648
  if (!numStr || !newStatusStr) {
3591
- console.error("Usage: pinsay status <number> <open|ready|applied|archived>");
3649
+ console.error('Usage: pinsay status <number> <open|ready|applied|archived|"custom status">');
3592
3650
  process.exit(2);
3593
3651
  }
3594
3652
  const n = parseCommentNumber(numStr);
@@ -3596,12 +3654,13 @@ async function statusCommand(cwd2, parsed, positionals = []) {
3596
3654
  console.error(invalidNumberMessage(numStr));
3597
3655
  process.exit(2);
3598
3656
  }
3599
- const statusNum = mapStatusToNumber2(newStatusStr);
3600
- if (statusNum === void 0) {
3601
- console.error(`Invalid status: ${newStatusStr}. Must be open, ready, applied, or archived.`);
3657
+ const catalog = await fetchStatuses({ server, token });
3658
+ const resolvedStatus = resolveStatus(newStatusStr, catalog);
3659
+ if (!resolvedStatus) {
3660
+ console.error(unknownStatusError(newStatusStr, catalog));
3602
3661
  process.exit(2);
3603
3662
  }
3604
- if (statusNum === 3) {
3663
+ if (resolvedStatus.status === 3) {
3605
3664
  console.error(
3606
3665
  'Marking a comment as \'applied\' is not permitted via `pinsay status`.\nUse `pinsay apply --mark <number> --reply "<explanation>"` (or `pinsay reply <number> "<text>"`) to record a properly structured reply.'
3607
3666
  );
@@ -3617,12 +3676,20 @@ async function statusCommand(cwd2, parsed, positionals = []) {
3617
3676
  }
3618
3677
  throw err;
3619
3678
  }
3620
- await api(server, `/api/comments/${raw.id}`, {
3621
- method: "PATCH",
3622
- body: { status: statusNum },
3623
- token
3624
- });
3625
- console.log(`Updated comment #${n} status to ${mapStatusToString(statusNum)}.`);
3679
+ try {
3680
+ await api(server, `/api/comments/${raw.id}`, {
3681
+ method: "PATCH",
3682
+ body: { statusKey: resolvedStatus.key },
3683
+ token
3684
+ });
3685
+ } catch (err) {
3686
+ if (err instanceof ApiError && err.code === 403) {
3687
+ console.error(err.message);
3688
+ process.exit(1);
3689
+ }
3690
+ throw err;
3691
+ }
3692
+ console.log(`Updated comment #${n} status to ${resolvedStatus.label}.`);
3626
3693
  process.exit(0);
3627
3694
  }
3628
3695
  async function replyCommand(cwd2, parsed, positionals = []) {
@@ -3667,6 +3734,7 @@ var init_comments = __esm({
3667
3734
  init_projection();
3668
3735
  init_media();
3669
3736
  init_server();
3737
+ init_statuses();
3670
3738
  }
3671
3739
  });
3672
3740
 
@@ -9103,6 +9171,16 @@ init_queue();
9103
9171
  init_projection();
9104
9172
  init_media();
9105
9173
  init_server();
9174
+ init_statuses();
9175
+ async function exitOnUnknownStatus(server, token, status) {
9176
+ if (typeof status !== "string")
9177
+ return;
9178
+ const catalog = await fetchStatuses({ server, token });
9179
+ if (!resolveStatus(status, catalog)) {
9180
+ console.error(unknownStatusError(status, catalog));
9181
+ process.exit(2);
9182
+ }
9183
+ }
9106
9184
  async function exitIfWorkspaceFrozen(server, token) {
9107
9185
  if (!token)
9108
9186
  return void 0;
@@ -9213,6 +9291,7 @@ async function applyCommand(cwd2, parsed, positionals = []) {
9213
9291
  cwd: root,
9214
9292
  workspaceName
9215
9293
  };
9294
+ await exitOnUnknownStatus(server, token, parsed["status"]);
9216
9295
  if (parsed["mark"] !== void 0) {
9217
9296
  const markVal2 = parsed["mark"];
9218
9297
  let markId;
@@ -9369,6 +9448,7 @@ async function applyAllProjects(root, cwd2, config, server, parsed) {
9369
9448
  process.exit(3);
9370
9449
  }
9371
9450
  const workspaceName = await exitIfWorkspaceFrozen(server, token);
9451
+ await exitOnUnknownStatus(server, token, parsed["status"]);
9372
9452
  const projects = listProjects(config);
9373
9453
  const plan = parsed["plan"] === true;
9374
9454
  const status = typeof parsed["status"] === "string" ? parsed["status"] : void 0;
@@ -14756,8 +14836,7 @@ var TOOL_PINSAY_LIST_COMMENTS = {
14756
14836
  },
14757
14837
  status: {
14758
14838
  type: "string",
14759
- enum: ["open", "ready", "applied", "archived"],
14760
- description: "Filter by comment status"
14839
+ description: "Filter by comment status: open, ready, applied, archived, or a custom status label/key"
14761
14840
  }
14762
14841
  },
14763
14842
  additionalProperties: false
@@ -14927,7 +15006,7 @@ var TOOL_PINSAY_REPLY = {
14927
15006
  };
14928
15007
  var TOOL_PINSAY_SET_STATUS = {
14929
15008
  name: "pinsay_set_status",
14930
- description: `Set a comment's status to open, ready or archived. "applied" is not accepted here: only pinsay_mark_applied and pinsay_commit_and_mark set it, because they also record the reply and the commit. Returns {number, status}. ${UNTRUSTED_NOTICE}`,
15009
+ description: `Set a comment's status to open, ready, archived or a custom status (by label or key). "applied" is not accepted here: only pinsay_mark_applied and pinsay_commit_and_mark set it, because they also record the reply and the commit. Returns {number, status}. ${UNTRUSTED_NOTICE}`,
14931
15010
  inputSchema: {
14932
15011
  type: "object",
14933
15012
  properties: {
@@ -14942,8 +15021,7 @@ var TOOL_PINSAY_SET_STATUS = {
14942
15021
  },
14943
15022
  status: {
14944
15023
  type: "string",
14945
- enum: ["open", "ready", "archived"],
14946
- description: "Status to set"
15024
+ description: "Status to set: open, ready, archived, or a custom status label/key (not applied)"
14947
15025
  }
14948
15026
  },
14949
15027
  required: ["number", "status"],
@@ -15012,26 +15090,11 @@ init_media();
15012
15090
  init_shared();
15013
15091
  init_member_rules();
15014
15092
  init_config();
15093
+ init_statuses();
15015
15094
  init_comment_ref();
15016
15095
  function mcpError(code, message) {
15017
15096
  return { code, message };
15018
15097
  }
15019
- function mapStatusToNumber3(status) {
15020
- if (status === void 0 || status === null)
15021
- return void 0;
15022
- if (typeof status === "number")
15023
- return status;
15024
- const s = String(status).toLowerCase();
15025
- if (s === "open" || s === "1")
15026
- return 1;
15027
- if (s === "ready" || s === "readytoapply" || s === "2")
15028
- return 2;
15029
- if (s === "applied" || s === "3")
15030
- return 3;
15031
- if (s === "archived" || s === "4")
15032
- return 4;
15033
- return void 0;
15034
- }
15035
15098
  function mapEnvironmentToNumber3(env) {
15036
15099
  if (env === void 0 || env === null)
15037
15100
  return void 0;
@@ -15179,13 +15242,20 @@ function reshapeComment(raw, page, saved) {
15179
15242
  };
15180
15243
  }
15181
15244
  async function handleListComments(args, ctx) {
15182
- const statusNum = mapStatusToNumber3(args?.status);
15245
+ const catalog = await fetchStatuses(ctx);
15246
+ let statusKey;
15247
+ if (args?.status !== void 0 && args.status !== null && String(args.status).trim() !== "") {
15248
+ const resolvedStatus = resolveStatus(args.status, catalog);
15249
+ if (!resolvedStatus)
15250
+ throw mcpError("invalid_input", unknownStatusError(String(args.status), catalog));
15251
+ statusKey = resolvedStatus.key;
15252
+ }
15183
15253
  const envNum = mapEnvironmentToNumber3(args?.environment);
15184
15254
  const page = typeof args?.page === "number" && args.page >= 1 ? args.page : 1;
15185
15255
  const pageSize = typeof args?.pageSize === "number" && args.pageSize >= 1 && args.pageSize <= 100 ? args.pageSize : 50;
15186
15256
  const queryParts = ["view=summary", `pageNumber=${page}`, `pageSize=${pageSize}`];
15187
- if (statusNum !== void 0)
15188
- queryParts.push(`status=${statusNum}`);
15257
+ if (statusKey !== void 0)
15258
+ queryParts.push(`statusKey=${encodeURIComponent(statusKey)}`);
15189
15259
  if (envNum !== void 0)
15190
15260
  queryParts.push(`environment=${envNum}`);
15191
15261
  const url = `/api/projects/${encodeURIComponent(ctx.project)}/comments?${queryParts.join("&")}`;
@@ -15195,6 +15265,7 @@ async function handleListComments(args, ctx) {
15195
15265
  number: item.number,
15196
15266
  internalId: item.id,
15197
15267
  status: item.status,
15268
+ ...statusJson(item, catalog),
15198
15269
  environment: item.environment,
15199
15270
  body: item.body ?? "",
15200
15271
  untrusted: {
@@ -15647,19 +15718,20 @@ async function handleReply(args, ctx) {
15647
15718
  }
15648
15719
  async function handleSetStatus(args, ctx) {
15649
15720
  const number = parseCommentNumber(args?.number);
15650
- const statusStr = String(args?.status || "").toLowerCase();
15721
+ const statusStr = String(args?.status || "").trim();
15651
15722
  if (number === null) {
15652
15723
  throw mcpError("invalid_input", invalidNumberMessage(args?.number, args?.id));
15653
15724
  }
15654
15725
  if (!statusStr) {
15655
15726
  throw mcpError("invalid_input", "number and status are required");
15656
15727
  }
15657
- if (statusStr === "applied") {
15658
- throw mcpError("invalid_input", 'Status "applied" is only settable via mark tools');
15728
+ const catalog = await fetchStatuses(ctx);
15729
+ const resolvedStatus = resolveStatus(statusStr, catalog);
15730
+ if (!resolvedStatus) {
15731
+ throw mcpError("invalid_input", unknownStatusError(statusStr, catalog));
15659
15732
  }
15660
- const statusNum = mapStatusToNumber3(statusStr);
15661
- if (statusNum === void 0) {
15662
- throw mcpError("invalid_input", `Invalid status: ${args.status}`);
15733
+ if (resolvedStatus.status === 3) {
15734
+ throw mcpError("invalid_input", 'Status "applied" is only settable via mark tools');
15663
15735
  }
15664
15736
  let id;
15665
15737
  try {
@@ -15673,10 +15745,10 @@ async function handleSetStatus(args, ctx) {
15673
15745
  }
15674
15746
  await api(ctx.server, `/api/comments/${id}`, {
15675
15747
  method: "PATCH",
15676
- body: { status: statusNum },
15748
+ body: { statusKey: resolvedStatus.key },
15677
15749
  token: ctx.token
15678
15750
  });
15679
- return { number, status: statusStr };
15751
+ return { number, status: resolvedStatus.key };
15680
15752
  }
15681
15753
  async function handleResolveSource(args, ctx) {
15682
15754
  const hash = args?.hash;
@@ -16379,7 +16451,7 @@ var HELP_TEXTS = {
16379
16451
  },
16380
16452
  status: {
16381
16453
  purpose: "Who you are, this folder's project, comments waiting",
16382
- usage: "npx pinsay-cli status\n npx pinsay-cli status <number> <open|ready|applied|archived>\n npx pinsay-cli status --deployed [sha]",
16454
+ usage: "npx pinsay-cli status\n npx pinsay-cli status <number> <open|ready|archived|status>\n npx pinsay-cli status --deployed [sha]",
16383
16455
  common: [
16384
16456
  ["--json", "Overview as JSON"],
16385
16457
  ["--deployed [sha]", "Mark applied comments live (default HEAD)"],
@@ -16395,7 +16467,7 @@ var HELP_TEXTS = {
16395
16467
  ],
16396
16468
  examples: [
16397
16469
  ["npx pinsay-cli status", "Who you are and what's waiting"],
16398
- ["npx pinsay-cli status 42 ready", "Mark comment #42 ready"],
16470
+ ["npx pinsay-cli status 42 ready", "Move #42 to Ready (or a custom status)"],
16399
16471
  ["npx pinsay-cli status --deployed", "Mark this build's applied comments live"]
16400
16472
  ]
16401
16473
  },
@@ -16421,13 +16493,13 @@ var HELP_TEXTS = {
16421
16493
  purpose: "List feedback comments",
16422
16494
  usage: "npx pinsay-cli list [status] [environment]",
16423
16495
  common: [
16424
- ["--status <status>", "Filter (open, ready, applied, archived)"],
16496
+ ["--status <status>", "Filter: open, ready, applied, archived, custom"],
16425
16497
  ["--env <env>", "Filter (local, staging, production)"],
16426
16498
  ["--project <key>", "Project (default: this folder's)"],
16427
16499
  ["--json", "Comments as JSON"]
16428
16500
  ],
16429
16501
  all: [
16430
- ["--status <status>", "Filter (open, ready, applied, archived)"],
16502
+ ["--status <status>", "Filter: open, ready, applied, archived, custom"],
16431
16503
  ["--env <env>", "Filter (local, staging, production)"],
16432
16504
  ["--project <key>", "Project (default: this folder's)"],
16433
16505
  ["--key <key>", "API key"],
@@ -16469,7 +16541,7 @@ var HELP_TEXTS = {
16469
16541
  ["--mark <number>|all", "Commit and mark applied (needs --models)"],
16470
16542
  ["--reply <text>", "Reply recorded with --mark"],
16471
16543
  ["--models <list>", '"<id>=<role>", mandatory with --mark'],
16472
- ["--status <status>", "Filter the queue (open, ready, applied, archived)"],
16544
+ ["--status <status>", "Queue status (default ready; custom label/key)"],
16473
16545
  ["--env <env>", "Filter (local, staging, production)"],
16474
16546
  ["--json", "Queue as JSON"]
16475
16547
  ],
@@ -16485,7 +16557,7 @@ var HELP_TEXTS = {
16485
16557
  ["--dry-run", "Print what --mark would do; change nothing"],
16486
16558
  ["--fail <number>", "Mark apply failed with a reply"],
16487
16559
  ["--reason <text>", "Why it failed (required with --fail)"],
16488
- ["--status <status>", "Filter the queue (open, ready, applied, archived)"],
16560
+ ["--status <status>", "Queue status (default ready; custom label/key)"],
16489
16561
  ["--env <env>", "Filter (local, staging, production)"],
16490
16562
  ["--key <key>", "API key"],
16491
16563
  ["--project <key>", "Project for the queue"],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pinsay-cli",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "Click-to-comment feedback for your web app: install the widget, then turn pending comments into AI apply prompts.",
5
5
  "type": "module",
6
6
  "license": "MIT",