pnote 0.2.1 → 0.4.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/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command8 } from "commander";
4
+ import { Command as Command10 } from "commander";
5
5
 
6
6
  // src/lib/errors.ts
7
7
  import pc from "picocolors";
@@ -311,6 +311,16 @@ async function search(params, options = {}) {
311
311
  if (params.limit) query.set("limit", String(params.limit));
312
312
  return callRestApi("GET", `/search?${query.toString()}`, void 0, options);
313
313
  }
314
+ async function checkPinStatus(options = {}) {
315
+ return callRestApi("GET", "/pin/status", void 0, options);
316
+ }
317
+ async function listProtectedNotes(params = {}, options = {}) {
318
+ const query = new URLSearchParams();
319
+ if (params.limit) query.set("limit", String(params.limit));
320
+ if (params.offset) query.set("offset", String(params.offset));
321
+ const queryString = query.toString();
322
+ return callRestApi("GET", `/pin/notes${queryString ? `?${queryString}` : ""}`, void 0, options);
323
+ }
314
324
  async function listSharedTags(options = {}) {
315
325
  return callRestApi("GET", "/share/tags", void 0, options);
316
326
  }
@@ -455,7 +465,7 @@ authCommand.command("logout").description("Remove stored credentials").action(lo
455
465
  authCommand.command("whoami").description("Show current authenticated user").action(whoamiAction);
456
466
 
457
467
  // src/commands/notes/index.ts
458
- import { Command as Command2 } from "commander";
468
+ import { Command as Command3 } from "commander";
459
469
 
460
470
  // src/lib/output.ts
461
471
  import pc6 from "picocolors";
@@ -554,9 +564,10 @@ function outputNotes(notes, ctx) {
554
564
  }
555
565
  }
556
566
  }
557
- function outputNote(note, snippet, ctx) {
567
+ function outputNote(note, snippets, ctx) {
568
+ const latestSnippet = snippets.length > 0 ? snippets[snippets.length - 1] : null;
558
569
  if (ctx.json) {
559
- outputJson({ ...note, latest_snippet: snippet });
570
+ outputJson({ ...note, snippets, latest_snippet: latestSnippet });
560
571
  return;
561
572
  }
562
573
  const c = getColors(ctx);
@@ -573,21 +584,24 @@ function outputNote(note, snippet, ctx) {
573
584
  if (status.length > 0) {
574
585
  console.log(c.dim("Status: ") + status.join(", "));
575
586
  }
576
- if (snippet) {
577
- console.log("");
578
- console.log(c.dim(`--- Snippet v${snippet.version} ---`));
579
- console.log(snippet.content);
580
- } else {
587
+ if (snippets.length === 0) {
581
588
  console.log("");
582
589
  console.log(c.dim("(no content)"));
590
+ } else {
591
+ for (const snippet of snippets) {
592
+ const header = `v${snippet.version}` + (snippet.favorite ? c.yellow(" \u2605") : "") + c.dim(` (${formatRelativeTime(snippet.created_at)})`);
593
+ console.log("");
594
+ console.log(c.dim("--- Snippet ") + c.bold(header) + c.dim(" ---"));
595
+ console.log(snippet.content);
596
+ }
583
597
  }
584
598
  } else {
585
599
  console.log(`Title: ${note.title}`);
586
600
  console.log(`ID: ${note.id}`);
587
601
  console.log(`Tags: ${note.tags.join(", ")}`);
588
- if (snippet) {
602
+ for (const snippet of snippets) {
589
603
  console.log("");
590
- console.log(snippet.content);
604
+ console.log(`v${snippet.version} ${snippet.content}`);
591
605
  }
592
606
  }
593
607
  }
@@ -828,6 +842,7 @@ async function listNotesAction(options, ctx) {
828
842
  archived: options.archived ?? false,
829
843
  pinned: options.pinned,
830
844
  deleted: options.deleted ?? false,
845
+ protected: options.protected,
831
846
  search: options.search,
832
847
  limit: options.limit ? parseInt(options.limit, 10) : 50
833
848
  },
@@ -844,13 +859,14 @@ async function listNotesAction(options, ctx) {
844
859
  }
845
860
 
846
861
  // src/commands/notes/get.ts
847
- async function getNoteAction(id, ctx) {
862
+ async function getNoteAction(id, options, ctx) {
848
863
  try {
849
864
  const result = await getNote(id, { pin: ctx.pin });
850
865
  if (!result) {
851
866
  throw new NotFoundError("Note", id);
852
867
  }
853
- outputNote(result, result.latest_snippet, ctx);
868
+ const snippets = options.latest ? result.latest_snippet ? [result.latest_snippet] : [] : result.snippets || [];
869
+ outputNote(result, snippets, ctx);
854
870
  if (result.protection_status?.error && !ctx.json) {
855
871
  console.error("");
856
872
  console.error("Warning: " + result.protection_status.error);
@@ -1001,224 +1017,8 @@ async function updateNoteAction(id, options, ctx) {
1001
1017
  }
1002
1018
  }
1003
1019
 
1004
- // src/lib/pin.ts
1005
- import { createInterface as createInterface2 } from "readline";
1006
- import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, statSync } from "fs";
1007
- import { tmpdir } from "os";
1008
- import { join as join2 } from "path";
1009
- import pc8 from "picocolors";
1010
- var PIN_CACHE_TTL_MS = 5 * 60 * 1e3;
1011
- var PIN_MAX_ATTEMPTS = 3;
1012
- function getPinCachePath() {
1013
- const uid = process.getuid?.() ?? "unknown";
1014
- return join2(tmpdir(), `pnote-pin-${uid}`);
1015
- }
1016
- function getCachedPin() {
1017
- const cachePath = getPinCachePath();
1018
- if (!existsSync2(cachePath)) {
1019
- return null;
1020
- }
1021
- try {
1022
- const stat = statSync(cachePath);
1023
- const age = Date.now() - stat.mtimeMs;
1024
- if (age > PIN_CACHE_TTL_MS) {
1025
- unlinkSync2(cachePath);
1026
- return null;
1027
- }
1028
- const content = readFileSync2(cachePath, "utf-8").trim();
1029
- return content || null;
1030
- } catch {
1031
- return null;
1032
- }
1033
- }
1034
- function setCachedPin(pin) {
1035
- const cachePath = getPinCachePath();
1036
- try {
1037
- writeFileSync2(cachePath, pin, { mode: 384 });
1038
- } catch {
1039
- }
1040
- }
1041
- async function readPinFromStdin() {
1042
- const chunks = [];
1043
- for await (const chunk of process.stdin) {
1044
- chunks.push(chunk);
1045
- const content2 = Buffer.concat(chunks).toString("utf-8");
1046
- if (content2.includes("\n")) {
1047
- break;
1048
- }
1049
- }
1050
- const content = Buffer.concat(chunks).toString("utf-8");
1051
- const firstLine = content.split("\n")[0]?.trim() || "";
1052
- if (!firstLine) {
1053
- throw new Error("No PIN provided via stdin");
1054
- }
1055
- return firstLine;
1056
- }
1057
- async function promptForPin(noteTitle, hint, maxAttempts = PIN_MAX_ATTEMPTS) {
1058
- if (!process.stdin.isTTY) {
1059
- return null;
1060
- }
1061
- if (noteTitle) {
1062
- console.error(pc8.yellow("\u{1F512}") + ` Note "${noteTitle}" is PIN-protected.`);
1063
- } else {
1064
- console.error(pc8.yellow("\u{1F512}") + " This action requires your PIN.");
1065
- }
1066
- if (hint) {
1067
- console.error(pc8.dim(`Hint: ${hint}`));
1068
- }
1069
- for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1070
- const pin = await readPinHidden(
1071
- attempt > 1 ? `Enter PIN (${attempt}/${maxAttempts}): ` : "Enter PIN: "
1072
- );
1073
- if (pin) {
1074
- return pin;
1075
- }
1076
- if (attempt < maxAttempts) {
1077
- console.error(pc8.red("Invalid or empty PIN. Try again."));
1078
- }
1079
- }
1080
- console.error(pc8.red("Too many attempts."));
1081
- return null;
1082
- }
1083
- async function readPinHidden(prompt) {
1084
- return new Promise((resolve2) => {
1085
- const rl = createInterface2({
1086
- input: process.stdin,
1087
- output: process.stderr,
1088
- terminal: true
1089
- });
1090
- if (process.stdin.isTTY) {
1091
- process.stdin.setRawMode?.(true);
1092
- }
1093
- process.stderr.write(prompt);
1094
- let pin = "";
1095
- const onKeypress = (key) => {
1096
- const char = key.toString();
1097
- if (char === "\r" || char === "\n") {
1098
- process.stderr.write("\n");
1099
- cleanup();
1100
- resolve2(pin);
1101
- } else if (char === "") {
1102
- process.stderr.write("\n");
1103
- cleanup();
1104
- process.exit(130);
1105
- } else if (char === "\x7F" || char === "\b") {
1106
- if (pin.length > 0) {
1107
- pin = pin.slice(0, -1);
1108
- process.stderr.write("\b \b");
1109
- }
1110
- } else if (char.length === 1 && char >= " ") {
1111
- pin += char;
1112
- process.stderr.write("*");
1113
- }
1114
- };
1115
- const cleanup = () => {
1116
- process.stdin.removeListener("data", onKeypress);
1117
- if (process.stdin.isTTY) {
1118
- process.stdin.setRawMode?.(false);
1119
- }
1120
- rl.close();
1121
- };
1122
- process.stdin.on("data", onKeypress);
1123
- });
1124
- }
1125
- async function resolvePin(options) {
1126
- const { pinArg, pinFromStdin, noteTitle, hint, skipCache, skipPrompt } = options;
1127
- if (pinArg) {
1128
- return pinArg;
1129
- }
1130
- if (pinFromStdin) {
1131
- try {
1132
- return await readPinFromStdin();
1133
- } catch {
1134
- return null;
1135
- }
1136
- }
1137
- const envPin = process.env.PNOTE_PIN;
1138
- if (envPin) {
1139
- return envPin;
1140
- }
1141
- if (!skipCache) {
1142
- const cached = getCachedPin();
1143
- if (cached) {
1144
- return cached;
1145
- }
1146
- }
1147
- if (!skipPrompt && process.stdin.isTTY) {
1148
- const pin = await promptForPin(noteTitle, hint);
1149
- if (pin) {
1150
- setCachedPin(pin);
1151
- return pin;
1152
- }
1153
- }
1154
- return null;
1155
- }
1156
-
1157
- // src/commands/notes/index.ts
1158
- async function buildContext(globalOpts) {
1159
- const pin = await resolvePin({
1160
- pinArg: globalOpts.pin,
1161
- pinFromStdin: globalOpts.pinStdin,
1162
- skipPrompt: true
1163
- // Don't prompt interactively for list/search operations
1164
- });
1165
- return {
1166
- json: globalOpts.json ?? false,
1167
- noColor: globalOpts.noColor ?? false,
1168
- plain: globalOpts.plain ?? false,
1169
- pin: pin ?? void 0
1170
- };
1171
- }
1172
- var notesCommand = new Command2("notes").description("List and manage notes").option("--tag <tag>", 'Filter by tag (e.g., "AI/art")').option("--archived", "Show archived notes").option("--pinned", "Show only pinned notes").option("--deleted", "Show deleted notes").option("--search <query>", "Search notes by title").option("--limit <n>", "Limit number of results", "50").action(async (options, cmd) => {
1173
- const globalOpts = cmd.parent?.opts() || {};
1174
- const ctx = await buildContext(globalOpts);
1175
- await listNotesAction(options, ctx);
1176
- }).addHelpText(
1177
- "after",
1178
- `
1179
- Examples:
1180
- $ pnote notes List all active notes
1181
- $ pnote notes --tag "AI/art" Filter by tag
1182
- $ pnote notes --archived Show archived notes
1183
- $ pnote notes --pinned Show only pinned notes
1184
- $ pnote notes get abc123 Get note details
1185
- $ pnote notes create "My Note" Create a new note
1186
- $ pnote notes update abc123 --title "New Title"
1187
- `
1188
- );
1189
- notesCommand.command("get").description("Get a note with its latest snippet").argument("<id>", "Note ID").action(async (id, _options, cmd) => {
1190
- const globalOpts = cmd.parent?.parent?.opts() || {};
1191
- const ctx = await buildContext(globalOpts);
1192
- await getNoteAction(id, ctx);
1193
- });
1194
- notesCommand.command("create").description("Create a new note").argument("<title>", "Note title").option("--tags <tags...>", "Tags for the note").option("--content <content>", "Initial snippet content").action(async (title, options, cmd) => {
1195
- const globalOpts = cmd.parent?.parent?.opts() || {};
1196
- const ctx = await buildContext(globalOpts);
1197
- await createNoteAction(title, options, ctx);
1198
- });
1199
- notesCommand.command("archive").description("Archive or unarchive a note").argument("<id>", "Note ID").option("--undo", "Unarchive the note").action(async (id, options, cmd) => {
1200
- const globalOpts = cmd.parent?.parent?.opts() || {};
1201
- const ctx = await buildContext(globalOpts);
1202
- await archiveNoteAction(id, options, ctx);
1203
- });
1204
- notesCommand.command("pin").description("Pin or unpin a note").argument("<id>", "Note ID").action(async (id, _options, cmd) => {
1205
- const globalOpts = cmd.parent?.parent?.opts() || {};
1206
- const ctx = await buildContext(globalOpts);
1207
- await pinNoteAction(id, ctx);
1208
- });
1209
- notesCommand.command("update").description("Update a note title or tags").argument("<id>", "Note ID").option("--title <title>", "New title").option("--tags <tags...>", "New tags (replaces existing)").action(async (id, options, cmd) => {
1210
- const globalOpts = cmd.parent?.parent?.opts() || {};
1211
- const ctx = await buildContext(globalOpts);
1212
- await updateNoteAction(id, options, ctx);
1213
- });
1214
- notesCommand.command("delete").description("Delete a note (soft delete)").argument("<id>", "Note ID").option("--force", "Skip confirmation").action(async (id, options, cmd) => {
1215
- const globalOpts = cmd.parent?.parent?.opts() || {};
1216
- const ctx = await buildContext(globalOpts);
1217
- await deleteNoteAction(id, options, ctx);
1218
- });
1219
-
1220
- // src/commands/snippet/index.ts
1221
- import { Command as Command3 } from "commander";
1020
+ // src/commands/notes/snippetCmd.ts
1021
+ import { Command as Command2 } from "commander";
1222
1022
 
1223
1023
  // src/commands/snippet/show.ts
1224
1024
  async function showSnippetAction(noteId, options, ctx) {
@@ -1246,7 +1046,7 @@ async function showSnippetAction(noteId, options, ctx) {
1246
1046
  }
1247
1047
 
1248
1048
  // src/commands/snippet/copy.ts
1249
- import pc9 from "picocolors";
1049
+ import pc8 from "picocolors";
1250
1050
 
1251
1051
  // src/lib/clipboard.ts
1252
1052
  import { default as clipboardy } from "clipboardy";
@@ -1297,7 +1097,7 @@ async function copySnippetAction(noteId, options, ctx) {
1297
1097
  }
1298
1098
  } catch (clipboardError) {
1299
1099
  if (!ctx.json) {
1300
- console.error(pc9.yellow("Clipboard not available, printing to stdout:"));
1100
+ console.error(pc8.yellow("Clipboard not available, printing to stdout:"));
1301
1101
  console.error("");
1302
1102
  }
1303
1103
  console.log(snippet.content);
@@ -1404,27 +1204,314 @@ async function favoriteSnippetAction(snippetId, ctx) {
1404
1204
  }
1405
1205
  }
1406
1206
 
1407
- // src/commands/snippet/index.ts
1408
- async function buildContext2(globalOpts) {
1409
- const pin = await resolvePin({
1410
- pinArg: globalOpts.pin,
1411
- pinFromStdin: globalOpts.pinStdin,
1412
- skipPrompt: true
1413
- });
1414
- return {
1415
- json: globalOpts.json ?? false,
1416
- noColor: globalOpts.noColor ?? false,
1417
- plain: globalOpts.plain ?? false,
1418
- pin: pin ?? void 0
1419
- };
1207
+ // src/lib/pin.ts
1208
+ import { createInterface as createInterface2 } from "readline";
1209
+ import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, statSync } from "fs";
1210
+ import { tmpdir } from "os";
1211
+ import { join as join2 } from "path";
1212
+ import pc9 from "picocolors";
1213
+ var PIN_CACHE_TTL_MS = 5 * 60 * 1e3;
1214
+ var PIN_MAX_ATTEMPTS = 3;
1215
+ function getPinCachePath() {
1216
+ const uid = process.getuid?.() ?? "unknown";
1217
+ return join2(tmpdir(), `pnote-pin-${uid}`);
1420
1218
  }
1421
- var snippetCommand = new Command3("snippet").description("Read and manage snippets (note versions)").argument("[note-id]", "Note ID to show snippet for").option("--all", "Show all snippet versions").action(async (noteId, options, cmd) => {
1422
- const globalOpts = cmd.parent?.opts() || {};
1423
- const ctx = await buildContext2(globalOpts);
1424
- if (noteId) {
1425
- await showSnippetAction(noteId, options, ctx);
1426
- } else {
1427
- cmd.help();
1219
+ function getCachedPin() {
1220
+ const cachePath = getPinCachePath();
1221
+ if (!existsSync2(cachePath)) {
1222
+ return null;
1223
+ }
1224
+ try {
1225
+ const stat = statSync(cachePath);
1226
+ const age = Date.now() - stat.mtimeMs;
1227
+ if (age > PIN_CACHE_TTL_MS) {
1228
+ unlinkSync2(cachePath);
1229
+ return null;
1230
+ }
1231
+ const content = readFileSync2(cachePath, "utf-8").trim();
1232
+ return content || null;
1233
+ } catch {
1234
+ return null;
1235
+ }
1236
+ }
1237
+ function setCachedPin(pin) {
1238
+ const cachePath = getPinCachePath();
1239
+ try {
1240
+ writeFileSync2(cachePath, pin, { mode: 384 });
1241
+ } catch {
1242
+ }
1243
+ }
1244
+ async function readPinFromStdin() {
1245
+ const chunks = [];
1246
+ for await (const chunk of process.stdin) {
1247
+ chunks.push(chunk);
1248
+ const content2 = Buffer.concat(chunks).toString("utf-8");
1249
+ if (content2.includes("\n")) {
1250
+ break;
1251
+ }
1252
+ }
1253
+ const content = Buffer.concat(chunks).toString("utf-8");
1254
+ const firstLine = content.split("\n")[0]?.trim() || "";
1255
+ if (!firstLine) {
1256
+ throw new Error("No PIN provided via stdin");
1257
+ }
1258
+ return firstLine;
1259
+ }
1260
+ async function promptForPin(noteTitle, hint, maxAttempts = PIN_MAX_ATTEMPTS) {
1261
+ if (!process.stdin.isTTY) {
1262
+ return null;
1263
+ }
1264
+ if (noteTitle) {
1265
+ console.error(pc9.yellow("\u{1F512}") + ` Note "${noteTitle}" is PIN-protected.`);
1266
+ } else {
1267
+ console.error(pc9.yellow("\u{1F512}") + " This action requires your PIN.");
1268
+ }
1269
+ if (hint) {
1270
+ console.error(pc9.dim(`Hint: ${hint}`));
1271
+ }
1272
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1273
+ const pin = await readPinHidden(
1274
+ attempt > 1 ? `Enter PIN (${attempt}/${maxAttempts}): ` : "Enter PIN: "
1275
+ );
1276
+ if (pin) {
1277
+ return pin;
1278
+ }
1279
+ if (attempt < maxAttempts) {
1280
+ console.error(pc9.red("Invalid or empty PIN. Try again."));
1281
+ }
1282
+ }
1283
+ console.error(pc9.red("Too many attempts."));
1284
+ return null;
1285
+ }
1286
+ async function readPinHidden(prompt) {
1287
+ return new Promise((resolve2) => {
1288
+ const rl = createInterface2({
1289
+ input: process.stdin,
1290
+ output: process.stderr,
1291
+ terminal: true
1292
+ });
1293
+ if (process.stdin.isTTY) {
1294
+ process.stdin.setRawMode?.(true);
1295
+ }
1296
+ process.stderr.write(prompt);
1297
+ let pin = "";
1298
+ const onKeypress = (key) => {
1299
+ const char = key.toString();
1300
+ if (char === "\r" || char === "\n") {
1301
+ process.stderr.write("\n");
1302
+ cleanup();
1303
+ resolve2(pin);
1304
+ } else if (char === "") {
1305
+ process.stderr.write("\n");
1306
+ cleanup();
1307
+ process.exit(130);
1308
+ } else if (char === "\x7F" || char === "\b") {
1309
+ if (pin.length > 0) {
1310
+ pin = pin.slice(0, -1);
1311
+ process.stderr.write("\b \b");
1312
+ }
1313
+ } else if (char.length === 1 && char >= " ") {
1314
+ pin += char;
1315
+ process.stderr.write("*");
1316
+ }
1317
+ };
1318
+ const cleanup = () => {
1319
+ process.stdin.removeListener("data", onKeypress);
1320
+ if (process.stdin.isTTY) {
1321
+ process.stdin.setRawMode?.(false);
1322
+ }
1323
+ rl.close();
1324
+ };
1325
+ process.stdin.on("data", onKeypress);
1326
+ });
1327
+ }
1328
+ async function resolvePin(options) {
1329
+ const { pinArg, pinFromStdin, noteTitle, hint, skipCache, skipPrompt } = options;
1330
+ if (pinArg) {
1331
+ return pinArg;
1332
+ }
1333
+ if (pinFromStdin) {
1334
+ try {
1335
+ return await readPinFromStdin();
1336
+ } catch {
1337
+ return null;
1338
+ }
1339
+ }
1340
+ const envPin = process.env.PNOTE_PIN;
1341
+ if (envPin) {
1342
+ return envPin;
1343
+ }
1344
+ if (!skipCache) {
1345
+ const cached = getCachedPin();
1346
+ if (cached) {
1347
+ return cached;
1348
+ }
1349
+ }
1350
+ if (!skipPrompt && process.stdin.isTTY) {
1351
+ const pin = await promptForPin(noteTitle, hint);
1352
+ if (pin) {
1353
+ setCachedPin(pin);
1354
+ return pin;
1355
+ }
1356
+ }
1357
+ return null;
1358
+ }
1359
+
1360
+ // src/commands/notes/snippetCmd.ts
1361
+ async function buildContext(globalOpts) {
1362
+ const pin = await resolvePin({
1363
+ pinArg: globalOpts.pin,
1364
+ pinFromStdin: globalOpts.pinStdin,
1365
+ skipPrompt: true
1366
+ });
1367
+ return {
1368
+ json: globalOpts.json ?? false,
1369
+ noColor: globalOpts.noColor ?? false,
1370
+ plain: globalOpts.plain ?? false,
1371
+ pin: pin ?? void 0
1372
+ };
1373
+ }
1374
+ var notesSnippetCommand = new Command2("snippet").description("Read and manage snippets for a note").argument("[note-id]", "Note ID to show latest snippet for").action(async (noteId, _options, cmd) => {
1375
+ const globalOpts = cmd.parent?.parent?.opts() || {};
1376
+ const ctx = await buildContext(globalOpts);
1377
+ if (noteId) {
1378
+ await showSnippetAction(noteId, {}, ctx);
1379
+ } else {
1380
+ cmd.help();
1381
+ }
1382
+ }).addHelpText(
1383
+ "after",
1384
+ `
1385
+ Examples:
1386
+ $ pnote notes snippet abc123 Show latest snippet (pipe-friendly)
1387
+ $ pnote notes snippet list abc123 Show all versions
1388
+ $ pnote notes snippet copy abc123 Copy to clipboard
1389
+ $ cat file.txt | pnote notes snippet add abc123
1390
+ `
1391
+ );
1392
+ notesSnippetCommand.command("list").description("List all snippet versions for a note").argument("<note-id>", "Note ID").action(async (noteId, _options, cmd) => {
1393
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1394
+ const ctx = await buildContext(globalOpts);
1395
+ try {
1396
+ const result = await getNote(noteId, { pin: ctx.pin });
1397
+ outputSnippets(result.snippets || [], ctx);
1398
+ } catch (error) {
1399
+ handleError(error, ctx.noColor);
1400
+ }
1401
+ });
1402
+ notesSnippetCommand.command("copy").description("Copy snippet content to clipboard").argument("<note-id>", "Note ID").option("--version <n>", "Specific version number").action(async (noteId, options, cmd) => {
1403
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1404
+ const ctx = await buildContext(globalOpts);
1405
+ await copySnippetAction(noteId, options, ctx);
1406
+ });
1407
+ notesSnippetCommand.command("add").description("Add a new snippet version (from stdin)").argument("<note-id>", "Note ID").option("--title <title>", "Snippet title").action(async (noteId, options, cmd) => {
1408
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1409
+ const ctx = await buildContext(globalOpts);
1410
+ await addSnippetAction(noteId, options, ctx);
1411
+ });
1412
+ notesSnippetCommand.command("update").description("Update an existing snippet (from stdin)").argument("<snippet-id>", "Snippet ID").option("--title <title>", "New snippet title").action(async (snippetId, options, cmd) => {
1413
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1414
+ const ctx = await buildContext(globalOpts);
1415
+ await updateSnippetAction(snippetId, options, ctx);
1416
+ });
1417
+ notesSnippetCommand.command("favorite").description("Toggle favorite status on a snippet").argument("<snippet-id>", "Snippet ID").action(async (snippetId, _options, cmd) => {
1418
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1419
+ const ctx = await buildContext(globalOpts);
1420
+ await favoriteSnippetAction(snippetId, ctx);
1421
+ });
1422
+
1423
+ // src/commands/notes/index.ts
1424
+ async function buildContext2(globalOpts) {
1425
+ const pin = await resolvePin({
1426
+ pinArg: globalOpts.pin,
1427
+ pinFromStdin: globalOpts.pinStdin,
1428
+ skipPrompt: true
1429
+ // Don't prompt interactively for list/search operations
1430
+ });
1431
+ return {
1432
+ json: globalOpts.json ?? false,
1433
+ noColor: globalOpts.noColor ?? false,
1434
+ plain: globalOpts.plain ?? false,
1435
+ pin: pin ?? void 0
1436
+ };
1437
+ }
1438
+ var notesCommand = new Command3("notes").description("List and manage notes").option("--tag <tag>", 'Filter by tag (e.g., "AI/art")').option("--archived", "Show archived notes").option("--pinned", "Show only pinned notes").option("--deleted", "Show deleted notes").option("--protected", "Show only PIN-protected notes").option("--search <query>", "Search notes by title").option("--limit <n>", "Limit number of results", "50").action(async (options, cmd) => {
1439
+ const globalOpts = cmd.parent?.opts() || {};
1440
+ const ctx = await buildContext2(globalOpts);
1441
+ await listNotesAction(options, ctx);
1442
+ }).addHelpText(
1443
+ "after",
1444
+ `
1445
+ Examples:
1446
+ $ pnote notes List all active notes
1447
+ $ pnote notes --tag "AI/art" Filter by tag
1448
+ $ pnote notes --archived Show archived notes
1449
+ $ pnote notes --pinned Show only pinned notes
1450
+ $ pnote notes get abc123 Get note with all snippet versions
1451
+ $ pnote notes get abc123 --latest Get note with only latest snippet
1452
+ $ pnote notes create "My Note" Create a new note
1453
+ $ pnote notes snippet abc123 Show latest snippet (pipe-friendly)
1454
+ $ pnote notes snippet list abc123 Show all snippet versions
1455
+ $ cat file.txt | pnote notes snippet add abc123
1456
+ `
1457
+ );
1458
+ notesCommand.command("get").description("Get a note with all its snippet versions").argument("<id>", "Note ID").option("--latest", "Show only the latest snippet version").action(async (id, options, cmd) => {
1459
+ const globalOpts = cmd.parent?.parent?.opts() || {};
1460
+ const ctx = await buildContext2(globalOpts);
1461
+ await getNoteAction(id, options, ctx);
1462
+ });
1463
+ notesCommand.command("create").description("Create a new note").argument("<title>", "Note title").option("--tags <tags...>", "Tags for the note").option("--content <content>", "Initial snippet content").action(async (title, options, cmd) => {
1464
+ const globalOpts = cmd.parent?.parent?.opts() || {};
1465
+ const ctx = await buildContext2(globalOpts);
1466
+ await createNoteAction(title, options, ctx);
1467
+ });
1468
+ notesCommand.command("archive").description("Archive or unarchive a note").argument("<id>", "Note ID").option("--undo", "Unarchive the note").action(async (id, options, cmd) => {
1469
+ const globalOpts = cmd.parent?.parent?.opts() || {};
1470
+ const ctx = await buildContext2(globalOpts);
1471
+ await archiveNoteAction(id, options, ctx);
1472
+ });
1473
+ notesCommand.command("pin").description("Pin or unpin a note").argument("<id>", "Note ID").action(async (id, _options, cmd) => {
1474
+ const globalOpts = cmd.parent?.parent?.opts() || {};
1475
+ const ctx = await buildContext2(globalOpts);
1476
+ await pinNoteAction(id, ctx);
1477
+ });
1478
+ notesCommand.command("update").description("Update a note title or tags").argument("<id>", "Note ID").option("--title <title>", "New title").option("--tags <tags...>", "New tags (replaces existing)").action(async (id, options, cmd) => {
1479
+ const globalOpts = cmd.parent?.parent?.opts() || {};
1480
+ const ctx = await buildContext2(globalOpts);
1481
+ await updateNoteAction(id, options, ctx);
1482
+ });
1483
+ notesCommand.command("delete").description("Delete a note (soft delete)").argument("<id>", "Note ID").option("--force", "Skip confirmation").action(async (id, options, cmd) => {
1484
+ const globalOpts = cmd.parent?.parent?.opts() || {};
1485
+ const ctx = await buildContext2(globalOpts);
1486
+ await deleteNoteAction(id, options, ctx);
1487
+ });
1488
+ notesCommand.addCommand(notesSnippetCommand);
1489
+
1490
+ // src/commands/snippet/index.ts
1491
+ import { Command as Command4 } from "commander";
1492
+ async function buildContext3(globalOpts) {
1493
+ const pin = await resolvePin({
1494
+ pinArg: globalOpts.pin,
1495
+ pinFromStdin: globalOpts.pinStdin,
1496
+ skipPrompt: true
1497
+ });
1498
+ return {
1499
+ json: globalOpts.json ?? false,
1500
+ noColor: globalOpts.noColor ?? false,
1501
+ plain: globalOpts.plain ?? false,
1502
+ pin: pin ?? void 0
1503
+ };
1504
+ }
1505
+ var DEPRECATION_NOTICE = '\nDeprecated: "pnote snippet" is deprecated. Use "pnote notes snippet" instead.\n\n';
1506
+ var snippetCommand = new Command4("snippet").description('Read and manage snippets (note versions) [deprecated: use "notes snippet"]').argument("[note-id]", "Note ID to show snippet for").option("--all", "Show all snippet versions").hook("preAction", () => {
1507
+ process.stderr.write(DEPRECATION_NOTICE);
1508
+ }).action(async (noteId, options, cmd) => {
1509
+ const globalOpts = cmd.parent?.opts() || {};
1510
+ const ctx = await buildContext3(globalOpts);
1511
+ if (noteId) {
1512
+ await showSnippetAction(noteId, options, ctx);
1513
+ } else {
1514
+ cmd.help();
1428
1515
  }
1429
1516
  }).addHelpText(
1430
1517
  "after",
@@ -1438,27 +1525,27 @@ Examples:
1438
1525
  );
1439
1526
  snippetCommand.command("copy").description("Copy snippet content to clipboard").argument("<note-id>", "Note ID").option("--version <n>", "Specific version number").action(async (noteId, options, cmd) => {
1440
1527
  const globalOpts = cmd.parent?.parent?.opts() || {};
1441
- const ctx = await buildContext2(globalOpts);
1528
+ const ctx = await buildContext3(globalOpts);
1442
1529
  await copySnippetAction(noteId, options, ctx);
1443
1530
  });
1444
1531
  snippetCommand.command("add").description("Add a new snippet version (from stdin)").argument("<note-id>", "Note ID").option("--title <title>", "Snippet title").action(async (noteId, options, cmd) => {
1445
1532
  const globalOpts = cmd.parent?.parent?.opts() || {};
1446
- const ctx = await buildContext2(globalOpts);
1533
+ const ctx = await buildContext3(globalOpts);
1447
1534
  await addSnippetAction(noteId, options, ctx);
1448
1535
  });
1449
1536
  snippetCommand.command("update").description("Update an existing snippet (from stdin)").argument("<snippet-id>", "Snippet ID").option("--title <title>", "New snippet title").action(async (snippetId, options, cmd) => {
1450
1537
  const globalOpts = cmd.parent?.parent?.opts() || {};
1451
- const ctx = await buildContext2(globalOpts);
1538
+ const ctx = await buildContext3(globalOpts);
1452
1539
  await updateSnippetAction(snippetId, options, ctx);
1453
1540
  });
1454
1541
  snippetCommand.command("favorite").description("Toggle favorite status on a snippet").argument("<snippet-id>", "Snippet ID").action(async (snippetId, _options, cmd) => {
1455
1542
  const globalOpts = cmd.parent?.parent?.opts() || {};
1456
- const ctx = await buildContext2(globalOpts);
1543
+ const ctx = await buildContext3(globalOpts);
1457
1544
  await favoriteSnippetAction(snippetId, ctx);
1458
1545
  });
1459
1546
 
1460
1547
  // src/commands/tags/index.ts
1461
- import { Command as Command4 } from "commander";
1548
+ import { Command as Command5 } from "commander";
1462
1549
 
1463
1550
  // src/commands/tags/list.ts
1464
1551
  async function listTagsAction(options, ctx) {
@@ -1521,7 +1608,7 @@ async function mergeTagsAction(sourceTags, targetTag, ctx) {
1521
1608
  }
1522
1609
 
1523
1610
  // src/commands/tags/index.ts
1524
- async function buildContext3(globalOpts) {
1611
+ async function buildContext4(globalOpts) {
1525
1612
  const pin = await resolvePin({
1526
1613
  pinArg: globalOpts.pin,
1527
1614
  pinFromStdin: globalOpts.pinStdin,
@@ -1534,9 +1621,9 @@ async function buildContext3(globalOpts) {
1534
1621
  pin: pin ?? void 0
1535
1622
  };
1536
1623
  }
1537
- var tagsCommand = new Command4("tags").description("List and manage tags").option("--include-archived", "Include tags from archived notes").action(async (options, cmd) => {
1624
+ var tagsCommand = new Command5("tags").description("List and manage tags").option("--include-archived", "Include tags from archived notes").action(async (options, cmd) => {
1538
1625
  const globalOpts = cmd.parent?.opts() || {};
1539
- const ctx = await buildContext3(globalOpts);
1626
+ const ctx = await buildContext4(globalOpts);
1540
1627
  await listTagsAction(options, ctx);
1541
1628
  }).addHelpText(
1542
1629
  "after",
@@ -1549,17 +1636,17 @@ Examples:
1549
1636
  );
1550
1637
  tagsCommand.command("rename").description("Rename a tag across all notes").argument("<old-tag>", "Current tag name").argument("<new-tag>", "New tag name").action(async (oldTag, newTag, _options, cmd) => {
1551
1638
  const globalOpts = cmd.parent?.parent?.opts() || {};
1552
- const ctx = await buildContext3(globalOpts);
1639
+ const ctx = await buildContext4(globalOpts);
1553
1640
  await renameTagAction(oldTag, newTag, ctx);
1554
1641
  });
1555
1642
  tagsCommand.command("merge").description("Merge multiple tags into one").argument("<source-tags...>", "Tags to merge (space-separated)").requiredOption("--into <target>", "Target tag to merge into").action(async (sourceTags, options, cmd) => {
1556
1643
  const globalOpts = cmd.parent?.parent?.opts() || {};
1557
- const ctx = await buildContext3(globalOpts);
1644
+ const ctx = await buildContext4(globalOpts);
1558
1645
  await mergeTagsAction(sourceTags, options.into, ctx);
1559
1646
  });
1560
1647
 
1561
1648
  // src/commands/search.ts
1562
- import { Command as Command5 } from "commander";
1649
+ import { Command as Command6 } from "commander";
1563
1650
  async function searchAction(query, options, ctx) {
1564
1651
  try {
1565
1652
  let search_notes = true;
@@ -1585,7 +1672,7 @@ async function searchAction(query, options, ctx) {
1585
1672
  handleError(error, ctx.noColor);
1586
1673
  }
1587
1674
  }
1588
- var searchCommand = new Command5("search").description("Search notes and snippets").argument("<query>", "Search query").option("--notes-only", "Only search note titles and tags").option("--snippets-only", "Only search snippet content").option("--limit <n>", "Limit results", "20").action(async (query, options, cmd) => {
1675
+ var searchCommand = new Command6("search").description("Search notes and snippets").argument("<query>", "Search query").option("--notes-only", "Only search note titles and tags").option("--snippets-only", "Only search snippet content").option("--limit <n>", "Limit results", "20").action(async (query, options, cmd) => {
1589
1676
  const globalOpts = cmd.parent?.opts() || {};
1590
1677
  const pin = await resolvePin({
1591
1678
  pinArg: globalOpts.pin,
@@ -1610,7 +1697,7 @@ Examples:
1610
1697
  );
1611
1698
 
1612
1699
  // src/commands/share/index.ts
1613
- import { Command as Command6 } from "commander";
1700
+ import { Command as Command7 } from "commander";
1614
1701
 
1615
1702
  // src/commands/share/tags.ts
1616
1703
  async function listSharedTagsAction(ctx) {
@@ -1648,7 +1735,7 @@ async function listSharedNotesAction(options, ctx) {
1648
1735
  }
1649
1736
 
1650
1737
  // src/commands/share/index.ts
1651
- async function buildContext4(globalOpts) {
1738
+ async function buildContext5(globalOpts) {
1652
1739
  const pin = await resolvePin({
1653
1740
  pinArg: globalOpts.pin,
1654
1741
  pinFromStdin: globalOpts.pinStdin,
@@ -1661,7 +1748,7 @@ async function buildContext4(globalOpts) {
1661
1748
  pin: pin ?? void 0
1662
1749
  };
1663
1750
  }
1664
- var shareCommand = new Command6("share").description("View shared tags and shared note links").addHelpText(
1751
+ var shareCommand = new Command7("share").description("View shared tags and shared note links").addHelpText(
1665
1752
  "after",
1666
1753
  `
1667
1754
  Examples:
@@ -1673,7 +1760,7 @@ Examples:
1673
1760
  );
1674
1761
  shareCommand.command("tags").description("List shared tags or view notes in a shared tag").argument("[id]", "Shared tag ID to view notes").option("--limit <n>", "Limit notes returned").action(async (id, options, cmd) => {
1675
1762
  const globalOpts = cmd.parent?.parent?.opts() || {};
1676
- const ctx = await buildContext4(globalOpts);
1763
+ const ctx = await buildContext5(globalOpts);
1677
1764
  if (id) {
1678
1765
  await getSharedTagNotesAction(id, options, ctx);
1679
1766
  } else {
@@ -1682,12 +1769,12 @@ shareCommand.command("tags").description("List shared tags or view notes in a sh
1682
1769
  });
1683
1770
  shareCommand.command("notes").description("List public share links for your notes").option("--include-revoked", "Include revoked share links").action(async (options, cmd) => {
1684
1771
  const globalOpts = cmd.parent?.parent?.opts() || {};
1685
- const ctx = await buildContext4(globalOpts);
1772
+ const ctx = await buildContext5(globalOpts);
1686
1773
  await listSharedNotesAction(options, ctx);
1687
1774
  });
1688
1775
 
1689
1776
  // src/commands/skills/index.ts
1690
- import { Command as Command7 } from "commander";
1777
+ import { Command as Command8 } from "commander";
1691
1778
 
1692
1779
  // src/commands/skills/list.ts
1693
1780
  import pc10 from "picocolors";
@@ -1886,7 +1973,7 @@ function collectFiles(baseDir, currentDir) {
1886
1973
  }
1887
1974
 
1888
1975
  // src/commands/skills/index.ts
1889
- async function buildContext5(globalOpts) {
1976
+ async function buildContext6(globalOpts) {
1890
1977
  const pin = await resolvePin({
1891
1978
  pinArg: globalOpts.pin,
1892
1979
  pinFromStdin: globalOpts.pinStdin,
@@ -1899,9 +1986,9 @@ async function buildContext5(globalOpts) {
1899
1986
  pin: pin ?? void 0
1900
1987
  };
1901
1988
  }
1902
- var skillsCommand = new Command7("skills").description("Manage agent skills (sync between cloud and local)").action(async (_options, cmd) => {
1989
+ var skillsCommand = new Command8("skills").description("Manage agent skills (sync between cloud and local)").action(async (_options, cmd) => {
1903
1990
  const globalOpts = cmd.parent?.opts() || {};
1904
- const ctx = await buildContext5(globalOpts);
1991
+ const ctx = await buildContext6(globalOpts);
1905
1992
  await listSkillsAction(ctx);
1906
1993
  }).addHelpText(
1907
1994
  "after",
@@ -1918,19 +2005,179 @@ They sync to ~/.claude/skills/<skill-name>/<filename> for use with Claude Code.
1918
2005
  );
1919
2006
  skillsCommand.command("pull").description("Download skills from cloud to local (~/.claude/skills/)").argument("[skill-name]", "Specific skill to pull (pulls all if omitted)").option("--dir <path>", "Custom output directory (default: ~/.claude/skills)").option("--dry-run", "Show what would be downloaded without writing files").action(async (skillName, options, cmd) => {
1920
2007
  const globalOpts = cmd.parent?.parent?.opts() || {};
1921
- const ctx = await buildContext5(globalOpts);
2008
+ const ctx = await buildContext6(globalOpts);
1922
2009
  await pullSkillsAction(skillName, options, ctx);
1923
2010
  });
1924
2011
  skillsCommand.command("push").description("Upload a local skill directory to cloud").argument("<dir>", "Path to skill directory (must contain SKILL.md)").option("--name <name>", "Override skill name (default: directory name)").action(async (dir, options, cmd) => {
1925
2012
  const globalOpts = cmd.parent?.parent?.opts() || {};
1926
- const ctx = await buildContext5(globalOpts);
2013
+ const ctx = await buildContext6(globalOpts);
1927
2014
  await pushSkillsAction(dir, options, ctx);
1928
2015
  });
1929
2016
 
2017
+ // src/commands/pin/index.ts
2018
+ import { Command as Command9 } from "commander";
2019
+
2020
+ // src/commands/pin/statusAction.ts
2021
+ import pc13 from "picocolors";
2022
+ async function pinStatusAction(ctx) {
2023
+ try {
2024
+ const result = await checkPinStatus({ pin: ctx.pin });
2025
+ if (ctx.json) {
2026
+ outputJson({
2027
+ ...result,
2028
+ pnote_pin_env_set: !!process.env.PNOTE_PIN,
2029
+ pin_cached: !!getCachedPin()
2030
+ });
2031
+ return;
2032
+ }
2033
+ const useColor = !ctx.noColor && process.stdout.isTTY;
2034
+ const c = {
2035
+ green: useColor ? pc13.green : (s) => s,
2036
+ red: useColor ? pc13.red : (s) => s,
2037
+ yellow: useColor ? pc13.yellow : (s) => s,
2038
+ dim: useColor ? pc13.dim : (s) => s,
2039
+ bold: useColor ? pc13.bold : (s) => s,
2040
+ magenta: useColor ? pc13.magenta : (s) => s
2041
+ };
2042
+ const yes = c.green("\u2713");
2043
+ const no = c.red("\u2717");
2044
+ console.log(c.bold("PIN Status"));
2045
+ console.log("");
2046
+ const hasPin = result.user_has_pin_setup;
2047
+ console.log(` Account PIN set: ${hasPin ? yes : no}`);
2048
+ if (result.pin_hint) {
2049
+ console.log(` PIN hint: ${c.dim(result.pin_hint)}`);
2050
+ }
2051
+ console.log(` Protected notes: ${c.magenta(String(result.protected_notes_count))}`);
2052
+ console.log("");
2053
+ console.log(c.bold("PIN Sources (CLI)"));
2054
+ console.log("");
2055
+ const envSet = !!process.env.PNOTE_PIN;
2056
+ console.log(` PNOTE_PIN env: ${envSet ? yes : no}${envSet ? c.dim(" (recommended)") : ""}`);
2057
+ const cached = !!getCachedPin();
2058
+ console.log(` Session cache: ${cached ? yes + c.dim(" (active, <5min)") : no}`);
2059
+ if (result.pin_provided) {
2060
+ const valid = result.pin_valid;
2061
+ console.log(` Provided PIN valid: ${valid ? yes : no}`);
2062
+ } else {
2063
+ console.log(` Provided PIN: ${c.dim("not provided")}`);
2064
+ }
2065
+ console.log("");
2066
+ const canAccess = result.can_access_protected;
2067
+ if (canAccess) {
2068
+ console.log(yes + " " + c.green("Can access protected notes"));
2069
+ } else if (!hasPin) {
2070
+ console.log(c.dim(" No protected notes \u2014 PIN not configured on account"));
2071
+ } else {
2072
+ console.log(no + " " + c.red("Cannot access protected notes"));
2073
+ console.log("");
2074
+ console.log(c.dim(" Set PIN via: export PNOTE_PIN=<pin>"));
2075
+ console.log(c.dim(" Or use: pnote notes get <id> -p <pin>"));
2076
+ console.log(c.dim(" Or use: echo <pin> | pnote notes get <id> --pin-stdin"));
2077
+ }
2078
+ } catch (error) {
2079
+ handleError(error, ctx.noColor);
2080
+ }
2081
+ }
2082
+
2083
+ // src/commands/pin/listAction.ts
2084
+ import pc14 from "picocolors";
2085
+ async function pinListAction(options, ctx) {
2086
+ try {
2087
+ const result = await listProtectedNotes(
2088
+ {
2089
+ limit: options.limit ? parseInt(options.limit, 10) : 50,
2090
+ offset: options.offset ? parseInt(options.offset, 10) : void 0
2091
+ },
2092
+ { pin: ctx.pin }
2093
+ );
2094
+ if (ctx.json) {
2095
+ outputJson(result);
2096
+ return;
2097
+ }
2098
+ const useColor = !ctx.noColor && process.stdout.isTTY;
2099
+ const c = {
2100
+ dim: useColor ? pc14.dim : (s) => s,
2101
+ bold: useColor ? pc14.bold : (s) => s,
2102
+ cyan: useColor ? pc14.cyan : (s) => s,
2103
+ yellow: useColor ? pc14.yellow : (s) => s,
2104
+ magenta: useColor ? pc14.magenta : (s) => s
2105
+ };
2106
+ const isHuman = !ctx.json && !ctx.plain && process.stdout.isTTY;
2107
+ if (result.notes.length === 0) {
2108
+ console.log(c.dim("No protected notes."));
2109
+ return;
2110
+ }
2111
+ if (isHuman) {
2112
+ console.log(c.bold(`Protected Notes (${result.count})`));
2113
+ if (result.hint) {
2114
+ console.log(c.dim(`PIN hint: ${result.hint}`));
2115
+ }
2116
+ if (!result.can_decrypt) {
2117
+ console.log(c.dim("Note: Content not shown \u2014 set PNOTE_PIN to access content."));
2118
+ }
2119
+ console.log("");
2120
+ const pad2 = (s, w) => s.length >= w ? s.slice(0, w) : s + " ".repeat(w - s.length);
2121
+ const truncate2 = (s, w) => s.length <= w ? s : s.slice(0, w - 1) + "\u2026";
2122
+ console.log(
2123
+ " " + c.dim(pad2("ID", 10)) + " " + c.dim(pad2("TITLE", 30)) + " " + c.dim(pad2("TAGS", 20)) + " " + c.dim("PROTECTED")
2124
+ );
2125
+ for (const note of result.notes) {
2126
+ const date = new Date(note.protected_at);
2127
+ const updated = date.toLocaleDateString();
2128
+ const pinned = note.pinned ? c.yellow("* ") : " ";
2129
+ console.log(
2130
+ pinned + pad2(note.id.slice(0, 8), 10) + " " + pad2(truncate2(note.title, 30), 30) + " " + pad2(truncate2(note.tags.join(", ") || "-", 20), 20) + " " + c.magenta(updated)
2131
+ );
2132
+ }
2133
+ } else {
2134
+ for (const note of result.notes) {
2135
+ console.log([note.id, note.title, note.tags.join(","), note.protected_at].join(" "));
2136
+ }
2137
+ }
2138
+ } catch (error) {
2139
+ handleError(error, ctx.noColor);
2140
+ }
2141
+ }
2142
+
2143
+ // src/commands/pin/index.ts
2144
+ async function buildContext7(globalOpts) {
2145
+ const pin = await resolvePin({
2146
+ pinArg: globalOpts.pin,
2147
+ pinFromStdin: globalOpts.pinStdin,
2148
+ skipPrompt: true
2149
+ });
2150
+ return {
2151
+ json: globalOpts.json ?? false,
2152
+ noColor: globalOpts.noColor ?? false,
2153
+ plain: globalOpts.plain ?? false,
2154
+ pin: pin ?? void 0
2155
+ };
2156
+ }
2157
+ var pinCommand = new Command9("pin").description("Manage PIN protection for notes").addHelpText(
2158
+ "after",
2159
+ `
2160
+ Examples:
2161
+ $ pnote pin status Check PIN configuration and access
2162
+ $ pnote pin list List all protected notes (metadata only)
2163
+ $ PNOTE_PIN=1234 pnote pin status Check with PIN provided via env
2164
+ `
2165
+ );
2166
+ pinCommand.command("status").description("Check PIN configuration status and access to protected notes").action(async (_options, cmd) => {
2167
+ const globalOpts = cmd.parent?.parent?.opts() || {};
2168
+ const ctx = await buildContext7(globalOpts);
2169
+ await pinStatusAction(ctx);
2170
+ });
2171
+ pinCommand.command("list").description("List all PIN-protected notes (metadata only, no content)").option("--limit <n>", "Maximum number of results", "50").option("--offset <n>", "Skip N results (for pagination)").action(async (options, cmd) => {
2172
+ const globalOpts = cmd.parent?.parent?.opts() || {};
2173
+ const ctx = await buildContext7(globalOpts);
2174
+ await pinListAction(options, ctx);
2175
+ });
2176
+
1930
2177
  // src/lib/update-check.ts
1931
2178
  import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
1932
2179
  import { join as join5 } from "path";
1933
- import pc13 from "picocolors";
2180
+ import pc15 from "picocolors";
1934
2181
  var CACHE_FILE = "update-check.json";
1935
2182
  var CHECK_INTERVAL = 60 * 60 * 1e3;
1936
2183
  var FETCH_TIMEOUT = 2e3;
@@ -1979,9 +2226,9 @@ function isOlderVersion(current, latest) {
1979
2226
  }
1980
2227
  function printNotice(current, latest, packageName) {
1981
2228
  const useColor = process.stderr.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
1982
- const yellow = useColor ? pc13.yellow : (s) => s;
1983
- const bold = useColor ? pc13.bold : (s) => s;
1984
- const dim = useColor ? pc13.dim : (s) => s;
2229
+ const yellow = useColor ? pc15.yellow : (s) => s;
2230
+ const bold = useColor ? pc15.bold : (s) => s;
2231
+ const dim = useColor ? pc15.dim : (s) => s;
1985
2232
  const msg = `Update available: ${dim(current)} \u2192 ${bold(latest)}`;
1986
2233
  const cmd = `Run ${bold(`npm i -g ${packageName}`)} to update`;
1987
2234
  const lines = [msg, cmd];
@@ -2027,8 +2274,8 @@ async function checkAndNotifyUpdate(currentVersion, packageName) {
2027
2274
  }
2028
2275
 
2029
2276
  // src/index.ts
2030
- var program = new Command8();
2031
- program.name("pnote").description("pnote - The PromptNote CLI").version("0.2.1", "-V, --version", "Show version number").option("--json", "Output as JSON (for scripting)").option("--no-color", "Disable colored output").option("--plain", "Force plain text output (no formatting)").option("-p, --pin <pin>", "PIN for accessing protected notes").option("--pin-stdin", "Read PIN from stdin (first line only)").configureHelp({
2277
+ var program = new Command10();
2278
+ program.name("pnote").description("pnote - The PromptNote CLI").version("0.4.0", "-V, --version", "Show version number").option("--json", "Output as JSON (for scripting)").option("--no-color", "Disable colored output").option("--plain", "Force plain text output (no formatting)").option("-p, --pin <pin>", "PIN for accessing protected notes").option("--pin-stdin", "Read PIN from stdin (first line only)").configureHelp({
2032
2279
  sortSubcommands: true,
2033
2280
  sortOptions: true
2034
2281
  }).addHelpText(
@@ -2058,9 +2305,10 @@ program.addCommand(snippetCommand);
2058
2305
  program.addCommand(tagsCommand);
2059
2306
  program.addCommand(searchCommand);
2060
2307
  program.addCommand(shareCommand);
2308
+ program.addCommand(pinCommand);
2061
2309
  program.addCommand(skillsCommand);
2062
2310
  program.hook("preAction", async () => {
2063
- await checkAndNotifyUpdate("0.2.1", "pnote");
2311
+ await checkAndNotifyUpdate("0.4.0", "pnote");
2064
2312
  });
2065
2313
  process.on("SIGINT", () => {
2066
2314
  console.error("\nInterrupted");