pnote 0.3.0 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { Command as Command9 } from "commander";
4
+ import { Command as Command10 } from "commander";
5
5
 
6
6
  // src/lib/errors.ts
7
7
  import pc from "picocolors";
@@ -311,6 +311,16 @@ async function search(params, options = {}) {
311
311
  if (params.limit) query.set("limit", String(params.limit));
312
312
  return callRestApi("GET", `/search?${query.toString()}`, void 0, options);
313
313
  }
314
+ async function checkPinStatus(options = {}) {
315
+ return callRestApi("GET", "/pin/status", void 0, options);
316
+ }
317
+ async function listProtectedNotes(params = {}, options = {}) {
318
+ const query = new URLSearchParams();
319
+ if (params.limit) query.set("limit", String(params.limit));
320
+ if (params.offset) query.set("offset", String(params.offset));
321
+ const queryString = query.toString();
322
+ return callRestApi("GET", `/pin/notes${queryString ? `?${queryString}` : ""}`, void 0, options);
323
+ }
314
324
  async function listSharedTags(options = {}) {
315
325
  return callRestApi("GET", "/share/tags", void 0, options);
316
326
  }
@@ -832,6 +842,7 @@ async function listNotesAction(options, ctx) {
832
842
  archived: options.archived ?? false,
833
843
  pinned: options.pinned,
834
844
  deleted: options.deleted ?? false,
845
+ protected: options.protected,
835
846
  search: options.search,
836
847
  limit: options.limit ? parseInt(options.limit, 10) : 50
837
848
  },
@@ -946,10 +957,10 @@ async function confirm(message) {
946
957
  input: process.stdin,
947
958
  output: process.stdout
948
959
  });
949
- return new Promise((resolve2) => {
960
+ return new Promise((resolve3) => {
950
961
  rl.question(message + " [y/N] ", (answer) => {
951
962
  rl.close();
952
- resolve2(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
963
+ resolve3(answer.toLowerCase() === "y" || answer.toLowerCase() === "yes");
953
964
  });
954
965
  });
955
966
  }
@@ -1273,7 +1284,7 @@ async function promptForPin(noteTitle, hint, maxAttempts = PIN_MAX_ATTEMPTS) {
1273
1284
  return null;
1274
1285
  }
1275
1286
  async function readPinHidden(prompt) {
1276
- return new Promise((resolve2) => {
1287
+ return new Promise((resolve3) => {
1277
1288
  const rl = createInterface2({
1278
1289
  input: process.stdin,
1279
1290
  output: process.stderr,
@@ -1289,7 +1300,7 @@ async function readPinHidden(prompt) {
1289
1300
  if (char === "\r" || char === "\n") {
1290
1301
  process.stderr.write("\n");
1291
1302
  cleanup();
1292
- resolve2(pin);
1303
+ resolve3(pin);
1293
1304
  } else if (char === "") {
1294
1305
  process.stderr.write("\n");
1295
1306
  cleanup();
@@ -1424,7 +1435,7 @@ async function buildContext2(globalOpts) {
1424
1435
  pin: pin ?? void 0
1425
1436
  };
1426
1437
  }
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) => {
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) => {
1428
1439
  const globalOpts = cmd.parent?.opts() || {};
1429
1440
  const ctx = await buildContext2(globalOpts);
1430
1441
  await listNotesAction(options, ctx);
@@ -1820,63 +1831,172 @@ import * as fs from "fs";
1820
1831
  import * as path from "path";
1821
1832
  import * as os from "os";
1822
1833
  import pc11 from "picocolors";
1834
+ var AGENTS_STORE = ".agents/skills";
1835
+ var AGENT_SYMLINK_DIRS = [
1836
+ ".claude/skills",
1837
+ ".windsurf/skills",
1838
+ ".augment/skills",
1839
+ ".agent/skills"
1840
+ ];
1841
+ function makeColors(useColor) {
1842
+ if (useColor) {
1843
+ return {
1844
+ green: pc11.green,
1845
+ dim: pc11.dim,
1846
+ yellow: pc11.yellow,
1847
+ bold: pc11.bold,
1848
+ red: pc11.red
1849
+ };
1850
+ }
1851
+ return {
1852
+ green: (s) => s,
1853
+ dim: (s) => s,
1854
+ yellow: (s) => s,
1855
+ bold: (s) => s,
1856
+ red: (s) => s
1857
+ };
1858
+ }
1859
+ function resolveMode(options) {
1860
+ if (options.dir) return { mode: "custom", root: path.resolve(options.dir) };
1861
+ if (options.project) return { mode: "project", root: process.cwd() };
1862
+ return { mode: "global", root: os.homedir() };
1863
+ }
1864
+ function ensureSymlink(linkPath, skillName, dryRun, c, log) {
1865
+ const relTarget = path.join("..", "..", AGENTS_STORE, skillName);
1866
+ if (dryRun) {
1867
+ log(` ${c.dim("would link")} ${linkPath} \u2192 ${relTarget}`);
1868
+ return;
1869
+ }
1870
+ fs.mkdirSync(path.dirname(linkPath), { recursive: true });
1871
+ let existingStat = null;
1872
+ try {
1873
+ existingStat = fs.lstatSync(linkPath);
1874
+ } catch {
1875
+ }
1876
+ if (existingStat) {
1877
+ if (existingStat.isSymbolicLink()) {
1878
+ if (fs.readlinkSync(linkPath) === relTarget) return;
1879
+ fs.unlinkSync(linkPath);
1880
+ } else if (existingStat.isDirectory()) {
1881
+ fs.rmSync(linkPath, { recursive: true });
1882
+ log(` ${c.yellow("\u2197")} migrated ${c.dim(linkPath)} (replaced directory with symlink)`);
1883
+ } else {
1884
+ fs.unlinkSync(linkPath);
1885
+ }
1886
+ }
1887
+ fs.symlinkSync(relTarget, linkPath);
1888
+ log(` ${c.dim("\u21E2")} ${linkPath}`);
1889
+ }
1890
+ function installSkill(root, mode, skillName, files, dryRun, c, log) {
1891
+ const home = os.homedir();
1892
+ if (mode === "custom") {
1893
+ for (const { fileName, content } of files) {
1894
+ const destPath = path.join(root, skillName, fileName);
1895
+ if (dryRun) {
1896
+ log(` ${c.dim("would write")} ${destPath}`);
1897
+ } else {
1898
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
1899
+ fs.writeFileSync(destPath, content, "utf-8");
1900
+ log(` ${c.green("\u2713")} ${destPath}`);
1901
+ }
1902
+ }
1903
+ return [];
1904
+ }
1905
+ for (const { fileName, content } of files) {
1906
+ const destPath = path.join(root, AGENTS_STORE, skillName, fileName);
1907
+ if (dryRun) {
1908
+ log(` ${c.dim("would write")} ${destPath}`);
1909
+ } else {
1910
+ fs.mkdirSync(path.dirname(destPath), { recursive: true });
1911
+ fs.writeFileSync(destPath, content, "utf-8");
1912
+ log(` ${c.green("\u2713")} ${destPath}`);
1913
+ }
1914
+ }
1915
+ const linkedAgents = [];
1916
+ for (const agentSkillsDir of AGENT_SYMLINK_DIRS) {
1917
+ const agentRootDir = agentSkillsDir.split("/")[0];
1918
+ const isClaude = agentRootDir === ".claude";
1919
+ if (!isClaude && !fs.existsSync(path.join(home, agentRootDir))) continue;
1920
+ const linkPath = path.join(root, agentSkillsDir, skillName);
1921
+ ensureSymlink(linkPath, skillName, dryRun, c, log);
1922
+ linkedAgents.push(agentRootDir);
1923
+ }
1924
+ return linkedAgents;
1925
+ }
1823
1926
  async function pullSkillsAction(skillName, options, ctx) {
1824
1927
  try {
1825
- const baseDir = options.dir || path.join(os.homedir(), ".claude", "skills");
1928
+ const { mode, root } = resolveMode(options);
1929
+ const useColor = !ctx.noColor && process.stdout.isTTY;
1930
+ const c = makeColors(useColor);
1931
+ const log = ctx.json ? (_msg) => {
1932
+ } : (msg) => console.log(msg);
1826
1933
  const result = await listNotes(
1827
1934
  { note_type: "skill", limit: 200 },
1828
1935
  { pin: ctx.pin }
1829
1936
  );
1830
1937
  let skills = result.notes;
1831
1938
  if (skillName) {
1832
- skills = skills.filter((n) => {
1833
- const prefix = n.title.split("/")[0];
1834
- return prefix === skillName;
1835
- });
1939
+ skills = skills.filter((n) => n.title.split("/")[0] === skillName);
1836
1940
  if (skills.length === 0) {
1837
1941
  console.error(pc11.red(`No skill found with name "${skillName}"`));
1838
1942
  process.exit(1);
1839
1943
  }
1840
1944
  }
1841
1945
  if (skills.length === 0) {
1842
- console.log(pc11.dim("No skills to pull."));
1946
+ if (!ctx.json) console.log(c.dim("No skills to pull."));
1947
+ if (ctx.json) outputJson({ pulled: 0, skipped: 0, mode, root, skills: {} });
1843
1948
  return;
1844
1949
  }
1845
- let pulled = 0;
1846
- let skipped = 0;
1950
+ const grouped = /* @__PURE__ */ new Map();
1847
1951
  for (const note of skills) {
1848
1952
  const slashIdx = note.title.indexOf("/");
1849
1953
  if (slashIdx <= 0) {
1850
- logStatus(`Skipping "${note.title}" (invalid title format, expected "name/file")`);
1851
- skipped++;
1954
+ if (!ctx.json) console.error(`Skipping "${note.title}" (invalid title format, expected "name/file")`);
1852
1955
  continue;
1853
1956
  }
1854
- const skillDir = note.title.slice(0, slashIdx);
1957
+ const sName = note.title.slice(0, slashIdx);
1855
1958
  const fileName = note.title.slice(slashIdx + 1);
1856
- const filePath = path.join(baseDir, skillDir, fileName);
1857
- const noteData = await getNote(note.id, { pin: ctx.pin });
1858
- const content = noteData.latest_snippet?.content;
1859
- if (!content) {
1860
- logStatus(`Skipping "${note.title}" (no snippet content)`);
1861
- skipped++;
1862
- continue;
1959
+ if (!grouped.has(sName)) grouped.set(sName, []);
1960
+ grouped.get(sName).push({ fileName, noteId: note.id });
1961
+ }
1962
+ let totalPulled = 0;
1963
+ let totalSkipped = 0;
1964
+ const jsonResults = {};
1965
+ for (const [sName, fileEntries] of grouped) {
1966
+ log(`
1967
+ ${c.bold(sName)}`);
1968
+ const files = [];
1969
+ for (const { fileName, noteId } of fileEntries) {
1970
+ const noteData = await getNote(noteId, { pin: ctx.pin });
1971
+ const content = noteData.latest_snippet?.content;
1972
+ if (!content) {
1973
+ if (!ctx.json) console.error(` Skipping "${sName}/${fileName}" (no snippet content)`);
1974
+ totalSkipped++;
1975
+ continue;
1976
+ }
1977
+ files.push({ fileName, content });
1863
1978
  }
1979
+ if (files.length === 0) continue;
1980
+ const linkedAgents = installSkill(root, mode, sName, files, options.dryRun ?? false, c, log);
1981
+ totalPulled += files.length;
1982
+ jsonResults[sName] = {
1983
+ files: files.map((f) => f.fileName),
1984
+ agents: linkedAgents
1985
+ };
1986
+ }
1987
+ if (!ctx.json) {
1988
+ console.log("");
1864
1989
  if (options.dryRun) {
1865
- console.log(`${pc11.dim("would write")} ${filePath} ${pc11.dim(`(${content.length} chars)`)}`);
1866
- pulled++;
1867
- continue;
1990
+ console.log(c.dim(`Dry run: ${totalPulled} file(s) would be pulled, ${totalSkipped} skipped`));
1991
+ } else {
1992
+ const scopeLabel = mode === "custom" ? root : mode === "project" ? "project (.agents/skills/)" : "global (~/.agents/skills/)";
1993
+ console.log(
1994
+ `Pulled ${totalPulled} file(s) \u2014 ${scopeLabel}` + (totalSkipped > 0 ? `, ${totalSkipped} skipped` : "")
1995
+ );
1868
1996
  }
1869
- const dir = path.dirname(filePath);
1870
- fs.mkdirSync(dir, { recursive: true });
1871
- fs.writeFileSync(filePath, content, "utf-8");
1872
- console.log(`${pc11.green("\u2713")} ${filePath}`);
1873
- pulled++;
1874
1997
  }
1875
- console.log("");
1876
- if (options.dryRun) {
1877
- console.log(pc11.dim(`Dry run: ${pulled} file(s) would be written, ${skipped} skipped`));
1878
- } else {
1879
- console.log(`Pulled ${pulled} file(s) to ${baseDir}` + (skipped > 0 ? `, ${skipped} skipped` : ""));
1998
+ if (ctx.json) {
1999
+ outputJson({ pulled: totalPulled, skipped: totalSkipped, mode, root, skills: jsonResults });
1880
2000
  }
1881
2001
  } catch (error) {
1882
2002
  handleError(error, ctx.noColor);
@@ -1983,16 +2103,20 @@ var skillsCommand = new Command8("skills").description("Manage agent skills (syn
1983
2103
  "after",
1984
2104
  `
1985
2105
  Examples:
1986
- $ pnote skills List all skills in cloud
1987
- $ pnote skills pull Download all skills to ~/.claude/skills/
1988
- $ pnote skills pull myskill Download a specific skill
1989
- $ pnote skills push ./my-skill Upload a local skill directory
2106
+ $ pnote skills List all skills in cloud
2107
+ $ pnote skills pull Pull all skills globally (~/.agents/skills/ + symlinks)
2108
+ $ pnote skills pull myskill Pull a specific skill globally
2109
+ $ pnote skills pull --project Pull project-level (.agents/skills/ + .claude/skills/ etc.)
2110
+ $ pnote skills pull --dry-run Preview without writing files
2111
+ $ pnote skills push ./my-skill Upload a local skill directory
1990
2112
 
1991
- Skills are notes with type "skill" and title format "skill-name/filename.md".
1992
- They sync to ~/.claude/skills/<skill-name>/<filename> for use with Claude Code.
2113
+ Skills are stored as notes with type "skill" and title "skill-name/filename.md".
2114
+ Pull writes to the canonical .agents/skills/ store and symlinks into each installed
2115
+ agent's directory (.claude/skills/, .windsurf/skills/, .augment/skills/, etc.),
2116
+ matching the layout used by npx skills for seamless coexistence.
1993
2117
  `
1994
2118
  );
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) => {
2119
+ skillsCommand.command("pull").description("Download skills from cloud to local (global by default)").argument("[skill-name]", "Specific skill to pull (pulls all if omitted)").option("--project", "Install project-level (./agents/skills/ + .claude/skills/ etc.)").option("--dir <path>", "Custom output directory \u2014 direct write, no symlinks").option("--dry-run", "Show what would be downloaded without writing files").action(async (skillName, options, cmd) => {
1996
2120
  const globalOpts = cmd.parent?.parent?.opts() || {};
1997
2121
  const ctx = await buildContext6(globalOpts);
1998
2122
  await pullSkillsAction(skillName, options, ctx);
@@ -2003,10 +2127,170 @@ skillsCommand.command("push").description("Upload a local skill directory to clo
2003
2127
  await pushSkillsAction(dir, options, ctx);
2004
2128
  });
2005
2129
 
2130
+ // src/commands/pin/index.ts
2131
+ import { Command as Command9 } from "commander";
2132
+
2133
+ // src/commands/pin/statusAction.ts
2134
+ import pc13 from "picocolors";
2135
+ async function pinStatusAction(ctx) {
2136
+ try {
2137
+ const result = await checkPinStatus({ pin: ctx.pin });
2138
+ if (ctx.json) {
2139
+ outputJson({
2140
+ ...result,
2141
+ pnote_pin_env_set: !!process.env.PNOTE_PIN,
2142
+ pin_cached: !!getCachedPin()
2143
+ });
2144
+ return;
2145
+ }
2146
+ const useColor = !ctx.noColor && process.stdout.isTTY;
2147
+ const c = {
2148
+ green: useColor ? pc13.green : (s) => s,
2149
+ red: useColor ? pc13.red : (s) => s,
2150
+ yellow: useColor ? pc13.yellow : (s) => s,
2151
+ dim: useColor ? pc13.dim : (s) => s,
2152
+ bold: useColor ? pc13.bold : (s) => s,
2153
+ magenta: useColor ? pc13.magenta : (s) => s
2154
+ };
2155
+ const yes = c.green("\u2713");
2156
+ const no = c.red("\u2717");
2157
+ console.log(c.bold("PIN Status"));
2158
+ console.log("");
2159
+ const hasPin = result.user_has_pin_setup;
2160
+ console.log(` Account PIN set: ${hasPin ? yes : no}`);
2161
+ if (result.pin_hint) {
2162
+ console.log(` PIN hint: ${c.dim(result.pin_hint)}`);
2163
+ }
2164
+ console.log(` Protected notes: ${c.magenta(String(result.protected_notes_count))}`);
2165
+ console.log("");
2166
+ console.log(c.bold("PIN Sources (CLI)"));
2167
+ console.log("");
2168
+ const envSet = !!process.env.PNOTE_PIN;
2169
+ console.log(` PNOTE_PIN env: ${envSet ? yes : no}${envSet ? c.dim(" (recommended)") : ""}`);
2170
+ const cached = !!getCachedPin();
2171
+ console.log(` Session cache: ${cached ? yes + c.dim(" (active, <5min)") : no}`);
2172
+ if (result.pin_provided) {
2173
+ const valid = result.pin_valid;
2174
+ console.log(` Provided PIN valid: ${valid ? yes : no}`);
2175
+ } else {
2176
+ console.log(` Provided PIN: ${c.dim("not provided")}`);
2177
+ }
2178
+ console.log("");
2179
+ const canAccess = result.can_access_protected;
2180
+ if (canAccess) {
2181
+ console.log(yes + " " + c.green("Can access protected notes"));
2182
+ } else if (!hasPin) {
2183
+ console.log(c.dim(" No protected notes \u2014 PIN not configured on account"));
2184
+ } else {
2185
+ console.log(no + " " + c.red("Cannot access protected notes"));
2186
+ console.log("");
2187
+ console.log(c.dim(" Set PIN via: export PNOTE_PIN=<pin>"));
2188
+ console.log(c.dim(" Or use: pnote notes get <id> -p <pin>"));
2189
+ console.log(c.dim(" Or use: echo <pin> | pnote notes get <id> --pin-stdin"));
2190
+ }
2191
+ } catch (error) {
2192
+ handleError(error, ctx.noColor);
2193
+ }
2194
+ }
2195
+
2196
+ // src/commands/pin/listAction.ts
2197
+ import pc14 from "picocolors";
2198
+ async function pinListAction(options, ctx) {
2199
+ try {
2200
+ const result = await listProtectedNotes(
2201
+ {
2202
+ limit: options.limit ? parseInt(options.limit, 10) : 50,
2203
+ offset: options.offset ? parseInt(options.offset, 10) : void 0
2204
+ },
2205
+ { pin: ctx.pin }
2206
+ );
2207
+ if (ctx.json) {
2208
+ outputJson(result);
2209
+ return;
2210
+ }
2211
+ const useColor = !ctx.noColor && process.stdout.isTTY;
2212
+ const c = {
2213
+ dim: useColor ? pc14.dim : (s) => s,
2214
+ bold: useColor ? pc14.bold : (s) => s,
2215
+ cyan: useColor ? pc14.cyan : (s) => s,
2216
+ yellow: useColor ? pc14.yellow : (s) => s,
2217
+ magenta: useColor ? pc14.magenta : (s) => s
2218
+ };
2219
+ const isHuman = !ctx.json && !ctx.plain && process.stdout.isTTY;
2220
+ if (result.notes.length === 0) {
2221
+ console.log(c.dim("No protected notes."));
2222
+ return;
2223
+ }
2224
+ if (isHuman) {
2225
+ console.log(c.bold(`Protected Notes (${result.count})`));
2226
+ if (result.hint) {
2227
+ console.log(c.dim(`PIN hint: ${result.hint}`));
2228
+ }
2229
+ if (!result.can_decrypt) {
2230
+ console.log(c.dim("Note: Content not shown \u2014 set PNOTE_PIN to access content."));
2231
+ }
2232
+ console.log("");
2233
+ const pad2 = (s, w) => s.length >= w ? s.slice(0, w) : s + " ".repeat(w - s.length);
2234
+ const truncate2 = (s, w) => s.length <= w ? s : s.slice(0, w - 1) + "\u2026";
2235
+ console.log(
2236
+ " " + c.dim(pad2("ID", 10)) + " " + c.dim(pad2("TITLE", 30)) + " " + c.dim(pad2("TAGS", 20)) + " " + c.dim("PROTECTED")
2237
+ );
2238
+ for (const note of result.notes) {
2239
+ const date = new Date(note.protected_at);
2240
+ const updated = date.toLocaleDateString();
2241
+ const pinned = note.pinned ? c.yellow("* ") : " ";
2242
+ console.log(
2243
+ pinned + pad2(note.id.slice(0, 8), 10) + " " + pad2(truncate2(note.title, 30), 30) + " " + pad2(truncate2(note.tags.join(", ") || "-", 20), 20) + " " + c.magenta(updated)
2244
+ );
2245
+ }
2246
+ } else {
2247
+ for (const note of result.notes) {
2248
+ console.log([note.id, note.title, note.tags.join(","), note.protected_at].join(" "));
2249
+ }
2250
+ }
2251
+ } catch (error) {
2252
+ handleError(error, ctx.noColor);
2253
+ }
2254
+ }
2255
+
2256
+ // src/commands/pin/index.ts
2257
+ async function buildContext7(globalOpts) {
2258
+ const pin = await resolvePin({
2259
+ pinArg: globalOpts.pin,
2260
+ pinFromStdin: globalOpts.pinStdin,
2261
+ skipPrompt: true
2262
+ });
2263
+ return {
2264
+ json: globalOpts.json ?? false,
2265
+ noColor: globalOpts.noColor ?? false,
2266
+ plain: globalOpts.plain ?? false,
2267
+ pin: pin ?? void 0
2268
+ };
2269
+ }
2270
+ var pinCommand = new Command9("pin").description("Manage PIN protection for notes").addHelpText(
2271
+ "after",
2272
+ `
2273
+ Examples:
2274
+ $ pnote pin status Check PIN configuration and access
2275
+ $ pnote pin list List all protected notes (metadata only)
2276
+ $ PNOTE_PIN=1234 pnote pin status Check with PIN provided via env
2277
+ `
2278
+ );
2279
+ pinCommand.command("status").description("Check PIN configuration status and access to protected notes").action(async (_options, cmd) => {
2280
+ const globalOpts = cmd.parent?.parent?.opts() || {};
2281
+ const ctx = await buildContext7(globalOpts);
2282
+ await pinStatusAction(ctx);
2283
+ });
2284
+ 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) => {
2285
+ const globalOpts = cmd.parent?.parent?.opts() || {};
2286
+ const ctx = await buildContext7(globalOpts);
2287
+ await pinListAction(options, ctx);
2288
+ });
2289
+
2006
2290
  // src/lib/update-check.ts
2007
- import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
2291
+ import { existsSync as existsSync5, readFileSync as readFileSync4, writeFileSync as writeFileSync4, mkdirSync as mkdirSync3 } from "fs";
2008
2292
  import { join as join5 } from "path";
2009
- import pc13 from "picocolors";
2293
+ import pc15 from "picocolors";
2010
2294
  var CACHE_FILE = "update-check.json";
2011
2295
  var CHECK_INTERVAL = 60 * 60 * 1e3;
2012
2296
  var FETCH_TIMEOUT = 2e3;
@@ -2016,7 +2300,7 @@ function getCachePath() {
2016
2300
  function readCache() {
2017
2301
  try {
2018
2302
  const path3 = getCachePath();
2019
- if (!existsSync4(path3)) return null;
2303
+ if (!existsSync5(path3)) return null;
2020
2304
  return JSON.parse(readFileSync4(path3, "utf-8"));
2021
2305
  } catch {
2022
2306
  return null;
@@ -2025,7 +2309,7 @@ function readCache() {
2025
2309
  function writeCache(cache) {
2026
2310
  try {
2027
2311
  const dir = getConfigDir();
2028
- if (!existsSync4(dir)) {
2312
+ if (!existsSync5(dir)) {
2029
2313
  mkdirSync3(dir, { recursive: true, mode: 448 });
2030
2314
  }
2031
2315
  writeFileSync4(getCachePath(), JSON.stringify(cache), "utf-8");
@@ -2055,9 +2339,9 @@ function isOlderVersion(current, latest) {
2055
2339
  }
2056
2340
  function printNotice(current, latest, packageName) {
2057
2341
  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;
2342
+ const yellow = useColor ? pc15.yellow : (s) => s;
2343
+ const bold = useColor ? pc15.bold : (s) => s;
2344
+ const dim = useColor ? pc15.dim : (s) => s;
2061
2345
  const msg = `Update available: ${dim(current)} \u2192 ${bold(latest)}`;
2062
2346
  const cmd = `Run ${bold(`npm i -g ${packageName}`)} to update`;
2063
2347
  const lines = [msg, cmd];
@@ -2103,8 +2387,8 @@ async function checkAndNotifyUpdate(currentVersion, packageName) {
2103
2387
  }
2104
2388
 
2105
2389
  // src/index.ts
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({
2390
+ var program = new Command10();
2391
+ program.name("pnote").description("pnote - The PromptNote CLI").version("0.4.1", "-V, --version", "Show version number").option("--json", "Output as JSON (for scripting)").option("--no-color", "Disable colored output").option("--plain", "Force plain text output (no formatting)").option("-p, --pin <pin>", "PIN for accessing protected notes").option("--pin-stdin", "Read PIN from stdin (first line only)").configureHelp({
2108
2392
  sortSubcommands: true,
2109
2393
  sortOptions: true
2110
2394
  }).addHelpText(
@@ -2134,9 +2418,10 @@ program.addCommand(snippetCommand);
2134
2418
  program.addCommand(tagsCommand);
2135
2419
  program.addCommand(searchCommand);
2136
2420
  program.addCommand(shareCommand);
2421
+ program.addCommand(pinCommand);
2137
2422
  program.addCommand(skillsCommand);
2138
2423
  program.hook("preAction", async () => {
2139
- await checkAndNotifyUpdate("0.3.0", "pnote");
2424
+ await checkAndNotifyUpdate("0.4.1", "pnote");
2140
2425
  });
2141
2426
  process.on("SIGINT", () => {
2142
2427
  console.error("\nInterrupted");