innernote 0.3.2 → 0.3.4

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.
Files changed (3) hide show
  1. package/README.md +1 -0
  2. package/dist/index.js +139 -51
  3. package/package.json +22 -1
package/README.md CHANGED
@@ -100,6 +100,7 @@ from it fails the suite, so what you read here is what the binary has.
100
100
  | `week` | what is scheduled for the next seven days |
101
101
  | `queue <post-id>` | schedule a post into your next open slot |
102
102
  | `unqueue <post-id>` | pull it back out before it goes live |
103
+ | `skill [--install]` | print the agent skill, or register it for Claude Code |
103
104
 
104
105
  ### write
105
106
 
package/dist/index.js CHANGED
@@ -83,6 +83,9 @@ class ApiError extends Error {
83
83
  get isRefusal() {
84
84
  return this.status === 402 || this.status === 429;
85
85
  }
86
+ get isNotConnected() {
87
+ return this.status === 401;
88
+ }
86
89
  }
87
90
 
88
91
  class NotLoggedIn extends Error {
@@ -497,13 +500,41 @@ function resolveRef(ref) {
497
500
  return /^[a-z0-9]{20,}$/.test(ref) ? { id: ref, title: ref } : null;
498
501
  }
499
502
 
500
- // src/commands/read.ts
501
- function parseFlags(args) {
503
+ // src/flags.ts
504
+ function parseFlags(args, valueFlags = []) {
505
+ const values = new Map;
506
+ const present = new Set;
507
+ const rest = [];
508
+ for (let i = 0;i < args.length; i++) {
509
+ const arg = args[i];
510
+ if (!arg.startsWith("--")) {
511
+ rest.push(arg);
512
+ continue;
513
+ }
514
+ const eq = arg.indexOf("=");
515
+ const name = eq === -1 ? arg.slice(2) : arg.slice(2, eq);
516
+ present.add(name);
517
+ if (!valueFlags.includes(name))
518
+ continue;
519
+ if (eq !== -1) {
520
+ values.set(name, arg.slice(eq + 1));
521
+ continue;
522
+ }
523
+ const next = args[i + 1];
524
+ if (next !== undefined && !next.startsWith("--")) {
525
+ values.set(name, next);
526
+ i++;
527
+ }
528
+ }
502
529
  return {
503
- json: args.includes("--json"),
504
- rest: args.filter((a) => !a.startsWith("--"))
530
+ json: present.has("json"),
531
+ rest,
532
+ value: (name) => values.get(name),
533
+ has: (name) => present.has(name)
505
534
  };
506
535
  }
536
+
537
+ // src/commands/read.ts
507
538
  function powers(scopes) {
508
539
  if (!scopes || scopes.length === 0)
509
540
  return "full access";
@@ -654,14 +685,6 @@ async function context(args) {
654
685
  return 0;
655
686
  }
656
687
 
657
- // src/commands/read.ts
658
- function parseFlags2(args) {
659
- return {
660
- json: args.includes("--json"),
661
- rest: args.filter((a) => !a.startsWith("--"))
662
- };
663
- }
664
-
665
688
  // src/progress.ts
666
689
  var DRAFTING = [
667
690
  { at: 0, text: "reading your voice..." },
@@ -831,9 +854,10 @@ async function whileThinking(phases, run) {
831
854
 
832
855
  // src/commands/write.ts
833
856
  async function capture(args) {
834
- const { json, rest } = parseFlags2(args);
835
- const piped = await readStdin();
836
- const content = (rest.join(" ").trim() || piped || "").trim();
857
+ const { json, rest } = parseFlags(args);
858
+ const typed = rest.join(" ").trim();
859
+ const piped = typed ? null : await readStdin();
860
+ const content = (typed || piped || "").trim();
837
861
  if (!content) {
838
862
  fail("Nothing to capture.");
839
863
  note(dim2('Try: innernote capture "the thing you just thought of"'));
@@ -853,21 +877,14 @@ async function capture(args) {
853
877
  return 0;
854
878
  }
855
879
  async function write(args) {
856
- const { json, rest } = parseFlags2(args);
857
- const flagValue = (name) => {
858
- const exact = args.indexOf(`--${name}`);
859
- if (exact !== -1 && args[exact + 1] && !args[exact + 1].startsWith("--")) {
860
- return args[exact + 1];
861
- }
862
- const inline = args.find((a) => a.startsWith(`--${name}=`));
863
- return inline?.slice(name.length + 3);
864
- };
865
- const piped = await readStdin();
866
- const thought = flagValue("thought") ?? (piped ?? undefined);
867
- const topic = rest.filter((a) => a !== thought).join(" ").trim();
868
- const format = flagValue("format");
869
- const series = flagValue("series");
870
- const save = args.includes("--save");
880
+ const { json, rest, value, has } = parseFlags(args, ["thought", "format", "series"]);
881
+ const topic = rest.join(" ").trim();
882
+ const thoughtFlag = value("thought");
883
+ const piped = thoughtFlag || topic ? null : await readStdin();
884
+ const thought = thoughtFlag ?? (piped ?? undefined);
885
+ const format = value("format");
886
+ const series = value("series");
887
+ const save = has("save");
871
888
  const call = () => request("/api/content/generate", {
872
889
  method: "POST",
873
890
  body: {
@@ -934,11 +951,11 @@ var SHAPES = [
934
951
  "jargon"
935
952
  ];
936
953
  async function shape(args) {
937
- const { json, rest } = parseFlags2(args);
938
- const piped = await readStdin();
954
+ const { json, rest } = parseFlags(args);
955
+ const held = getDraft();
956
+ const piped = held ? null : await readStdin();
939
957
  const chosen = rest.filter((a) => SHAPES.includes(a));
940
958
  const freeText = rest.filter((a) => !SHAPES.includes(a)).join(" ").trim();
941
- const held = getDraft();
942
959
  const content = (piped ?? held?.content ?? "").trim();
943
960
  if (!content) {
944
961
  fail("Nothing to reshape.");
@@ -977,14 +994,15 @@ async function shape(args) {
977
994
  return 0;
978
995
  }
979
996
  async function queue(args) {
980
- const { json, rest } = parseFlags2(args);
981
- const id = rest[0];
982
- if (!id) {
997
+ const { json, rest } = parseFlags(args);
998
+ const ref = rest[0] ? resolveRef(rest[0]) : null;
999
+ if (!ref) {
983
1000
  fail("Which post?");
984
- note(dim2(`${CMD} queue <post-id> (ids come from \`${CMD} drafts --json\`)`));
1001
+ note(dim2(`${CMD} drafts then ${CMD} queue 3`));
1002
+ note(dim2(`Or pass the id: ${CMD} queue <post-id>`));
985
1003
  return 1;
986
1004
  }
987
- const result = await request(`/api/posts/${encodeURIComponent(id)}/queue`, { method: "POST", timeoutMs: 60000 });
1005
+ const result = await request(`/api/posts/${encodeURIComponent(ref.id)}/queue`, { method: "POST", timeoutMs: 60000 });
988
1006
  if (json) {
989
1007
  out(JSON.stringify(result, null, 2));
990
1008
  return 0;
@@ -999,7 +1017,7 @@ async function queue(args) {
999
1017
  return 0;
1000
1018
  }
1001
1019
  async function save(args) {
1002
- const { json } = parseFlags2(args);
1020
+ const { json } = parseFlags(args);
1003
1021
  const held = getDraft();
1004
1022
  if (!held?.content) {
1005
1023
  fail("Nothing to save.");
@@ -1026,7 +1044,7 @@ async function save(args) {
1026
1044
  return 0;
1027
1045
  }
1028
1046
  async function open(args) {
1029
- const { json, rest } = parseFlags2(args);
1047
+ const { json, rest } = parseFlags(args);
1030
1048
  const ref = rest[0];
1031
1049
  if (!ref) {
1032
1050
  fail("Open which one?");
@@ -1058,7 +1076,7 @@ async function open(args) {
1058
1076
  return 0;
1059
1077
  }
1060
1078
  async function ask(args) {
1061
- const { json, rest } = parseFlags2(args);
1079
+ const { json, rest } = parseFlags(args);
1062
1080
  const message = rest.join(" ").trim();
1063
1081
  const held = getDraft();
1064
1082
  if (!message) {
@@ -1112,7 +1130,7 @@ async function ask(args) {
1112
1130
  return 0;
1113
1131
  }
1114
1132
  async function show(args) {
1115
- const { json } = parseFlags2(args);
1133
+ const { json } = parseFlags(args);
1116
1134
  const held = getDraft();
1117
1135
  if (!held?.content) {
1118
1136
  note("Nothing in hand.");
@@ -1150,7 +1168,7 @@ async function drop() {
1150
1168
  return 0;
1151
1169
  }
1152
1170
  async function unqueue(args) {
1153
- const { json, rest } = parseFlags2(args);
1171
+ const { json, rest } = parseFlags(args);
1154
1172
  const ref = rest[0] ? resolveRef(rest[0]) : null;
1155
1173
  if (!ref) {
1156
1174
  fail("Which post?");
@@ -1410,6 +1428,12 @@ var COMMANDS = {
1410
1428
  detail: "The undo for queue, while there is still time. The post returns to your drafts and nothing goes live. Once it has been published this refuses, because a live post cannot be unsent this way.",
1411
1429
  examples: ["innernote unqueue p17abc...", "innernote unqueue 3"]
1412
1430
  },
1431
+ skill: {
1432
+ summary: "print the agent skill, or install it for Claude Code",
1433
+ usage: "innernote skill [--install]",
1434
+ detail: "The playbook an agent should read before driving this CLI: the loop, the read-before-write rule, what the exit codes mean, and the one rule that protects real people. Prints to stdout so any agent can take it. --install also registers it in ~/.claude/skills so Claude Code knows it in every future session.",
1435
+ examples: ["innernote skill", "innernote skill --install"]
1436
+ },
1413
1437
  context: {
1414
1438
  summary: "what you post about: your pillars and your series",
1415
1439
  usage: "innernote context",
@@ -1419,8 +1443,8 @@ var COMMANDS = {
1419
1443
  queue: {
1420
1444
  summary: "schedule a post into your next open slot",
1421
1445
  usage: "innernote queue <post-id>",
1422
- detail: "A queued post GOES LIVE on LinkedIn on its own at your next cadence slot. Nobody presses anything again. Needs a connection you allowed to publish: tick that box in Settings when you generate the code.",
1423
- examples: ["innernote drafts", "innernote queue p17a1yk936ywnan10643efjtc58dhkah"]
1446
+ detail: "A queued post GOES LIVE on LinkedIn on its own at your next cadence slot. Nobody presses anything again. Needs a connection you allowed to publish: tick that box in Settings when you generate the code. Takes a number from the last list, or a full id. It never assumes the draft in hand: this is the one command that makes you say which post.",
1447
+ examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah"]
1424
1448
  }
1425
1449
  };
1426
1450
  function commandHelp(name) {
@@ -1533,8 +1557,29 @@ function suggest(state) {
1533
1557
  // package.json
1534
1558
  var package_default = {
1535
1559
  name: "innernote",
1536
- version: "0.3.2",
1560
+ version: "0.3.4",
1537
1561
  description: "Write LinkedIn posts in your voice, from the terminal.",
1562
+ keywords: [
1563
+ "linkedin",
1564
+ "mcp",
1565
+ "mcp-server",
1566
+ "model-context-protocol",
1567
+ "ai-agent",
1568
+ "claude",
1569
+ "claude-code",
1570
+ "cursor",
1571
+ "agent-skill",
1572
+ "writing",
1573
+ "content",
1574
+ "personal-branding",
1575
+ "ghostwriting",
1576
+ "cli"
1577
+ ],
1578
+ homepage: "https://www.innernote.space/cli",
1579
+ license: "UNLICENSED",
1580
+ bugs: {
1581
+ url: "https://www.innernote.space/cli"
1582
+ },
1538
1583
  type: "module",
1539
1584
  bin: {
1540
1585
  innernote: "dist/index.js"
@@ -1616,6 +1661,42 @@ async function home(inSession = false) {
1616
1661
  return 0;
1617
1662
  }
1618
1663
 
1664
+ // src/commands/skill.ts
1665
+ import { chmod as chmod2, mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
1666
+ import { homedir as homedir2 } from "node:os";
1667
+ import { dirname, join as join2 } from "node:path";
1668
+ import { fileURLToPath } from "node:url";
1669
+ async function readSkill() {
1670
+ const here = dirname(fileURLToPath(import.meta.url));
1671
+ for (const rel of ["../../skills/innernote-cli/SKILL.md", "../skills/innernote-cli/SKILL.md"]) {
1672
+ try {
1673
+ return await readFile2(join2(here, rel), "utf8");
1674
+ } catch {}
1675
+ }
1676
+ return null;
1677
+ }
1678
+ async function skill(args) {
1679
+ const install = args.includes("--install");
1680
+ const text = await readSkill();
1681
+ if (!text) {
1682
+ fail("The skill file is missing from this install.");
1683
+ note(dim2("Reinstall with `npm i -g innernote`, or read it on any machine with the package."));
1684
+ return 1;
1685
+ }
1686
+ if (install) {
1687
+ const dir = join2(homedir2(), ".claude", "skills", "innernote-cli");
1688
+ await mkdir2(dir, { recursive: true });
1689
+ const target = join2(dir, "SKILL.md");
1690
+ await writeFile2(target, text);
1691
+ await chmod2(target, 420);
1692
+ ok(`Installed for Claude Code at ${target}`);
1693
+ note(dim2("It loads in future sessions on its own. Here it is for this one:"));
1694
+ note("");
1695
+ }
1696
+ out(text);
1697
+ return 0;
1698
+ }
1699
+
1619
1700
  // src/session.ts
1620
1701
  import { createInterface } from "node:readline";
1621
1702
 
@@ -1761,6 +1842,12 @@ var COMMANDS2 = {
1761
1842
  detail: "The undo for queue, while there is still time. The post returns to your drafts and nothing goes live. Once it has been published this refuses, because a live post cannot be unsent this way.",
1762
1843
  examples: ["innernote unqueue p17abc...", "innernote unqueue 3"]
1763
1844
  },
1845
+ skill: {
1846
+ summary: "print the agent skill, or install it for Claude Code",
1847
+ usage: "innernote skill [--install]",
1848
+ detail: "The playbook an agent should read before driving this CLI: the loop, the read-before-write rule, what the exit codes mean, and the one rule that protects real people. Prints to stdout so any agent can take it. --install also registers it in ~/.claude/skills so Claude Code knows it in every future session.",
1849
+ examples: ["innernote skill", "innernote skill --install"]
1850
+ },
1764
1851
  context: {
1765
1852
  summary: "what you post about: your pillars and your series",
1766
1853
  usage: "innernote context",
@@ -1770,8 +1857,8 @@ var COMMANDS2 = {
1770
1857
  queue: {
1771
1858
  summary: "schedule a post into your next open slot",
1772
1859
  usage: "innernote queue <post-id>",
1773
- detail: "A queued post GOES LIVE on LinkedIn on its own at your next cadence slot. Nobody presses anything again. Needs a connection you allowed to publish: tick that box in Settings when you generate the code.",
1774
- examples: ["innernote drafts", "innernote queue p17a1yk936ywnan10643efjtc58dhkah"]
1860
+ detail: "A queued post GOES LIVE on LinkedIn on its own at your next cadence slot. Nobody presses anything again. Needs a connection you allowed to publish: tick that box in Settings when you generate the code. Takes a number from the last list, or a full id. It never assumes the draft in hand: this is the one command that makes you say which post.",
1861
+ examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah"]
1775
1862
  }
1776
1863
  };
1777
1864
  function commandHelp2(name) {
@@ -2029,7 +2116,7 @@ function whatThereIs() {
2029
2116
  ["Writing", ["write", "shape", "ask", "save", "capture"]],
2030
2117
  ["Looking", ["show", "ideas", "drafts", "open", "week", "context", "whoami"]],
2031
2118
  ["Shipping", ["queue", "unqueue"]],
2032
- ["Connection", ["login", "logout"]]
2119
+ ["Connection", ["login", "logout", "skill"]]
2033
2120
  ];
2034
2121
  for (const [title, cmds] of groups) {
2035
2122
  lines.push(` ${bold2(title)}`);
@@ -2055,7 +2142,7 @@ var VERSION2 = package_default.version;
2055
2142
  // src/index.ts
2056
2143
  function commandList() {
2057
2144
  const groups = [
2058
- ["Getting connected", ["login", "logout", "whoami"]],
2145
+ ["Getting connected", ["login", "logout", "whoami", "skill"]],
2059
2146
  ["Writing", ["write", "shape", "ask", "save", "capture"]],
2060
2147
  ["Looking", ["show", "ideas", "drafts", "open", "week", "context"]],
2061
2148
  ["Shipping", ["queue", "unqueue"]]
@@ -2084,6 +2171,7 @@ var HELP = () => [
2084
2171
  `);
2085
2172
  var COMMANDS3 = {
2086
2173
  context,
2174
+ skill,
2087
2175
  login,
2088
2176
  logout: () => logout(),
2089
2177
  whoami,
@@ -2156,7 +2244,7 @@ main().then((code) => {
2156
2244
  }
2157
2245
  if (isApiError(err)) {
2158
2246
  fail2(err.message);
2159
- process.exitCode = err.isRefusal ? 3 : 1;
2247
+ process.exitCode = err.isNotConnected ? 2 : err.isRefusal ? 3 : 1;
2160
2248
  return;
2161
2249
  }
2162
2250
  fail2(err instanceof Error ? err.message : String(err));
package/package.json CHANGED
@@ -1,7 +1,28 @@
1
1
  {
2
2
  "name": "innernote",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "Write LinkedIn posts in your voice, from the terminal.",
5
+ "keywords": [
6
+ "linkedin",
7
+ "mcp",
8
+ "mcp-server",
9
+ "model-context-protocol",
10
+ "ai-agent",
11
+ "claude",
12
+ "claude-code",
13
+ "cursor",
14
+ "agent-skill",
15
+ "writing",
16
+ "content",
17
+ "personal-branding",
18
+ "ghostwriting",
19
+ "cli"
20
+ ],
21
+ "homepage": "https://www.innernote.space/cli",
22
+ "license": "UNLICENSED",
23
+ "bugs": {
24
+ "url": "https://www.innernote.space/cli"
25
+ },
5
26
  "type": "module",
6
27
  "bin": {
7
28
  "innernote": "dist/index.js"