pnote 0.2.0 → 0.3.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 Command9 } from "commander";
5
5
 
6
6
  // src/lib/errors.ts
7
7
  import pc from "picocolors";
@@ -455,7 +455,7 @@ authCommand.command("logout").description("Remove stored credentials").action(lo
455
455
  authCommand.command("whoami").description("Show current authenticated user").action(whoamiAction);
456
456
 
457
457
  // src/commands/notes/index.ts
458
- import { Command as Command2 } from "commander";
458
+ import { Command as Command3 } from "commander";
459
459
 
460
460
  // src/lib/output.ts
461
461
  import pc6 from "picocolors";
@@ -554,9 +554,10 @@ function outputNotes(notes, ctx) {
554
554
  }
555
555
  }
556
556
  }
557
- function outputNote(note, snippet, ctx) {
557
+ function outputNote(note, snippets, ctx) {
558
+ const latestSnippet = snippets.length > 0 ? snippets[snippets.length - 1] : null;
558
559
  if (ctx.json) {
559
- outputJson({ ...note, latest_snippet: snippet });
560
+ outputJson({ ...note, snippets, latest_snippet: latestSnippet });
560
561
  return;
561
562
  }
562
563
  const c = getColors(ctx);
@@ -573,21 +574,24 @@ function outputNote(note, snippet, ctx) {
573
574
  if (status.length > 0) {
574
575
  console.log(c.dim("Status: ") + status.join(", "));
575
576
  }
576
- if (snippet) {
577
- console.log("");
578
- console.log(c.dim(`--- Snippet v${snippet.version} ---`));
579
- console.log(snippet.content);
580
- } else {
577
+ if (snippets.length === 0) {
581
578
  console.log("");
582
579
  console.log(c.dim("(no content)"));
580
+ } else {
581
+ for (const snippet of snippets) {
582
+ const header = `v${snippet.version}` + (snippet.favorite ? c.yellow(" \u2605") : "") + c.dim(` (${formatRelativeTime(snippet.created_at)})`);
583
+ console.log("");
584
+ console.log(c.dim("--- Snippet ") + c.bold(header) + c.dim(" ---"));
585
+ console.log(snippet.content);
586
+ }
583
587
  }
584
588
  } else {
585
589
  console.log(`Title: ${note.title}`);
586
590
  console.log(`ID: ${note.id}`);
587
591
  console.log(`Tags: ${note.tags.join(", ")}`);
588
- if (snippet) {
592
+ for (const snippet of snippets) {
589
593
  console.log("");
590
- console.log(snippet.content);
594
+ console.log(`v${snippet.version} ${snippet.content}`);
591
595
  }
592
596
  }
593
597
  }
@@ -844,13 +848,14 @@ async function listNotesAction(options, ctx) {
844
848
  }
845
849
 
846
850
  // src/commands/notes/get.ts
847
- async function getNoteAction(id, ctx) {
851
+ async function getNoteAction(id, options, ctx) {
848
852
  try {
849
853
  const result = await getNote(id, { pin: ctx.pin });
850
854
  if (!result) {
851
855
  throw new NotFoundError("Note", id);
852
856
  }
853
- outputNote(result, result.latest_snippet, ctx);
857
+ const snippets = options.latest ? result.latest_snippet ? [result.latest_snippet] : [] : result.snippets || [];
858
+ outputNote(result, snippets, ctx);
854
859
  if (result.protection_status?.error && !ctx.json) {
855
860
  console.error("");
856
861
  console.error("Warning: " + result.protection_status.error);
@@ -1001,12 +1006,199 @@ async function updateNoteAction(id, options, ctx) {
1001
1006
  }
1002
1007
  }
1003
1008
 
1009
+ // src/commands/notes/snippetCmd.ts
1010
+ import { Command as Command2 } from "commander";
1011
+
1012
+ // src/commands/snippet/show.ts
1013
+ async function showSnippetAction(noteId, options, ctx) {
1014
+ try {
1015
+ if (options.all) {
1016
+ const result = await listSnippets(
1017
+ { note_id: noteId, limit: 100 },
1018
+ { pin: ctx.pin }
1019
+ );
1020
+ outputSnippets(result.snippets, ctx);
1021
+ } else {
1022
+ const result = await getNote(noteId, { pin: ctx.pin });
1023
+ if (result.latest_snippet) {
1024
+ outputSnippet(result.latest_snippet, ctx);
1025
+ } else {
1026
+ if (!ctx.json) {
1027
+ console.error("No snippet content found.");
1028
+ }
1029
+ process.exit(3);
1030
+ }
1031
+ }
1032
+ } catch (error) {
1033
+ handleError(error, ctx.noColor);
1034
+ }
1035
+ }
1036
+
1037
+ // src/commands/snippet/copy.ts
1038
+ import pc8 from "picocolors";
1039
+
1040
+ // src/lib/clipboard.ts
1041
+ import { default as clipboardy } from "clipboardy";
1042
+ async function copyToClipboard(text) {
1043
+ try {
1044
+ await clipboardy.write(text);
1045
+ } catch (error) {
1046
+ if (error instanceof Error && error.message.includes("xsel")) {
1047
+ throw new CLIError(
1048
+ "Clipboard not available. Install xsel or xclip on Linux.",
1049
+ 1,
1050
+ "Content will be printed to stdout instead."
1051
+ );
1052
+ }
1053
+ throw new CLIError("Failed to copy to clipboard");
1054
+ }
1055
+ }
1056
+
1057
+ // src/commands/snippet/copy.ts
1058
+ async function copySnippetAction(noteId, options, ctx) {
1059
+ try {
1060
+ let snippet = null;
1061
+ if (options.version) {
1062
+ const versionNum = parseInt(options.version, 10);
1063
+ if (isNaN(versionNum)) {
1064
+ throw new CLIError("Invalid version number");
1065
+ }
1066
+ const result = await listSnippets(
1067
+ { note_id: noteId, limit: 100 },
1068
+ { pin: ctx.pin }
1069
+ );
1070
+ snippet = result.snippets.find((s) => s.version === versionNum) || null;
1071
+ if (!snippet) {
1072
+ throw new CLIError(`Version ${versionNum} not found`);
1073
+ }
1074
+ } else {
1075
+ const result = await getNote(noteId, { pin: ctx.pin });
1076
+ snippet = result.latest_snippet;
1077
+ }
1078
+ if (!snippet) {
1079
+ throw new CLIError("No snippet content found");
1080
+ }
1081
+ try {
1082
+ await copyToClipboard(snippet.content);
1083
+ logStatus("Copied to clipboard");
1084
+ if (ctx.json) {
1085
+ console.log(JSON.stringify({ copied: true, content: snippet.content }));
1086
+ }
1087
+ } catch (clipboardError) {
1088
+ if (!ctx.json) {
1089
+ console.error(pc8.yellow("Clipboard not available, printing to stdout:"));
1090
+ console.error("");
1091
+ }
1092
+ console.log(snippet.content);
1093
+ }
1094
+ } catch (error) {
1095
+ handleError(error, ctx.noColor);
1096
+ }
1097
+ }
1098
+
1099
+ // src/commands/snippet/add.ts
1100
+ async function addSnippetAction(noteId, options, ctx) {
1101
+ try {
1102
+ let content;
1103
+ if (process.stdin.isTTY) {
1104
+ throw new CLIError(
1105
+ "No content provided",
1106
+ 1,
1107
+ "Pipe content to this command: echo 'content' | pnote snippet add <note-id>"
1108
+ );
1109
+ }
1110
+ const chunks = [];
1111
+ for await (const chunk of process.stdin) {
1112
+ chunks.push(chunk);
1113
+ }
1114
+ content = Buffer.concat(chunks).toString("utf-8");
1115
+ if (content.endsWith("\n")) {
1116
+ content = content.slice(0, -1);
1117
+ }
1118
+ if (!content) {
1119
+ throw new CLIError("Empty content provided");
1120
+ }
1121
+ const result = await createSnippet(
1122
+ {
1123
+ note_id: noteId,
1124
+ content,
1125
+ title: options.title
1126
+ },
1127
+ { pin: ctx.pin }
1128
+ );
1129
+ if (ctx.json) {
1130
+ outputJson(result);
1131
+ } else {
1132
+ outputMessage(`Created snippet v${result.snippet.version}`, ctx);
1133
+ }
1134
+ } catch (error) {
1135
+ handleError(error, ctx.noColor);
1136
+ }
1137
+ }
1138
+
1139
+ // src/commands/snippet/update.ts
1140
+ async function updateSnippetAction(snippetId, options, ctx) {
1141
+ try {
1142
+ let content;
1143
+ if (!process.stdin.isTTY) {
1144
+ const chunks = [];
1145
+ for await (const chunk of process.stdin) {
1146
+ chunks.push(chunk);
1147
+ }
1148
+ content = Buffer.concat(chunks).toString("utf-8");
1149
+ if (content.endsWith("\n")) {
1150
+ content = content.slice(0, -1);
1151
+ }
1152
+ if (!content) {
1153
+ content = void 0;
1154
+ }
1155
+ }
1156
+ if (!content && !options.title) {
1157
+ throw new CLIError(
1158
+ "No content or title provided",
1159
+ 1,
1160
+ "Pipe content to this command: echo 'content' | pnote snippet update <snippet-id>"
1161
+ );
1162
+ }
1163
+ const data = {};
1164
+ if (content) data.content = content;
1165
+ if (options.title) data.title = options.title;
1166
+ const result = await updateSnippet(
1167
+ snippetId,
1168
+ data,
1169
+ { pin: ctx.pin }
1170
+ );
1171
+ if (ctx.json) {
1172
+ outputJson(result);
1173
+ } else {
1174
+ outputMessage("Updated snippet", ctx);
1175
+ }
1176
+ } catch (error) {
1177
+ handleError(error, ctx.noColor);
1178
+ }
1179
+ }
1180
+
1181
+ // src/commands/snippet/favorite.ts
1182
+ async function favoriteSnippetAction(snippetId, ctx) {
1183
+ try {
1184
+ const result = await toggleFavorite(snippetId, { pin: ctx.pin });
1185
+ if (ctx.json) {
1186
+ outputJson(result);
1187
+ } else {
1188
+ const status = result.snippet.favorite ? "favorited" : "unfavorited";
1189
+ outputMessage(`Snippet ${status}`, ctx);
1190
+ }
1191
+ } catch (error) {
1192
+ handleError(error, ctx.noColor);
1193
+ }
1194
+ }
1195
+
1004
1196
  // src/lib/pin.ts
1005
1197
  import { createInterface as createInterface2 } from "readline";
1006
1198
  import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, statSync } from "fs";
1007
1199
  import { tmpdir } from "os";
1008
1200
  import { join as join2 } from "path";
1009
- import pc8 from "picocolors";
1201
+ import pc9 from "picocolors";
1010
1202
  var PIN_CACHE_TTL_MS = 5 * 60 * 1e3;
1011
1203
  var PIN_MAX_ATTEMPTS = 3;
1012
1204
  function getPinCachePath() {
@@ -1059,12 +1251,12 @@ async function promptForPin(noteTitle, hint, maxAttempts = PIN_MAX_ATTEMPTS) {
1059
1251
  return null;
1060
1252
  }
1061
1253
  if (noteTitle) {
1062
- console.error(pc8.yellow("\u{1F512}") + ` Note "${noteTitle}" is PIN-protected.`);
1254
+ console.error(pc9.yellow("\u{1F512}") + ` Note "${noteTitle}" is PIN-protected.`);
1063
1255
  } else {
1064
- console.error(pc8.yellow("\u{1F512}") + " This action requires your PIN.");
1256
+ console.error(pc9.yellow("\u{1F512}") + " This action requires your PIN.");
1065
1257
  }
1066
1258
  if (hint) {
1067
- console.error(pc8.dim(`Hint: ${hint}`));
1259
+ console.error(pc9.dim(`Hint: ${hint}`));
1068
1260
  }
1069
1261
  for (let attempt = 1; attempt <= maxAttempts; attempt++) {
1070
1262
  const pin = await readPinHidden(
@@ -1074,10 +1266,10 @@ async function promptForPin(noteTitle, hint, maxAttempts = PIN_MAX_ATTEMPTS) {
1074
1266
  return pin;
1075
1267
  }
1076
1268
  if (attempt < maxAttempts) {
1077
- console.error(pc8.red("Invalid or empty PIN. Try again."));
1269
+ console.error(pc9.red("Invalid or empty PIN. Try again."));
1078
1270
  }
1079
1271
  }
1080
- console.error(pc8.red("Too many attempts."));
1272
+ console.error(pc9.red("Too many attempts."));
1081
1273
  return null;
1082
1274
  }
1083
1275
  async function readPinHidden(prompt) {
@@ -1154,8 +1346,71 @@ async function resolvePin(options) {
1154
1346
  return null;
1155
1347
  }
1156
1348
 
1157
- // src/commands/notes/index.ts
1349
+ // src/commands/notes/snippetCmd.ts
1158
1350
  async function buildContext(globalOpts) {
1351
+ const pin = await resolvePin({
1352
+ pinArg: globalOpts.pin,
1353
+ pinFromStdin: globalOpts.pinStdin,
1354
+ skipPrompt: true
1355
+ });
1356
+ return {
1357
+ json: globalOpts.json ?? false,
1358
+ noColor: globalOpts.noColor ?? false,
1359
+ plain: globalOpts.plain ?? false,
1360
+ pin: pin ?? void 0
1361
+ };
1362
+ }
1363
+ 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) => {
1364
+ const globalOpts = cmd.parent?.parent?.opts() || {};
1365
+ const ctx = await buildContext(globalOpts);
1366
+ if (noteId) {
1367
+ await showSnippetAction(noteId, {}, ctx);
1368
+ } else {
1369
+ cmd.help();
1370
+ }
1371
+ }).addHelpText(
1372
+ "after",
1373
+ `
1374
+ Examples:
1375
+ $ pnote notes snippet abc123 Show latest snippet (pipe-friendly)
1376
+ $ pnote notes snippet list abc123 Show all versions
1377
+ $ pnote notes snippet copy abc123 Copy to clipboard
1378
+ $ cat file.txt | pnote notes snippet add abc123
1379
+ `
1380
+ );
1381
+ notesSnippetCommand.command("list").description("List all snippet versions for a note").argument("<note-id>", "Note ID").action(async (noteId, _options, cmd) => {
1382
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1383
+ const ctx = await buildContext(globalOpts);
1384
+ try {
1385
+ const result = await getNote(noteId, { pin: ctx.pin });
1386
+ outputSnippets(result.snippets || [], ctx);
1387
+ } catch (error) {
1388
+ handleError(error, ctx.noColor);
1389
+ }
1390
+ });
1391
+ 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) => {
1392
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1393
+ const ctx = await buildContext(globalOpts);
1394
+ await copySnippetAction(noteId, options, ctx);
1395
+ });
1396
+ 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) => {
1397
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1398
+ const ctx = await buildContext(globalOpts);
1399
+ await addSnippetAction(noteId, options, ctx);
1400
+ });
1401
+ 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) => {
1402
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1403
+ const ctx = await buildContext(globalOpts);
1404
+ await updateSnippetAction(snippetId, options, ctx);
1405
+ });
1406
+ notesSnippetCommand.command("favorite").description("Toggle favorite status on a snippet").argument("<snippet-id>", "Snippet ID").action(async (snippetId, _options, cmd) => {
1407
+ const globalOpts = cmd.parent?.parent?.parent?.opts() || {};
1408
+ const ctx = await buildContext(globalOpts);
1409
+ await favoriteSnippetAction(snippetId, ctx);
1410
+ });
1411
+
1412
+ // src/commands/notes/index.ts
1413
+ async function buildContext2(globalOpts) {
1159
1414
  const pin = await resolvePin({
1160
1415
  pinArg: globalOpts.pin,
1161
1416
  pinFromStdin: globalOpts.pinStdin,
@@ -1169,243 +1424,61 @@ async function buildContext(globalOpts) {
1169
1424
  pin: pin ?? void 0
1170
1425
  };
1171
1426
  }
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) => {
1427
+ 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("--search <query>", "Search notes by title").option("--limit <n>", "Limit number of results", "50").action(async (options, cmd) => {
1173
1428
  const globalOpts = cmd.parent?.opts() || {};
1174
- const ctx = await buildContext(globalOpts);
1429
+ const ctx = await buildContext2(globalOpts);
1175
1430
  await listNotesAction(options, ctx);
1176
1431
  }).addHelpText(
1177
1432
  "after",
1178
1433
  `
1179
1434
  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"
1435
+ $ pnote notes List all active notes
1436
+ $ pnote notes --tag "AI/art" Filter by tag
1437
+ $ pnote notes --archived Show archived notes
1438
+ $ pnote notes --pinned Show only pinned notes
1439
+ $ pnote notes get abc123 Get note with all snippet versions
1440
+ $ pnote notes get abc123 --latest Get note with only latest snippet
1441
+ $ pnote notes create "My Note" Create a new note
1442
+ $ pnote notes snippet abc123 Show latest snippet (pipe-friendly)
1443
+ $ pnote notes snippet list abc123 Show all snippet versions
1444
+ $ cat file.txt | pnote notes snippet add abc123
1187
1445
  `
1188
1446
  );
1189
- notesCommand.command("get").description("Get a note with its latest snippet").argument("<id>", "Note ID").action(async (id, _options, cmd) => {
1447
+ 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) => {
1190
1448
  const globalOpts = cmd.parent?.parent?.opts() || {};
1191
- const ctx = await buildContext(globalOpts);
1192
- await getNoteAction(id, ctx);
1449
+ const ctx = await buildContext2(globalOpts);
1450
+ await getNoteAction(id, options, ctx);
1193
1451
  });
1194
1452
  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
1453
  const globalOpts = cmd.parent?.parent?.opts() || {};
1196
- const ctx = await buildContext(globalOpts);
1454
+ const ctx = await buildContext2(globalOpts);
1197
1455
  await createNoteAction(title, options, ctx);
1198
1456
  });
1199
1457
  notesCommand.command("archive").description("Archive or unarchive a note").argument("<id>", "Note ID").option("--undo", "Unarchive the note").action(async (id, options, cmd) => {
1200
1458
  const globalOpts = cmd.parent?.parent?.opts() || {};
1201
- const ctx = await buildContext(globalOpts);
1459
+ const ctx = await buildContext2(globalOpts);
1202
1460
  await archiveNoteAction(id, options, ctx);
1203
1461
  });
1204
1462
  notesCommand.command("pin").description("Pin or unpin a note").argument("<id>", "Note ID").action(async (id, _options, cmd) => {
1205
1463
  const globalOpts = cmd.parent?.parent?.opts() || {};
1206
- const ctx = await buildContext(globalOpts);
1464
+ const ctx = await buildContext2(globalOpts);
1207
1465
  await pinNoteAction(id, ctx);
1208
1466
  });
1209
1467
  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
1468
  const globalOpts = cmd.parent?.parent?.opts() || {};
1211
- const ctx = await buildContext(globalOpts);
1469
+ const ctx = await buildContext2(globalOpts);
1212
1470
  await updateNoteAction(id, options, ctx);
1213
1471
  });
1214
1472
  notesCommand.command("delete").description("Delete a note (soft delete)").argument("<id>", "Note ID").option("--force", "Skip confirmation").action(async (id, options, cmd) => {
1215
1473
  const globalOpts = cmd.parent?.parent?.opts() || {};
1216
- const ctx = await buildContext(globalOpts);
1474
+ const ctx = await buildContext2(globalOpts);
1217
1475
  await deleteNoteAction(id, options, ctx);
1218
1476
  });
1477
+ notesCommand.addCommand(notesSnippetCommand);
1219
1478
 
1220
1479
  // src/commands/snippet/index.ts
1221
- import { Command as Command3 } from "commander";
1222
-
1223
- // src/commands/snippet/show.ts
1224
- async function showSnippetAction(noteId, options, ctx) {
1225
- try {
1226
- if (options.all) {
1227
- const result = await listSnippets(
1228
- { note_id: noteId, limit: 100 },
1229
- { pin: ctx.pin }
1230
- );
1231
- outputSnippets(result.snippets, ctx);
1232
- } else {
1233
- const result = await getNote(noteId, { pin: ctx.pin });
1234
- if (result.latest_snippet) {
1235
- outputSnippet(result.latest_snippet, ctx);
1236
- } else {
1237
- if (!ctx.json) {
1238
- console.error("No snippet content found.");
1239
- }
1240
- process.exit(3);
1241
- }
1242
- }
1243
- } catch (error) {
1244
- handleError(error, ctx.noColor);
1245
- }
1246
- }
1247
-
1248
- // src/commands/snippet/copy.ts
1249
- import pc9 from "picocolors";
1250
-
1251
- // src/lib/clipboard.ts
1252
- import { default as clipboardy } from "clipboardy";
1253
- async function copyToClipboard(text) {
1254
- try {
1255
- await clipboardy.write(text);
1256
- } catch (error) {
1257
- if (error instanceof Error && error.message.includes("xsel")) {
1258
- throw new CLIError(
1259
- "Clipboard not available. Install xsel or xclip on Linux.",
1260
- 1,
1261
- "Content will be printed to stdout instead."
1262
- );
1263
- }
1264
- throw new CLIError("Failed to copy to clipboard");
1265
- }
1266
- }
1267
-
1268
- // src/commands/snippet/copy.ts
1269
- async function copySnippetAction(noteId, options, ctx) {
1270
- try {
1271
- let snippet = null;
1272
- if (options.version) {
1273
- const versionNum = parseInt(options.version, 10);
1274
- if (isNaN(versionNum)) {
1275
- throw new CLIError("Invalid version number");
1276
- }
1277
- const result = await listSnippets(
1278
- { note_id: noteId, limit: 100 },
1279
- { pin: ctx.pin }
1280
- );
1281
- snippet = result.snippets.find((s) => s.version === versionNum) || null;
1282
- if (!snippet) {
1283
- throw new CLIError(`Version ${versionNum} not found`);
1284
- }
1285
- } else {
1286
- const result = await getNote(noteId, { pin: ctx.pin });
1287
- snippet = result.latest_snippet;
1288
- }
1289
- if (!snippet) {
1290
- throw new CLIError("No snippet content found");
1291
- }
1292
- try {
1293
- await copyToClipboard(snippet.content);
1294
- logStatus("Copied to clipboard");
1295
- if (ctx.json) {
1296
- console.log(JSON.stringify({ copied: true, content: snippet.content }));
1297
- }
1298
- } catch (clipboardError) {
1299
- if (!ctx.json) {
1300
- console.error(pc9.yellow("Clipboard not available, printing to stdout:"));
1301
- console.error("");
1302
- }
1303
- console.log(snippet.content);
1304
- }
1305
- } catch (error) {
1306
- handleError(error, ctx.noColor);
1307
- }
1308
- }
1309
-
1310
- // src/commands/snippet/add.ts
1311
- async function addSnippetAction(noteId, options, ctx) {
1312
- try {
1313
- let content;
1314
- if (process.stdin.isTTY) {
1315
- throw new CLIError(
1316
- "No content provided",
1317
- 1,
1318
- "Pipe content to this command: echo 'content' | pnote snippet add <note-id>"
1319
- );
1320
- }
1321
- const chunks = [];
1322
- for await (const chunk of process.stdin) {
1323
- chunks.push(chunk);
1324
- }
1325
- content = Buffer.concat(chunks).toString("utf-8");
1326
- if (content.endsWith("\n")) {
1327
- content = content.slice(0, -1);
1328
- }
1329
- if (!content) {
1330
- throw new CLIError("Empty content provided");
1331
- }
1332
- const result = await createSnippet(
1333
- {
1334
- note_id: noteId,
1335
- content,
1336
- title: options.title
1337
- },
1338
- { pin: ctx.pin }
1339
- );
1340
- if (ctx.json) {
1341
- outputJson(result);
1342
- } else {
1343
- outputMessage(`Created snippet v${result.snippet.version}`, ctx);
1344
- }
1345
- } catch (error) {
1346
- handleError(error, ctx.noColor);
1347
- }
1348
- }
1349
-
1350
- // src/commands/snippet/update.ts
1351
- async function updateSnippetAction(snippetId, options, ctx) {
1352
- try {
1353
- let content;
1354
- if (!process.stdin.isTTY) {
1355
- const chunks = [];
1356
- for await (const chunk of process.stdin) {
1357
- chunks.push(chunk);
1358
- }
1359
- content = Buffer.concat(chunks).toString("utf-8");
1360
- if (content.endsWith("\n")) {
1361
- content = content.slice(0, -1);
1362
- }
1363
- if (!content) {
1364
- content = void 0;
1365
- }
1366
- }
1367
- if (!content && !options.title) {
1368
- throw new CLIError(
1369
- "No content or title provided",
1370
- 1,
1371
- "Pipe content to this command: echo 'content' | pnote snippet update <snippet-id>"
1372
- );
1373
- }
1374
- const data = {};
1375
- if (content) data.content = content;
1376
- if (options.title) data.title = options.title;
1377
- const result = await updateSnippet(
1378
- snippetId,
1379
- data,
1380
- { pin: ctx.pin }
1381
- );
1382
- if (ctx.json) {
1383
- outputJson(result);
1384
- } else {
1385
- outputMessage("Updated snippet", ctx);
1386
- }
1387
- } catch (error) {
1388
- handleError(error, ctx.noColor);
1389
- }
1390
- }
1391
-
1392
- // src/commands/snippet/favorite.ts
1393
- async function favoriteSnippetAction(snippetId, ctx) {
1394
- try {
1395
- const result = await toggleFavorite(snippetId, { pin: ctx.pin });
1396
- if (ctx.json) {
1397
- outputJson(result);
1398
- } else {
1399
- const status = result.snippet.favorite ? "favorited" : "unfavorited";
1400
- outputMessage(`Snippet ${status}`, ctx);
1401
- }
1402
- } catch (error) {
1403
- handleError(error, ctx.noColor);
1404
- }
1405
- }
1406
-
1407
- // src/commands/snippet/index.ts
1408
- async function buildContext2(globalOpts) {
1480
+ import { Command as Command4 } from "commander";
1481
+ async function buildContext3(globalOpts) {
1409
1482
  const pin = await resolvePin({
1410
1483
  pinArg: globalOpts.pin,
1411
1484
  pinFromStdin: globalOpts.pinStdin,
@@ -1418,9 +1491,12 @@ async function buildContext2(globalOpts) {
1418
1491
  pin: pin ?? void 0
1419
1492
  };
1420
1493
  }
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) => {
1494
+ var DEPRECATION_NOTICE = '\nDeprecated: "pnote snippet" is deprecated. Use "pnote notes snippet" instead.\n\n';
1495
+ 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", () => {
1496
+ process.stderr.write(DEPRECATION_NOTICE);
1497
+ }).action(async (noteId, options, cmd) => {
1422
1498
  const globalOpts = cmd.parent?.opts() || {};
1423
- const ctx = await buildContext2(globalOpts);
1499
+ const ctx = await buildContext3(globalOpts);
1424
1500
  if (noteId) {
1425
1501
  await showSnippetAction(noteId, options, ctx);
1426
1502
  } else {
@@ -1438,27 +1514,27 @@ Examples:
1438
1514
  );
1439
1515
  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
1516
  const globalOpts = cmd.parent?.parent?.opts() || {};
1441
- const ctx = await buildContext2(globalOpts);
1517
+ const ctx = await buildContext3(globalOpts);
1442
1518
  await copySnippetAction(noteId, options, ctx);
1443
1519
  });
1444
1520
  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
1521
  const globalOpts = cmd.parent?.parent?.opts() || {};
1446
- const ctx = await buildContext2(globalOpts);
1522
+ const ctx = await buildContext3(globalOpts);
1447
1523
  await addSnippetAction(noteId, options, ctx);
1448
1524
  });
1449
1525
  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
1526
  const globalOpts = cmd.parent?.parent?.opts() || {};
1451
- const ctx = await buildContext2(globalOpts);
1527
+ const ctx = await buildContext3(globalOpts);
1452
1528
  await updateSnippetAction(snippetId, options, ctx);
1453
1529
  });
1454
1530
  snippetCommand.command("favorite").description("Toggle favorite status on a snippet").argument("<snippet-id>", "Snippet ID").action(async (snippetId, _options, cmd) => {
1455
1531
  const globalOpts = cmd.parent?.parent?.opts() || {};
1456
- const ctx = await buildContext2(globalOpts);
1532
+ const ctx = await buildContext3(globalOpts);
1457
1533
  await favoriteSnippetAction(snippetId, ctx);
1458
1534
  });
1459
1535
 
1460
1536
  // src/commands/tags/index.ts
1461
- import { Command as Command4 } from "commander";
1537
+ import { Command as Command5 } from "commander";
1462
1538
 
1463
1539
  // src/commands/tags/list.ts
1464
1540
  async function listTagsAction(options, ctx) {
@@ -1521,7 +1597,7 @@ async function mergeTagsAction(sourceTags, targetTag, ctx) {
1521
1597
  }
1522
1598
 
1523
1599
  // src/commands/tags/index.ts
1524
- async function buildContext3(globalOpts) {
1600
+ async function buildContext4(globalOpts) {
1525
1601
  const pin = await resolvePin({
1526
1602
  pinArg: globalOpts.pin,
1527
1603
  pinFromStdin: globalOpts.pinStdin,
@@ -1534,9 +1610,9 @@ async function buildContext3(globalOpts) {
1534
1610
  pin: pin ?? void 0
1535
1611
  };
1536
1612
  }
1537
- var tagsCommand = new Command4("tags").description("List and manage tags").option("--include-archived", "Include tags from archived notes").action(async (options, cmd) => {
1613
+ var tagsCommand = new Command5("tags").description("List and manage tags").option("--include-archived", "Include tags from archived notes").action(async (options, cmd) => {
1538
1614
  const globalOpts = cmd.parent?.opts() || {};
1539
- const ctx = await buildContext3(globalOpts);
1615
+ const ctx = await buildContext4(globalOpts);
1540
1616
  await listTagsAction(options, ctx);
1541
1617
  }).addHelpText(
1542
1618
  "after",
@@ -1549,17 +1625,17 @@ Examples:
1549
1625
  );
1550
1626
  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
1627
  const globalOpts = cmd.parent?.parent?.opts() || {};
1552
- const ctx = await buildContext3(globalOpts);
1628
+ const ctx = await buildContext4(globalOpts);
1553
1629
  await renameTagAction(oldTag, newTag, ctx);
1554
1630
  });
1555
1631
  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
1632
  const globalOpts = cmd.parent?.parent?.opts() || {};
1557
- const ctx = await buildContext3(globalOpts);
1633
+ const ctx = await buildContext4(globalOpts);
1558
1634
  await mergeTagsAction(sourceTags, options.into, ctx);
1559
1635
  });
1560
1636
 
1561
1637
  // src/commands/search.ts
1562
- import { Command as Command5 } from "commander";
1638
+ import { Command as Command6 } from "commander";
1563
1639
  async function searchAction(query, options, ctx) {
1564
1640
  try {
1565
1641
  let search_notes = true;
@@ -1585,7 +1661,7 @@ async function searchAction(query, options, ctx) {
1585
1661
  handleError(error, ctx.noColor);
1586
1662
  }
1587
1663
  }
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) => {
1664
+ 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
1665
  const globalOpts = cmd.parent?.opts() || {};
1590
1666
  const pin = await resolvePin({
1591
1667
  pinArg: globalOpts.pin,
@@ -1610,7 +1686,7 @@ Examples:
1610
1686
  );
1611
1687
 
1612
1688
  // src/commands/share/index.ts
1613
- import { Command as Command6 } from "commander";
1689
+ import { Command as Command7 } from "commander";
1614
1690
 
1615
1691
  // src/commands/share/tags.ts
1616
1692
  async function listSharedTagsAction(ctx) {
@@ -1648,7 +1724,7 @@ async function listSharedNotesAction(options, ctx) {
1648
1724
  }
1649
1725
 
1650
1726
  // src/commands/share/index.ts
1651
- async function buildContext4(globalOpts) {
1727
+ async function buildContext5(globalOpts) {
1652
1728
  const pin = await resolvePin({
1653
1729
  pinArg: globalOpts.pin,
1654
1730
  pinFromStdin: globalOpts.pinStdin,
@@ -1661,7 +1737,7 @@ async function buildContext4(globalOpts) {
1661
1737
  pin: pin ?? void 0
1662
1738
  };
1663
1739
  }
1664
- var shareCommand = new Command6("share").description("View shared tags and shared note links").addHelpText(
1740
+ var shareCommand = new Command7("share").description("View shared tags and shared note links").addHelpText(
1665
1741
  "after",
1666
1742
  `
1667
1743
  Examples:
@@ -1673,7 +1749,7 @@ Examples:
1673
1749
  );
1674
1750
  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
1751
  const globalOpts = cmd.parent?.parent?.opts() || {};
1676
- const ctx = await buildContext4(globalOpts);
1752
+ const ctx = await buildContext5(globalOpts);
1677
1753
  if (id) {
1678
1754
  await getSharedTagNotesAction(id, options, ctx);
1679
1755
  } else {
@@ -1682,12 +1758,12 @@ shareCommand.command("tags").description("List shared tags or view notes in a sh
1682
1758
  });
1683
1759
  shareCommand.command("notes").description("List public share links for your notes").option("--include-revoked", "Include revoked share links").action(async (options, cmd) => {
1684
1760
  const globalOpts = cmd.parent?.parent?.opts() || {};
1685
- const ctx = await buildContext4(globalOpts);
1761
+ const ctx = await buildContext5(globalOpts);
1686
1762
  await listSharedNotesAction(options, ctx);
1687
1763
  });
1688
1764
 
1689
1765
  // src/commands/skills/index.ts
1690
- import { Command as Command7 } from "commander";
1766
+ import { Command as Command8 } from "commander";
1691
1767
 
1692
1768
  // src/commands/skills/list.ts
1693
1769
  import pc10 from "picocolors";
@@ -1886,7 +1962,7 @@ function collectFiles(baseDir, currentDir) {
1886
1962
  }
1887
1963
 
1888
1964
  // src/commands/skills/index.ts
1889
- async function buildContext5(globalOpts) {
1965
+ async function buildContext6(globalOpts) {
1890
1966
  const pin = await resolvePin({
1891
1967
  pinArg: globalOpts.pin,
1892
1968
  pinFromStdin: globalOpts.pinStdin,
@@ -1899,9 +1975,9 @@ async function buildContext5(globalOpts) {
1899
1975
  pin: pin ?? void 0
1900
1976
  };
1901
1977
  }
1902
- var skillsCommand = new Command7("skills").description("Manage agent skills (sync between cloud and local)").action(async (_options, cmd) => {
1978
+ var skillsCommand = new Command8("skills").description("Manage agent skills (sync between cloud and local)").action(async (_options, cmd) => {
1903
1979
  const globalOpts = cmd.parent?.opts() || {};
1904
- const ctx = await buildContext5(globalOpts);
1980
+ const ctx = await buildContext6(globalOpts);
1905
1981
  await listSkillsAction(ctx);
1906
1982
  }).addHelpText(
1907
1983
  "after",
@@ -1918,18 +1994,117 @@ They sync to ~/.claude/skills/<skill-name>/<filename> for use with Claude Code.
1918
1994
  );
1919
1995
  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
1996
  const globalOpts = cmd.parent?.parent?.opts() || {};
1921
- const ctx = await buildContext5(globalOpts);
1997
+ const ctx = await buildContext6(globalOpts);
1922
1998
  await pullSkillsAction(skillName, options, ctx);
1923
1999
  });
1924
2000
  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
2001
  const globalOpts = cmd.parent?.parent?.opts() || {};
1926
- const ctx = await buildContext5(globalOpts);
2002
+ const ctx = await buildContext6(globalOpts);
1927
2003
  await pushSkillsAction(dir, options, ctx);
1928
2004
  });
1929
2005
 
2006
+ // src/lib/update-check.ts
2007
+ import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
2008
+ import { join as join5 } from "path";
2009
+ import pc13 from "picocolors";
2010
+ var CACHE_FILE = "update-check.json";
2011
+ var CHECK_INTERVAL = 60 * 60 * 1e3;
2012
+ var FETCH_TIMEOUT = 2e3;
2013
+ function getCachePath() {
2014
+ return join5(getConfigDir(), CACHE_FILE);
2015
+ }
2016
+ function readCache() {
2017
+ try {
2018
+ const path3 = getCachePath();
2019
+ if (!existsSync4(path3)) return null;
2020
+ return JSON.parse(readFileSync4(path3, "utf-8"));
2021
+ } catch {
2022
+ return null;
2023
+ }
2024
+ }
2025
+ function writeCache(cache) {
2026
+ try {
2027
+ const dir = getConfigDir();
2028
+ if (!existsSync4(dir)) {
2029
+ mkdirSync3(dir, { recursive: true, mode: 448 });
2030
+ }
2031
+ writeFileSync4(getCachePath(), JSON.stringify(cache), "utf-8");
2032
+ } catch {
2033
+ }
2034
+ }
2035
+ async function fetchLatestVersion(packageName) {
2036
+ try {
2037
+ const res = await fetch(
2038
+ `https://registry.npmjs.org/${packageName}/latest`,
2039
+ { signal: AbortSignal.timeout(FETCH_TIMEOUT) }
2040
+ );
2041
+ if (!res.ok) return null;
2042
+ const data = await res.json();
2043
+ return data.version ?? null;
2044
+ } catch {
2045
+ return null;
2046
+ }
2047
+ }
2048
+ function isOlderVersion(current, latest) {
2049
+ const parse = (v) => v.split(".").map(Number);
2050
+ const [cMaj, cMin, cPat] = parse(current);
2051
+ const [lMaj, lMin, lPat] = parse(latest);
2052
+ if (cMaj !== lMaj) return cMaj < lMaj;
2053
+ if (cMin !== lMin) return cMin < lMin;
2054
+ return cPat < lPat;
2055
+ }
2056
+ function printNotice(current, latest, packageName) {
2057
+ const useColor = process.stderr.isTTY && !process.env.NO_COLOR && process.env.TERM !== "dumb";
2058
+ const yellow = useColor ? pc13.yellow : (s) => s;
2059
+ const bold = useColor ? pc13.bold : (s) => s;
2060
+ const dim = useColor ? pc13.dim : (s) => s;
2061
+ const msg = `Update available: ${dim(current)} \u2192 ${bold(latest)}`;
2062
+ const cmd = `Run ${bold(`npm i -g ${packageName}`)} to update`;
2063
+ const lines = [msg, cmd];
2064
+ const strip = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
2065
+ const maxLen = Math.max(...lines.map((l) => strip(l).length));
2066
+ const pad2 = (s) => s + " ".repeat(maxLen - strip(s).length);
2067
+ const top = yellow("\u250C" + "\u2500".repeat(maxLen + 2) + "\u2510");
2068
+ const bot = yellow("\u2514" + "\u2500".repeat(maxLen + 2) + "\u2518");
2069
+ const row = (s) => yellow("\u2502") + " " + pad2(s) + " " + yellow("\u2502");
2070
+ const box = [
2071
+ "",
2072
+ top,
2073
+ row(lines[0]),
2074
+ row(lines[1]),
2075
+ bot,
2076
+ ""
2077
+ ].join("\n");
2078
+ process.stderr.write(box);
2079
+ }
2080
+ async function checkAndNotifyUpdate(currentVersion, packageName) {
2081
+ if (!process.stderr.isTTY) return;
2082
+ if (process.env.NO_UPDATE_NOTIFIER) return;
2083
+ if (process.env.CI) return;
2084
+ if (process.env.NODE_ENV === "test") return;
2085
+ try {
2086
+ const cache = readCache();
2087
+ let latestVersion = null;
2088
+ if (cache && Date.now() - cache.lastCheck < CHECK_INTERVAL) {
2089
+ latestVersion = cache.latestVersion;
2090
+ } else {
2091
+ latestVersion = await fetchLatestVersion(packageName);
2092
+ if (latestVersion) {
2093
+ writeCache({ lastCheck: Date.now(), latestVersion });
2094
+ } else if (cache) {
2095
+ latestVersion = cache.latestVersion;
2096
+ }
2097
+ }
2098
+ if (latestVersion && isOlderVersion(currentVersion, latestVersion)) {
2099
+ printNotice(currentVersion, latestVersion, packageName);
2100
+ }
2101
+ } catch {
2102
+ }
2103
+ }
2104
+
1930
2105
  // src/index.ts
1931
- var program = new Command8();
1932
- program.name("pnote").description("pnote - The PromptNote CLI").version("0.1.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({
2106
+ var program = new Command9();
2107
+ program.name("pnote").description("pnote - The PromptNote CLI").version("0.3.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({
1933
2108
  sortSubcommands: true,
1934
2109
  sortOptions: true
1935
2110
  }).addHelpText(
@@ -1960,6 +2135,9 @@ program.addCommand(tagsCommand);
1960
2135
  program.addCommand(searchCommand);
1961
2136
  program.addCommand(shareCommand);
1962
2137
  program.addCommand(skillsCommand);
2138
+ program.hook("preAction", async () => {
2139
+ await checkAndNotifyUpdate("0.3.0", "pnote");
2140
+ });
1963
2141
  process.on("SIGINT", () => {
1964
2142
  console.error("\nInterrupted");
1965
2143
  process.exit(130);