pinsay-cli 0.7.0 → 0.7.1
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 +127 -37
- 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.7.
|
|
18
|
+
BUILD_CLI_VERSION = true ? "0.7.1" : "0.0.0-dev";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -705,7 +705,7 @@ var init_map = __esm({
|
|
|
705
705
|
});
|
|
706
706
|
|
|
707
707
|
// src/auth.ts
|
|
708
|
-
import { promises as
|
|
708
|
+
import { promises as fs18 } from "node:fs";
|
|
709
709
|
import { dirname as dirname8 } from "node:path";
|
|
710
710
|
async function readApiKey(cwd2, server) {
|
|
711
711
|
const { key } = await resolveApiKey(cwd2, server);
|
|
@@ -730,7 +730,7 @@ async function resolveToken(server, cwd2, explicitApiKey) {
|
|
|
730
730
|
return void 0;
|
|
731
731
|
const cacheFile = tokenCacheFile(server, apiKey);
|
|
732
732
|
try {
|
|
733
|
-
const cached = JSON.parse(await
|
|
733
|
+
const cached = JSON.parse(await fs18.readFile(cacheFile, "utf8"));
|
|
734
734
|
const token = typeof cached?.token === "string" ? cached.token.trim() : "";
|
|
735
735
|
if (token && !isJwtExpired(token))
|
|
736
736
|
return token;
|
|
@@ -747,8 +747,8 @@ async function resolveToken(server, cwd2, explicitApiKey) {
|
|
|
747
747
|
);
|
|
748
748
|
if (login?.token) {
|
|
749
749
|
try {
|
|
750
|
-
await
|
|
751
|
-
await
|
|
750
|
+
await fs18.mkdir(dirname8(cacheFile), { recursive: true });
|
|
751
|
+
await fs18.writeFile(cacheFile, JSON.stringify({ token: login.token }), "utf8");
|
|
752
752
|
} catch {
|
|
753
753
|
}
|
|
754
754
|
return login.token;
|
|
@@ -770,7 +770,7 @@ var init_auth = __esm({
|
|
|
770
770
|
|
|
771
771
|
// src/vite/resolve.ts
|
|
772
772
|
import { existsSync as existsSync3, readFileSync } from "node:fs";
|
|
773
|
-
import { join as
|
|
773
|
+
import { join as join18 } from "node:path";
|
|
774
774
|
function entryOf(json, hash) {
|
|
775
775
|
if (!json || typeof json !== "object")
|
|
776
776
|
return void 0;
|
|
@@ -799,11 +799,11 @@ function resolveSource(cwd2, hash) {
|
|
|
799
799
|
const miss = { kind: "unknown", path: null, component: null };
|
|
800
800
|
if (!hash || !/^[0-9a-f]{8}$/.test(hash))
|
|
801
801
|
return miss;
|
|
802
|
-
const manifestPath =
|
|
802
|
+
const manifestPath = join18(cwd2, ".pinsay", "manifest.json");
|
|
803
803
|
const current = normalise(entryOf(readJson2(manifestPath), hash));
|
|
804
804
|
if (current)
|
|
805
805
|
return { kind: "manifest", path: current.path, component: current.component };
|
|
806
|
-
const prevPath =
|
|
806
|
+
const prevPath = join18(cwd2, ".pinsay", "manifest.prev.json");
|
|
807
807
|
const previous = normalise(entryOf(readJson2(prevPath), hash));
|
|
808
808
|
if (previous) {
|
|
809
809
|
return {
|
|
@@ -1103,6 +1103,12 @@ async function statusCommand(cwd2, parsed, positionals = []) {
|
|
|
1103
1103
|
console.error(`Invalid status: ${newStatusStr}. Must be open, ready, applied, or archived.`);
|
|
1104
1104
|
process.exit(2);
|
|
1105
1105
|
}
|
|
1106
|
+
if (statusNum === 3) {
|
|
1107
|
+
console.error(
|
|
1108
|
+
'Marking a comment as \'applied\' is not permitted via `pinsay status`.\nUse `pinsay apply --mark <id> --reply "<explanation>"` (or `pinsay reply <id> "<text>"`) to record a properly structured reply.'
|
|
1109
|
+
);
|
|
1110
|
+
process.exit(2);
|
|
1111
|
+
}
|
|
1106
1112
|
await api(server, `/api/comments/${id}`, {
|
|
1107
1113
|
method: "PATCH",
|
|
1108
1114
|
body: { status: statusNum },
|
|
@@ -4741,14 +4747,66 @@ async function updateCommand(cwd2, options) {
|
|
|
4741
4747
|
// src/commands/apply.ts
|
|
4742
4748
|
init_config();
|
|
4743
4749
|
init_api();
|
|
4750
|
+
|
|
4751
|
+
// src/lib/notify-update.ts
|
|
4752
|
+
import { promises as fs17 } from "node:fs";
|
|
4753
|
+
import { join as join17 } from "node:path";
|
|
4754
|
+
init_build_constants();
|
|
4755
|
+
async function checkAndNotifyUpdates(options) {
|
|
4756
|
+
const result = {
|
|
4757
|
+
cliUpdateAvailable: false,
|
|
4758
|
+
skillsUpdateAvailable: false
|
|
4759
|
+
};
|
|
4760
|
+
if (options.silent || !options.meta)
|
|
4761
|
+
return result;
|
|
4762
|
+
const meta = options.meta;
|
|
4763
|
+
if (meta.latestCliVersion && compareSemver(BUILD_CLI_VERSION, meta.latestCliVersion) < 0) {
|
|
4764
|
+
result.cliUpdateAvailable = true;
|
|
4765
|
+
console.error(
|
|
4766
|
+
`\x1B[33m\u{1F4A1} Update available for pinsay-cli: ${BUILD_CLI_VERSION} \u2192 ${meta.latestCliVersion}\x1B[0m
|
|
4767
|
+
\x1B[33m Run 'npx -y pinsay@latest' to update.\x1B[0m
|
|
4768
|
+
`
|
|
4769
|
+
);
|
|
4770
|
+
}
|
|
4771
|
+
if (meta.skillVersion && options.config && options.cwd) {
|
|
4772
|
+
try {
|
|
4773
|
+
const files = skillFilesFor(options.config);
|
|
4774
|
+
let hasStale = false;
|
|
4775
|
+
for (const rel of files) {
|
|
4776
|
+
const abs = join17(options.cwd, rel);
|
|
4777
|
+
try {
|
|
4778
|
+
await fs17.access(abs);
|
|
4779
|
+
const installed = await readStamp(abs);
|
|
4780
|
+
if (installed && installed !== meta.skillVersion) {
|
|
4781
|
+
hasStale = true;
|
|
4782
|
+
break;
|
|
4783
|
+
}
|
|
4784
|
+
} catch {
|
|
4785
|
+
}
|
|
4786
|
+
}
|
|
4787
|
+
if (hasStale) {
|
|
4788
|
+
result.skillsUpdateAvailable = true;
|
|
4789
|
+
console.error(
|
|
4790
|
+
`\x1B[33m\u{1F4A1} Local AI skills are out of date with the server.\x1B[0m
|
|
4791
|
+
\x1B[33m Run 'npx pinsay-cli update' to refresh your prompt skills.\x1B[0m
|
|
4792
|
+
`
|
|
4793
|
+
);
|
|
4794
|
+
}
|
|
4795
|
+
} catch {
|
|
4796
|
+
}
|
|
4797
|
+
}
|
|
4798
|
+
return result;
|
|
4799
|
+
}
|
|
4800
|
+
|
|
4801
|
+
// src/commands/apply.ts
|
|
4744
4802
|
init_auth();
|
|
4745
4803
|
init_build_constants();
|
|
4746
4804
|
|
|
4747
4805
|
// src/apply/run.ts
|
|
4748
4806
|
init_resolve();
|
|
4749
4807
|
init_api();
|
|
4750
|
-
import { promises as
|
|
4751
|
-
import { join as
|
|
4808
|
+
import { promises as fs20 } from "node:fs";
|
|
4809
|
+
import { join as join20, dirname as dirname9 } from "node:path";
|
|
4752
4810
|
import { spawnSync } from "node:child_process";
|
|
4753
4811
|
|
|
4754
4812
|
// src/apply/queue.ts
|
|
@@ -4906,8 +4964,8 @@ async function fetchQueue(ctx, filter) {
|
|
|
4906
4964
|
|
|
4907
4965
|
// src/apply/context.ts
|
|
4908
4966
|
init_api();
|
|
4909
|
-
import { promises as
|
|
4910
|
-
import { join as
|
|
4967
|
+
import { promises as fs19 } from "node:fs";
|
|
4968
|
+
import { join as join19 } from "node:path";
|
|
4911
4969
|
init_config();
|
|
4912
4970
|
async function loadProjectContext(ctx) {
|
|
4913
4971
|
const branding = await getBranding(ctx.server);
|
|
@@ -4918,7 +4976,7 @@ async function loadProjectContext(ctx) {
|
|
|
4918
4976
|
if (config.delegation === "off")
|
|
4919
4977
|
delegation = "off";
|
|
4920
4978
|
const relPath = isMultiProject(config) ? stackFileRelPath(ctx.project) : stackFileRelPath();
|
|
4921
|
-
const stackRaw = await
|
|
4979
|
+
const stackRaw = await fs19.readFile(join19(ctx.cwd, relPath), "utf8");
|
|
4922
4980
|
stack = JSON.parse(stackRaw);
|
|
4923
4981
|
} catch {
|
|
4924
4982
|
}
|
|
@@ -5241,24 +5299,29 @@ function buildApplyPrompt(items, context, opts) {
|
|
|
5241
5299
|
'(Separate style: after each item; Single style: run `npx pinsay-cli apply --mark all --reply "..." --models "..." --tool <your-tool-name>`'
|
|
5242
5300
|
);
|
|
5243
5301
|
lines.push(
|
|
5244
|
-
"once at the end). Never run git push. `--models` MUST list EVERY model that touched the item and its role:"
|
|
5302
|
+
"once at the end). Never run git push. `--models` is MANDATORY and MUST list EVERY model that touched the item and its role:"
|
|
5245
5303
|
);
|
|
5246
5304
|
lines.push(
|
|
5247
|
-
"the planner/reviewer (your own model id) and each worker model you delegated to (`implementer`).
|
|
5305
|
+
"the planner/reviewer (your own model id) and each worker model you delegated to (`implementer`). NEVER guess or hardcode"
|
|
5306
|
+
);
|
|
5307
|
+
lines.push(
|
|
5308
|
+
'outdated model names from memory (like "gemini-2.5-pro"). Always inspect your active runtime model name directly. Use exact'
|
|
5248
5309
|
);
|
|
5249
5310
|
lines.push(
|
|
5250
5311
|
'model ids (e.g. `claude-opus-5-5`, `claude-sonnet-5-5`, `gpt-5.2`), never aliases like "haiku" or "sonnet". With one'
|
|
5251
5312
|
);
|
|
5252
5313
|
lines.push(
|
|
5253
|
-
"model and no delegation, pass just yours as `<your-model-id>=implementer`.
|
|
5314
|
+
"model and no delegation, pass just yours as `<your-model-id>=implementer`. Explicit role `=<role>` is required on each entry."
|
|
5315
|
+
);
|
|
5316
|
+
lines.push(
|
|
5317
|
+
"`--tool` (e.g. `claude-code`, `opencode`, `cursor`, `windsurf`, `antigravity`) records which agent you are running as."
|
|
5254
5318
|
);
|
|
5255
5319
|
lines.push(
|
|
5256
|
-
"
|
|
5320
|
+
"ALWAYS pass both explicitly, even on a project you ran `init` on \u2014 `--tool` silently falls back to whichever tool happened"
|
|
5257
5321
|
);
|
|
5258
5322
|
lines.push(
|
|
5259
|
-
"
|
|
5323
|
+
"to run `init`, which is wrong the moment a different tool applies a comment later."
|
|
5260
5324
|
);
|
|
5261
|
-
lines.push("the moment a different tool applies a comment later.");
|
|
5262
5325
|
return lines.join("\n") + "\n";
|
|
5263
5326
|
}
|
|
5264
5327
|
|
|
@@ -5285,10 +5348,10 @@ function detectAiTool(override) {
|
|
|
5285
5348
|
async function ensureToolRegistered(ctx, tool) {
|
|
5286
5349
|
const config = await readConfig(ctx.cwd).catch(() => ({}));
|
|
5287
5350
|
const relStackPath = isMultiProject(config) ? stackFileRelPath(ctx.project) : stackFileRelPath();
|
|
5288
|
-
const stackPath =
|
|
5351
|
+
const stackPath = join20(ctx.cwd, relStackPath);
|
|
5289
5352
|
let stackData = {};
|
|
5290
5353
|
try {
|
|
5291
|
-
const raw = await
|
|
5354
|
+
const raw = await fs20.readFile(stackPath, "utf8");
|
|
5292
5355
|
stackData = JSON.parse(raw);
|
|
5293
5356
|
} catch {
|
|
5294
5357
|
}
|
|
@@ -5307,8 +5370,8 @@ async function ensureToolRegistered(ctx, tool) {
|
|
|
5307
5370
|
}
|
|
5308
5371
|
);
|
|
5309
5372
|
if (res) {
|
|
5310
|
-
await
|
|
5311
|
-
await
|
|
5373
|
+
await fs20.mkdir(dirname9(stackPath), { recursive: true });
|
|
5374
|
+
await fs20.writeFile(stackPath, JSON.stringify(res, null, 2) + "\n", "utf8");
|
|
5312
5375
|
return;
|
|
5313
5376
|
}
|
|
5314
5377
|
} catch {
|
|
@@ -5317,8 +5380,8 @@ async function ensureToolRegistered(ctx, tool) {
|
|
|
5317
5380
|
aiTools.push(tool);
|
|
5318
5381
|
stackData.aiTools = aiTools;
|
|
5319
5382
|
try {
|
|
5320
|
-
await
|
|
5321
|
-
await
|
|
5383
|
+
await fs20.mkdir(dirname9(stackPath), { recursive: true });
|
|
5384
|
+
await fs20.writeFile(stackPath, JSON.stringify(stackData, null, 2) + "\n", "utf8");
|
|
5322
5385
|
} catch {
|
|
5323
5386
|
}
|
|
5324
5387
|
}
|
|
@@ -5392,9 +5455,9 @@ async function runApply(options, ctx) {
|
|
|
5392
5455
|
console.error(`Failed to spawn opencode: ${res.error.message}`);
|
|
5393
5456
|
}
|
|
5394
5457
|
} else if (tool === "cursor") {
|
|
5395
|
-
const promptFile =
|
|
5396
|
-
await
|
|
5397
|
-
await
|
|
5458
|
+
const promptFile = join20(ctx.cwd, ".pinsay/apply-prompt.md");
|
|
5459
|
+
await fs20.mkdir(join20(ctx.cwd, ".pinsay"), { recursive: true });
|
|
5460
|
+
await fs20.writeFile(promptFile, prompt, "utf8");
|
|
5398
5461
|
console.log(`Saved apply prompt to ${promptFile}`);
|
|
5399
5462
|
} else if (tool === "clipboard") {
|
|
5400
5463
|
const copied = copyToClipboard(prompt);
|
|
@@ -5803,6 +5866,12 @@ async function applyCommand(cwd2, parsed, positionals = []) {
|
|
|
5803
5866
|
console.error(tooOldMessage(BUILD_CLI_VERSION, minCli));
|
|
5804
5867
|
process.exit(5);
|
|
5805
5868
|
}
|
|
5869
|
+
await checkAndNotifyUpdates({
|
|
5870
|
+
meta,
|
|
5871
|
+
config,
|
|
5872
|
+
cwd: root,
|
|
5873
|
+
silent: Boolean(parsed["json"])
|
|
5874
|
+
});
|
|
5806
5875
|
} catch (err) {
|
|
5807
5876
|
if (!(err instanceof ApiError && err.code === 404)) {
|
|
5808
5877
|
}
|
|
@@ -5841,8 +5910,10 @@ async function applyCommand(cwd2, parsed, positionals = []) {
|
|
|
5841
5910
|
markId = parsedNum;
|
|
5842
5911
|
}
|
|
5843
5912
|
const reply = typeof parsed["reply"] === "string" ? parsed["reply"] : "";
|
|
5844
|
-
if (!reply) {
|
|
5845
|
-
console.error(
|
|
5913
|
+
if (!reply || reply.trim().length < 15 || /^(done|fixed|applied|ok|yes)$/i.test(reply.trim())) {
|
|
5914
|
+
console.error(
|
|
5915
|
+
'--reply "<text>" is required when using --mark and must provide a substantive explanation of the changes (at least 15 characters, e.g. "Applied \u2713 \u2014 updated logo source in header").'
|
|
5916
|
+
);
|
|
5846
5917
|
process.exit(2);
|
|
5847
5918
|
}
|
|
5848
5919
|
const noCommit = parsed["no-commit"] === true;
|
|
@@ -5855,6 +5926,19 @@ async function applyCommand(cwd2, parsed, positionals = []) {
|
|
|
5855
5926
|
);
|
|
5856
5927
|
}
|
|
5857
5928
|
const models = parseModelFlags(parsed);
|
|
5929
|
+
if (models.length === 0) {
|
|
5930
|
+
console.error(
|
|
5931
|
+
'--models "<model>=<role>" is mandatory when marking a comment as applied.\nSpecify your actual active runtime model and role (e.g. --models "<active-model>=implementer").\nIf multiple models or subagents participated (planner, implementer, reviewer), list all of them separated by commas.\nNever guess or hardcode model names from memory.'
|
|
5932
|
+
);
|
|
5933
|
+
process.exit(2);
|
|
5934
|
+
}
|
|
5935
|
+
const missingRole = models.find((m) => !m.role);
|
|
5936
|
+
if (missingRole) {
|
|
5937
|
+
console.error(
|
|
5938
|
+
`Invalid --models entry "${missingRole.model}": explicit role is required. Expected "<model>=<role>" where role is one of: planner, implementer, reviewer (e.g. "${missingRole.model}=implementer").`
|
|
5939
|
+
);
|
|
5940
|
+
process.exit(2);
|
|
5941
|
+
}
|
|
5858
5942
|
const markStatus = typeof parsed["status"] === "string" ? parsed["status"] : void 0;
|
|
5859
5943
|
const markEnv = typeof parsed["env"] === "string" ? parsed["env"] : void 0;
|
|
5860
5944
|
const marked = await markApplied(
|
|
@@ -5937,6 +6021,12 @@ async function applyAllProjects(root, cwd2, config, server, parsed) {
|
|
|
5937
6021
|
console.error(tooOldMessage(BUILD_CLI_VERSION, minCli));
|
|
5938
6022
|
process.exit(5);
|
|
5939
6023
|
}
|
|
6024
|
+
await checkAndNotifyUpdates({
|
|
6025
|
+
meta,
|
|
6026
|
+
config,
|
|
6027
|
+
cwd: root,
|
|
6028
|
+
silent: Boolean(parsed["json"])
|
|
6029
|
+
});
|
|
5940
6030
|
} catch (err) {
|
|
5941
6031
|
if (!(err instanceof ApiError && err.code === 404)) {
|
|
5942
6032
|
}
|
|
@@ -5988,7 +6078,7 @@ init_build_constants();
|
|
|
5988
6078
|
|
|
5989
6079
|
// src/mcp/server.ts
|
|
5990
6080
|
import { readFileSync as readFileSync3 } from "node:fs";
|
|
5991
|
-
import { join as
|
|
6081
|
+
import { join as join22 } from "node:path";
|
|
5992
6082
|
|
|
5993
6083
|
// node_modules/zod/v3/external.js
|
|
5994
6084
|
var external_exports = {};
|
|
@@ -11560,7 +11650,7 @@ var ALL_TOOLS = [
|
|
|
11560
11650
|
// src/mcp/tools.ts
|
|
11561
11651
|
init_api();
|
|
11562
11652
|
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
|
|
11563
|
-
import { isAbsolute as isAbsolute3, join as
|
|
11653
|
+
import { isAbsolute as isAbsolute3, join as join21, relative as relative5, resolve as resolve4 } from "node:path";
|
|
11564
11654
|
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
11565
11655
|
init_build_constants();
|
|
11566
11656
|
init_projection();
|
|
@@ -12100,7 +12190,7 @@ async function handleResolveSource(args, ctx) {
|
|
|
12100
12190
|
if (!hash || typeof hash !== "string") {
|
|
12101
12191
|
throw mcpError("forbidden", "hash is required");
|
|
12102
12192
|
}
|
|
12103
|
-
const manifestPath =
|
|
12193
|
+
const manifestPath = join21(ctx.cwd, ".pinsay/manifest.json");
|
|
12104
12194
|
if (!existsSync4(manifestPath)) {
|
|
12105
12195
|
return { path: null, reason: "no-manifest" };
|
|
12106
12196
|
}
|
|
@@ -12280,12 +12370,12 @@ function createMcpServer(ctx) {
|
|
|
12280
12370
|
let configObj = {};
|
|
12281
12371
|
let stackObj = {};
|
|
12282
12372
|
try {
|
|
12283
|
-
const configPath =
|
|
12373
|
+
const configPath = join22(ctx.cwd, ".pinsay/config.json");
|
|
12284
12374
|
configObj = JSON.parse(readFileSync3(configPath, "utf8"));
|
|
12285
12375
|
} catch {
|
|
12286
12376
|
}
|
|
12287
12377
|
try {
|
|
12288
|
-
const stackPath =
|
|
12378
|
+
const stackPath = join22(ctx.cwd, ".pinsay/stack.json");
|
|
12289
12379
|
stackObj = JSON.parse(readFileSync3(stackPath, "utf8"));
|
|
12290
12380
|
} catch {
|
|
12291
12381
|
}
|
|
@@ -12350,8 +12440,8 @@ function createMcpServer(ctx) {
|
|
|
12350
12440
|
async function runMcpServer(ctx, logFile) {
|
|
12351
12441
|
if (logFile) {
|
|
12352
12442
|
try {
|
|
12353
|
-
const
|
|
12354
|
-
const logStream =
|
|
12443
|
+
const fs21 = await import("node:fs");
|
|
12444
|
+
const logStream = fs21.createWriteStream(logFile, { flags: "a" });
|
|
12355
12445
|
const origWrite = process.stderr.write;
|
|
12356
12446
|
process.stderr.write = function(chunk, encoding, cb) {
|
|
12357
12447
|
logStream.write(chunk);
|
package/package.json
CHANGED