pnote 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -3
- package/dist/index.js +553 -121
- 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 Command8 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/lib/errors.ts
|
|
7
7
|
import pc from "picocolors";
|
|
@@ -105,12 +105,12 @@ function loadCredentials() {
|
|
|
105
105
|
created_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
106
106
|
};
|
|
107
107
|
}
|
|
108
|
-
const
|
|
109
|
-
if (!existsSync(
|
|
108
|
+
const path3 = getCredentialsPath();
|
|
109
|
+
if (!existsSync(path3)) {
|
|
110
110
|
return null;
|
|
111
111
|
}
|
|
112
112
|
try {
|
|
113
|
-
const content = readFileSync(
|
|
113
|
+
const content = readFileSync(path3, "utf-8");
|
|
114
114
|
return JSON.parse(content);
|
|
115
115
|
} catch {
|
|
116
116
|
return null;
|
|
@@ -118,14 +118,14 @@ function loadCredentials() {
|
|
|
118
118
|
}
|
|
119
119
|
function saveCredentials(credentials) {
|
|
120
120
|
const dir = ensureConfigDir();
|
|
121
|
-
const
|
|
122
|
-
writeFileSync(
|
|
123
|
-
chmodSync(
|
|
121
|
+
const path3 = join(dir, CREDENTIALS_FILE);
|
|
122
|
+
writeFileSync(path3, JSON.stringify(credentials, null, 2), "utf-8");
|
|
123
|
+
chmodSync(path3, 384);
|
|
124
124
|
}
|
|
125
125
|
function deleteCredentials() {
|
|
126
|
-
const
|
|
127
|
-
if (existsSync(
|
|
128
|
-
unlinkSync(
|
|
126
|
+
const path3 = getCredentialsPath();
|
|
127
|
+
if (existsSync(path3)) {
|
|
128
|
+
unlinkSync(path3);
|
|
129
129
|
return true;
|
|
130
130
|
}
|
|
131
131
|
return false;
|
|
@@ -142,12 +142,12 @@ function validateTokenFormat(token) {
|
|
|
142
142
|
}
|
|
143
143
|
var CONFIG_FILE = "config.json";
|
|
144
144
|
function loadConfig() {
|
|
145
|
-
const
|
|
146
|
-
if (!existsSync(
|
|
145
|
+
const path3 = join(getConfigDir(), CONFIG_FILE);
|
|
146
|
+
if (!existsSync(path3)) {
|
|
147
147
|
return {};
|
|
148
148
|
}
|
|
149
149
|
try {
|
|
150
|
-
const content = readFileSync(
|
|
150
|
+
const content = readFileSync(path3, "utf-8");
|
|
151
151
|
return JSON.parse(content);
|
|
152
152
|
} catch {
|
|
153
153
|
return {};
|
|
@@ -178,7 +178,7 @@ async function loginAction() {
|
|
|
178
178
|
console.log(" 3. Generate a new token");
|
|
179
179
|
console.log(" 4. Run: " + pc2.cyan("pnote auth token <your-token>"));
|
|
180
180
|
console.log("");
|
|
181
|
-
console.log(pc2.dim("Or set the
|
|
181
|
+
console.log(pc2.dim("Or set the PNOTE_TOKEN environment variable."));
|
|
182
182
|
}
|
|
183
183
|
|
|
184
184
|
// src/commands/auth/token.ts
|
|
@@ -189,7 +189,7 @@ function getRestApiBase() {
|
|
|
189
189
|
const mcpEndpoint = getApiEndpoint();
|
|
190
190
|
return mcpEndpoint.replace(/\/mcp$/, "/v1");
|
|
191
191
|
}
|
|
192
|
-
async function callRestApi(method,
|
|
192
|
+
async function callRestApi(method, path3, body, options = {}) {
|
|
193
193
|
const token = getToken();
|
|
194
194
|
if (!token) {
|
|
195
195
|
throw new AuthError(
|
|
@@ -198,7 +198,7 @@ async function callRestApi(method, path, body, options = {}) {
|
|
|
198
198
|
);
|
|
199
199
|
}
|
|
200
200
|
const baseUrl = getRestApiBase();
|
|
201
|
-
const url = `${baseUrl}${
|
|
201
|
+
const url = `${baseUrl}${path3}`;
|
|
202
202
|
const headers = {
|
|
203
203
|
"Content-Type": "application/json",
|
|
204
204
|
Authorization: `Bearer ${token}`
|
|
@@ -256,6 +256,7 @@ async function listNotes(params = {}, options = {}) {
|
|
|
256
256
|
if (params.pinned !== void 0) query.set("pinned", String(params.pinned));
|
|
257
257
|
if (params.deleted !== void 0) query.set("deleted", String(params.deleted));
|
|
258
258
|
if (params.protected !== void 0) query.set("protected", String(params.protected));
|
|
259
|
+
if (params.note_type) query.set("note_type", params.note_type);
|
|
259
260
|
if (params.search) query.set("search", params.search);
|
|
260
261
|
if (params.limit) query.set("limit", String(params.limit));
|
|
261
262
|
if (params.offset) query.set("offset", String(params.offset));
|
|
@@ -284,6 +285,9 @@ async function listSnippets(params, options = {}) {
|
|
|
284
285
|
async function createSnippet(data, options = {}) {
|
|
285
286
|
return callRestApi("POST", "/snippets", data, options);
|
|
286
287
|
}
|
|
288
|
+
async function updateSnippet(id, data, options = {}) {
|
|
289
|
+
return callRestApi("PATCH", `/snippets/${id}`, data, options);
|
|
290
|
+
}
|
|
287
291
|
async function toggleFavorite(id, options = {}) {
|
|
288
292
|
return callRestApi("POST", `/snippets/${id}/favorite`, {}, options);
|
|
289
293
|
}
|
|
@@ -307,93 +311,20 @@ async function search(params, options = {}) {
|
|
|
307
311
|
if (params.limit) query.set("limit", String(params.limit));
|
|
308
312
|
return callRestApi("GET", `/search?${query.toString()}`, void 0, options);
|
|
309
313
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
return ++requestId;
|
|
314
|
+
async function listSharedTags(options = {}) {
|
|
315
|
+
return callRestApi("GET", "/share/tags", void 0, options);
|
|
313
316
|
}
|
|
314
|
-
async function
|
|
315
|
-
const
|
|
316
|
-
if (
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
const
|
|
324
|
-
|
|
325
|
-
id: getNextId(),
|
|
326
|
-
method: "tools/call",
|
|
327
|
-
params: {
|
|
328
|
-
name: toolName,
|
|
329
|
-
arguments: args
|
|
330
|
-
}
|
|
331
|
-
};
|
|
332
|
-
let response;
|
|
333
|
-
try {
|
|
334
|
-
response = await fetch(endpoint, {
|
|
335
|
-
method: "POST",
|
|
336
|
-
headers: {
|
|
337
|
-
"Content-Type": "application/json",
|
|
338
|
-
Authorization: `Bearer ${token}`
|
|
339
|
-
},
|
|
340
|
-
body: JSON.stringify(request),
|
|
341
|
-
signal: AbortSignal.timeout(3e4)
|
|
342
|
-
});
|
|
343
|
-
} catch (error) {
|
|
344
|
-
if (error instanceof Error) {
|
|
345
|
-
if (error.name === "TimeoutError" || error.name === "AbortError") {
|
|
346
|
-
throw new NetworkError("Request timed out. Please try again.");
|
|
347
|
-
}
|
|
348
|
-
if (error.message.includes("fetch")) {
|
|
349
|
-
throw new NetworkError("Unable to connect to PromptNote API. Check your internet connection.");
|
|
350
|
-
}
|
|
351
|
-
}
|
|
352
|
-
throw new NetworkError();
|
|
353
|
-
}
|
|
354
|
-
if (!response.ok) {
|
|
355
|
-
const body = await response.text();
|
|
356
|
-
if (response.status === 401) {
|
|
357
|
-
throw new AuthError(
|
|
358
|
-
"Invalid or expired token",
|
|
359
|
-
"Run 'pnote auth login' to re-authenticate"
|
|
360
|
-
);
|
|
361
|
-
}
|
|
362
|
-
if (response.status === 403) {
|
|
363
|
-
throw new CLIError("Permission denied", ExitCode.AUTH_ERROR);
|
|
364
|
-
}
|
|
365
|
-
throw new CLIError(`API error (${response.status}): ${body}`);
|
|
366
|
-
}
|
|
367
|
-
const mcpResponse = await response.json();
|
|
368
|
-
if (mcpResponse.error) {
|
|
369
|
-
throw new CLIError(`MCP error: ${mcpResponse.error.message}`);
|
|
370
|
-
}
|
|
371
|
-
if (!mcpResponse.result) {
|
|
372
|
-
throw new CLIError("Invalid response from API");
|
|
373
|
-
}
|
|
374
|
-
const content = mcpResponse.result.content;
|
|
375
|
-
if (!content || content.length === 0) {
|
|
376
|
-
throw new CLIError("Empty response from API");
|
|
377
|
-
}
|
|
378
|
-
if (mcpResponse.result.isError) {
|
|
379
|
-
const errorText = content[0]?.text || "Unknown error";
|
|
380
|
-
if (errorText.includes("not found") || errorText.includes("PGRST116")) {
|
|
381
|
-
throw new CLIError("Resource not found", ExitCode.NOT_FOUND);
|
|
382
|
-
}
|
|
383
|
-
if (errorText.includes("Unauthorized")) {
|
|
384
|
-
throw new AuthError(errorText);
|
|
385
|
-
}
|
|
386
|
-
throw new CLIError(errorText);
|
|
387
|
-
}
|
|
388
|
-
const textContent = content[0]?.text;
|
|
389
|
-
if (!textContent) {
|
|
390
|
-
throw new CLIError("No content in response");
|
|
391
|
-
}
|
|
392
|
-
try {
|
|
393
|
-
return JSON.parse(textContent);
|
|
394
|
-
} catch {
|
|
395
|
-
return textContent;
|
|
396
|
-
}
|
|
317
|
+
async function getSharedTagNotes(id, params = {}, options = {}) {
|
|
318
|
+
const query = new URLSearchParams();
|
|
319
|
+
if (params.limit) query.set("limit", String(params.limit));
|
|
320
|
+
const queryString = query.toString();
|
|
321
|
+
return callRestApi("GET", `/share/tags/${id}/notes${queryString ? `?${queryString}` : ""}`, void 0, options);
|
|
322
|
+
}
|
|
323
|
+
async function listSharedNotes(params = {}, options = {}) {
|
|
324
|
+
const query = new URLSearchParams();
|
|
325
|
+
if (params.include_revoked !== void 0) query.set("include_revoked", String(params.include_revoked));
|
|
326
|
+
const queryString = query.toString();
|
|
327
|
+
return callRestApi("GET", `/share/notes${queryString ? `?${queryString}` : ""}`, void 0, options);
|
|
397
328
|
}
|
|
398
329
|
async function verifyToken(token) {
|
|
399
330
|
const baseUrl = getApiEndpoint().replace(/\/mcp$/, "/v1");
|
|
@@ -464,12 +395,12 @@ async function logoutAction() {
|
|
|
464
395
|
} else {
|
|
465
396
|
console.log(pc4.dim("No credentials to remove."));
|
|
466
397
|
}
|
|
467
|
-
if (process.env.
|
|
398
|
+
if (process.env.PNOTE_TOKEN) {
|
|
468
399
|
console.log("");
|
|
469
400
|
console.log(
|
|
470
|
-
pc4.yellow("Note:") + "
|
|
401
|
+
pc4.yellow("Note:") + " PNOTE_TOKEN environment variable is still set."
|
|
471
402
|
);
|
|
472
|
-
console.log(pc4.dim("Unset it with: unset
|
|
403
|
+
console.log(pc4.dim("Unset it with: unset PNOTE_TOKEN"));
|
|
473
404
|
}
|
|
474
405
|
}
|
|
475
406
|
|
|
@@ -487,9 +418,7 @@ async function whoamiAction() {
|
|
|
487
418
|
throw new AuthError("No credentials found");
|
|
488
419
|
}
|
|
489
420
|
try {
|
|
490
|
-
const result = await
|
|
491
|
-
limit: 1
|
|
492
|
-
});
|
|
421
|
+
const result = await listNotes({ limit: 1 });
|
|
493
422
|
console.log(pc5.green("\u2713") + " Authenticated");
|
|
494
423
|
console.log("");
|
|
495
424
|
if (creds.email) {
|
|
@@ -497,8 +426,8 @@ async function whoamiAction() {
|
|
|
497
426
|
}
|
|
498
427
|
console.log(pc5.dim("Token: ") + creds.token.slice(0, 7) + "..." + creds.token.slice(-4));
|
|
499
428
|
console.log(pc5.dim("Stored: ") + getCredentialsPath());
|
|
500
|
-
if (process.env.
|
|
501
|
-
console.log(pc5.dim("Source: ") + "
|
|
429
|
+
if (process.env.PNOTE_TOKEN) {
|
|
430
|
+
console.log(pc5.dim("Source: ") + "PNOTE_TOKEN environment variable");
|
|
502
431
|
} else {
|
|
503
432
|
console.log(pc5.dim("Source: ") + "credentials file");
|
|
504
433
|
}
|
|
@@ -779,6 +708,103 @@ function outputSearchResults(query, notes, snippets, ctx) {
|
|
|
779
708
|
}
|
|
780
709
|
}
|
|
781
710
|
}
|
|
711
|
+
function outputSharedTags(data, ctx) {
|
|
712
|
+
if (ctx.json) {
|
|
713
|
+
outputJson(data);
|
|
714
|
+
return;
|
|
715
|
+
}
|
|
716
|
+
const c = getColors(ctx);
|
|
717
|
+
const totalCount = data.owned.count + data.shared_with_me.count;
|
|
718
|
+
if (totalCount === 0) {
|
|
719
|
+
console.log(c.dim("No shared tags."));
|
|
720
|
+
return;
|
|
721
|
+
}
|
|
722
|
+
if (isHumanOutput(ctx)) {
|
|
723
|
+
if (data.owned.count > 0) {
|
|
724
|
+
console.log(c.bold(`Owned (${data.owned.count})`));
|
|
725
|
+
console.log("");
|
|
726
|
+
for (const tag of data.owned.tags) {
|
|
727
|
+
console.log(
|
|
728
|
+
" " + c.cyan(tag.id.slice(0, 8)) + " " + pad(tag.tag_path, 24) + " " + c.dim(formatRelativeTime(tag.created_at))
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
console.log("");
|
|
732
|
+
}
|
|
733
|
+
if (data.shared_with_me.count > 0) {
|
|
734
|
+
console.log(c.bold(`Shared with me (${data.shared_with_me.count})`));
|
|
735
|
+
console.log("");
|
|
736
|
+
for (const tag of data.shared_with_me.tags) {
|
|
737
|
+
console.log(
|
|
738
|
+
" " + c.cyan(tag.id.slice(0, 8)) + " " + pad(tag.tag_path, 24) + " " + c.dim("joined " + formatRelativeTime(tag.joined_at))
|
|
739
|
+
);
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
} else {
|
|
743
|
+
for (const tag of data.owned.tags) {
|
|
744
|
+
console.log(["owned", tag.id, tag.tag_path, tag.created_at].join(" "));
|
|
745
|
+
}
|
|
746
|
+
for (const tag of data.shared_with_me.tags) {
|
|
747
|
+
console.log(["shared", tag.id, tag.tag_path, tag.joined_at].join(" "));
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
function outputSharedTagNotes(data, ctx) {
|
|
752
|
+
if (ctx.json) {
|
|
753
|
+
outputJson(data);
|
|
754
|
+
return;
|
|
755
|
+
}
|
|
756
|
+
const c = getColors(ctx);
|
|
757
|
+
if (isHumanOutput(ctx)) {
|
|
758
|
+
const role = data.shared_tag.is_owner ? "owner" : "member";
|
|
759
|
+
console.log(c.bold(data.shared_tag.tag_path) + c.dim(` (${role}, ${data.members_count} members)`));
|
|
760
|
+
console.log("");
|
|
761
|
+
if (data.notes.length === 0) {
|
|
762
|
+
console.log(c.dim(" No notes in this shared tag."));
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
console.log(
|
|
766
|
+
" " + c.dim(pad("ID", 10)) + " " + c.dim(pad("TITLE", 24)) + " " + c.dim(pad("AUTHOR", 20)) + " " + c.dim("UPDATED")
|
|
767
|
+
);
|
|
768
|
+
for (const note of data.notes) {
|
|
769
|
+
const author = note.is_own ? "you" : note.author.email || "unknown";
|
|
770
|
+
console.log(
|
|
771
|
+
" " + pad(note.id.slice(0, 8), 10) + " " + pad(truncate(note.title, 24), 24) + " " + pad(truncate(author, 20), 20) + " " + formatRelativeTime(note.updated_at)
|
|
772
|
+
);
|
|
773
|
+
}
|
|
774
|
+
} else {
|
|
775
|
+
for (const note of data.notes) {
|
|
776
|
+
const author = note.is_own ? "you" : note.author.email || "unknown";
|
|
777
|
+
console.log([note.id, note.title, author, note.updated_at].join(" "));
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
function outputSharedNotes(data, ctx) {
|
|
782
|
+
if (ctx.json) {
|
|
783
|
+
outputJson(data);
|
|
784
|
+
return;
|
|
785
|
+
}
|
|
786
|
+
const c = getColors(ctx);
|
|
787
|
+
if (data.count === 0) {
|
|
788
|
+
console.log(c.dim("No shared notes."));
|
|
789
|
+
return;
|
|
790
|
+
}
|
|
791
|
+
if (isHumanOutput(ctx)) {
|
|
792
|
+
console.log(c.bold(`Shared Notes (${data.count})`));
|
|
793
|
+
console.log("");
|
|
794
|
+
for (const share of data.shares) {
|
|
795
|
+
const revoked = share.is_revoked ? c.red(" [R]") : "";
|
|
796
|
+
console.log(
|
|
797
|
+
" " + truncate(share.note_title, 20) + " " + c.cyan(share.share_url) + " " + c.dim(`${share.view_count} views`) + " " + c.dim(formatRelativeTime(share.created_at)) + revoked
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
} else {
|
|
801
|
+
for (const share of data.shares) {
|
|
802
|
+
console.log(
|
|
803
|
+
[share.note_title, share.share_url, share.view_count, share.is_revoked, share.created_at].join(" ")
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
}
|
|
782
808
|
function outputMessage(message, ctx) {
|
|
783
809
|
if (ctx.json) {
|
|
784
810
|
outputJson({ message });
|
|
@@ -851,7 +877,7 @@ async function createNoteAction(title, options, ctx) {
|
|
|
851
877
|
const result = await createNote(
|
|
852
878
|
{
|
|
853
879
|
title,
|
|
854
|
-
tags: options.
|
|
880
|
+
tags: options.tags && options.tags.length > 0 ? options.tags : void 0,
|
|
855
881
|
content
|
|
856
882
|
},
|
|
857
883
|
{ pin: ctx.pin }
|
|
@@ -911,17 +937,14 @@ async function pinNoteAction(id, ctx) {
|
|
|
911
937
|
import pc7 from "picocolors";
|
|
912
938
|
import { createInterface } from "readline";
|
|
913
939
|
async function confirm(message) {
|
|
914
|
-
if (!process.stdin.isTTY) {
|
|
915
|
-
return false;
|
|
916
|
-
}
|
|
917
940
|
const rl = createInterface({
|
|
918
941
|
input: process.stdin,
|
|
919
942
|
output: process.stdout
|
|
920
943
|
});
|
|
921
|
-
return new Promise((
|
|
944
|
+
return new Promise((resolve2) => {
|
|
922
945
|
rl.question(message + " [y/N] ", (answer) => {
|
|
923
946
|
rl.close();
|
|
924
|
-
|
|
947
|
+
resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
|
|
925
948
|
});
|
|
926
949
|
});
|
|
927
950
|
}
|
|
@@ -929,6 +952,13 @@ async function deleteNoteAction(id, options, ctx) {
|
|
|
929
952
|
try {
|
|
930
953
|
const note = await getNote(id, { pin: ctx.pin });
|
|
931
954
|
if (!options.force && !ctx.json) {
|
|
955
|
+
if (!process.stdin.isTTY) {
|
|
956
|
+
throw new CLIError(
|
|
957
|
+
"Cannot confirm deletion in non-interactive mode",
|
|
958
|
+
ExitCode.GENERAL_ERROR,
|
|
959
|
+
"Use --force for non-interactive deletion"
|
|
960
|
+
);
|
|
961
|
+
}
|
|
932
962
|
console.log(`About to delete: ${pc7.bold(note.title)}`);
|
|
933
963
|
const confirmed = await confirm("Are you sure?");
|
|
934
964
|
if (!confirmed) {
|
|
@@ -947,6 +977,30 @@ async function deleteNoteAction(id, options, ctx) {
|
|
|
947
977
|
}
|
|
948
978
|
}
|
|
949
979
|
|
|
980
|
+
// src/commands/notes/update.ts
|
|
981
|
+
async function updateNoteAction(id, options, ctx) {
|
|
982
|
+
try {
|
|
983
|
+
if (!options.title && !options.tags) {
|
|
984
|
+
throw new CLIError(
|
|
985
|
+
"No changes provided",
|
|
986
|
+
1,
|
|
987
|
+
"Use --title or --tags to specify what to update"
|
|
988
|
+
);
|
|
989
|
+
}
|
|
990
|
+
const data = {};
|
|
991
|
+
if (options.title) data.title = options.title;
|
|
992
|
+
if (options.tags) data.tags = options.tags;
|
|
993
|
+
const result = await updateNote(id, data, { pin: ctx.pin });
|
|
994
|
+
if (ctx.json) {
|
|
995
|
+
outputJson(result);
|
|
996
|
+
} else {
|
|
997
|
+
outputMessage(`Updated note: ${result.note.title}`, ctx);
|
|
998
|
+
}
|
|
999
|
+
} catch (error) {
|
|
1000
|
+
handleError(error, ctx.noColor);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
|
|
950
1004
|
// src/lib/pin.ts
|
|
951
1005
|
import { createInterface as createInterface2 } from "readline";
|
|
952
1006
|
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2, unlinkSync as unlinkSync2, statSync } from "fs";
|
|
@@ -1027,7 +1081,7 @@ async function promptForPin(noteTitle, hint, maxAttempts = PIN_MAX_ATTEMPTS) {
|
|
|
1027
1081
|
return null;
|
|
1028
1082
|
}
|
|
1029
1083
|
async function readPinHidden(prompt) {
|
|
1030
|
-
return new Promise((
|
|
1084
|
+
return new Promise((resolve2) => {
|
|
1031
1085
|
const rl = createInterface2({
|
|
1032
1086
|
input: process.stdin,
|
|
1033
1087
|
output: process.stderr,
|
|
@@ -1043,7 +1097,7 @@ async function readPinHidden(prompt) {
|
|
|
1043
1097
|
if (char === "\r" || char === "\n") {
|
|
1044
1098
|
process.stderr.write("\n");
|
|
1045
1099
|
cleanup();
|
|
1046
|
-
|
|
1100
|
+
resolve2(pin);
|
|
1047
1101
|
} else if (char === "") {
|
|
1048
1102
|
process.stderr.write("\n");
|
|
1049
1103
|
cleanup();
|
|
@@ -1129,6 +1183,7 @@ Examples:
|
|
|
1129
1183
|
$ pnote notes --pinned Show only pinned notes
|
|
1130
1184
|
$ pnote notes get abc123 Get note details
|
|
1131
1185
|
$ pnote notes create "My Note" Create a new note
|
|
1186
|
+
$ pnote notes update abc123 --title "New Title"
|
|
1132
1187
|
`
|
|
1133
1188
|
);
|
|
1134
1189
|
notesCommand.command("get").description("Get a note with its latest snippet").argument("<id>", "Note ID").action(async (id, _options, cmd) => {
|
|
@@ -1136,7 +1191,7 @@ notesCommand.command("get").description("Get a note with its latest snippet").ar
|
|
|
1136
1191
|
const ctx = await buildContext(globalOpts);
|
|
1137
1192
|
await getNoteAction(id, ctx);
|
|
1138
1193
|
});
|
|
1139
|
-
notesCommand.command("create").description("Create a new note").argument("<title>", "Note title").option("--
|
|
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) => {
|
|
1140
1195
|
const globalOpts = cmd.parent?.parent?.opts() || {};
|
|
1141
1196
|
const ctx = await buildContext(globalOpts);
|
|
1142
1197
|
await createNoteAction(title, options, ctx);
|
|
@@ -1151,6 +1206,11 @@ notesCommand.command("pin").description("Pin or unpin a note").argument("<id>",
|
|
|
1151
1206
|
const ctx = await buildContext(globalOpts);
|
|
1152
1207
|
await pinNoteAction(id, ctx);
|
|
1153
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
|
+
});
|
|
1154
1214
|
notesCommand.command("delete").description("Delete a note (soft delete)").argument("<id>", "Note ID").option("--force", "Skip confirmation").action(async (id, options, cmd) => {
|
|
1155
1215
|
const globalOpts = cmd.parent?.parent?.opts() || {};
|
|
1156
1216
|
const ctx = await buildContext(globalOpts);
|
|
@@ -1287,6 +1347,48 @@ async function addSnippetAction(noteId, options, ctx) {
|
|
|
1287
1347
|
}
|
|
1288
1348
|
}
|
|
1289
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
|
+
|
|
1290
1392
|
// src/commands/snippet/favorite.ts
|
|
1291
1393
|
async function favoriteSnippetAction(snippetId, ctx) {
|
|
1292
1394
|
try {
|
|
@@ -1344,6 +1446,11 @@ snippetCommand.command("add").description("Add a new snippet version (from stdin
|
|
|
1344
1446
|
const ctx = await buildContext2(globalOpts);
|
|
1345
1447
|
await addSnippetAction(noteId, options, ctx);
|
|
1346
1448
|
});
|
|
1449
|
+
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
|
+
const globalOpts = cmd.parent?.parent?.opts() || {};
|
|
1451
|
+
const ctx = await buildContext2(globalOpts);
|
|
1452
|
+
await updateSnippetAction(snippetId, options, ctx);
|
|
1453
|
+
});
|
|
1347
1454
|
snippetCommand.command("favorite").description("Toggle favorite status on a snippet").argument("<snippet-id>", "Snippet ID").action(async (snippetId, _options, cmd) => {
|
|
1348
1455
|
const globalOpts = cmd.parent?.parent?.opts() || {};
|
|
1349
1456
|
const ctx = await buildContext2(globalOpts);
|
|
@@ -1480,11 +1587,16 @@ async function searchAction(query, options, ctx) {
|
|
|
1480
1587
|
}
|
|
1481
1588
|
var searchCommand = new Command5("search").description("Search notes and snippets").argument("<query>", "Search query").option("--notes-only", "Only search note titles and tags").option("--snippets-only", "Only search snippet content").option("--limit <n>", "Limit results", "20").action(async (query, options, cmd) => {
|
|
1482
1589
|
const globalOpts = cmd.parent?.opts() || {};
|
|
1590
|
+
const pin = await resolvePin({
|
|
1591
|
+
pinArg: globalOpts.pin,
|
|
1592
|
+
pinFromStdin: globalOpts.pinStdin,
|
|
1593
|
+
skipPrompt: true
|
|
1594
|
+
});
|
|
1483
1595
|
const ctx = {
|
|
1484
1596
|
json: globalOpts.json ?? false,
|
|
1485
1597
|
noColor: globalOpts.noColor ?? false,
|
|
1486
1598
|
plain: globalOpts.plain ?? false,
|
|
1487
|
-
pin:
|
|
1599
|
+
pin: pin ?? void 0
|
|
1488
1600
|
};
|
|
1489
1601
|
await searchAction(query, options, ctx);
|
|
1490
1602
|
}).addHelpText(
|
|
@@ -1497,8 +1609,326 @@ Examples:
|
|
|
1497
1609
|
`
|
|
1498
1610
|
);
|
|
1499
1611
|
|
|
1612
|
+
// src/commands/share/index.ts
|
|
1613
|
+
import { Command as Command6 } from "commander";
|
|
1614
|
+
|
|
1615
|
+
// src/commands/share/tags.ts
|
|
1616
|
+
async function listSharedTagsAction(ctx) {
|
|
1617
|
+
try {
|
|
1618
|
+
const result = await listSharedTags({ pin: ctx.pin });
|
|
1619
|
+
outputSharedTags(result, ctx);
|
|
1620
|
+
} catch (error) {
|
|
1621
|
+
handleError(error, ctx.noColor);
|
|
1622
|
+
}
|
|
1623
|
+
}
|
|
1624
|
+
async function getSharedTagNotesAction(id, options, ctx) {
|
|
1625
|
+
try {
|
|
1626
|
+
const result = await getSharedTagNotes(
|
|
1627
|
+
id,
|
|
1628
|
+
{ limit: options.limit ? parseInt(options.limit, 10) : void 0 },
|
|
1629
|
+
{ pin: ctx.pin }
|
|
1630
|
+
);
|
|
1631
|
+
outputSharedTagNotes(result, ctx);
|
|
1632
|
+
} catch (error) {
|
|
1633
|
+
handleError(error, ctx.noColor);
|
|
1634
|
+
}
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/commands/share/notes.ts
|
|
1638
|
+
async function listSharedNotesAction(options, ctx) {
|
|
1639
|
+
try {
|
|
1640
|
+
const result = await listSharedNotes(
|
|
1641
|
+
{ include_revoked: options.includeRevoked },
|
|
1642
|
+
{ pin: ctx.pin }
|
|
1643
|
+
);
|
|
1644
|
+
outputSharedNotes(result, ctx);
|
|
1645
|
+
} catch (error) {
|
|
1646
|
+
handleError(error, ctx.noColor);
|
|
1647
|
+
}
|
|
1648
|
+
}
|
|
1649
|
+
|
|
1650
|
+
// src/commands/share/index.ts
|
|
1651
|
+
async function buildContext4(globalOpts) {
|
|
1652
|
+
const pin = await resolvePin({
|
|
1653
|
+
pinArg: globalOpts.pin,
|
|
1654
|
+
pinFromStdin: globalOpts.pinStdin,
|
|
1655
|
+
skipPrompt: true
|
|
1656
|
+
});
|
|
1657
|
+
return {
|
|
1658
|
+
json: globalOpts.json ?? false,
|
|
1659
|
+
noColor: globalOpts.noColor ?? false,
|
|
1660
|
+
plain: globalOpts.plain ?? false,
|
|
1661
|
+
pin: pin ?? void 0
|
|
1662
|
+
};
|
|
1663
|
+
}
|
|
1664
|
+
var shareCommand = new Command6("share").description("View shared tags and shared note links").addHelpText(
|
|
1665
|
+
"after",
|
|
1666
|
+
`
|
|
1667
|
+
Examples:
|
|
1668
|
+
$ pnote share tags List shared tags
|
|
1669
|
+
$ pnote share tags abc123 View notes in shared tag
|
|
1670
|
+
$ pnote share notes List public share links
|
|
1671
|
+
$ pnote share notes --include-revoked
|
|
1672
|
+
`
|
|
1673
|
+
);
|
|
1674
|
+
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
|
+
const globalOpts = cmd.parent?.parent?.opts() || {};
|
|
1676
|
+
const ctx = await buildContext4(globalOpts);
|
|
1677
|
+
if (id) {
|
|
1678
|
+
await getSharedTagNotesAction(id, options, ctx);
|
|
1679
|
+
} else {
|
|
1680
|
+
await listSharedTagsAction(ctx);
|
|
1681
|
+
}
|
|
1682
|
+
});
|
|
1683
|
+
shareCommand.command("notes").description("List public share links for your notes").option("--include-revoked", "Include revoked share links").action(async (options, cmd) => {
|
|
1684
|
+
const globalOpts = cmd.parent?.parent?.opts() || {};
|
|
1685
|
+
const ctx = await buildContext4(globalOpts);
|
|
1686
|
+
await listSharedNotesAction(options, ctx);
|
|
1687
|
+
});
|
|
1688
|
+
|
|
1689
|
+
// src/commands/skills/index.ts
|
|
1690
|
+
import { Command as Command7 } from "commander";
|
|
1691
|
+
|
|
1692
|
+
// src/commands/skills/list.ts
|
|
1693
|
+
import pc10 from "picocolors";
|
|
1694
|
+
async function listSkillsAction(ctx) {
|
|
1695
|
+
try {
|
|
1696
|
+
const result = await listNotes(
|
|
1697
|
+
{ note_type: "skill", limit: 200 },
|
|
1698
|
+
{ pin: ctx.pin }
|
|
1699
|
+
);
|
|
1700
|
+
const skills = result.notes;
|
|
1701
|
+
if (ctx.json) {
|
|
1702
|
+
const grouped2 = {};
|
|
1703
|
+
for (const note of skills) {
|
|
1704
|
+
const slashIdx = note.title.indexOf("/");
|
|
1705
|
+
const skillName = slashIdx > 0 ? note.title.slice(0, slashIdx) : note.title;
|
|
1706
|
+
if (!grouped2[skillName]) grouped2[skillName] = [];
|
|
1707
|
+
grouped2[skillName].push(note);
|
|
1708
|
+
}
|
|
1709
|
+
outputJson(grouped2);
|
|
1710
|
+
return;
|
|
1711
|
+
}
|
|
1712
|
+
if (skills.length === 0) {
|
|
1713
|
+
console.log(pc10.dim("No skills found. Create one with:"));
|
|
1714
|
+
console.log(pc10.dim(" pnote skills push ./my-skill"));
|
|
1715
|
+
return;
|
|
1716
|
+
}
|
|
1717
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1718
|
+
for (const note of skills) {
|
|
1719
|
+
const slashIdx = note.title.indexOf("/");
|
|
1720
|
+
const skillName = slashIdx > 0 ? note.title.slice(0, slashIdx) : note.title;
|
|
1721
|
+
if (!grouped.has(skillName)) grouped.set(skillName, []);
|
|
1722
|
+
grouped.get(skillName).push(note);
|
|
1723
|
+
}
|
|
1724
|
+
console.log(pc10.bold(`Skills (${grouped.size})`));
|
|
1725
|
+
console.log("");
|
|
1726
|
+
for (const [name, files] of grouped) {
|
|
1727
|
+
const fileNames = files.map((f) => {
|
|
1728
|
+
const slashIdx = f.title.indexOf("/");
|
|
1729
|
+
return slashIdx > 0 ? f.title.slice(slashIdx + 1) : f.title;
|
|
1730
|
+
});
|
|
1731
|
+
console.log(
|
|
1732
|
+
" " + pc10.cyan(name) + pc10.dim(` (${files.length} file${files.length !== 1 ? "s" : ""})`) + " " + pc10.dim(fileNames.join(", "))
|
|
1733
|
+
);
|
|
1734
|
+
}
|
|
1735
|
+
console.log("");
|
|
1736
|
+
console.log(pc10.dim("Pull to local: pnote skills pull"));
|
|
1737
|
+
} catch (error) {
|
|
1738
|
+
handleError(error, ctx.noColor);
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
|
|
1742
|
+
// src/commands/skills/pull.ts
|
|
1743
|
+
import * as fs from "fs";
|
|
1744
|
+
import * as path from "path";
|
|
1745
|
+
import * as os from "os";
|
|
1746
|
+
import pc11 from "picocolors";
|
|
1747
|
+
async function pullSkillsAction(skillName, options, ctx) {
|
|
1748
|
+
try {
|
|
1749
|
+
const baseDir = options.dir || path.join(os.homedir(), ".claude", "skills");
|
|
1750
|
+
const result = await listNotes(
|
|
1751
|
+
{ note_type: "skill", limit: 200 },
|
|
1752
|
+
{ pin: ctx.pin }
|
|
1753
|
+
);
|
|
1754
|
+
let skills = result.notes;
|
|
1755
|
+
if (skillName) {
|
|
1756
|
+
skills = skills.filter((n) => {
|
|
1757
|
+
const prefix = n.title.split("/")[0];
|
|
1758
|
+
return prefix === skillName;
|
|
1759
|
+
});
|
|
1760
|
+
if (skills.length === 0) {
|
|
1761
|
+
console.error(pc11.red(`No skill found with name "${skillName}"`));
|
|
1762
|
+
process.exit(1);
|
|
1763
|
+
}
|
|
1764
|
+
}
|
|
1765
|
+
if (skills.length === 0) {
|
|
1766
|
+
console.log(pc11.dim("No skills to pull."));
|
|
1767
|
+
return;
|
|
1768
|
+
}
|
|
1769
|
+
let pulled = 0;
|
|
1770
|
+
let skipped = 0;
|
|
1771
|
+
for (const note of skills) {
|
|
1772
|
+
const slashIdx = note.title.indexOf("/");
|
|
1773
|
+
if (slashIdx <= 0) {
|
|
1774
|
+
logStatus(`Skipping "${note.title}" (invalid title format, expected "name/file")`);
|
|
1775
|
+
skipped++;
|
|
1776
|
+
continue;
|
|
1777
|
+
}
|
|
1778
|
+
const skillDir = note.title.slice(0, slashIdx);
|
|
1779
|
+
const fileName = note.title.slice(slashIdx + 1);
|
|
1780
|
+
const filePath = path.join(baseDir, skillDir, fileName);
|
|
1781
|
+
const noteData = await getNote(note.id, { pin: ctx.pin });
|
|
1782
|
+
const content = noteData.latest_snippet?.content;
|
|
1783
|
+
if (!content) {
|
|
1784
|
+
logStatus(`Skipping "${note.title}" (no snippet content)`);
|
|
1785
|
+
skipped++;
|
|
1786
|
+
continue;
|
|
1787
|
+
}
|
|
1788
|
+
if (options.dryRun) {
|
|
1789
|
+
console.log(`${pc11.dim("would write")} ${filePath} ${pc11.dim(`(${content.length} chars)`)}`);
|
|
1790
|
+
pulled++;
|
|
1791
|
+
continue;
|
|
1792
|
+
}
|
|
1793
|
+
const dir = path.dirname(filePath);
|
|
1794
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1795
|
+
fs.writeFileSync(filePath, content, "utf-8");
|
|
1796
|
+
console.log(`${pc11.green("\u2713")} ${filePath}`);
|
|
1797
|
+
pulled++;
|
|
1798
|
+
}
|
|
1799
|
+
console.log("");
|
|
1800
|
+
if (options.dryRun) {
|
|
1801
|
+
console.log(pc11.dim(`Dry run: ${pulled} file(s) would be written, ${skipped} skipped`));
|
|
1802
|
+
} else {
|
|
1803
|
+
console.log(`Pulled ${pulled} file(s) to ${baseDir}` + (skipped > 0 ? `, ${skipped} skipped` : ""));
|
|
1804
|
+
}
|
|
1805
|
+
} catch (error) {
|
|
1806
|
+
handleError(error, ctx.noColor);
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
|
|
1810
|
+
// src/commands/skills/push.ts
|
|
1811
|
+
import * as fs2 from "fs";
|
|
1812
|
+
import * as path2 from "path";
|
|
1813
|
+
import pc12 from "picocolors";
|
|
1814
|
+
async function pushSkillsAction(dir, options, ctx) {
|
|
1815
|
+
try {
|
|
1816
|
+
const resolvedDir = path2.resolve(dir);
|
|
1817
|
+
if (!fs2.existsSync(resolvedDir) || !fs2.statSync(resolvedDir).isDirectory()) {
|
|
1818
|
+
console.error(pc12.red(`Not a directory: ${resolvedDir}`));
|
|
1819
|
+
process.exit(1);
|
|
1820
|
+
}
|
|
1821
|
+
const skillMdPath = path2.join(resolvedDir, "SKILL.md");
|
|
1822
|
+
if (!fs2.existsSync(skillMdPath)) {
|
|
1823
|
+
console.error(pc12.red(`No SKILL.md found in ${resolvedDir}`));
|
|
1824
|
+
console.error(pc12.dim("A valid skill directory must contain a SKILL.md file."));
|
|
1825
|
+
process.exit(1);
|
|
1826
|
+
}
|
|
1827
|
+
const skillName = options.name || path2.basename(resolvedDir);
|
|
1828
|
+
const files = collectFiles(resolvedDir, resolvedDir);
|
|
1829
|
+
if (files.length === 0) {
|
|
1830
|
+
console.log(pc12.dim("No files to push."));
|
|
1831
|
+
return;
|
|
1832
|
+
}
|
|
1833
|
+
const existing = await listNotes(
|
|
1834
|
+
{ note_type: "skill", limit: 200 },
|
|
1835
|
+
{ pin: ctx.pin }
|
|
1836
|
+
);
|
|
1837
|
+
const existingMap = /* @__PURE__ */ new Map();
|
|
1838
|
+
for (const note of existing.notes) {
|
|
1839
|
+
existingMap.set(note.title, note.id);
|
|
1840
|
+
}
|
|
1841
|
+
let created = 0;
|
|
1842
|
+
let updated = 0;
|
|
1843
|
+
for (const relPath of files) {
|
|
1844
|
+
const noteTitle = `${skillName}/${relPath}`;
|
|
1845
|
+
const filePath = path2.join(resolvedDir, relPath);
|
|
1846
|
+
const content = fs2.readFileSync(filePath, "utf-8");
|
|
1847
|
+
const existingId = existingMap.get(noteTitle);
|
|
1848
|
+
if (existingId) {
|
|
1849
|
+
await createSnippet(
|
|
1850
|
+
{ note_id: existingId, content },
|
|
1851
|
+
{ pin: ctx.pin }
|
|
1852
|
+
);
|
|
1853
|
+
console.log(`${pc12.yellow("\u21BB")} ${noteTitle} ${pc12.dim("(new version)")}`);
|
|
1854
|
+
updated++;
|
|
1855
|
+
} else {
|
|
1856
|
+
await createNote(
|
|
1857
|
+
{ title: noteTitle, content, note_type: "skill" },
|
|
1858
|
+
{ pin: ctx.pin }
|
|
1859
|
+
);
|
|
1860
|
+
console.log(`${pc12.green("+")} ${noteTitle}`);
|
|
1861
|
+
created++;
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
console.log("");
|
|
1865
|
+
console.log(
|
|
1866
|
+
`Pushed ${pc12.bold(skillName)}: ` + (created > 0 ? `${created} created` : "") + (created > 0 && updated > 0 ? ", " : "") + (updated > 0 ? `${updated} updated` : "")
|
|
1867
|
+
);
|
|
1868
|
+
} catch (error) {
|
|
1869
|
+
handleError(error, ctx.noColor);
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1872
|
+
function collectFiles(baseDir, currentDir) {
|
|
1873
|
+
const files = [];
|
|
1874
|
+
const entries = fs2.readdirSync(currentDir, { withFileTypes: true });
|
|
1875
|
+
for (const entry of entries) {
|
|
1876
|
+
if (entry.name.startsWith(".") || entry.name === "node_modules") continue;
|
|
1877
|
+
const fullPath = path2.join(currentDir, entry.name);
|
|
1878
|
+
const relPath = path2.relative(baseDir, fullPath);
|
|
1879
|
+
if (entry.isFile()) {
|
|
1880
|
+
files.push(relPath);
|
|
1881
|
+
} else if (entry.isDirectory()) {
|
|
1882
|
+
files.push(...collectFiles(baseDir, fullPath));
|
|
1883
|
+
}
|
|
1884
|
+
}
|
|
1885
|
+
return files;
|
|
1886
|
+
}
|
|
1887
|
+
|
|
1888
|
+
// src/commands/skills/index.ts
|
|
1889
|
+
async function buildContext5(globalOpts) {
|
|
1890
|
+
const pin = await resolvePin({
|
|
1891
|
+
pinArg: globalOpts.pin,
|
|
1892
|
+
pinFromStdin: globalOpts.pinStdin,
|
|
1893
|
+
skipPrompt: true
|
|
1894
|
+
});
|
|
1895
|
+
return {
|
|
1896
|
+
json: globalOpts.json ?? false,
|
|
1897
|
+
noColor: globalOpts.noColor ?? false,
|
|
1898
|
+
plain: globalOpts.plain ?? false,
|
|
1899
|
+
pin: pin ?? void 0
|
|
1900
|
+
};
|
|
1901
|
+
}
|
|
1902
|
+
var skillsCommand = new Command7("skills").description("Manage agent skills (sync between cloud and local)").action(async (_options, cmd) => {
|
|
1903
|
+
const globalOpts = cmd.parent?.opts() || {};
|
|
1904
|
+
const ctx = await buildContext5(globalOpts);
|
|
1905
|
+
await listSkillsAction(ctx);
|
|
1906
|
+
}).addHelpText(
|
|
1907
|
+
"after",
|
|
1908
|
+
`
|
|
1909
|
+
Examples:
|
|
1910
|
+
$ pnote skills List all skills in cloud
|
|
1911
|
+
$ pnote skills pull Download all skills to ~/.claude/skills/
|
|
1912
|
+
$ pnote skills pull myskill Download a specific skill
|
|
1913
|
+
$ pnote skills push ./my-skill Upload a local skill directory
|
|
1914
|
+
|
|
1915
|
+
Skills are notes with type "skill" and title format "skill-name/filename.md".
|
|
1916
|
+
They sync to ~/.claude/skills/<skill-name>/<filename> for use with Claude Code.
|
|
1917
|
+
`
|
|
1918
|
+
);
|
|
1919
|
+
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
|
+
const globalOpts = cmd.parent?.parent?.opts() || {};
|
|
1921
|
+
const ctx = await buildContext5(globalOpts);
|
|
1922
|
+
await pullSkillsAction(skillName, options, ctx);
|
|
1923
|
+
});
|
|
1924
|
+
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
|
+
const globalOpts = cmd.parent?.parent?.opts() || {};
|
|
1926
|
+
const ctx = await buildContext5(globalOpts);
|
|
1927
|
+
await pushSkillsAction(dir, options, ctx);
|
|
1928
|
+
});
|
|
1929
|
+
|
|
1500
1930
|
// src/index.ts
|
|
1501
|
-
var program = new
|
|
1931
|
+
var program = new Command8();
|
|
1502
1932
|
program.name("pnote").description("pnote - The PromptNote CLI").version("0.1.0", "-V, --version", "Show version number").option("--json", "Output as JSON (for scripting)").option("--no-color", "Disable colored output").option("--plain", "Force plain text output (no formatting)").option("-p, --pin <pin>", "PIN for accessing protected notes").option("--pin-stdin", "Read PIN from stdin (first line only)").configureHelp({
|
|
1503
1933
|
sortSubcommands: true,
|
|
1504
1934
|
sortOptions: true
|
|
@@ -1528,6 +1958,8 @@ program.addCommand(notesCommand);
|
|
|
1528
1958
|
program.addCommand(snippetCommand);
|
|
1529
1959
|
program.addCommand(tagsCommand);
|
|
1530
1960
|
program.addCommand(searchCommand);
|
|
1961
|
+
program.addCommand(shareCommand);
|
|
1962
|
+
program.addCommand(skillsCommand);
|
|
1531
1963
|
process.on("SIGINT", () => {
|
|
1532
1964
|
console.error("\nInterrupted");
|
|
1533
1965
|
process.exit(130);
|