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 +531 -283
- 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 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
|
|
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,
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
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
|
-
|
|
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/
|
|
1005
|
-
import {
|
|
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
|
|
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(
|
|
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/
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
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
|
-
|
|
1422
|
-
const
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ?
|
|
1983
|
-
const bold = useColor ?
|
|
1984
|
-
const dim = useColor ?
|
|
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
|
|
2031
|
-
program.name("pnote").description("pnote - The PromptNote CLI").version("0.
|
|
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.
|
|
2311
|
+
await checkAndNotifyUpdate("0.4.0", "pnote");
|
|
2064
2312
|
});
|
|
2065
2313
|
process.on("SIGINT", () => {
|
|
2066
2314
|
console.error("\nInterrupted");
|