innernote 0.4.0 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +5 -1
  2. package/dist/index.js +554 -38
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -86,12 +86,16 @@ from it fails the suite, so what you read here is what the binary has.
86
86
  | `login [code]` | connect this machine |
87
87
  | `logout` | forget the token stored here |
88
88
  | `whoami` | show which account is connected, and what this key can do |
89
- | `context` | what you post about: your pillars and your series |
89
+ | `context` | what you post about: your pillars, your series and your products |
90
90
  | `capture <text>` | save an idea to your inbox |
91
91
  | `write [topic]` | draft a post in your voice |
92
92
  | `shape <shape...>` | push a post shorter, punchier, warmer |
93
93
  | `ask [--post <id>] <anything>` | talk to the post in your own words; with `--post`, a saved post in one command. `--passage`, `--hook`, `--ending`, `--preset` and `--shape` edit one part of it, as the editor's selection, toolbar and format picker do |
94
94
  | `edit <id>` | put new text, a title, a first comment, a series or a ready state on a saved post |
95
+ | `repurpose <link or text>` | write your own post from a link, a capture (`--capture <id>`) or pasted text |
96
+ | `captures` | your Capture page: list, read one, `add` a link or a thought, `delete` |
97
+ | `radar` | recent posts from the creators you watch |
98
+ | `history <id>` | every earlier version of a post; `--restore <n>` puts one back |
95
99
  | `attach <id> <file...>` | put an image, images, a PDF or a video on a saved post |
96
100
  | `save` | keep the draft you are holding |
97
101
  | `show [id]` | a post with everything the app knows about it, or the one you are holding |
package/dist/index.js CHANGED
@@ -714,13 +714,15 @@ async function week(args) {
714
714
  }
715
715
  async function context(args) {
716
716
  const { json } = parseFlags(args);
717
- const [pillars, series, notebook] = await Promise.all([
717
+ const [pillars, series, notebook, products] = await Promise.all([
718
718
  request("/api/pillars"),
719
719
  request("/api/campaigns"),
720
- request("/api/notebook/brief").catch(() => ({ brief: null }))
720
+ request("/api/notebook/brief").catch(() => ({ brief: null })),
721
+ request("/api/products").catch(() => ({ products: [] }))
721
722
  ]);
723
+ const productRows = products.products.map((p) => ({ name: p.name, oneLiner: p.oneLiner, postCount: p.postCount }));
722
724
  if (json) {
723
- out(JSON.stringify({ pillars: pillars.pillars, series: series.campaigns, brief: notebook.brief }, null, 2));
725
+ out(JSON.stringify({ pillars: pillars.pillars, series: series.campaigns, products: productRows, brief: notebook.brief }, null, 2));
724
726
  return 0;
725
727
  }
726
728
  out();
@@ -755,6 +757,15 @@ async function context(args) {
755
757
  } else {
756
758
  out(` ${dim2("No series running.")}`);
757
759
  }
760
+ if (productRows.length) {
761
+ out();
762
+ out(` ${bold2("Products")}${dim2(" work one into a post with")} ${caramel('ask --post <id> --product "<name>"')}`);
763
+ for (const p of productRows) {
764
+ out(` ${caramel(p.name)}${p.postCount ? dim2(` ${p.postCount} ${p.postCount === 1 ? "post" : "posts"}`) : ""}`);
765
+ if (p.oneLiner)
766
+ out(` ${dim2(oneLine(p.oneLiner, 64))}`);
767
+ }
768
+ }
758
769
  out();
759
770
  return 0;
760
771
  }
@@ -1087,7 +1098,9 @@ function parseWhen(input) {
1087
1098
  return Number.isFinite(t) ? t : null;
1088
1099
  }
1089
1100
  async function queue(args) {
1090
- const { json, rest, value } = parseFlags(args, ["at"]);
1101
+ const { json, rest, value, has } = parseFlags(args, ["at"]);
1102
+ if (has("ready"))
1103
+ return queueReady(json, rest, value("at"));
1091
1104
  const ref = rest[0] ? resolveRef(rest[0]) : null;
1092
1105
  if (!ref) {
1093
1106
  fail("Which post?");
@@ -1122,6 +1135,40 @@ async function queue(args) {
1122
1135
  note(dim2("`week` to see where it landed"));
1123
1136
  return 0;
1124
1137
  }
1138
+ async function queueReady(json, rest, at) {
1139
+ if (rest.length || at !== undefined) {
1140
+ fail("--ready queues every Ready post in order, so it takes no post and no time.");
1141
+ note(dim2(`${CMD} queue --ready or ${CMD} queue <post-id>`));
1142
+ return 1;
1143
+ }
1144
+ const res = await request("/api/posts/queue-ready", { method: "POST", timeoutMs: 90000 });
1145
+ if (json) {
1146
+ out(JSON.stringify(res, null, 2));
1147
+ return 0;
1148
+ }
1149
+ if (res.queued.length === 0) {
1150
+ fail("Nothing was queued.");
1151
+ if (res.stopped)
1152
+ note(res.stopped);
1153
+ return 1;
1154
+ }
1155
+ const n = res.queued.length;
1156
+ ok(`Queued ${n} ${n === 1 ? "post" : "posts"}, in your Ready order. Each goes live on its own.`);
1157
+ for (const q of res.queued) {
1158
+ const when = new Date(q.scheduledFor).toLocaleString(undefined, {
1159
+ weekday: "short",
1160
+ day: "numeric",
1161
+ month: "short",
1162
+ hour: "numeric",
1163
+ minute: "2-digit"
1164
+ });
1165
+ out(` ${dim2(when.padEnd(22))} ${q.title || q.id}`);
1166
+ }
1167
+ if (res.waiting > 0)
1168
+ note(`${res.waiting} still in Ready. ${res.stopped ?? ""}`.trim());
1169
+ note(dim2("`week` to see where they landed"));
1170
+ return 0;
1171
+ }
1125
1172
  async function save(args) {
1126
1173
  const { json } = parseFlags(args);
1127
1174
  const held = getDraft();
@@ -1182,7 +1229,7 @@ async function open(args) {
1182
1229
  return 0;
1183
1230
  }
1184
1231
  async function ask(args) {
1185
- const { json, rest, value, has } = parseFlags(args, ["post", "passage", "preset", "shape"]);
1232
+ const { json, rest, value, has } = parseFlags(args, ["post", "passage", "preset", "shape", "product"]);
1186
1233
  const message = rest.join(" ").trim();
1187
1234
  const held = getDraft();
1188
1235
  const postRef = value("post");
@@ -1191,7 +1238,9 @@ async function ask(args) {
1191
1238
  passage: value("passage"),
1192
1239
  preset: value("preset"),
1193
1240
  shape: value("shape"),
1194
- region: has("hook") ? "hook" : has("ending") ? "ending" : undefined
1241
+ region: has("hook") ? "hook" : has("ending") ? "ending" : undefined,
1242
+ product: value("product"),
1243
+ productMode: has("rewrite") ? "rewrite" : "weave"
1195
1244
  };
1196
1245
  return askAboutPost(postRef, message, target, json);
1197
1246
  }
@@ -1342,6 +1391,9 @@ function askBody(content, message, t) {
1342
1391
  if (preset && ![...PRESETS.selection, ...PRESETS.whole, ...PRESETS.region].includes(preset)) {
1343
1392
  return `There is no preset called "${preset}". Presets: ${[...PRESETS.selection, ...PRESETS.whole, ...PRESETS.region].join(", ")}`;
1344
1393
  }
1394
+ if (t.productId) {
1395
+ return { text: content, productId: t.productId, productMode: t.productMode ?? "weave" };
1396
+ }
1345
1397
  if (t.shape) {
1346
1398
  if (!SHAPE_NAMES.includes(t.shape))
1347
1399
  return `There is no shape called "${t.shape}". Shapes: ${SHAPE_NAMES.join(", ")}`;
@@ -1389,6 +1441,16 @@ async function askAboutPost(refArg, message, target, json) {
1389
1441
  fail("That post is already live on LinkedIn, so it cannot be changed here.");
1390
1442
  return 1;
1391
1443
  }
1444
+ if (target.product) {
1445
+ const { products } = await request("/api/products");
1446
+ const want = target.product.trim().toLowerCase();
1447
+ const hit = products.find((p) => p.name.trim().toLowerCase() === want) ?? products.find((p) => p.name.toLowerCase().includes(want));
1448
+ if (!hit) {
1449
+ fail(products.length ? `There is no product called "${target.product}". Products: ${products.map((p) => p.name).join(", ")}` : "There are no products yet. Add one in Settings, under Products.");
1450
+ return 1;
1451
+ }
1452
+ target = { ...target, productId: hit._id };
1453
+ }
1392
1454
  const body = askBody(post.content, message, target);
1393
1455
  if (typeof body === "string") {
1394
1456
  fail(body);
@@ -1466,7 +1528,7 @@ async function showPost(refArg, json) {
1466
1528
  return 0;
1467
1529
  }
1468
1530
  async function edit(args) {
1469
- const { json, rest, value } = parseFlags(args, ["text", "file", "title", "first-comment", "series", "status"]);
1531
+ const { json, rest, value, has } = parseFlags(args, ["text", "file", "title", "first-comment", "series", "status", "after"]);
1470
1532
  const refArg = rest[0];
1471
1533
  if (!refArg) {
1472
1534
  fail("Edit which post?");
@@ -1478,17 +1540,23 @@ async function edit(args) {
1478
1540
  const firstComment = value("first-comment");
1479
1541
  const seriesName = value("series");
1480
1542
  const status = value("status");
1543
+ const first = has("first");
1544
+ const afterRef = value("after");
1481
1545
  const file = value("file");
1482
1546
  let text = value("text");
1483
1547
  if (file)
1484
1548
  text = await readFile2(file, "utf8");
1485
- const fieldsGiven = title !== undefined || firstComment !== undefined || seriesName !== undefined || status !== undefined;
1549
+ const fieldsGiven = title !== undefined || firstComment !== undefined || seriesName !== undefined || status !== undefined || first || afterRef !== undefined;
1486
1550
  if (text === undefined && !fieldsGiven) {
1487
1551
  text = await readStdin() ?? undefined;
1488
1552
  }
1489
1553
  if (text === undefined && !fieldsGiven) {
1490
1554
  fail("Nothing to change.");
1491
- note(dim2("--file, --text or piped text for the post, --title, --first-comment, --series, --status"));
1555
+ note(dim2("--file, --text or piped text for the post, --title, --first-comment, --series, --status, --first, --after"));
1556
+ return 1;
1557
+ }
1558
+ if (first && afterRef !== undefined) {
1559
+ fail("--first or --after, not both.");
1492
1560
  return 1;
1493
1561
  }
1494
1562
  if (status !== undefined && status !== "ready" && status !== "draft") {
@@ -1555,6 +1623,17 @@ async function edit(args) {
1555
1623
  await request(`/api/posts/${encodeURIComponent(post._id)}/ready`, { method: status === "ready" ? "POST" : "DELETE" });
1556
1624
  changed.push(`status (${status})`);
1557
1625
  }
1626
+ if (first || afterRef !== undefined) {
1627
+ let after = null;
1628
+ if (afterRef !== undefined) {
1629
+ const target = await postFromRef(afterRef, `${CMD} edit <post-id> --after <post-id>`);
1630
+ if (!target)
1631
+ return 1;
1632
+ after = target._id;
1633
+ }
1634
+ await request(`/api/posts/${encodeURIComponent(post._id)}/move`, { method: "POST", body: { after } });
1635
+ changed.push(after ? "place in Ready" : "place in Ready (top)");
1636
+ }
1558
1637
  if (json) {
1559
1638
  out(JSON.stringify({ id: post._id, changed }, null, 2));
1560
1639
  return 0;
@@ -1754,14 +1833,16 @@ var COMMANDS = {
1754
1833
  },
1755
1834
  ask: {
1756
1835
  summary: "talk to the post in your own words, or edit one passage of it",
1757
- usage: "innernote ask [--post <id>] [--passage <text> | --hook | --ending] [--preset <name> | --shape <name>] [what you want]",
1836
+ usage: "innernote ask [--post <id>] [--passage <text> | --hook | --ending] [--preset <name> | --shape <name> | --product <name>] [what you want]",
1758
1837
  detail: "The Writer, here. The app's composer is a conversation with the post in front of you, and this is the same assistant: it can change the post, answer a question about it, or say it would rather not. In a session it works on the post in hand. With --post it works on a saved post in one command, and the change is kept in the post's history. --passage is the editor's selection: the exact words to change, and only those change. --hook and --ending are the opening and closing block. --preset is a toolbar edit by name, --shape the format picker.",
1759
1838
  options: [
1760
1839
  ["--post <id>", "a saved post, by id, instead of the one in hand"],
1761
1840
  ["--passage <text>", "change only these exact words of the post"],
1762
1841
  ["--hook, --ending", "change only the opening, or the closing"],
1763
1842
  ["--preset <name>", "on a passage: sharpen shorter punchier simpler example deslop concrete warmer list. On the post: tighten_all as_list as_story fix_spacing rewrite_hook rewrite_ending ending_question"],
1764
- ["--shape <name>", "story opinion listicle contrarian experience howto lessons casestudy framework question announcement prediction beliefbreak"]
1843
+ ["--shape <name>", "story opinion listicle contrarian experience howto lessons casestudy framework question announcement prediction beliefbreak"],
1844
+ ["--product <name>", "work one of your products into the post, where it honestly belongs (`context` lists them)"],
1845
+ ["--rewrite", "with --product: rewrite the post around it instead"]
1765
1846
  ],
1766
1847
  examples: [
1767
1848
  "ask cut the second half and land it harder",
@@ -1770,12 +1851,13 @@ var COMMANDS = {
1770
1851
  'innernote ask --post p17abc... --passage "we got lucky" "say what the luck actually was"',
1771
1852
  'innernote ask --post p17abc... --passage "we got lucky" --preset concrete',
1772
1853
  "innernote ask --post p17abc... --preset ending_question",
1773
- "innernote ask --post p17abc... --shape story"
1854
+ "innernote ask --post p17abc... --shape story",
1855
+ 'innernote ask --post p17abc... --product "innernote"'
1774
1856
  ]
1775
1857
  },
1776
1858
  edit: {
1777
1859
  summary: "change a saved post: its text, title, first comment, series or status",
1778
- usage: "innernote edit <id> [--file <path> | --text <text>] [--title <text>] [--first-comment <text>] [--series <name>] [--status ready|draft]",
1860
+ usage: "innernote edit <id> [--file <path> | --text <text>] [--title <text>] [--first-comment <text>] [--series <name>] [--status ready|draft] [--first | --after <id>]",
1779
1861
  detail: "Puts your own words into a saved post. New text comes from --file, --text or a pipe, and is saved the way the app saves a draft, so the old text is one Undo away in the post's history. A post already live on LinkedIn keeps its text. The id comes from `drafts --json` or `show`.",
1780
1862
  options: [
1781
1863
  ["--file <path>", "the new text of the post, from a file"],
@@ -1783,15 +1865,72 @@ var COMMANDS = {
1783
1865
  ["--title <text>", "the post's title in the app"],
1784
1866
  ["--first-comment <text>", "the comment posted under it when it goes live"],
1785
1867
  ["--series <name>", "file it into a series, or `none` to take it out"],
1786
- ["--status <ready|draft>", "mark it ready, or send it back to draft"]
1868
+ ["--status <ready|draft>", "mark it ready, or send it back to draft"],
1869
+ ["--first", "put a Ready post at the top of Ready, so it goes out next"],
1870
+ ["--after <id>", "put a Ready post right after another one in Ready"]
1787
1871
  ],
1788
1872
  examples: [
1789
1873
  "innernote edit p17abc... --file post.txt",
1790
1874
  "pbpaste | innernote edit p17abc...",
1791
1875
  'innernote edit p17abc... --title "The sales call" --first-comment "link: innernote.space"',
1792
- 'innernote edit p17abc... --series "Build in Public" --status ready'
1876
+ 'innernote edit p17abc... --series "Build in Public" --status ready',
1877
+ "innernote edit 3 --first"
1793
1878
  ]
1794
1879
  },
1880
+ captures: {
1881
+ summary: "your Capture page: what you keep to write from later",
1882
+ usage: "innernote captures [<id>] | captures add <link or text> [--note <why>] | captures delete <id>",
1883
+ detail: "The same list as Capture in the app. With no argument it lists what you kept, newest first, with ids. With an id it prints that capture in full. `add` keeps a link (a LinkedIn post, an article, a page) or a thought in your own words: a link is read once, through innernote's cache, and kept with its text. `repurpose --capture <id>` writes a post from one.",
1884
+ options: [
1885
+ ["--note <why>", "with add: why you kept it, in a line"],
1886
+ ["--limit <n>", "how many to list (default 30)"]
1887
+ ],
1888
+ examples: [
1889
+ "innernote captures",
1890
+ 'innernote captures add https://www.linkedin.com/posts/... --note "the pricing argument"',
1891
+ "innernote captures j57abc..."
1892
+ ]
1893
+ },
1894
+ repurpose: {
1895
+ summary: "write your own post from a link, a capture or some text",
1896
+ usage: "innernote repurpose <link or text> | repurpose --capture <id> [--borrow <what>] [--instruction <text>] [--shape <name>] [--save]",
1897
+ detail: "Turn into a post, from here. The source is read, and the post is written in your voice from what you choose to borrow, then checked so it reads as yours and not theirs. --borrow idea takes their point and uses your proof, structure takes only how it is built, spark only reacts to it, and faithful relays what it reports in your words, with credit. A profile link yields its recent posts: the first is used, --pick chooses another.",
1898
+ options: [
1899
+ ["--capture <id>", "a capture from `captures`, instead of a link"],
1900
+ ["--borrow <what>", "idea, structure, spark or faithful"],
1901
+ ["--instruction <text>", "anything else it should do, in your words"],
1902
+ ["--shape <name>", "story, listicle, howto, lessons and the rest, as in `ask --shape`"],
1903
+ ["--pick <n>", "which of a profile's posts to write from"],
1904
+ ["--save", "keep it as a draft"],
1905
+ ["--series <name>", "with --save: file it into a series"],
1906
+ ["--title <text>", "with --save: its title (default: one is written for you)"]
1907
+ ],
1908
+ examples: [
1909
+ "innernote repurpose https://www.linkedin.com/posts/... --borrow structure",
1910
+ "innernote repurpose --capture j57abc... --borrow idea --save",
1911
+ "pbpaste | innernote repurpose --borrow spark"
1912
+ ]
1913
+ },
1914
+ radar: {
1915
+ summary: "recent posts from the creators you watch",
1916
+ usage: "innernote radar [--limit <n>] [--full]",
1917
+ detail: "The same feed as Radar in the app, newest first, with each post's link. Who you watch is set on the Radar page. `repurpose <link>` writes your own post from one.",
1918
+ options: [
1919
+ ["--limit <n>", "how many posts (default 15)"],
1920
+ ["--full", "print each post in full, not one line"]
1921
+ ],
1922
+ examples: ["innernote radar", "innernote radar --full --limit 5", "innernote radar --json"]
1923
+ },
1924
+ history: {
1925
+ summary: "every earlier version of a post, and putting one back",
1926
+ usage: "innernote history <id> [--show <n>] [--restore <n>]",
1927
+ detail: "Every change to a post's words is kept as a version first: yours, the assistant's, an edit from here. This lists them, newest first. --restore puts one back, and the text it replaces is kept as the newest version, so a restore is itself one step from undone.",
1928
+ options: [
1929
+ ["--show <n>", "print version n in full"],
1930
+ ["--restore <n>", "put version n back"]
1931
+ ],
1932
+ examples: ["innernote history p17abc...", "innernote history p17abc... --show 3", "innernote history p17abc... --restore 3"]
1933
+ },
1795
1934
  attach: {
1796
1935
  summary: "put an image, images, a PDF or a video on a saved post",
1797
1936
  usage: "innernote attach <id> <file...> [--title <text>] | innernote attach <id> --remove",
@@ -1871,17 +2010,20 @@ var COMMANDS = {
1871
2010
  examples: ["innernote skill", "innernote skill --install"]
1872
2011
  },
1873
2012
  context: {
1874
- summary: "what you post about: your topics and your series",
2013
+ summary: "what you post about: your topics, your series and your products",
1875
2014
  usage: "innernote context",
1876
- detail: "Your standing context: your topics with their weights, and every running series with what it is about and what it is for. This is the thing to read before deciding what to write, and the thing an agent should read first for the same reason. --json prints the raw payload for scripts and models.",
2015
+ detail: "Your standing context: your topics with their weights, every running series with what it is about and what it is for, and your products. This is the thing to read before deciding what to write, and the thing an agent should read first for the same reason. --json prints the raw payload for scripts and models.",
1877
2016
  examples: ["innernote context", "innernote context --json"]
1878
2017
  },
1879
2018
  queue: {
1880
2019
  summary: "schedule a post into your next open slot",
1881
- usage: "innernote queue <post-id> [--at <time>]",
1882
- 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.",
1883
- options: [["--at <time>", `a time you pick, like "2026-10-12 12:31" (this machine's timezone), instead of the next open slot`]],
1884
- examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah"]
2020
+ usage: "innernote queue <post-id> [--at <time>] | queue --ready",
2021
+ 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. `--ready` queues every Ready post at once, in your Ready order, the top one first: Queue these in the app.",
2022
+ options: [
2023
+ ["--at <time>", `a time you pick, like "2026-10-12 12:31" (this machine's timezone), instead of the next open slot`],
2024
+ ["--ready", "every Ready post, in your Ready order, into your next open slots"]
2025
+ ],
2026
+ examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah", "innernote drafts ready", "innernote queue --ready"]
1885
2027
  }
1886
2028
  };
1887
2029
  function commandHelp(name) {
@@ -1994,7 +2136,7 @@ function suggest(state) {
1994
2136
  // package.json
1995
2137
  var package_default = {
1996
2138
  name: "innernote",
1997
- version: "0.4.0",
2139
+ version: "0.6.0",
1998
2140
  description: "Write LinkedIn posts in your voice, from the terminal.",
1999
2141
  keywords: [
2000
2142
  "linkedin",
@@ -2285,17 +2427,323 @@ async function restorePost(args) {
2285
2427
  ok(`Restored. It is back in your posts. ${dim2(ref.id)}`);
2286
2428
  return 0;
2287
2429
  }
2430
+ async function history(args) {
2431
+ const { json, rest, value } = parseFlags(args, ["show", "restore"]);
2432
+ const ref = rest[0] ? resolveRef(rest[0]) : null;
2433
+ if (!ref) {
2434
+ fail("Whose history?");
2435
+ note(dim2(`${CMD} history <post-id>`));
2436
+ return 1;
2437
+ }
2438
+ const post = await fetchPost(ref.id);
2439
+ if (!post) {
2440
+ fail("That post could not be found.");
2441
+ note(dim2(`\`${CMD} drafts\` lists them with their ids`));
2442
+ return 1;
2443
+ }
2444
+ const { versions } = await request(`/api/posts/${encodeURIComponent(post._id)}/versions`);
2445
+ const pick = (n) => versions.find((v) => String(v.versionNumber) === n || v.id === n);
2446
+ const restore = value("restore");
2447
+ if (restore) {
2448
+ const v = pick(restore);
2449
+ if (!v) {
2450
+ fail(`There is no version ${restore}. \`${CMD} history ${post._id}\` lists them.`);
2451
+ return 1;
2452
+ }
2453
+ if (post.status === "published") {
2454
+ fail("That post is live on LinkedIn, so its text cannot go back to an older version here.");
2455
+ return 1;
2456
+ }
2457
+ const res = await request(`/api/posts/${encodeURIComponent(post._id)}/versions`, {
2458
+ method: "POST",
2459
+ body: { versionId: v.id }
2460
+ });
2461
+ if (json) {
2462
+ out(JSON.stringify({ id: post._id, restored: v.versionNumber, content: res.content }, null, 2));
2463
+ return 0;
2464
+ }
2465
+ out(res.content);
2466
+ note();
2467
+ ok(`Back to version ${v.versionNumber}. What it replaced is kept as the newest version. ${dim2(post._id)}`);
2468
+ return 0;
2469
+ }
2470
+ const show2 = value("show");
2471
+ if (show2) {
2472
+ const v = pick(show2);
2473
+ if (!v) {
2474
+ fail(`There is no version ${show2}.`);
2475
+ return 1;
2476
+ }
2477
+ if (json) {
2478
+ out(JSON.stringify(v, null, 2));
2479
+ return 0;
2480
+ }
2481
+ out(v.content);
2482
+ return 0;
2483
+ }
2484
+ if (json) {
2485
+ out(JSON.stringify(versions, null, 2));
2486
+ return 0;
2487
+ }
2488
+ if (!versions.length) {
2489
+ out(dim2("No earlier versions. Every change from now on is kept."));
2490
+ return 0;
2491
+ }
2492
+ out();
2493
+ for (const v of versions) {
2494
+ const why = v.instruction ? oneLine(v.instruction, 50) : v.source;
2495
+ out(` ${`v${v.versionNumber}`.padEnd(5)}${dim2(`${ago(v.createdAt).padEnd(5)} ${why}`)}`);
2496
+ out(` ${oneLine(v.content, 66)}`);
2497
+ }
2498
+ out();
2499
+ note(dim2(`\`${CMD} history ${post._id} --show <n>\` reads one, \`--restore <n>\` puts it back`));
2500
+ return 0;
2501
+ }
2502
+
2503
+ // src/commands/write.ts
2504
+ async function saveNew2(content, series, seriesSummary, title) {
2505
+ return request("/api/posts", {
2506
+ method: "POST",
2507
+ body: {
2508
+ content,
2509
+ source: "cli",
2510
+ ...title ? { title } : {},
2511
+ ...series ? { series, seriesSummary: seriesSummary ?? undefined } : {}
2512
+ }
2513
+ });
2514
+ }
2515
+
2516
+ // src/commands/sources.ts
2517
+ function looksLikeUrl(input) {
2518
+ const t = input.trim();
2519
+ return !/\s/.test(t) && (/^https?:\/\//i.test(t) || /^[\w-]+\.[\w.-]+\/\S/.test(t));
2520
+ }
2521
+ function capturedAt(c) {
2522
+ return c.createdAt ?? c._creationTime;
2523
+ }
2524
+ async function captures(args) {
2525
+ const { json, rest, value } = parseFlags(args, ["note", "limit"]);
2526
+ const [sub, ...more] = rest;
2527
+ if (sub === "add") {
2528
+ const piped = more.length ? null : await readStdin();
2529
+ const input = (more.join(" ") || piped || "").trim();
2530
+ if (!input) {
2531
+ fail("Add what? A link, or a thought in your own words.");
2532
+ note(dim2(`${CMD} captures add https://www.linkedin.com/posts/...`));
2533
+ return 1;
2534
+ }
2535
+ const noteText = value("note");
2536
+ const body = looksLikeUrl(input) ? { kind: "link", input, ...noteText ? { note: noteText } : {} } : { kind: "thought", content: input, ...noteText ? { note: noteText } : {} };
2537
+ const call = () => request("/api/captures", { method: "POST", body, timeoutMs: 120000 });
2538
+ const { capture: capture2 } = json ? await call() : await whileThinking([{ at: 0, text: "reading it..." }, { at: 8, text: "still reading..." }], call);
2539
+ if (json) {
2540
+ out(JSON.stringify(capture2, null, 2));
2541
+ return 0;
2542
+ }
2543
+ ok(`Kept on your Capture page. ${dim2(capture2._id)}`);
2544
+ note(dim2(capture2.title ? oneLine(capture2.title, 72) : oneLine(capture2.content, 72)));
2545
+ note(dim2(`\`${CMD} repurpose --capture ${capture2._id}\` writes a post from it`));
2546
+ return 0;
2547
+ }
2548
+ if (sub === "delete") {
2549
+ const id = more[0];
2550
+ if (!id) {
2551
+ fail("Delete which capture?");
2552
+ note(dim2(`${CMD} captures delete <capture-id>`));
2553
+ return 1;
2554
+ }
2555
+ await request(`/api/captures/${encodeURIComponent(id)}`, { method: "DELETE" });
2556
+ if (json) {
2557
+ out(JSON.stringify({ id, deleted: true }, null, 2));
2558
+ return 0;
2559
+ }
2560
+ ok(`Deleted. ${dim2(id)}`);
2561
+ return 0;
2562
+ }
2563
+ if (sub) {
2564
+ const { capture: capture2 } = await request(`/api/captures/${encodeURIComponent(sub)}`);
2565
+ if (json) {
2566
+ out(JSON.stringify(capture2, null, 2));
2567
+ return 0;
2568
+ }
2569
+ out(capture2.content);
2570
+ note();
2571
+ const facts = [
2572
+ ["title", capture2.title],
2573
+ ["kind", [capture2.kind, capture2.format].filter(Boolean).join(", ")],
2574
+ ["by", [capture2.author, capture2.publication].filter(Boolean).join(", ") || undefined],
2575
+ ["link", capture2.sourceUrl],
2576
+ ["your note", capture2.note],
2577
+ ["topics", capture2.topics?.length ? capture2.topics.join(", ") : undefined],
2578
+ ["saved", capturedAt(capture2) ? `${ago(capturedAt(capture2))} ago` : undefined],
2579
+ ["id", capture2._id]
2580
+ ];
2581
+ for (const [k, v] of facts)
2582
+ if (v)
2583
+ note(`${dim2(k.padEnd(10))} ${v}`);
2584
+ return 0;
2585
+ }
2586
+ const { captures: rows } = await request("/api/captures");
2587
+ const limit = Number(value("limit") ?? 30) || 30;
2588
+ if (json) {
2589
+ out(JSON.stringify(rows.slice(0, limit), null, 2));
2590
+ return 0;
2591
+ }
2592
+ if (!rows.length) {
2593
+ out(dim2("Nothing kept yet."));
2594
+ note(dim2(`${CMD} captures add <link>`));
2595
+ return 0;
2596
+ }
2597
+ out();
2598
+ for (const c of rows.slice(0, limit)) {
2599
+ const when = capturedAt(c) ? ago(capturedAt(c)) : "";
2600
+ const what = c.title || c.content;
2601
+ out(` ${caramel(oneLine(what, 66))}`);
2602
+ out(` ${dim2([c.format ?? c.kind, c.author, when, c._id].filter(Boolean).join(" "))}`);
2603
+ out();
2604
+ }
2605
+ if (rows.length > limit)
2606
+ note(dim2(`${rows.length - limit} more. --limit ${rows.length} to see them all`));
2607
+ note(dim2(`\`${CMD} captures <id>\` reads one in full`));
2608
+ return 0;
2609
+ }
2610
+ var BORROW = ["idea", "structure", "spark", "faithful"];
2611
+ async function repurpose(args) {
2612
+ const { json, rest, value, has } = parseFlags(args, ["capture", "borrow", "instruction", "shape", "pick", "series", "title"]);
2613
+ const borrow = value("borrow");
2614
+ if (borrow && !BORROW.includes(borrow)) {
2615
+ fail(`There is no --borrow "${borrow}". One of: ${BORROW.join(", ")}`);
2616
+ return 1;
2617
+ }
2618
+ let sourceText = "";
2619
+ let captureId;
2620
+ let from = "";
2621
+ const capture2 = value("capture");
2622
+ if (capture2) {
2623
+ const { capture: c } = await request(`/api/captures/${encodeURIComponent(capture2)}`);
2624
+ sourceText = c.content;
2625
+ captureId = c._id;
2626
+ from = c.title ?? oneLine(c.content, 60);
2627
+ } else {
2628
+ const piped = rest.length ? null : await readStdin();
2629
+ const input = (rest.join(" ") || piped || "").trim();
2630
+ if (!input) {
2631
+ fail("Repurpose what? A link, some text, or --capture <id>.");
2632
+ note(dim2(`${CMD} repurpose https://www.linkedin.com/posts/...`));
2633
+ note(dim2(`${CMD} repurpose --capture <capture-id> --borrow structure`));
2634
+ return 1;
2635
+ }
2636
+ const read = () => request("/api/reframe/extract", { method: "POST", body: { input }, timeoutMs: 120000 });
2637
+ const src = json ? await read() : await whileThinking([{ at: 0, text: "reading it..." }, { at: 8, text: "still reading..." }], read);
2638
+ if (src.rejected) {
2639
+ fail(src.message ?? "That could not be read. Paste the text instead.");
2640
+ return 1;
2641
+ }
2642
+ sourceText = src.text ?? "";
2643
+ if (src.candidates && src.candidates.length > 1) {
2644
+ const pick = Number(value("pick") ?? 1);
2645
+ const chosen = src.candidates[pick - 1];
2646
+ if (!chosen) {
2647
+ fail(`There are ${src.candidates.length} posts there. --pick 1 to ${src.candidates.length}.`);
2648
+ return 1;
2649
+ }
2650
+ sourceText = chosen.text;
2651
+ if (!json) {
2652
+ note(dim2(`${src.candidates.length} posts found there, writing from number ${pick}:`));
2653
+ src.candidates.forEach((c, i) => note(dim2(` ${i + 1}. ${oneLine(c.title ?? c.text, 70)}`)));
2654
+ }
2655
+ }
2656
+ from = src.title ?? src.author ?? oneLine(sourceText, 60);
2657
+ }
2658
+ const shape2 = value("shape");
2659
+ const call = () => request("/api/reframe/rewrite", {
2660
+ method: "POST",
2661
+ body: {
2662
+ sourceText,
2663
+ origin: "found",
2664
+ ...captureId ? { captureId } : {},
2665
+ ...borrow ? { borrowMode: borrow } : {},
2666
+ ...value("instruction") ? { instruction: value("instruction") } : {},
2667
+ ...shape2 ? { postFormat: shape2 } : {}
2668
+ },
2669
+ timeoutMs: 200000
2670
+ });
2671
+ const result = json ? await call() : await whileThinking([
2672
+ { at: 0, text: "reading what it argues..." },
2673
+ { at: 6, text: "writing it in your voice..." },
2674
+ { at: 30, text: "checking it is yours, not theirs..." }
2675
+ ], call);
2676
+ const post = result.variations[0];
2677
+ if (!post?.content) {
2678
+ fail("Nothing came back. Try again, or try another --borrow.");
2679
+ return 1;
2680
+ }
2681
+ const series = value("series");
2682
+ const title = value("title");
2683
+ setDraft({ content: post.content, series });
2684
+ const saved = has("save") ? await saveNew2(post.content, series, undefined, title) : null;
2685
+ if (saved)
2686
+ updateDraft({ id: saved.id });
2687
+ if (saved && !title) {
2688
+ await request(`/api/posts/${encodeURIComponent(saved.id)}/title`, { method: "POST", timeoutMs: 30000 }).catch(() => {});
2689
+ }
2690
+ if (json) {
2691
+ out(JSON.stringify({ ...post, ...saved ? { id: saved.id } : {} }, null, 2));
2692
+ return 0;
2693
+ }
2694
+ out();
2695
+ await reveal(post.content, (chunk) => process.stdout.write(chunk));
2696
+ out();
2697
+ note(dim2(`${bar(post.qualityScore)} ${post.qualityScore} from: ${oneLine(from, 60)}`));
2698
+ if (saved)
2699
+ ok(series ? `Saved into "${series}". ${dim2(saved.id)}` : `Saved as a draft. ${dim2(saved.id)}`);
2700
+ else
2701
+ note(dim2("Not saved yet. `save` to keep it, or run again with --save."));
2702
+ return 0;
2703
+ }
2704
+ async function radar(args) {
2705
+ const { json, value, has } = parseFlags(args, ["limit"]);
2706
+ const feed = await request("/api/trend/feed");
2707
+ const limit = Number(value("limit") ?? 15) || 15;
2708
+ const cards = feed.cards.filter((c) => !c.pending);
2709
+ if (json) {
2710
+ out(JSON.stringify({ creators: feed.creators, cards: cards.slice(0, limit) }, null, 2));
2711
+ return 0;
2712
+ }
2713
+ if (!feed.creators.length) {
2714
+ out(dim2("You are not watching anyone yet. Add creators on the Radar page, under Who you watch."));
2715
+ return 0;
2716
+ }
2717
+ out();
2718
+ for (const c of cards.slice(0, limit)) {
2719
+ const m = c.metrics;
2720
+ const stats = m ? [m.likes ? `${m.likes} likes` : "", m.comments ? `${m.comments} comments` : ""].filter(Boolean).join(", ") : "";
2721
+ out(` ${bold2(c.authorName)}${dim2(` ${[c.postedAt ? ago(c.postedAt) : "", stats].filter(Boolean).join(" ")}`)}`);
2722
+ if (c.locked)
2723
+ out(` ${dim2("held back on the trial")}`);
2724
+ else if (has("full") && c.text)
2725
+ out(c.text.split(`
2726
+ `).map((l) => ` ${l}`).join(`
2727
+ `));
2728
+ else if (c.text)
2729
+ out(` ${oneLine(c.text, 72)}`);
2730
+ out(` ${dim2(c.postUrl)}`);
2731
+ out();
2732
+ }
2733
+ note(dim2(`\`${CMD} repurpose <link>\` writes your own post from one`));
2734
+ return 0;
2735
+ }
2288
2736
 
2289
2737
  // src/session.ts
2290
2738
  import { createInterface } from "node:readline";
2291
2739
 
2292
2740
  // src/ghost.ts
2293
- function ghostFor(line2, cursor, history, names) {
2741
+ function ghostFor(line2, cursor, history2, names) {
2294
2742
  if (!line2 || cursor !== line2.length)
2295
2743
  return null;
2296
2744
  if (line2.startsWith(" "))
2297
2745
  return null;
2298
- const fromHistory = history.find((h) => h.startsWith(line2) && h.length > line2.length);
2746
+ const fromHistory = history2.find((h) => h.startsWith(line2) && h.length > line2.length);
2299
2747
  if (fromHistory)
2300
2748
  return fromHistory.slice(line2.length);
2301
2749
  if (!line2.includes(" ")) {
@@ -2378,14 +2826,16 @@ var COMMANDS2 = {
2378
2826
  },
2379
2827
  ask: {
2380
2828
  summary: "talk to the post in your own words, or edit one passage of it",
2381
- usage: "innernote ask [--post <id>] [--passage <text> | --hook | --ending] [--preset <name> | --shape <name>] [what you want]",
2829
+ usage: "innernote ask [--post <id>] [--passage <text> | --hook | --ending] [--preset <name> | --shape <name> | --product <name>] [what you want]",
2382
2830
  detail: "The Writer, here. The app's composer is a conversation with the post in front of you, and this is the same assistant: it can change the post, answer a question about it, or say it would rather not. In a session it works on the post in hand. With --post it works on a saved post in one command, and the change is kept in the post's history. --passage is the editor's selection: the exact words to change, and only those change. --hook and --ending are the opening and closing block. --preset is a toolbar edit by name, --shape the format picker.",
2383
2831
  options: [
2384
2832
  ["--post <id>", "a saved post, by id, instead of the one in hand"],
2385
2833
  ["--passage <text>", "change only these exact words of the post"],
2386
2834
  ["--hook, --ending", "change only the opening, or the closing"],
2387
2835
  ["--preset <name>", "on a passage: sharpen shorter punchier simpler example deslop concrete warmer list. On the post: tighten_all as_list as_story fix_spacing rewrite_hook rewrite_ending ending_question"],
2388
- ["--shape <name>", "story opinion listicle contrarian experience howto lessons casestudy framework question announcement prediction beliefbreak"]
2836
+ ["--shape <name>", "story opinion listicle contrarian experience howto lessons casestudy framework question announcement prediction beliefbreak"],
2837
+ ["--product <name>", "work one of your products into the post, where it honestly belongs (`context` lists them)"],
2838
+ ["--rewrite", "with --product: rewrite the post around it instead"]
2389
2839
  ],
2390
2840
  examples: [
2391
2841
  "ask cut the second half and land it harder",
@@ -2394,12 +2844,13 @@ var COMMANDS2 = {
2394
2844
  'innernote ask --post p17abc... --passage "we got lucky" "say what the luck actually was"',
2395
2845
  'innernote ask --post p17abc... --passage "we got lucky" --preset concrete',
2396
2846
  "innernote ask --post p17abc... --preset ending_question",
2397
- "innernote ask --post p17abc... --shape story"
2847
+ "innernote ask --post p17abc... --shape story",
2848
+ 'innernote ask --post p17abc... --product "innernote"'
2398
2849
  ]
2399
2850
  },
2400
2851
  edit: {
2401
2852
  summary: "change a saved post: its text, title, first comment, series or status",
2402
- usage: "innernote edit <id> [--file <path> | --text <text>] [--title <text>] [--first-comment <text>] [--series <name>] [--status ready|draft]",
2853
+ usage: "innernote edit <id> [--file <path> | --text <text>] [--title <text>] [--first-comment <text>] [--series <name>] [--status ready|draft] [--first | --after <id>]",
2403
2854
  detail: "Puts your own words into a saved post. New text comes from --file, --text or a pipe, and is saved the way the app saves a draft, so the old text is one Undo away in the post's history. A post already live on LinkedIn keeps its text. The id comes from `drafts --json` or `show`.",
2404
2855
  options: [
2405
2856
  ["--file <path>", "the new text of the post, from a file"],
@@ -2407,15 +2858,72 @@ var COMMANDS2 = {
2407
2858
  ["--title <text>", "the post's title in the app"],
2408
2859
  ["--first-comment <text>", "the comment posted under it when it goes live"],
2409
2860
  ["--series <name>", "file it into a series, or `none` to take it out"],
2410
- ["--status <ready|draft>", "mark it ready, or send it back to draft"]
2861
+ ["--status <ready|draft>", "mark it ready, or send it back to draft"],
2862
+ ["--first", "put a Ready post at the top of Ready, so it goes out next"],
2863
+ ["--after <id>", "put a Ready post right after another one in Ready"]
2411
2864
  ],
2412
2865
  examples: [
2413
2866
  "innernote edit p17abc... --file post.txt",
2414
2867
  "pbpaste | innernote edit p17abc...",
2415
2868
  'innernote edit p17abc... --title "The sales call" --first-comment "link: innernote.space"',
2416
- 'innernote edit p17abc... --series "Build in Public" --status ready'
2869
+ 'innernote edit p17abc... --series "Build in Public" --status ready',
2870
+ "innernote edit 3 --first"
2871
+ ]
2872
+ },
2873
+ captures: {
2874
+ summary: "your Capture page: what you keep to write from later",
2875
+ usage: "innernote captures [<id>] | captures add <link or text> [--note <why>] | captures delete <id>",
2876
+ detail: "The same list as Capture in the app. With no argument it lists what you kept, newest first, with ids. With an id it prints that capture in full. `add` keeps a link (a LinkedIn post, an article, a page) or a thought in your own words: a link is read once, through innernote's cache, and kept with its text. `repurpose --capture <id>` writes a post from one.",
2877
+ options: [
2878
+ ["--note <why>", "with add: why you kept it, in a line"],
2879
+ ["--limit <n>", "how many to list (default 30)"]
2880
+ ],
2881
+ examples: [
2882
+ "innernote captures",
2883
+ 'innernote captures add https://www.linkedin.com/posts/... --note "the pricing argument"',
2884
+ "innernote captures j57abc..."
2885
+ ]
2886
+ },
2887
+ repurpose: {
2888
+ summary: "write your own post from a link, a capture or some text",
2889
+ usage: "innernote repurpose <link or text> | repurpose --capture <id> [--borrow <what>] [--instruction <text>] [--shape <name>] [--save]",
2890
+ detail: "Turn into a post, from here. The source is read, and the post is written in your voice from what you choose to borrow, then checked so it reads as yours and not theirs. --borrow idea takes their point and uses your proof, structure takes only how it is built, spark only reacts to it, and faithful relays what it reports in your words, with credit. A profile link yields its recent posts: the first is used, --pick chooses another.",
2891
+ options: [
2892
+ ["--capture <id>", "a capture from `captures`, instead of a link"],
2893
+ ["--borrow <what>", "idea, structure, spark or faithful"],
2894
+ ["--instruction <text>", "anything else it should do, in your words"],
2895
+ ["--shape <name>", "story, listicle, howto, lessons and the rest, as in `ask --shape`"],
2896
+ ["--pick <n>", "which of a profile's posts to write from"],
2897
+ ["--save", "keep it as a draft"],
2898
+ ["--series <name>", "with --save: file it into a series"],
2899
+ ["--title <text>", "with --save: its title (default: one is written for you)"]
2900
+ ],
2901
+ examples: [
2902
+ "innernote repurpose https://www.linkedin.com/posts/... --borrow structure",
2903
+ "innernote repurpose --capture j57abc... --borrow idea --save",
2904
+ "pbpaste | innernote repurpose --borrow spark"
2417
2905
  ]
2418
2906
  },
2907
+ radar: {
2908
+ summary: "recent posts from the creators you watch",
2909
+ usage: "innernote radar [--limit <n>] [--full]",
2910
+ detail: "The same feed as Radar in the app, newest first, with each post's link. Who you watch is set on the Radar page. `repurpose <link>` writes your own post from one.",
2911
+ options: [
2912
+ ["--limit <n>", "how many posts (default 15)"],
2913
+ ["--full", "print each post in full, not one line"]
2914
+ ],
2915
+ examples: ["innernote radar", "innernote radar --full --limit 5", "innernote radar --json"]
2916
+ },
2917
+ history: {
2918
+ summary: "every earlier version of a post, and putting one back",
2919
+ usage: "innernote history <id> [--show <n>] [--restore <n>]",
2920
+ detail: "Every change to a post's words is kept as a version first: yours, the assistant's, an edit from here. This lists them, newest first. --restore puts one back, and the text it replaces is kept as the newest version, so a restore is itself one step from undone.",
2921
+ options: [
2922
+ ["--show <n>", "print version n in full"],
2923
+ ["--restore <n>", "put version n back"]
2924
+ ],
2925
+ examples: ["innernote history p17abc...", "innernote history p17abc... --show 3", "innernote history p17abc... --restore 3"]
2926
+ },
2419
2927
  attach: {
2420
2928
  summary: "put an image, images, a PDF or a video on a saved post",
2421
2929
  usage: "innernote attach <id> <file...> [--title <text>] | innernote attach <id> --remove",
@@ -2495,17 +3003,20 @@ var COMMANDS2 = {
2495
3003
  examples: ["innernote skill", "innernote skill --install"]
2496
3004
  },
2497
3005
  context: {
2498
- summary: "what you post about: your topics and your series",
3006
+ summary: "what you post about: your topics, your series and your products",
2499
3007
  usage: "innernote context",
2500
- detail: "Your standing context: your topics with their weights, and every running series with what it is about and what it is for. This is the thing to read before deciding what to write, and the thing an agent should read first for the same reason. --json prints the raw payload for scripts and models.",
3008
+ detail: "Your standing context: your topics with their weights, every running series with what it is about and what it is for, and your products. This is the thing to read before deciding what to write, and the thing an agent should read first for the same reason. --json prints the raw payload for scripts and models.",
2501
3009
  examples: ["innernote context", "innernote context --json"]
2502
3010
  },
2503
3011
  queue: {
2504
3012
  summary: "schedule a post into your next open slot",
2505
- usage: "innernote queue <post-id> [--at <time>]",
2506
- 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.",
2507
- options: [["--at <time>", `a time you pick, like "2026-10-12 12:31" (this machine's timezone), instead of the next open slot`]],
2508
- examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah"]
3013
+ usage: "innernote queue <post-id> [--at <time>] | queue --ready",
3014
+ 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. `--ready` queues every Ready post at once, in your Ready order, the top one first: Queue these in the app.",
3015
+ options: [
3016
+ ["--at <time>", `a time you pick, like "2026-10-12 12:31" (this machine's timezone), instead of the next open slot`],
3017
+ ["--ready", "every Ready post, in your Ready order, into your next open slots"]
3018
+ ],
3019
+ examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah", "innernote drafts ready", "innernote queue --ready"]
2509
3020
  }
2510
3021
  };
2511
3022
  function commandHelp2(name) {
@@ -2790,8 +3301,9 @@ var VERSION2 = package_default.version;
2790
3301
  function commandList() {
2791
3302
  const groups = [
2792
3303
  ["Getting connected", ["login", "logout", "whoami", "skill"]],
2793
- ["Writing", ["write", "shape", "ask", "edit", "attach", "save", "capture"]],
2794
- ["Looking", ["show", "ideas", "drafts", "open", "week", "context"]],
3304
+ ["Writing", ["write", "repurpose", "shape", "ask", "edit", "attach", "save", "capture"]],
3305
+ ["Sources", ["captures", "radar"]],
3306
+ ["Looking", ["show", "history", "ideas", "drafts", "open", "week", "context"]],
2795
3307
  ["Shipping", ["queue", "unqueue"]],
2796
3308
  ["Tidying", ["delete", "restore"]]
2797
3309
  ];
@@ -2831,6 +3343,10 @@ var COMMANDS3 = {
2831
3343
  attach,
2832
3344
  delete: deletePost,
2833
3345
  restore: restorePost,
3346
+ history,
3347
+ captures,
3348
+ repurpose,
3349
+ radar,
2834
3350
  save,
2835
3351
  show,
2836
3352
  drop,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "innernote",
3
- "version": "0.4.0",
3
+ "version": "0.6.0",
4
4
  "description": "Write LinkedIn posts in your voice, from the terminal.",
5
5
  "keywords": [
6
6
  "linkedin",