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.
- package/README.md +5 -1
- package/dist/index.js +554 -38
- 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
|
|
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
|
|
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,
|
|
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: [
|
|
1884
|
-
|
|
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.
|
|
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,
|
|
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 =
|
|
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
|
|
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,
|
|
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: [
|
|
2508
|
-
|
|
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
|
-
["
|
|
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,
|