innernote 0.5.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/index.js +101 -25
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1098,7 +1098,9 @@ function parseWhen(input) {
1098
1098
  return Number.isFinite(t) ? t : null;
1099
1099
  }
1100
1100
  async function queue(args) {
1101
- 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"));
1102
1104
  const ref = rest[0] ? resolveRef(rest[0]) : null;
1103
1105
  if (!ref) {
1104
1106
  fail("Which post?");
@@ -1133,6 +1135,40 @@ async function queue(args) {
1133
1135
  note(dim2("`week` to see where it landed"));
1134
1136
  return 0;
1135
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
+ }
1136
1172
  async function save(args) {
1137
1173
  const { json } = parseFlags(args);
1138
1174
  const held = getDraft();
@@ -1439,13 +1475,22 @@ async function askAboutPost(refArg, message, target, json) {
1439
1475
  }
1440
1476
  }
1441
1477
  if (json) {
1442
- out(JSON.stringify({ id: post._id, changed: !!result.changed, post: stored, passage: result.fragment ?? null, reply: result.reply ?? null }, null, 2));
1478
+ out(JSON.stringify({
1479
+ id: post._id,
1480
+ changed: !!result.changed,
1481
+ post: stored,
1482
+ passage: result.fragment ?? null,
1483
+ removed: !!result.removed,
1484
+ reply: result.reply ?? null
1485
+ }, null, 2));
1443
1486
  return 0;
1444
1487
  }
1445
1488
  out();
1446
1489
  out(stored);
1447
1490
  out();
1448
- if (result.changed && result.fragment && target.passage !== undefined) {
1491
+ if (result.changed && result.removed && target.passage !== undefined) {
1492
+ note(dim2(`took out: ${target.passage}`));
1493
+ } else if (result.changed && result.fragment && target.passage !== undefined) {
1449
1494
  note(dim2(`was: ${target.passage}`));
1450
1495
  note(dim2(`now: ${result.fragment}`));
1451
1496
  }
@@ -1492,7 +1537,7 @@ async function showPost(refArg, json) {
1492
1537
  return 0;
1493
1538
  }
1494
1539
  async function edit(args) {
1495
- const { json, rest, value } = parseFlags(args, ["text", "file", "title", "first-comment", "series", "status"]);
1540
+ const { json, rest, value, has } = parseFlags(args, ["text", "file", "title", "first-comment", "series", "status", "after"]);
1496
1541
  const refArg = rest[0];
1497
1542
  if (!refArg) {
1498
1543
  fail("Edit which post?");
@@ -1504,17 +1549,23 @@ async function edit(args) {
1504
1549
  const firstComment = value("first-comment");
1505
1550
  const seriesName = value("series");
1506
1551
  const status = value("status");
1552
+ const first = has("first");
1553
+ const afterRef = value("after");
1507
1554
  const file = value("file");
1508
1555
  let text = value("text");
1509
1556
  if (file)
1510
1557
  text = await readFile2(file, "utf8");
1511
- const fieldsGiven = title !== undefined || firstComment !== undefined || seriesName !== undefined || status !== undefined;
1558
+ const fieldsGiven = title !== undefined || firstComment !== undefined || seriesName !== undefined || status !== undefined || first || afterRef !== undefined;
1512
1559
  if (text === undefined && !fieldsGiven) {
1513
1560
  text = await readStdin() ?? undefined;
1514
1561
  }
1515
1562
  if (text === undefined && !fieldsGiven) {
1516
1563
  fail("Nothing to change.");
1517
- note(dim2("--file, --text or piped text for the post, --title, --first-comment, --series, --status"));
1564
+ note(dim2("--file, --text or piped text for the post, --title, --first-comment, --series, --status, --first, --after"));
1565
+ return 1;
1566
+ }
1567
+ if (first && afterRef !== undefined) {
1568
+ fail("--first or --after, not both.");
1518
1569
  return 1;
1519
1570
  }
1520
1571
  if (status !== undefined && status !== "ready" && status !== "draft") {
@@ -1581,6 +1632,17 @@ async function edit(args) {
1581
1632
  await request(`/api/posts/${encodeURIComponent(post._id)}/ready`, { method: status === "ready" ? "POST" : "DELETE" });
1582
1633
  changed.push(`status (${status})`);
1583
1634
  }
1635
+ if (first || afterRef !== undefined) {
1636
+ let after = null;
1637
+ if (afterRef !== undefined) {
1638
+ const target = await postFromRef(afterRef, `${CMD} edit <post-id> --after <post-id>`);
1639
+ if (!target)
1640
+ return 1;
1641
+ after = target._id;
1642
+ }
1643
+ await request(`/api/posts/${encodeURIComponent(post._id)}/move`, { method: "POST", body: { after } });
1644
+ changed.push(after ? "place in Ready" : "place in Ready (top)");
1645
+ }
1584
1646
  if (json) {
1585
1647
  out(JSON.stringify({ id: post._id, changed }, null, 2));
1586
1648
  return 0;
@@ -1781,10 +1843,10 @@ var COMMANDS = {
1781
1843
  ask: {
1782
1844
  summary: "talk to the post in your own words, or edit one passage of it",
1783
1845
  usage: "innernote ask [--post <id>] [--passage <text> | --hook | --ending] [--preset <name> | --shape <name> | --product <name>] [what you want]",
1784
- 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.",
1846
+ 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; ask for them to go and they are taken out. --hook and --ending are the opening and closing block. --preset is a toolbar edit by name, --shape the format picker.",
1785
1847
  options: [
1786
1848
  ["--post <id>", "a saved post, by id, instead of the one in hand"],
1787
- ["--passage <text>", "change only these exact words of the post"],
1849
+ ["--passage <text>", "change, or take out, only these exact words of the post"],
1788
1850
  ["--hook, --ending", "change only the opening, or the closing"],
1789
1851
  ["--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"],
1790
1852
  ["--shape <name>", "story opinion listicle contrarian experience howto lessons casestudy framework question announcement prediction beliefbreak"],
@@ -1797,6 +1859,7 @@ var COMMANDS = {
1797
1859
  'innernote ask --post p17abc... "make the opening shorter"',
1798
1860
  'innernote ask --post p17abc... --passage "we got lucky" "say what the luck actually was"',
1799
1861
  'innernote ask --post p17abc... --passage "we got lucky" --preset concrete',
1862
+ 'innernote ask --post p17abc... --passage "comment voice for the link." "take this line out"',
1800
1863
  "innernote ask --post p17abc... --preset ending_question",
1801
1864
  "innernote ask --post p17abc... --shape story",
1802
1865
  'innernote ask --post p17abc... --product "innernote"'
@@ -1804,7 +1867,7 @@ var COMMANDS = {
1804
1867
  },
1805
1868
  edit: {
1806
1869
  summary: "change a saved post: its text, title, first comment, series or status",
1807
- usage: "innernote edit <id> [--file <path> | --text <text>] [--title <text>] [--first-comment <text>] [--series <name>] [--status ready|draft]",
1870
+ usage: "innernote edit <id> [--file <path> | --text <text>] [--title <text>] [--first-comment <text>] [--series <name>] [--status ready|draft] [--first | --after <id>]",
1808
1871
  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`.",
1809
1872
  options: [
1810
1873
  ["--file <path>", "the new text of the post, from a file"],
@@ -1812,13 +1875,16 @@ var COMMANDS = {
1812
1875
  ["--title <text>", "the post's title in the app"],
1813
1876
  ["--first-comment <text>", "the comment posted under it when it goes live"],
1814
1877
  ["--series <name>", "file it into a series, or `none` to take it out"],
1815
- ["--status <ready|draft>", "mark it ready, or send it back to draft"]
1878
+ ["--status <ready|draft>", "mark it ready, or send it back to draft"],
1879
+ ["--first", "put a Ready post at the top of Ready, so it goes out next"],
1880
+ ["--after <id>", "put a Ready post right after another one in Ready"]
1816
1881
  ],
1817
1882
  examples: [
1818
1883
  "innernote edit p17abc... --file post.txt",
1819
1884
  "pbpaste | innernote edit p17abc...",
1820
1885
  'innernote edit p17abc... --title "The sales call" --first-comment "link: innernote.space"',
1821
- 'innernote edit p17abc... --series "Build in Public" --status ready'
1886
+ 'innernote edit p17abc... --series "Build in Public" --status ready',
1887
+ "innernote edit 3 --first"
1822
1888
  ]
1823
1889
  },
1824
1890
  captures: {
@@ -1961,10 +2027,13 @@ var COMMANDS = {
1961
2027
  },
1962
2028
  queue: {
1963
2029
  summary: "schedule a post into your next open slot",
1964
- usage: "innernote queue <post-id> [--at <time>]",
1965
- 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.",
1966
- options: [["--at <time>", `a time you pick, like "2026-10-12 12:31" (this machine's timezone), instead of the next open slot`]],
1967
- examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah"]
2030
+ usage: "innernote queue <post-id> [--at <time>] | queue --ready",
2031
+ 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.",
2032
+ options: [
2033
+ ["--at <time>", `a time you pick, like "2026-10-12 12:31" (this machine's timezone), instead of the next open slot`],
2034
+ ["--ready", "every Ready post, in your Ready order, into your next open slots"]
2035
+ ],
2036
+ examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah", "innernote drafts ready", "innernote queue --ready"]
1968
2037
  }
1969
2038
  };
1970
2039
  function commandHelp(name) {
@@ -2077,7 +2146,7 @@ function suggest(state) {
2077
2146
  // package.json
2078
2147
  var package_default = {
2079
2148
  name: "innernote",
2080
- version: "0.5.0",
2149
+ version: "0.6.1",
2081
2150
  description: "Write LinkedIn posts in your voice, from the terminal.",
2082
2151
  keywords: [
2083
2152
  "linkedin",
@@ -2768,10 +2837,10 @@ var COMMANDS2 = {
2768
2837
  ask: {
2769
2838
  summary: "talk to the post in your own words, or edit one passage of it",
2770
2839
  usage: "innernote ask [--post <id>] [--passage <text> | --hook | --ending] [--preset <name> | --shape <name> | --product <name>] [what you want]",
2771
- 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.",
2840
+ 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; ask for them to go and they are taken out. --hook and --ending are the opening and closing block. --preset is a toolbar edit by name, --shape the format picker.",
2772
2841
  options: [
2773
2842
  ["--post <id>", "a saved post, by id, instead of the one in hand"],
2774
- ["--passage <text>", "change only these exact words of the post"],
2843
+ ["--passage <text>", "change, or take out, only these exact words of the post"],
2775
2844
  ["--hook, --ending", "change only the opening, or the closing"],
2776
2845
  ["--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"],
2777
2846
  ["--shape <name>", "story opinion listicle contrarian experience howto lessons casestudy framework question announcement prediction beliefbreak"],
@@ -2784,6 +2853,7 @@ var COMMANDS2 = {
2784
2853
  'innernote ask --post p17abc... "make the opening shorter"',
2785
2854
  'innernote ask --post p17abc... --passage "we got lucky" "say what the luck actually was"',
2786
2855
  'innernote ask --post p17abc... --passage "we got lucky" --preset concrete',
2856
+ 'innernote ask --post p17abc... --passage "comment voice for the link." "take this line out"',
2787
2857
  "innernote ask --post p17abc... --preset ending_question",
2788
2858
  "innernote ask --post p17abc... --shape story",
2789
2859
  'innernote ask --post p17abc... --product "innernote"'
@@ -2791,7 +2861,7 @@ var COMMANDS2 = {
2791
2861
  },
2792
2862
  edit: {
2793
2863
  summary: "change a saved post: its text, title, first comment, series or status",
2794
- usage: "innernote edit <id> [--file <path> | --text <text>] [--title <text>] [--first-comment <text>] [--series <name>] [--status ready|draft]",
2864
+ usage: "innernote edit <id> [--file <path> | --text <text>] [--title <text>] [--first-comment <text>] [--series <name>] [--status ready|draft] [--first | --after <id>]",
2795
2865
  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`.",
2796
2866
  options: [
2797
2867
  ["--file <path>", "the new text of the post, from a file"],
@@ -2799,13 +2869,16 @@ var COMMANDS2 = {
2799
2869
  ["--title <text>", "the post's title in the app"],
2800
2870
  ["--first-comment <text>", "the comment posted under it when it goes live"],
2801
2871
  ["--series <name>", "file it into a series, or `none` to take it out"],
2802
- ["--status <ready|draft>", "mark it ready, or send it back to draft"]
2872
+ ["--status <ready|draft>", "mark it ready, or send it back to draft"],
2873
+ ["--first", "put a Ready post at the top of Ready, so it goes out next"],
2874
+ ["--after <id>", "put a Ready post right after another one in Ready"]
2803
2875
  ],
2804
2876
  examples: [
2805
2877
  "innernote edit p17abc... --file post.txt",
2806
2878
  "pbpaste | innernote edit p17abc...",
2807
2879
  'innernote edit p17abc... --title "The sales call" --first-comment "link: innernote.space"',
2808
- 'innernote edit p17abc... --series "Build in Public" --status ready'
2880
+ 'innernote edit p17abc... --series "Build in Public" --status ready',
2881
+ "innernote edit 3 --first"
2809
2882
  ]
2810
2883
  },
2811
2884
  captures: {
@@ -2948,10 +3021,13 @@ var COMMANDS2 = {
2948
3021
  },
2949
3022
  queue: {
2950
3023
  summary: "schedule a post into your next open slot",
2951
- usage: "innernote queue <post-id> [--at <time>]",
2952
- 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.",
2953
- options: [["--at <time>", `a time you pick, like "2026-10-12 12:31" (this machine's timezone), instead of the next open slot`]],
2954
- examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah"]
3024
+ usage: "innernote queue <post-id> [--at <time>] | queue --ready",
3025
+ 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.",
3026
+ options: [
3027
+ ["--at <time>", `a time you pick, like "2026-10-12 12:31" (this machine's timezone), instead of the next open slot`],
3028
+ ["--ready", "every Ready post, in your Ready order, into your next open slots"]
3029
+ ],
3030
+ examples: ["innernote drafts", "innernote queue 3", "innernote queue p17a1yk936ywnan10643efjtc58dhkah", "innernote drafts ready", "innernote queue --ready"]
2955
3031
  }
2956
3032
  };
2957
3033
  function commandHelp2(name) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "innernote",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Write LinkedIn posts in your voice, from the terminal.",
5
5
  "keywords": [
6
6
  "linkedin",