pinsay-cli 0.7.3 → 0.8.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/README.md +3 -3
- package/dist/cli.js +187 -94
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -17,8 +17,8 @@ npx pinsay-cli init
|
|
|
17
17
|
|
|
18
18
|
| Command | What it does |
|
|
19
19
|
|---|---|
|
|
20
|
-
| `npx pinsay-cli init` | Configure a repo (
|
|
21
|
-
| `npx pinsay-cli login` / `whoami` / `logout` | Sign in
|
|
20
|
+
| `npx pinsay-cli init` | Configure a repo (project, widget snippet, skills) |
|
|
21
|
+
| `npx pinsay-cli login` / `whoami` / `logout` | Sign in (asks: this machine or this repo only); check / clear credentials |
|
|
22
22
|
| `npx pinsay-cli list` / `get <id>` | List pending feedback; inspect one comment |
|
|
23
23
|
| `npx pinsay-cli apply` | Generate the fix-it prompt for your AI tool, then record what changed |
|
|
24
24
|
| `npx pinsay-cli apply --plan` | Preview proposed edits without touching code |
|
|
@@ -38,7 +38,7 @@ npx pinsay-cli init
|
|
|
38
38
|
|
|
39
39
|
## API keys
|
|
40
40
|
|
|
41
|
-
Keys look like `pnsy_` + 40 hex characters. They resolve in this order: `PINSAY_API_KEY` env var → `.pinsay/credentials.env` → the per-machine store (`~/.config/
|
|
41
|
+
Keys look like `pnsy_` + 40 hex characters. They resolve in this order: `PINSAY_API_KEY` env var → `.pinsay/credentials.env` → the per-machine store (`~/.config/pinsay/credentials.json`, mode 0600, never committed).
|
|
42
42
|
|
|
43
43
|
## MCP (Claude Code / Cursor)
|
|
44
44
|
|
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.8.0" : "0.0.0-dev";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -124,8 +124,6 @@ async function writeCredentials(cwd2, token, extra = {}) {
|
|
|
124
124
|
const file = join(cwd2, CREDENTIALS_FILE);
|
|
125
125
|
await fs.mkdir(dirname(file), { recursive: true });
|
|
126
126
|
const lines = [`PINSAY_API_KEY=${token}`];
|
|
127
|
-
if (extra.server)
|
|
128
|
-
lines.push(`PINSAY_SERVER=${extra.server}`);
|
|
129
127
|
if (extra.project)
|
|
130
128
|
lines.push(`PINSAY_PROJECT=${extra.project}`);
|
|
131
129
|
await fs.writeFile(file, lines.join("\n") + "\n", { encoding: "utf8", mode: 384 });
|
|
@@ -278,38 +276,83 @@ function normalizeServerOrigin(server) {
|
|
|
278
276
|
return server.replace(/\/+$/, "");
|
|
279
277
|
}
|
|
280
278
|
}
|
|
279
|
+
function configBase() {
|
|
280
|
+
if (process.platform === "win32") {
|
|
281
|
+
return process.env.APPDATA || join10(homedir(), "AppData", "Roaming");
|
|
282
|
+
}
|
|
283
|
+
return process.env.XDG_CONFIG_HOME || join10(homedir(), ".config");
|
|
284
|
+
}
|
|
281
285
|
function globalConfigDir() {
|
|
282
286
|
if (process.env.PINSAY_CONFIG_DIR)
|
|
283
287
|
return process.env.PINSAY_CONFIG_DIR;
|
|
284
|
-
|
|
285
|
-
return join10(process.env.APPDATA || join10(homedir(), "AppData", "Roaming"), "pointer");
|
|
286
|
-
}
|
|
287
|
-
return join10(process.env.XDG_CONFIG_HOME || join10(homedir(), ".config"), "pointer");
|
|
288
|
+
return join10(configBase(), DIR_NAME);
|
|
288
289
|
}
|
|
289
290
|
function globalCredentialsPath() {
|
|
290
291
|
return join10(globalConfigDir(), "credentials.json");
|
|
291
292
|
}
|
|
293
|
+
function legacyGlobalCredentialsPath() {
|
|
294
|
+
if (process.env.PINSAY_CONFIG_DIR)
|
|
295
|
+
return void 0;
|
|
296
|
+
return join10(configBase(), LEGACY_DIR_NAME, "credentials.json");
|
|
297
|
+
}
|
|
292
298
|
function globalCacheDir() {
|
|
293
299
|
if (process.env.PINSAY_CONFIG_DIR)
|
|
294
300
|
return join10(process.env.PINSAY_CONFIG_DIR, "cache");
|
|
295
301
|
if (process.platform === "win32") {
|
|
296
|
-
return join10(process.env.LOCALAPPDATA || join10(homedir(), "AppData", "Local"),
|
|
302
|
+
return join10(process.env.LOCALAPPDATA || join10(homedir(), "AppData", "Local"), DIR_NAME, "cache");
|
|
297
303
|
}
|
|
298
|
-
return join10(process.env.XDG_CACHE_HOME || join10(homedir(), ".cache"),
|
|
304
|
+
return join10(process.env.XDG_CACHE_HOME || join10(homedir(), ".cache"), DIR_NAME);
|
|
299
305
|
}
|
|
300
306
|
function tokenCacheFile(server, apiKey) {
|
|
301
307
|
const hash = createHash("sha256").update(`${normalizeServerOrigin(server)}:${apiKey}`).digest("hex").slice(0, 32);
|
|
302
308
|
return join10(globalCacheDir(), `${hash}.json`);
|
|
303
309
|
}
|
|
310
|
+
function isCredentialStore(value) {
|
|
311
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
312
|
+
return false;
|
|
313
|
+
return Object.values(value).every(
|
|
314
|
+
(entry) => !!entry && typeof entry === "object" && typeof entry.apiKey === "string"
|
|
315
|
+
);
|
|
316
|
+
}
|
|
304
317
|
async function readGlobalStore() {
|
|
318
|
+
let raw;
|
|
319
|
+
try {
|
|
320
|
+
raw = await fs10.readFile(globalCredentialsPath(), "utf8");
|
|
321
|
+
} catch (err) {
|
|
322
|
+
if (err?.code === "ENOENT")
|
|
323
|
+
return migrateLegacyStore();
|
|
324
|
+
return {};
|
|
325
|
+
}
|
|
305
326
|
try {
|
|
306
|
-
const raw = await fs10.readFile(globalCredentialsPath(), "utf8");
|
|
307
327
|
const parsed = JSON.parse(raw);
|
|
308
328
|
return parsed && typeof parsed === "object" ? parsed : {};
|
|
309
329
|
} catch {
|
|
310
330
|
return {};
|
|
311
331
|
}
|
|
312
332
|
}
|
|
333
|
+
async function migrateLegacyStore() {
|
|
334
|
+
const legacyFile = legacyGlobalCredentialsPath();
|
|
335
|
+
if (!legacyFile)
|
|
336
|
+
return {};
|
|
337
|
+
let legacy;
|
|
338
|
+
try {
|
|
339
|
+
legacy = JSON.parse(await fs10.readFile(legacyFile, "utf8"));
|
|
340
|
+
} catch {
|
|
341
|
+
return {};
|
|
342
|
+
}
|
|
343
|
+
if (!isCredentialStore(legacy))
|
|
344
|
+
return {};
|
|
345
|
+
try {
|
|
346
|
+
await writeGlobalStore(legacy);
|
|
347
|
+
} catch {
|
|
348
|
+
return legacy;
|
|
349
|
+
}
|
|
350
|
+
await fs10.rm(legacyFile, { force: true }).catch(() => {
|
|
351
|
+
});
|
|
352
|
+
await fs10.rmdir(dirname4(legacyFile)).catch(() => {
|
|
353
|
+
});
|
|
354
|
+
return legacy;
|
|
355
|
+
}
|
|
313
356
|
async function writeGlobalStore(store) {
|
|
314
357
|
const file = globalCredentialsPath();
|
|
315
358
|
await fs10.mkdir(dirname4(file), { recursive: true });
|
|
@@ -332,9 +375,12 @@ async function saveGlobalCredential(server, entry) {
|
|
|
332
375
|
async function removeGlobalCredential(server) {
|
|
333
376
|
const store = await readGlobalStore();
|
|
334
377
|
const origin = normalizeServerOrigin(server);
|
|
335
|
-
|
|
378
|
+
const origins = origin === "https://app.pinsay.dev" ? [origin, "https://api.pinsay.dev"] : [origin];
|
|
379
|
+
const present = origins.filter((o) => o in store);
|
|
380
|
+
if (present.length === 0)
|
|
336
381
|
return false;
|
|
337
|
-
|
|
382
|
+
for (const o of present)
|
|
383
|
+
delete store[o];
|
|
338
384
|
await writeGlobalStore(store);
|
|
339
385
|
return true;
|
|
340
386
|
}
|
|
@@ -364,9 +410,39 @@ async function removeStaleRepoTokenCache(root) {
|
|
|
364
410
|
await fs10.rm(join10(root, ".pinsay", ".token_cache"), { force: true }).catch(() => {
|
|
365
411
|
});
|
|
366
412
|
}
|
|
413
|
+
var DIR_NAME, LEGACY_DIR_NAME;
|
|
367
414
|
var init_credentials = __esm({
|
|
368
415
|
"src/credentials.ts"() {
|
|
369
416
|
"use strict";
|
|
417
|
+
DIR_NAME = "pinsay";
|
|
418
|
+
LEGACY_DIR_NAME = "pointer";
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
// src/server.ts
|
|
423
|
+
function resolveServer() {
|
|
424
|
+
const override = process.env.PINSAY_SERVER?.trim();
|
|
425
|
+
return canonicalServer(override || BUILD_DEFAULT_SERVER).replace(/\/+$/, "");
|
|
426
|
+
}
|
|
427
|
+
async function serverSettingError(cwd2, parsed) {
|
|
428
|
+
const server = resolveServer();
|
|
429
|
+
if (parsed["server"] !== void 0) {
|
|
430
|
+
return `--server was removed: this CLI talks only to ${server}.`;
|
|
431
|
+
}
|
|
432
|
+
const root = await findRepoRoot(cwd2);
|
|
433
|
+
const config = await readConfig(root).catch(() => ({}));
|
|
434
|
+
if (config.server && normalizeServerOrigin(config.server) !== normalizeServerOrigin(server)) {
|
|
435
|
+
return `.pinsay/config.json sets "server": "${config.server}", but this CLI talks only to ${server}.
|
|
436
|
+
Remove the "server" line from .pinsay/config.json, then run the command again.`;
|
|
437
|
+
}
|
|
438
|
+
return null;
|
|
439
|
+
}
|
|
440
|
+
var init_server = __esm({
|
|
441
|
+
"src/server.ts"() {
|
|
442
|
+
"use strict";
|
|
443
|
+
init_build_constants();
|
|
444
|
+
init_config();
|
|
445
|
+
init_credentials();
|
|
370
446
|
}
|
|
371
447
|
});
|
|
372
448
|
|
|
@@ -931,11 +1007,7 @@ function mapEnvironmentToString(env) {
|
|
|
931
1007
|
async function getClient(cwd2, parsed, opts = {}) {
|
|
932
1008
|
const root = await findRepoRoot(cwd2);
|
|
933
1009
|
const config = await readConfig(root);
|
|
934
|
-
const server = (
|
|
935
|
-
if (!server) {
|
|
936
|
-
console.error("No server configured.");
|
|
937
|
-
process.exit(2);
|
|
938
|
-
}
|
|
1010
|
+
const server = resolveServer();
|
|
939
1011
|
const explicitKey = typeof parsed["key"] === "string" ? parsed["key"] : void 0;
|
|
940
1012
|
const token = await resolveToken(server, root, explicitKey);
|
|
941
1013
|
const apiKey = explicitKey || await readApiKey(root, server);
|
|
@@ -1145,8 +1217,8 @@ var init_comments = __esm({
|
|
|
1145
1217
|
init_resolve();
|
|
1146
1218
|
init_api();
|
|
1147
1219
|
init_auth();
|
|
1148
|
-
init_build_constants();
|
|
1149
1220
|
init_projection();
|
|
1221
|
+
init_server();
|
|
1150
1222
|
}
|
|
1151
1223
|
});
|
|
1152
1224
|
|
|
@@ -1261,7 +1333,7 @@ function assertInteractive() {
|
|
|
1261
1333
|
if (process.stdin.isTTY)
|
|
1262
1334
|
return;
|
|
1263
1335
|
console.error(
|
|
1264
|
-
"\x1B[31mThis command is interactive, but stdin is not a terminal.\x1B[0m\nPiped input, CI, and some editor-embedded shells have no TTY, so there is no way to ask you anything.\n\nEither run it in a real terminal, or pass every answer as a flag:\n npx -y pinsay-cli init --
|
|
1336
|
+
"\x1B[31mThis command is interactive, but stdin is not a terminal.\x1B[0m\nPiped input, CI, and some editor-embedded shells have no TTY, so there is no way to ask you anything.\n\nEither run it in a real terminal, or pass every answer as a flag:\n npx -y pinsay-cli init --key ptr_... --project <key> --yes\n\nRun 'npx -y pinsay-cli init --help' for the full list of flags."
|
|
1265
1337
|
);
|
|
1266
1338
|
process.exit(2);
|
|
1267
1339
|
}
|
|
@@ -2271,6 +2343,7 @@ async function writeStackFile(cwd2, stack, projectKey) {
|
|
|
2271
2343
|
|
|
2272
2344
|
// src/checks.ts
|
|
2273
2345
|
init_credentials();
|
|
2346
|
+
init_server();
|
|
2274
2347
|
var execFileAsync = promisify(execFile);
|
|
2275
2348
|
function tooOldMessage(cliVersion, min) {
|
|
2276
2349
|
return `CLI ${cliVersion} is older than the server requires (${min}). Upgrade with: npx -y pinsay@latest`;
|
|
@@ -2308,7 +2381,7 @@ async function fetchWithTimeout(url, ms, init) {
|
|
|
2308
2381
|
async function runInitChecks(cwd2, overrides = {}, cliVersion = "0.0.0") {
|
|
2309
2382
|
const checks = [];
|
|
2310
2383
|
const config = await readConfig(cwd2);
|
|
2311
|
-
const server = (overrides.server ||
|
|
2384
|
+
const server = (overrides.server || resolveServer()).replace(/\/$/, "");
|
|
2312
2385
|
const multiProject = isMultiProject(config);
|
|
2313
2386
|
const allProjects = listProjects(config);
|
|
2314
2387
|
const projectTargets = multiProject && overrides.project ? allProjects.filter((p) => p.key === overrides.project) : allProjects;
|
|
@@ -3355,7 +3428,35 @@ async function runDeviceLogin(server, options = {}) {
|
|
|
3355
3428
|
return { ok: false, reason: "expired" };
|
|
3356
3429
|
}
|
|
3357
3430
|
|
|
3431
|
+
// src/key-scope.ts
|
|
3432
|
+
var KEY_SCOPE_QUESTION = "Where should this API key be stored?";
|
|
3433
|
+
var KEY_SCOPE_GLOBAL = "Global \u2014 this machine, every repo (~/.config/pinsay/credentials.json)";
|
|
3434
|
+
var KEY_SCOPE_REPO = "Repo \u2014 .pinsay/credentials.env in this repo only (gitignored)";
|
|
3435
|
+
function scopeFromFlags(options) {
|
|
3436
|
+
const raw = options["scope"];
|
|
3437
|
+
if (typeof raw === "string") {
|
|
3438
|
+
const value = raw.toLowerCase();
|
|
3439
|
+
if (value !== "global" && value !== "repo") {
|
|
3440
|
+
return { error: `Invalid --scope "${raw}". Valid values: global, repo.` };
|
|
3441
|
+
}
|
|
3442
|
+
return { scope: value };
|
|
3443
|
+
}
|
|
3444
|
+
if (options["local-credentials"] === true)
|
|
3445
|
+
return { scope: "repo" };
|
|
3446
|
+
return {};
|
|
3447
|
+
}
|
|
3448
|
+
function decideKeyScope(flagScope, interactive) {
|
|
3449
|
+
if (flagScope)
|
|
3450
|
+
return flagScope;
|
|
3451
|
+
return interactive ? "ask" : "global";
|
|
3452
|
+
}
|
|
3453
|
+
async function askKeyScope() {
|
|
3454
|
+
const choice = await select(KEY_SCOPE_QUESTION, [KEY_SCOPE_GLOBAL, KEY_SCOPE_REPO]);
|
|
3455
|
+
return choice === KEY_SCOPE_REPO ? "repo" : "global";
|
|
3456
|
+
}
|
|
3457
|
+
|
|
3358
3458
|
// src/commands/init.ts
|
|
3459
|
+
init_server();
|
|
3359
3460
|
async function initCommand(cwd2, options = {}) {
|
|
3360
3461
|
const isYes = options["yes"] || options["json"];
|
|
3361
3462
|
const isJson = options["json"];
|
|
@@ -3373,12 +3474,9 @@ async function initCommand(cwd2, options = {}) {
|
|
|
3373
3474
|
const pathFlag = typeof options["path"] === "string" ? String(options["path"]).replace(/^\.\/+/, "").replace(/\/+$/, "") : void 0;
|
|
3374
3475
|
const isAddProject = Boolean(pathFlag);
|
|
3375
3476
|
const configIsMulti = isMultiProject(config);
|
|
3376
|
-
const isJoin = !isAddProject &&
|
|
3477
|
+
const isJoin = !isAddProject && (Boolean(config.project) || configIsMulti);
|
|
3377
3478
|
const mode = isJoin ? "join" : "install";
|
|
3378
|
-
|
|
3379
|
-
if (!isYes && !options["server"] && !config.server) {
|
|
3380
|
-
server = await ask("Server URL", { default: server });
|
|
3381
|
-
}
|
|
3479
|
+
const server = resolveServer();
|
|
3382
3480
|
const scopeFlag = typeof options["scope"] === "string" ? String(options["scope"]).toLowerCase() : void 0;
|
|
3383
3481
|
if (scopeFlag !== void 0 && scopeFlag !== "global" && scopeFlag !== "repo") {
|
|
3384
3482
|
console.error(`Invalid --scope "${options["scope"]}". Valid values: global, repo.`);
|
|
@@ -3521,11 +3619,7 @@ async function initCommand(cwd2, options = {}) {
|
|
|
3521
3619
|
if (freshlyAuthenticated) {
|
|
3522
3620
|
let saveGlobally = !localCredentialsFlag;
|
|
3523
3621
|
if (saveGlobally && !isYes && scopeFlag === void 0) {
|
|
3524
|
-
|
|
3525
|
-
"Global \u2014 this machine, every repo (~/.config/pointer/credentials.json)",
|
|
3526
|
-
"Repo \u2014 .pinsay/credentials.env in this repo only (gitignored)"
|
|
3527
|
-
]);
|
|
3528
|
-
saveGlobally = choice.startsWith("Global");
|
|
3622
|
+
saveGlobally = await askKeyScope() === "global";
|
|
3529
3623
|
}
|
|
3530
3624
|
if (saveGlobally) {
|
|
3531
3625
|
await saveGlobalCredential(server, { apiKey: key, email: me?.email, displayName: me?.displayName });
|
|
@@ -3534,7 +3628,7 @@ async function initCommand(cwd2, options = {}) {
|
|
|
3534
3628
|
}
|
|
3535
3629
|
}
|
|
3536
3630
|
if (writeLocalCreds) {
|
|
3537
|
-
await writeCredentials(cwd2, key
|
|
3631
|
+
await writeCredentials(cwd2, key);
|
|
3538
3632
|
}
|
|
3539
3633
|
await upsertGitignore(cwd2, product, options["skills-dir"] || config.skillsDir);
|
|
3540
3634
|
if (isJoin && configIsMulti) {
|
|
@@ -3856,7 +3950,6 @@ and the pinsay-init skill uses them to mount the widget for you afterwards.
|
|
|
3856
3950
|
}
|
|
3857
3951
|
const injectedHtml = injected ? filesMod.find((f) => f.toLowerCase().endsWith(".html"))?.replace(`${cwd2}/`, "") : void 0;
|
|
3858
3952
|
const configPatch = {
|
|
3859
|
-
server,
|
|
3860
3953
|
project: finalProjectKey,
|
|
3861
3954
|
aiTool: tool,
|
|
3862
3955
|
skillsDir: options["skills-dir"],
|
|
@@ -3868,7 +3961,7 @@ and the pinsay-init skill uses them to mount the widget for you afterwards.
|
|
|
3868
3961
|
await writeConfig(cwd2, configPatch);
|
|
3869
3962
|
filesMod.push(".pinsay/config.json");
|
|
3870
3963
|
if (writeLocalCreds) {
|
|
3871
|
-
await writeCredentials(cwd2, key, {
|
|
3964
|
+
await writeCredentials(cwd2, key, { project: finalProjectKey });
|
|
3872
3965
|
filesMod.push(".pinsay/credentials.env");
|
|
3873
3966
|
}
|
|
3874
3967
|
filesMod.push(".gitignore");
|
|
@@ -3912,7 +4005,7 @@ and the pinsay-init skill uses them to mount the widget for you afterwards.
|
|
|
3912
4005
|
const cyan = (s) => `\x1B[36m${s}\x1B[0m`;
|
|
3913
4006
|
const rule = dim("\u2500".repeat(60));
|
|
3914
4007
|
const envLabel = environmentPinned ? envs.length > 1 ? envs.join(", ") : env : null;
|
|
3915
|
-
const keyLine = keyLivesGlobally ? `this machine's global store ${dim("(~/.config/
|
|
4008
|
+
const keyLine = keyLivesGlobally ? `this machine's global store ${dim("(~/.config/pinsay/credentials.json)")}` : `.pinsay/credentials.env ${dim("(gitignored)")}`;
|
|
3916
4009
|
if (isJoin) {
|
|
3917
4010
|
console.log(`
|
|
3918
4011
|
${rule}
|
|
@@ -3944,7 +4037,7 @@ ${rule}`);
|
|
|
3944
4037
|
${bold("Next")} Reviewers open the widget through the ${product} Chrome extension \u2014 nothing was injected.
|
|
3945
4038
|
|
|
3946
4039
|
${installLine}
|
|
3947
|
-
2.
|
|
4040
|
+
2. Open the extension and sign in with your ${product} account.
|
|
3948
4041
|
3. Open your app, click the extension icon, choose project ${dim(finalProjectKey)}, Activate.
|
|
3949
4042
|
4. Then ${bold("npx pinsay-cli list")} / ${bold("apply")} as usual.
|
|
3950
4043
|
${dim(`Dashboard: ${branding.urls?.app || server}`)}`);
|
|
@@ -4426,7 +4519,6 @@ async function handleMultiProjectSetup(args) {
|
|
|
4426
4519
|
for (const r of results)
|
|
4427
4520
|
projectsMap[r.key] = r.entry;
|
|
4428
4521
|
await writeConfigFull(cwd2, {
|
|
4429
|
-
server,
|
|
4430
4522
|
aiTool: tool,
|
|
4431
4523
|
skillsDir: (options["skills-dir"] || config.skillsDir) ?? void 0,
|
|
4432
4524
|
cliVersion: BUILD_CLI_VERSION,
|
|
@@ -4434,7 +4526,7 @@ async function handleMultiProjectSetup(args) {
|
|
|
4434
4526
|
projects: projectsMap
|
|
4435
4527
|
});
|
|
4436
4528
|
if (writeLocalCreds) {
|
|
4437
|
-
await writeCredentials(cwd2, key
|
|
4529
|
+
await writeCredentials(cwd2, key);
|
|
4438
4530
|
}
|
|
4439
4531
|
await postEvent(server, token, {
|
|
4440
4532
|
type: "installed",
|
|
@@ -4471,6 +4563,7 @@ import { promises as fs15 } from "node:fs";
|
|
|
4471
4563
|
import { join as join15 } from "node:path";
|
|
4472
4564
|
init_api();
|
|
4473
4565
|
init_credentials();
|
|
4566
|
+
init_server();
|
|
4474
4567
|
var ICON = { ok: "\u2714", warn: "\u26A0", error: "\u2718" };
|
|
4475
4568
|
function exitCodeFor(checks) {
|
|
4476
4569
|
const failed = checks.filter((c) => c.status === "error");
|
|
@@ -4500,11 +4593,12 @@ async function doctorCommand(cwd2, options, cliVersion) {
|
|
|
4500
4593
|
}
|
|
4501
4594
|
return 0;
|
|
4502
4595
|
}
|
|
4503
|
-
|
|
4596
|
+
const server = (options.server || resolveServer()).replace(/\/$/, "");
|
|
4597
|
+
let checks = await runInitChecks(cwd2, { server, project: options.project }, cliVersion);
|
|
4504
4598
|
if (options.fix) {
|
|
4505
|
-
const repaired = await applyFixes(cwd2, checks);
|
|
4599
|
+
const repaired = await applyFixes(cwd2, checks, server);
|
|
4506
4600
|
if (repaired.length > 0) {
|
|
4507
|
-
checks = await runInitChecks(cwd2, { server
|
|
4601
|
+
checks = await runInitChecks(cwd2, { server, project: options.project }, cliVersion);
|
|
4508
4602
|
}
|
|
4509
4603
|
}
|
|
4510
4604
|
const code = exitCodeFor(checks);
|
|
@@ -4534,7 +4628,7 @@ async function reportRun(cwd2, options, checks, ok) {
|
|
|
4534
4628
|
return;
|
|
4535
4629
|
try {
|
|
4536
4630
|
const config = await readConfig(cwd2);
|
|
4537
|
-
const server = (options.server ||
|
|
4631
|
+
const server = (options.server || resolveServer()).replace(/\/$/, "");
|
|
4538
4632
|
if (!server)
|
|
4539
4633
|
return;
|
|
4540
4634
|
const { key: apiKey } = await resolveApiKey(cwd2, server);
|
|
@@ -4551,10 +4645,9 @@ async function reportRun(cwd2, options, checks, ok) {
|
|
|
4551
4645
|
} catch {
|
|
4552
4646
|
}
|
|
4553
4647
|
}
|
|
4554
|
-
async function applyFixes(cwd2, checks) {
|
|
4648
|
+
async function applyFixes(cwd2, checks, server) {
|
|
4555
4649
|
const repaired = [];
|
|
4556
4650
|
const config = await readConfig(cwd2);
|
|
4557
|
-
const server = (config.server || "").replace(/\/$/, "");
|
|
4558
4651
|
let token;
|
|
4559
4652
|
const tokenFor = async () => {
|
|
4560
4653
|
if (token)
|
|
@@ -4626,6 +4719,7 @@ init_config();
|
|
|
4626
4719
|
init_api();
|
|
4627
4720
|
import { promises as fs16 } from "node:fs";
|
|
4628
4721
|
import { dirname as dirname7, join as join16 } from "node:path";
|
|
4722
|
+
init_server();
|
|
4629
4723
|
function sourceFor(path) {
|
|
4630
4724
|
if (path.endsWith("pinsay.sh"))
|
|
4631
4725
|
return "/pinsay.sh";
|
|
@@ -4649,9 +4743,9 @@ async function updateCommand(cwd2, options) {
|
|
|
4649
4743
|
for (const f of removedLegacyFiles)
|
|
4650
4744
|
console.log(`\x1B[2mremoved legacy ${f}\x1B[0m`);
|
|
4651
4745
|
const config = await readConfig(cwd2);
|
|
4652
|
-
const server = (options.server ||
|
|
4653
|
-
if (!
|
|
4654
|
-
console.error("No
|
|
4746
|
+
const server = (options.server || resolveServer()).replace(/\/$/, "");
|
|
4747
|
+
if (!config.project && !isMultiProject(config)) {
|
|
4748
|
+
console.error("No .pinsay/config.json here \u2014 run `npx -y pinsay-cli init` first.");
|
|
4655
4749
|
return 1;
|
|
4656
4750
|
}
|
|
4657
4751
|
let served = null;
|
|
@@ -4659,7 +4753,7 @@ async function updateCommand(cwd2, options) {
|
|
|
4659
4753
|
const meta = await api(server, "/api/meta");
|
|
4660
4754
|
served = meta?.skillVersion ?? null;
|
|
4661
4755
|
} catch {
|
|
4662
|
-
console.error(`Could not reach ${server}
|
|
4756
|
+
console.error(`Could not reach ${server}.`);
|
|
4663
4757
|
return 1;
|
|
4664
4758
|
}
|
|
4665
4759
|
const files = skillFilesFor(config);
|
|
@@ -5823,6 +5917,7 @@ function describeFilter(filter) {
|
|
|
5823
5917
|
|
|
5824
5918
|
// src/commands/apply.ts
|
|
5825
5919
|
init_projection();
|
|
5920
|
+
init_server();
|
|
5826
5921
|
async function exitIfWorkspaceFrozen(server, token) {
|
|
5827
5922
|
if (!token)
|
|
5828
5923
|
return;
|
|
@@ -5871,11 +5966,7 @@ function parseModelFlags(parsed) {
|
|
|
5871
5966
|
async function applyCommand(cwd2, parsed, positionals = []) {
|
|
5872
5967
|
const root = await findRepoRoot(cwd2);
|
|
5873
5968
|
const config = await readConfig(root);
|
|
5874
|
-
const server = (
|
|
5875
|
-
if (!server) {
|
|
5876
|
-
console.error("No server configured. Run `npx pinsay-cli init` or pass --server.");
|
|
5877
|
-
process.exit(2);
|
|
5878
|
-
}
|
|
5969
|
+
const server = resolveServer();
|
|
5879
5970
|
const projectFlag = typeof parsed["project"] === "string" ? parsed["project"] : void 0;
|
|
5880
5971
|
const resolved = resolveProject(config, cwd2, root, projectFlag);
|
|
5881
5972
|
const markVal = parsed["mark"];
|
|
@@ -12468,10 +12559,11 @@ async function runMcpServer(ctx, logFile) {
|
|
|
12468
12559
|
}
|
|
12469
12560
|
|
|
12470
12561
|
// src/commands/mcp.ts
|
|
12562
|
+
init_server();
|
|
12471
12563
|
async function mcpCommand(cwd2, parsed = {}) {
|
|
12472
12564
|
const root = await findRepoRoot(cwd2);
|
|
12473
12565
|
const config = await readConfig(root).catch(() => ({}));
|
|
12474
|
-
const server = (
|
|
12566
|
+
const server = resolveServer();
|
|
12475
12567
|
const projectFlag = typeof parsed["project"] === "string" ? parsed["project"] : void 0;
|
|
12476
12568
|
const project = projectFlag || config.project || process.env.PINSAY_PROJECT || "";
|
|
12477
12569
|
const explicitKey = typeof parsed["key"] === "string" ? parsed["key"] : void 0;
|
|
@@ -12508,11 +12600,15 @@ async function mcpCommand(cwd2, parsed = {}) {
|
|
|
12508
12600
|
init_config();
|
|
12509
12601
|
init_api();
|
|
12510
12602
|
init_credentials();
|
|
12511
|
-
|
|
12603
|
+
init_server();
|
|
12512
12604
|
async function loginCommand(cwd2, options = {}) {
|
|
12605
|
+
const flags = scopeFromFlags(options);
|
|
12606
|
+
if (flags.error) {
|
|
12607
|
+
console.error(flags.error);
|
|
12608
|
+
process.exit(2);
|
|
12609
|
+
}
|
|
12513
12610
|
const root = await findRepoRoot(cwd2);
|
|
12514
|
-
const
|
|
12515
|
-
const server = ((typeof options["server"] === "string" ? options["server"] : void 0) || config.server || canonicalServer(process.env.PINSAY_SERVER) || BUILD_DEFAULT_SERVER).replace(/\/$/, "");
|
|
12611
|
+
const server = resolveServer();
|
|
12516
12612
|
const branding = await getBranding(server);
|
|
12517
12613
|
const product = branding.productName;
|
|
12518
12614
|
const flagKey = typeof options["key"] === "string" ? options["key"] : void 0;
|
|
@@ -12547,14 +12643,14 @@ async function loginCommand(cwd2, options = {}) {
|
|
|
12547
12643
|
console.error("No key provided and no terminal to sign in from \u2014 run `npx pinsay-cli login --key <key>` or set PINSAY_API_KEY.");
|
|
12548
12644
|
process.exit(2);
|
|
12549
12645
|
}
|
|
12550
|
-
const
|
|
12551
|
-
|
|
12552
|
-
|
|
12553
|
-
|
|
12554
|
-
}
|
|
12646
|
+
const interactive = Boolean(process.stdin.isTTY) && options["yes"] !== true;
|
|
12647
|
+
const decided = decideKeyScope(flags.scope, interactive);
|
|
12648
|
+
const scope = decided === "ask" ? await askKeyScope() : decided;
|
|
12649
|
+
closePrompts();
|
|
12555
12650
|
const who = me?.displayName ? `${me.displayName}${me?.email ? ` (${me.email})` : ""}` : me?.email ?? "you";
|
|
12556
12651
|
if (scope === "repo") {
|
|
12557
|
-
await writeCredentials(root, key
|
|
12652
|
+
await writeCredentials(root, key);
|
|
12653
|
+
await upsertGitignore(root, product);
|
|
12558
12654
|
console.log(`\u2714 Signed in to ${server} as ${who} \u2014 saved to .pinsay/credentials.env (this repo only; overrides the global store here)`);
|
|
12559
12655
|
process.exit(0);
|
|
12560
12656
|
}
|
|
@@ -12564,13 +12660,10 @@ async function loginCommand(cwd2, options = {}) {
|
|
|
12564
12660
|
}
|
|
12565
12661
|
|
|
12566
12662
|
// src/commands/logout.ts
|
|
12567
|
-
init_config();
|
|
12568
12663
|
init_credentials();
|
|
12569
|
-
|
|
12570
|
-
async function logoutCommand(
|
|
12571
|
-
const
|
|
12572
|
-
const config = await readConfig(root).catch(() => ({}));
|
|
12573
|
-
const server = ((typeof options["server"] === "string" ? options["server"] : void 0) || config.server || canonicalServer(process.env.PINSAY_SERVER) || BUILD_DEFAULT_SERVER).replace(/\/$/, "");
|
|
12664
|
+
init_server();
|
|
12665
|
+
async function logoutCommand(_cwd, options = {}) {
|
|
12666
|
+
const server = resolveServer();
|
|
12574
12667
|
const origin = normalizeServerOrigin(server);
|
|
12575
12668
|
const removed = await removeGlobalCredential(server);
|
|
12576
12669
|
if (options["json"] === true) {
|
|
@@ -12587,11 +12680,10 @@ async function logoutCommand(cwd2, options = {}) {
|
|
|
12587
12680
|
init_config();
|
|
12588
12681
|
init_api();
|
|
12589
12682
|
init_credentials();
|
|
12590
|
-
|
|
12683
|
+
init_server();
|
|
12591
12684
|
async function whoamiCommand(cwd2, options = {}) {
|
|
12592
12685
|
const root = await findRepoRoot(cwd2);
|
|
12593
|
-
const
|
|
12594
|
-
const server = ((typeof options["server"] === "string" ? options["server"] : void 0) || config.server || canonicalServer(process.env.PINSAY_SERVER) || BUILD_DEFAULT_SERVER).replace(/\/$/, "");
|
|
12686
|
+
const server = resolveServer();
|
|
12595
12687
|
const isJson = options["json"] === true;
|
|
12596
12688
|
const { key, source } = await resolveApiKey(root, server);
|
|
12597
12689
|
if (!key) {
|
|
@@ -12631,6 +12723,7 @@ async function whoamiCommand(cwd2, options = {}) {
|
|
|
12631
12723
|
// src/cli.ts
|
|
12632
12724
|
init_build_constants();
|
|
12633
12725
|
init_api();
|
|
12726
|
+
init_server();
|
|
12634
12727
|
import { argv, cwd } from "node:process";
|
|
12635
12728
|
function parseArgs(args) {
|
|
12636
12729
|
const parsed = {};
|
|
@@ -12687,8 +12780,8 @@ Usage: npx pinsay-cli <command> [options]
|
|
|
12687
12780
|
|
|
12688
12781
|
Commands:
|
|
12689
12782
|
init Set up the feedback widget in your project
|
|
12690
|
-
login
|
|
12691
|
-
logout Remove this machine's saved key
|
|
12783
|
+
login Sign in and save your API key (this machine, or this repo only)
|
|
12784
|
+
logout Remove this machine's saved key
|
|
12692
12785
|
whoami Show the signed-in account and where the API key came from
|
|
12693
12786
|
doctor Diagnose an install and report what is wrong
|
|
12694
12787
|
update Refresh the served skills to the server's current version
|
|
@@ -12719,13 +12812,19 @@ async function main() {
|
|
|
12719
12812
|
process.exit(0);
|
|
12720
12813
|
}
|
|
12721
12814
|
}
|
|
12815
|
+
if (command !== "map" && !parsed["help"]) {
|
|
12816
|
+
const serverError = await serverSettingError(cwd(), parsed);
|
|
12817
|
+
if (serverError) {
|
|
12818
|
+
console.error(serverError);
|
|
12819
|
+
process.exit(2);
|
|
12820
|
+
}
|
|
12821
|
+
}
|
|
12722
12822
|
if (command === "init") {
|
|
12723
12823
|
if (parsed["help"]) {
|
|
12724
12824
|
console.log(`
|
|
12725
12825
|
Usage: npx pinsay-cli init [options]
|
|
12726
12826
|
|
|
12727
12827
|
Options:
|
|
12728
|
-
--server <url> Feedback server URL
|
|
12729
12828
|
--key <key> API key
|
|
12730
12829
|
--project <key> Project key
|
|
12731
12830
|
--create <name> Create project with name
|
|
@@ -12744,9 +12843,10 @@ Options:
|
|
|
12744
12843
|
--no-skills Skip skills installation
|
|
12745
12844
|
--no-design Skip design token detection
|
|
12746
12845
|
--source-map Wire in the Vite plugin that stamps component source hashes
|
|
12747
|
-
--scope <global|repo> Where the API key is stored: global (
|
|
12748
|
-
~/.config/
|
|
12749
|
-
gitignored, this repo only).
|
|
12846
|
+
--scope <global|repo> Where the API key is stored, without asking: global (this machine, all
|
|
12847
|
+
repos, ~/.config/pinsay/credentials.json; the default with --yes) or
|
|
12848
|
+
repo (.pinsay/credentials.env, gitignored, this repo only).
|
|
12849
|
+
--local-credentials is an alias for --scope repo
|
|
12750
12850
|
--no-browser When signing in in the browser (first run, no key resolved yet), print
|
|
12751
12851
|
the link/code but don't try to open a browser
|
|
12752
12852
|
-y, --yes Non-interactive
|
|
@@ -12761,21 +12861,21 @@ Options:
|
|
|
12761
12861
|
console.log(`
|
|
12762
12862
|
Usage: npx pinsay-cli login [options]
|
|
12763
12863
|
|
|
12764
|
-
|
|
12765
|
-
~/.config/
|
|
12766
|
-
|
|
12864
|
+
Sign in and save your API key. On a terminal it asks where to save it:
|
|
12865
|
+
Global this machine, every repo (~/.config/pinsay/credentials.json, honours $XDG_CONFIG_HOME)
|
|
12866
|
+
Repo .pinsay/credentials.env in this repo only (gitignored)
|
|
12867
|
+
With no terminal (CI, a pipe) or with --yes it saves globally, unless --scope repo is given.
|
|
12767
12868
|
|
|
12768
12869
|
With no --key on a real terminal, opens your browser to sign in (mirrors \`gh auth login\`): prints
|
|
12769
12870
|
a link and a short code, waits for you to approve it in the dashboard, then saves the personal API
|
|
12770
|
-
key it hands back. Pass --key to skip the browser and validate a pasted key instead
|
|
12771
|
-
before.
|
|
12871
|
+
key it hands back. Pass --key to skip the browser and validate a pasted key instead.
|
|
12772
12872
|
|
|
12773
12873
|
Options:
|
|
12774
|
-
--server <url> Server URL (default: this repo's .pinsay/config.json, then $PINSAY_SERVER)
|
|
12775
12874
|
--key <key> API key \u2014 skips the browser flow entirely
|
|
12776
12875
|
--no-browser Print the sign-in link/code but don't try to open a browser
|
|
12777
|
-
--scope <global|repo>
|
|
12778
|
-
|
|
12876
|
+
--scope <global|repo> Where to save the key, without asking
|
|
12877
|
+
--local-credentials Alias for --scope repo
|
|
12878
|
+
-y, --yes Don't ask; save globally unless --scope repo is given
|
|
12779
12879
|
-h, --help Show this help
|
|
12780
12880
|
`);
|
|
12781
12881
|
process.exit(0);
|
|
@@ -12786,10 +12886,9 @@ Options:
|
|
|
12786
12886
|
console.log(`
|
|
12787
12887
|
Usage: npx pinsay-cli logout [options]
|
|
12788
12888
|
|
|
12789
|
-
Removes this machine's saved global key
|
|
12889
|
+
Removes this machine's saved global key.
|
|
12790
12890
|
|
|
12791
12891
|
Options:
|
|
12792
|
-
--server <url> Server URL (default: this repo's .pinsay/config.json, then $PINSAY_SERVER)
|
|
12793
12892
|
--json Emit { ok, server, removed } as JSON
|
|
12794
12893
|
-h, --help Show this help
|
|
12795
12894
|
`);
|
|
@@ -12805,7 +12904,6 @@ Prints the server, the signed-in account, and which of env/repo/global answered
|
|
|
12805
12904
|
never the key itself.
|
|
12806
12905
|
|
|
12807
12906
|
Options:
|
|
12808
|
-
--server <url> Server URL (default: this repo's .pinsay/config.json, then $PINSAY_SERVER)
|
|
12809
12907
|
--json Emit { ok, server, displayName, email, source } as JSON
|
|
12810
12908
|
-h, --help Show this help
|
|
12811
12909
|
`);
|
|
@@ -12820,7 +12918,6 @@ Usage: npx pinsay-cli doctor [options]
|
|
|
12820
12918
|
Checks an existing install and prints one line per check.
|
|
12821
12919
|
|
|
12822
12920
|
Options:
|
|
12823
|
-
--server <url> Override the server from .pinsay/config.json
|
|
12824
12921
|
--project <key> Override the project key
|
|
12825
12922
|
--json Emit { ok, checks } as JSON
|
|
12826
12923
|
--fix Apply the idempotent repairs (gitignore, skills, stack)
|
|
@@ -12836,7 +12933,6 @@ Exit codes:
|
|
|
12836
12933
|
process.exit(0);
|
|
12837
12934
|
}
|
|
12838
12935
|
const code = await doctorCommand(cwd(), {
|
|
12839
|
-
server: typeof parsed["server"] === "string" ? parsed["server"] : void 0,
|
|
12840
12936
|
project: typeof parsed["project"] === "string" ? parsed["project"] : void 0,
|
|
12841
12937
|
json: parsed["json"] === true,
|
|
12842
12938
|
fix: parsed["fix"] === true,
|
|
@@ -12848,17 +12944,15 @@ Exit codes:
|
|
|
12848
12944
|
console.log(`
|
|
12849
12945
|
Usage: npx pinsay-cli update [options]
|
|
12850
12946
|
|
|
12851
|
-
Refreshes the AI skills and pinsay.sh from the
|
|
12947
|
+
Refreshes the AI skills and pinsay.sh from the server.
|
|
12852
12948
|
|
|
12853
12949
|
Options:
|
|
12854
|
-
--server <url> Override the server from .pinsay/config.json
|
|
12855
12950
|
--check Report what is out of date without writing anything
|
|
12856
12951
|
-h, --help Show this help
|
|
12857
12952
|
`);
|
|
12858
12953
|
process.exit(0);
|
|
12859
12954
|
}
|
|
12860
12955
|
const code = await updateCommand(cwd(), {
|
|
12861
|
-
server: typeof parsed["server"] === "string" ? parsed["server"] : void 0,
|
|
12862
12956
|
check: parsed["check"] === true
|
|
12863
12957
|
});
|
|
12864
12958
|
process.exit(code);
|
|
@@ -12979,7 +13073,6 @@ Start the PinSay stdio MCP server for AI tools.
|
|
|
12979
13073
|
|
|
12980
13074
|
Options:
|
|
12981
13075
|
--log <file> Log MCP server traffic to file
|
|
12982
|
-
--server <url> Feedback server URL
|
|
12983
13076
|
--project <key> Project key
|
|
12984
13077
|
--key <key> API key
|
|
12985
13078
|
-h, --help Show this help
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pinsay-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.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",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"scripts": {
|
|
39
39
|
"build": "node build.mjs",
|
|
40
40
|
"typecheck": "tsc --noEmit",
|
|
41
|
-
"test": "node --import tsx --test test/*.test.ts test/**/*.test.ts",
|
|
41
|
+
"test": "node --import tsx --import ./test/setup.ts --test test/*.test.ts test/**/*.test.ts",
|
|
42
42
|
"prepublishOnly": "npm run typecheck && npm test && npm run build"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|