pinsay-cli 0.6.10 → 0.7.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 +194 -31
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_build_constants = __esm({
|
|
|
15
15
|
"src/build-constants.ts"() {
|
|
16
16
|
"use strict";
|
|
17
17
|
BUILD_DEFAULT_SERVER = true ? "https://app.pinsay.dev" : "https://app.pinsay.dev";
|
|
18
|
-
BUILD_CLI_VERSION = true ? "0.
|
|
18
|
+
BUILD_CLI_VERSION = true ? "0.7.0" : "0.0.0-dev";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -5235,23 +5235,30 @@ function buildApplyPrompt(items, context, opts) {
|
|
|
5235
5235
|
}
|
|
5236
5236
|
lines.push("## When you finish an item");
|
|
5237
5237
|
lines.push(
|
|
5238
|
-
'Run exactly: `npx pinsay-cli apply --mark <id> --reply "<what changed>" --
|
|
5238
|
+
'Run exactly: `npx pinsay-cli apply --mark <id> --reply "<what changed>" --models "<planner-id>=planner,<worker-id>=implementer,<reviewer-id>=reviewer" --tool <your-tool-name>`'
|
|
5239
5239
|
);
|
|
5240
5240
|
lines.push(
|
|
5241
|
-
'(Separate style: after each item; Single style: run `npx pinsay-cli apply --mark all --reply "..." --
|
|
5241
|
+
'(Separate style: after each item; Single style: run `npx pinsay-cli apply --mark all --reply "..." --models "..." --tool <your-tool-name>`'
|
|
5242
5242
|
);
|
|
5243
5243
|
lines.push(
|
|
5244
|
-
"once at the end). Never run git push. `--
|
|
5244
|
+
"once at the end). Never run git push. `--models` MUST list EVERY model that touched the item and its role:"
|
|
5245
5245
|
);
|
|
5246
5246
|
lines.push(
|
|
5247
|
-
"
|
|
5247
|
+
"the planner/reviewer (your own model id) and each worker model you delegated to (`implementer`). Use exact"
|
|
5248
5248
|
);
|
|
5249
5249
|
lines.push(
|
|
5250
|
-
|
|
5250
|
+
'model ids (e.g. `claude-opus-5-5`, `claude-sonnet-5-5`, `gpt-5.2`), never aliases like "haiku" or "sonnet". With one'
|
|
5251
5251
|
);
|
|
5252
5252
|
lines.push(
|
|
5253
|
-
"
|
|
5253
|
+
"model and no delegation, pass just yours as `<your-model-id>=implementer`. `--tool` (e.g. `claude-code`, `opencode`,"
|
|
5254
5254
|
);
|
|
5255
|
+
lines.push(
|
|
5256
|
+
"`cursor`, `windsurf`, `antigravity`) records which agent you are running as. ALWAYS pass both explicitly, even on a"
|
|
5257
|
+
);
|
|
5258
|
+
lines.push(
|
|
5259
|
+
"project you ran `init` on \u2014 `--tool` silently falls back to whichever tool happened to run `init`, which is wrong"
|
|
5260
|
+
);
|
|
5261
|
+
lines.push("the moment a different tool applies a comment later.");
|
|
5255
5262
|
return lines.join("\n") + "\n";
|
|
5256
5263
|
}
|
|
5257
5264
|
|
|
@@ -5475,6 +5482,106 @@ function commitUrlFor(sha, remoteUrl) {
|
|
|
5475
5482
|
return null;
|
|
5476
5483
|
}
|
|
5477
5484
|
|
|
5485
|
+
// src/apply/models.ts
|
|
5486
|
+
var MODEL_ROLES = ["planner", "implementer", "reviewer"];
|
|
5487
|
+
var MAX_AI_MODELS = 8;
|
|
5488
|
+
var MODEL_RE = /^[A-Za-z0-9][A-Za-z0-9._:/+-]*$/;
|
|
5489
|
+
var ModelsError = class extends Error {
|
|
5490
|
+
};
|
|
5491
|
+
function normalizeEntry(raw) {
|
|
5492
|
+
const model = typeof raw.model === "string" ? raw.model.trim() : "";
|
|
5493
|
+
if (!model || model.length > 64 || !MODEL_RE.test(model)) {
|
|
5494
|
+
throw new ModelsError(
|
|
5495
|
+
`Invalid model id "${String(raw.model ?? "")}": use the exact model id (1-64 chars, letters/digits and . _ : / + -), e.g. "claude-opus-5-5".`
|
|
5496
|
+
);
|
|
5497
|
+
}
|
|
5498
|
+
let role = null;
|
|
5499
|
+
if (raw.role !== void 0 && raw.role !== null && raw.role !== "") {
|
|
5500
|
+
const r = String(raw.role).trim().toLowerCase();
|
|
5501
|
+
if (!MODEL_ROLES.includes(r)) {
|
|
5502
|
+
throw new ModelsError(
|
|
5503
|
+
`Invalid role "${String(raw.role)}" for model "${model}": expected one of ${MODEL_ROLES.join(", ")}.`
|
|
5504
|
+
);
|
|
5505
|
+
}
|
|
5506
|
+
role = r;
|
|
5507
|
+
}
|
|
5508
|
+
return { model, role };
|
|
5509
|
+
}
|
|
5510
|
+
function parseModelsFlag(value) {
|
|
5511
|
+
const out = [];
|
|
5512
|
+
for (const part of value.split(",")) {
|
|
5513
|
+
const p = part.trim();
|
|
5514
|
+
if (!p)
|
|
5515
|
+
continue;
|
|
5516
|
+
const eq = p.indexOf("=");
|
|
5517
|
+
out.push(
|
|
5518
|
+
eq === -1 ? normalizeEntry({ model: p }) : normalizeEntry({ model: p.slice(0, eq), role: p.slice(eq + 1) })
|
|
5519
|
+
);
|
|
5520
|
+
}
|
|
5521
|
+
return out;
|
|
5522
|
+
}
|
|
5523
|
+
function dedupeModels(entries) {
|
|
5524
|
+
const seen = /* @__PURE__ */ new Set();
|
|
5525
|
+
const out = [];
|
|
5526
|
+
for (const e of entries) {
|
|
5527
|
+
const key = `${e.model.toLowerCase()}|${e.role ?? ""}`;
|
|
5528
|
+
if (seen.has(key))
|
|
5529
|
+
continue;
|
|
5530
|
+
seen.add(key);
|
|
5531
|
+
out.push(e);
|
|
5532
|
+
}
|
|
5533
|
+
if (out.length > MAX_AI_MODELS) {
|
|
5534
|
+
throw new ModelsError(`Too many models (${out.length}); at most ${MAX_AI_MODELS} entries are allowed.`);
|
|
5535
|
+
}
|
|
5536
|
+
return out;
|
|
5537
|
+
}
|
|
5538
|
+
function mergeModels(...sources) {
|
|
5539
|
+
return dedupeModels(sources.flatMap((s) => s ?? []));
|
|
5540
|
+
}
|
|
5541
|
+
function primaryModel(entries) {
|
|
5542
|
+
return (entries.find((e) => e.role === "implementer") ?? entries[0])?.model;
|
|
5543
|
+
}
|
|
5544
|
+
function resolveModels(flags, env = process.env) {
|
|
5545
|
+
const explicit = !!(flags.models || flags.model);
|
|
5546
|
+
const modelsStr = explicit ? flags.models : env["PINSAY_AI_MODELS"];
|
|
5547
|
+
const modelStr = explicit ? flags.model : env["PINSAY_AI_MODEL"];
|
|
5548
|
+
return mergeModels(
|
|
5549
|
+
modelsStr ? parseModelsFlag(modelsStr) : void 0,
|
|
5550
|
+
modelStr ? [normalizeEntry({ model: modelStr })] : void 0
|
|
5551
|
+
);
|
|
5552
|
+
}
|
|
5553
|
+
function resolveModelsFromArgs(args, env = process.env) {
|
|
5554
|
+
const hasArray = args?.models !== void 0 && args.models !== null;
|
|
5555
|
+
const single = typeof args?.model === "string" && args.model ? args.model : void 0;
|
|
5556
|
+
if (!hasArray && !single) {
|
|
5557
|
+
return resolveModels({}, env);
|
|
5558
|
+
}
|
|
5559
|
+
let fromArray;
|
|
5560
|
+
if (hasArray) {
|
|
5561
|
+
if (!Array.isArray(args.models))
|
|
5562
|
+
throw new ModelsError("models must be an array of {model, role?}.");
|
|
5563
|
+
fromArray = args.models.map(
|
|
5564
|
+
(m) => typeof m === "string" ? normalizeEntry({ model: m }) : normalizeEntry(m ?? {})
|
|
5565
|
+
);
|
|
5566
|
+
}
|
|
5567
|
+
return mergeModels(fromArray, single ? [normalizeEntry({ model: single })] : void 0);
|
|
5568
|
+
}
|
|
5569
|
+
function modelsBody(entries) {
|
|
5570
|
+
if (entries.length === 0)
|
|
5571
|
+
return {};
|
|
5572
|
+
return { aiModels: entries, aiModel: primaryModel(entries) };
|
|
5573
|
+
}
|
|
5574
|
+
function distinctModelCount(entries) {
|
|
5575
|
+
return new Set(entries.map((e) => e.model.toLowerCase())).size;
|
|
5576
|
+
}
|
|
5577
|
+
var DELEGATION_WARNING = '\u26A0 Delegation is on for this project, but fewer than 2 distinct models were reported. Pass every model that worked on the item (planner, worker, reviewer) via --models "<planner>=planner,<worker>=implementer,<reviewer>=reviewer".';
|
|
5578
|
+
function delegationWarning(delegation, entries) {
|
|
5579
|
+
return delegation === "auto" && distinctModelCount(entries) < 2 ? DELEGATION_WARNING : null;
|
|
5580
|
+
}
|
|
5581
|
+
function describeModels(entries) {
|
|
5582
|
+
return entries.length === 0 ? "(none)" : entries.map((e) => `${e.model}${e.role ? `=${e.role}` : ""}`).join(", ");
|
|
5583
|
+
}
|
|
5584
|
+
|
|
5478
5585
|
// src/apply/mark.ts
|
|
5479
5586
|
async function markApplied(options, ctx) {
|
|
5480
5587
|
if (!options.noCommit && !isStaged(ctx.cwd)) {
|
|
@@ -5488,10 +5595,19 @@ async function markApplied(options, ctx) {
|
|
|
5488
5595
|
const projectCtx = await loadProjectContext(ctx);
|
|
5489
5596
|
const email = getUserEmail(ctx.cwd);
|
|
5490
5597
|
const appliedByLabel = options.tool ? `${email} via ${options.tool}` : email;
|
|
5598
|
+
const models = mergeModels(
|
|
5599
|
+
options.models,
|
|
5600
|
+
options.model ? [normalizeEntry({ model: options.model })] : void 0
|
|
5601
|
+
);
|
|
5602
|
+
const aiModelFields = modelsBody(models);
|
|
5603
|
+
const warning = delegationWarning(projectCtx.delegation ?? "auto", models);
|
|
5604
|
+
if (warning)
|
|
5605
|
+
console.error(warning);
|
|
5491
5606
|
if (options.dryRun) {
|
|
5492
5607
|
console.log(`[dry-run] Would mark comment ${options.id} as applied`);
|
|
5493
5608
|
console.log(`[dry-run] Reply: ${options.reply}`);
|
|
5494
5609
|
console.log(`[dry-run] AppliedBy: ${appliedByLabel}`);
|
|
5610
|
+
console.log(`[dry-run] Models: ${describeModels(models)}`);
|
|
5495
5611
|
return {
|
|
5496
5612
|
committed: false,
|
|
5497
5613
|
patchedIds: typeof options.id === "number" ? [options.id] : []
|
|
@@ -5535,7 +5651,7 @@ async function markApplied(options, ctx) {
|
|
|
5535
5651
|
// Structured AI attribution on the reply itself (Reply.AiTool/AiModel), distinct from the
|
|
5536
5652
|
// free-text appliedByLabel above — see CommentService.Normalize server-side.
|
|
5537
5653
|
aiTool: options.tool || void 0,
|
|
5538
|
-
|
|
5654
|
+
...aiModelFields
|
|
5539
5655
|
},
|
|
5540
5656
|
token: ctx.token
|
|
5541
5657
|
});
|
|
@@ -5579,7 +5695,7 @@ async function markApplied(options, ctx) {
|
|
|
5579
5695
|
// against a deployed build.
|
|
5580
5696
|
commitSha: sha || null,
|
|
5581
5697
|
aiTool: options.tool || void 0,
|
|
5582
|
-
|
|
5698
|
+
...aiModelFields
|
|
5583
5699
|
},
|
|
5584
5700
|
token: ctx.token
|
|
5585
5701
|
});
|
|
@@ -5599,9 +5715,10 @@ async function markApplied(options, ctx) {
|
|
|
5599
5715
|
}
|
|
5600
5716
|
async function markFailed(id, reason, ctx, tool, model) {
|
|
5601
5717
|
const replyBody = `Could not apply: ${reason}`;
|
|
5718
|
+
const models = typeof model === "string" ? model ? [normalizeEntry({ model })] : [] : model ?? [];
|
|
5602
5719
|
await api(ctx.server, `/api/comments/${id}/replies`, {
|
|
5603
5720
|
method: "POST",
|
|
5604
|
-
body: { body: replyBody, aiTool: tool || void 0,
|
|
5721
|
+
body: { body: replyBody, aiTool: tool || void 0, ...modelsBody(models) },
|
|
5605
5722
|
token: ctx.token
|
|
5606
5723
|
});
|
|
5607
5724
|
await postEvent(ctx.server, ctx.token, {
|
|
@@ -5636,6 +5753,20 @@ async function exitIfWorkspaceFrozen(server, token) {
|
|
|
5636
5753
|
} catch {
|
|
5637
5754
|
}
|
|
5638
5755
|
}
|
|
5756
|
+
function parseModelFlags(parsed) {
|
|
5757
|
+
try {
|
|
5758
|
+
return resolveModels({
|
|
5759
|
+
models: typeof parsed["models"] === "string" ? parsed["models"] : void 0,
|
|
5760
|
+
model: typeof parsed["model"] === "string" ? parsed["model"] : void 0
|
|
5761
|
+
});
|
|
5762
|
+
} catch (err) {
|
|
5763
|
+
if (err instanceof ModelsError) {
|
|
5764
|
+
console.error(err.message);
|
|
5765
|
+
process.exit(2);
|
|
5766
|
+
}
|
|
5767
|
+
throw err;
|
|
5768
|
+
}
|
|
5769
|
+
}
|
|
5639
5770
|
async function applyCommand(cwd2, parsed, positionals = []) {
|
|
5640
5771
|
const root = await findRepoRoot(cwd2);
|
|
5641
5772
|
const config = await readConfig(root);
|
|
@@ -5723,7 +5854,7 @@ async function applyCommand(cwd2, parsed, positionals = []) {
|
|
|
5723
5854
|
`\u26A0 --tool not passed \u2014 defaulting to "${config.aiTool}" (recorded when this project was initialized). Pass --tool <your-tool-name> explicitly if you are not that tool.`
|
|
5724
5855
|
);
|
|
5725
5856
|
}
|
|
5726
|
-
const
|
|
5857
|
+
const models = parseModelFlags(parsed);
|
|
5727
5858
|
const markStatus = typeof parsed["status"] === "string" ? parsed["status"] : void 0;
|
|
5728
5859
|
const markEnv = typeof parsed["env"] === "string" ? parsed["env"] : void 0;
|
|
5729
5860
|
const marked = await markApplied(
|
|
@@ -5733,7 +5864,7 @@ async function applyCommand(cwd2, parsed, positionals = []) {
|
|
|
5733
5864
|
noCommit,
|
|
5734
5865
|
dryRun,
|
|
5735
5866
|
tool: tool2,
|
|
5736
|
-
|
|
5867
|
+
models,
|
|
5737
5868
|
filter: markStatus !== void 0 || markEnv !== void 0 ? { status: markStatus, environment: markEnv } : void 0
|
|
5738
5869
|
},
|
|
5739
5870
|
clientCtx
|
|
@@ -5763,8 +5894,8 @@ async function applyCommand(cwd2, parsed, positionals = []) {
|
|
|
5763
5894
|
`\u26A0 --tool not passed \u2014 defaulting to "${config.aiTool}" (recorded when this project was initialized). Pass --tool <your-tool-name> explicitly if you are not that tool.`
|
|
5764
5895
|
);
|
|
5765
5896
|
}
|
|
5766
|
-
const
|
|
5767
|
-
await markFailed(failId, reason, clientCtx, failTool,
|
|
5897
|
+
const failModels = parseModelFlags(parsed);
|
|
5898
|
+
await markFailed(failId, reason, clientCtx, failTool, failModels);
|
|
5768
5899
|
process.exit(0);
|
|
5769
5900
|
}
|
|
5770
5901
|
if (parsed["json"] === true && !parsed["plan"] && !parsed["tool"]) {
|
|
@@ -11242,9 +11373,24 @@ var TOOL_PINSAY_GET_COMMENT = {
|
|
|
11242
11373
|
additionalProperties: false
|
|
11243
11374
|
}
|
|
11244
11375
|
};
|
|
11376
|
+
var MODELS_NOTICE = "Pass `models` with EVERY model that touched the item and its role (planner/reviewer = your own id, implementer = each worker model you delegated to), using exact model ids, not aliases.";
|
|
11377
|
+
var MODELS_PROPERTY = {
|
|
11378
|
+
type: "array",
|
|
11379
|
+
maxItems: 8,
|
|
11380
|
+
description: 'ALWAYS pass this: EVERY model that worked on the item, each with its role. Include the planner/reviewer (your own model id) and every worker model you delegated to (role "implementer"). Use exact model ids, not aliases like "haiku"/"sonnet". With one model and no delegation, pass just yours as implementer.',
|
|
11381
|
+
items: {
|
|
11382
|
+
type: "object",
|
|
11383
|
+
properties: {
|
|
11384
|
+
model: { type: "string", description: 'Exact model id, e.g. "claude-opus-5-5"' },
|
|
11385
|
+
role: { type: "string", enum: ["planner", "implementer", "reviewer"] }
|
|
11386
|
+
},
|
|
11387
|
+
required: ["model"],
|
|
11388
|
+
additionalProperties: false
|
|
11389
|
+
}
|
|
11390
|
+
};
|
|
11245
11391
|
var TOOL_PINSAY_MARK_APPLIED = {
|
|
11246
11392
|
name: "pinsay_mark_applied",
|
|
11247
|
-
description: `Mark one comment applied and post the reply on it, recording the optional commitUrl, the git user.email as the applier, and the tool/model attribution. Runs no git command and records no commit sha, so a comment marked this way is never flipped to Live by \`npx pinsay-cli status --deployed\`. Use it when a human will commit the change; to commit now, use pinsay_commit_and_mark. Returns {id, status, commitUrl}. ${UNTRUSTED_NOTICE}`,
|
|
11393
|
+
description: `Mark one comment applied and post the reply on it, recording the optional commitUrl, the git user.email as the applier, and the tool/model attribution. Runs no git command and records no commit sha, so a comment marked this way is never flipped to Live by \`npx pinsay-cli status --deployed\`. Use it when a human will commit the change; to commit now, use pinsay_commit_and_mark. Returns {id, status, commitUrl}. ${MODELS_NOTICE} ${UNTRUSTED_NOTICE}`,
|
|
11248
11394
|
inputSchema: {
|
|
11249
11395
|
type: "object",
|
|
11250
11396
|
properties: {
|
|
@@ -11266,8 +11412,9 @@ var TOOL_PINSAY_MARK_APPLIED = {
|
|
|
11266
11412
|
},
|
|
11267
11413
|
model: {
|
|
11268
11414
|
type: "string",
|
|
11269
|
-
description: '
|
|
11270
|
-
}
|
|
11415
|
+
description: 'Legacy single model id (role unset); prefer `models`. Exact id, not an alias (e.g. "claude-sonnet-5-5", "gpt-5.2").'
|
|
11416
|
+
},
|
|
11417
|
+
models: MODELS_PROPERTY
|
|
11271
11418
|
},
|
|
11272
11419
|
required: ["id", "reply"],
|
|
11273
11420
|
additionalProperties: false
|
|
@@ -11275,7 +11422,7 @@ var TOOL_PINSAY_MARK_APPLIED = {
|
|
|
11275
11422
|
};
|
|
11276
11423
|
var TOOL_PINSAY_COMMIT_AND_MARK = {
|
|
11277
11424
|
name: "pinsay_commit_and_mark",
|
|
11278
|
-
description: `Commit the applied changes with git and mark the comments applied, posting the same reply on each and recording its commit URL. Paths in files are relative to the repository root and must exist inside it; they are staged first. Omit files to commit what is already staged (errors when nothing is). The commit takes everything in the index, not only files. The project's commitStyle decides the commits: "single" makes one commit for all ids; "separate" makes one commit per id, and with more than one id files is required and each entry is prefixed "<id>:" to assign it to that comment (e.g. "12:src/Button.tsx"; unprefixed entries go to the first id). Never pushes. Returns [{id, commitUrl}]. ${UNTRUSTED_NOTICE}`,
|
|
11425
|
+
description: `Commit the applied changes with git and mark the comments applied, posting the same reply on each and recording its commit URL. Paths in files are relative to the repository root and must exist inside it; they are staged first. Omit files to commit what is already staged (errors when nothing is). The commit takes everything in the index, not only files. The project's commitStyle decides the commits: "single" makes one commit for all ids; "separate" makes one commit per id, and with more than one id files is required and each entry is prefixed "<id>:" to assign it to that comment (e.g. "12:src/Button.tsx"; unprefixed entries go to the first id). Never pushes. Returns [{id, commitUrl}]. ${MODELS_NOTICE} ${UNTRUSTED_NOTICE}`,
|
|
11279
11426
|
inputSchema: {
|
|
11280
11427
|
type: "object",
|
|
11281
11428
|
properties: {
|
|
@@ -11303,8 +11450,9 @@ var TOOL_PINSAY_COMMIT_AND_MARK = {
|
|
|
11303
11450
|
},
|
|
11304
11451
|
model: {
|
|
11305
11452
|
type: "string",
|
|
11306
|
-
description: '
|
|
11307
|
-
}
|
|
11453
|
+
description: 'Legacy single model id (role unset); prefer `models`. Exact id, not an alias (e.g. "claude-sonnet-5-5", "gpt-5.2").'
|
|
11454
|
+
},
|
|
11455
|
+
models: MODELS_PROPERTY
|
|
11308
11456
|
},
|
|
11309
11457
|
required: ["ids", "reply"],
|
|
11310
11458
|
additionalProperties: false
|
|
@@ -11312,7 +11460,7 @@ var TOOL_PINSAY_COMMIT_AND_MARK = {
|
|
|
11312
11460
|
};
|
|
11313
11461
|
var TOOL_PINSAY_REPLY = {
|
|
11314
11462
|
name: "pinsay_reply",
|
|
11315
|
-
description: `Post a reply on a comment, visible to its author and the other stakeholders, with tool/model attribution. Does not change the comment's status; to close a comment out with a reply, use pinsay_mark_applied or pinsay_commit_and_mark. Returns {replyId}. ${UNTRUSTED_NOTICE}`,
|
|
11463
|
+
description: `Post a reply on a comment, visible to its author and the other stakeholders, with tool/model attribution. Does not change the comment's status; to close a comment out with a reply, use pinsay_mark_applied or pinsay_commit_and_mark. Returns {replyId}. ${MODELS_NOTICE} ${UNTRUSTED_NOTICE}`,
|
|
11316
11464
|
inputSchema: {
|
|
11317
11465
|
type: "object",
|
|
11318
11466
|
properties: {
|
|
@@ -11330,8 +11478,9 @@ var TOOL_PINSAY_REPLY = {
|
|
|
11330
11478
|
},
|
|
11331
11479
|
model: {
|
|
11332
11480
|
type: "string",
|
|
11333
|
-
description: '
|
|
11334
|
-
}
|
|
11481
|
+
description: 'Legacy single model id (role unset); prefer `models`. Exact id, not an alias (e.g. "claude-sonnet-5-5", "gpt-5.2").'
|
|
11482
|
+
},
|
|
11483
|
+
models: MODELS_PROPERTY
|
|
11335
11484
|
},
|
|
11336
11485
|
required: ["id", "body"],
|
|
11337
11486
|
additionalProperties: false
|
|
@@ -11670,6 +11819,15 @@ async function handleGetComment(args, ctx) {
|
|
|
11670
11819
|
throw err;
|
|
11671
11820
|
}
|
|
11672
11821
|
}
|
|
11822
|
+
function parseModelsArgs(args) {
|
|
11823
|
+
try {
|
|
11824
|
+
return resolveModelsFromArgs(args);
|
|
11825
|
+
} catch (err) {
|
|
11826
|
+
if (err instanceof ModelsError)
|
|
11827
|
+
throw mcpError("forbidden", err.message);
|
|
11828
|
+
throw err;
|
|
11829
|
+
}
|
|
11830
|
+
}
|
|
11673
11831
|
async function handleMarkApplied(args, ctx) {
|
|
11674
11832
|
const id = Number(args?.id);
|
|
11675
11833
|
const reply = args?.reply;
|
|
@@ -11680,7 +11838,7 @@ async function handleMarkApplied(args, ctx) {
|
|
|
11680
11838
|
const appliedByLabel = email;
|
|
11681
11839
|
const commitUrl = args.commitUrl || null;
|
|
11682
11840
|
const tool = args.tool || ctx.configAiTool;
|
|
11683
|
-
const
|
|
11841
|
+
const models = parseModelsArgs(args);
|
|
11684
11842
|
await api(ctx.server, `/api/comments/${id}`, {
|
|
11685
11843
|
method: "PATCH",
|
|
11686
11844
|
body: {
|
|
@@ -11689,7 +11847,7 @@ async function handleMarkApplied(args, ctx) {
|
|
|
11689
11847
|
appliedByLabel,
|
|
11690
11848
|
commitUrl,
|
|
11691
11849
|
aiTool: tool || void 0,
|
|
11692
|
-
|
|
11850
|
+
...modelsBody(models)
|
|
11693
11851
|
},
|
|
11694
11852
|
token: ctx.token
|
|
11695
11853
|
});
|
|
@@ -11709,7 +11867,7 @@ async function handleCommitAndMark(args, ctx) {
|
|
|
11709
11867
|
const reply = args?.reply;
|
|
11710
11868
|
const files = args?.files;
|
|
11711
11869
|
const tool = args?.tool || ctx.configAiTool;
|
|
11712
|
-
const
|
|
11870
|
+
const models = parseModelsArgs(args);
|
|
11713
11871
|
if (!Array.isArray(ids) || ids.length === 0 || typeof reply !== "string") {
|
|
11714
11872
|
throw mcpError("git", "ids (non-empty array) and reply (string) are required");
|
|
11715
11873
|
}
|
|
@@ -11800,7 +11958,7 @@ async function handleCommitAndMark(args, ctx) {
|
|
|
11800
11958
|
// so the same fix that shipped for `pinsay apply` has to hold here.
|
|
11801
11959
|
commitSha: sha,
|
|
11802
11960
|
aiTool: tool || void 0,
|
|
11803
|
-
|
|
11961
|
+
...modelsBody(models)
|
|
11804
11962
|
},
|
|
11805
11963
|
token: ctx.token
|
|
11806
11964
|
});
|
|
@@ -11846,7 +12004,7 @@ async function handleCommitAndMark(args, ctx) {
|
|
|
11846
12004
|
// Same reason as the single-comment path above.
|
|
11847
12005
|
commitSha: sha,
|
|
11848
12006
|
aiTool: tool || void 0,
|
|
11849
|
-
|
|
12007
|
+
...modelsBody(models)
|
|
11850
12008
|
},
|
|
11851
12009
|
token: ctx.token
|
|
11852
12010
|
});
|
|
@@ -11888,7 +12046,7 @@ async function handleCommitAndMark(args, ctx) {
|
|
|
11888
12046
|
// stays "applied" forever even once the fix ships.
|
|
11889
12047
|
commitSha: sha,
|
|
11890
12048
|
aiTool: tool || void 0,
|
|
11891
|
-
|
|
12049
|
+
...modelsBody(models)
|
|
11892
12050
|
},
|
|
11893
12051
|
token: ctx.token
|
|
11894
12052
|
});
|
|
@@ -11909,10 +12067,10 @@ async function handleReply(args, ctx) {
|
|
|
11909
12067
|
throw mcpError("forbidden", "id and body are required");
|
|
11910
12068
|
}
|
|
11911
12069
|
const tool = args?.tool || ctx.configAiTool;
|
|
11912
|
-
const
|
|
12070
|
+
const models = parseModelsArgs(args);
|
|
11913
12071
|
const res = await api(ctx.server, `/api/comments/${id}/replies`, {
|
|
11914
12072
|
method: "POST",
|
|
11915
|
-
body: { body, aiTool: tool || void 0,
|
|
12073
|
+
body: { body, aiTool: tool || void 0, ...modelsBody(models) },
|
|
11916
12074
|
token: ctx.token
|
|
11917
12075
|
});
|
|
11918
12076
|
return { replyId: res?.id ?? res?.data?.id ?? null };
|
|
@@ -12616,6 +12774,11 @@ Options:
|
|
|
12616
12774
|
--reply <text> Reply text for applied comment (required with --mark)
|
|
12617
12775
|
--no-commit Skip git commit during --mark (PATCH only)
|
|
12618
12776
|
--dry-run Print what --mark would do without making git/API changes
|
|
12777
|
+
--models <list> Every model that worked on the item, with its role, for --mark/--fail:
|
|
12778
|
+
"<id>=<role>,<id>=<role>" (role: planner|implementer|reviewer, optional;
|
|
12779
|
+
e.g. "claude-opus-5-5=planner,claude-sonnet-5-5=implementer,claude-opus-5-5=reviewer").
|
|
12780
|
+
Env fallback: PINSAY_AI_MODELS
|
|
12781
|
+
--model <id> One model id (role unset); merged after --models. Env: PINSAY_AI_MODEL
|
|
12619
12782
|
--fail <id> Mark apply failed with a reply
|
|
12620
12783
|
--reason <text> Failure reason (required with --fail)
|
|
12621
12784
|
--status <status> Filter queue by status (open, ready, applied, archived)
|
package/package.json
CHANGED