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 +430 -252
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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
|
|
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,
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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(
|
|
1254
|
+
console.error(pc9.yellow("\u{1F512}") + ` Note "${noteTitle}" is PIN-protected.`);
|
|
1063
1255
|
} else {
|
|
1064
|
-
console.error(
|
|
1256
|
+
console.error(pc9.yellow("\u{1F512}") + " This action requires your PIN.");
|
|
1065
1257
|
}
|
|
1066
1258
|
if (hint) {
|
|
1067
|
-
console.error(
|
|
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(
|
|
1269
|
+
console.error(pc9.red("Invalid or empty PIN. Try again."));
|
|
1078
1270
|
}
|
|
1079
1271
|
}
|
|
1080
|
-
console.error(
|
|
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/
|
|
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
|
|
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
|
|
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
|
|
1181
|
-
$ pnote notes --tag "AI/art"
|
|
1182
|
-
$ pnote notes --archived
|
|
1183
|
-
$ pnote notes --pinned
|
|
1184
|
-
$ pnote notes get abc123
|
|
1185
|
-
$ pnote notes
|
|
1186
|
-
$ pnote notes
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1932
|
-
program.name("pnote").description("pnote - The PromptNote CLI").version("0.
|
|
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);
|