innernote 0.2.0 → 0.3.2

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 CHANGED
@@ -78,18 +78,28 @@ any time.
78
78
 
79
79
  ## Commands
80
80
 
81
+ The table below is asserted by `cli/src/parity.test.ts`: a command missing
82
+ from it fails the suite, so what you read here is what the binary has.
83
+
81
84
  | Command | What it does |
82
85
  | --- | --- |
83
86
  | `login [code]` | connect this machine |
84
87
  | `logout` | forget the token stored here |
85
- | `whoami` | show which account is connected |
88
+ | `whoami` | show which account is connected, and what this key can do |
89
+ | `context` | what you post about: your pillars and your series |
86
90
  | `capture <text>` | save an idea to your inbox |
87
91
  | `write [topic]` | draft a post in your voice |
92
+ | `shape <shape...>` | push a post shorter, punchier, warmer |
93
+ | `ask <anything>` | talk to the post in your own words |
94
+ | `save` | keep the draft you are holding |
95
+ | `show` | reprint the post you are holding |
96
+ | `drop` | put down whatever you are holding |
97
+ | `open <n or id>` | pull a post out of the last list and work on it |
88
98
  | `ideas [status]` | list what you have captured |
89
- | `drafts [status]` | list your posts, with their ids |
99
+ | `drafts [status]` | list your posts, numbered |
90
100
  | `week` | what is scheduled for the next seven days |
91
- | `shape <shape...>` | push a post shorter, punchier, warmer (reads stdin) |
92
101
  | `queue <post-id>` | schedule a post into your next open slot |
102
+ | `unqueue <post-id>` | pull it back out before it goes live |
93
103
 
94
104
  ### write
95
105
 
package/dist/index.js CHANGED
@@ -261,6 +261,14 @@ var MARK_BLOCK_COUNT = MARK_GRID.reduce((n, row) => n + row.split("").filter((c)
261
261
  function paintRGB(r, g, b, s) {
262
262
  return paint(r, g, b, s);
263
263
  }
264
+ function paintBgRGB(r, g, b, s) {
265
+ if (!wantsColor)
266
+ return s;
267
+ if (truecolor)
268
+ return `\x1B[48;2;${r};${g};${b}m${s}\x1B[49m`;
269
+ const q = (v) => Math.round(v / 255 * 5);
270
+ return `\x1B[48;5;${16 + 36 * q(r) + 6 * q(g) + q(b)}m${s}\x1B[49m`;
271
+ }
264
272
  function logo(cells = 2) {
265
273
  return MARK_GRID.map((row) => row.split("").map((c) => c === "#" ? "█".repeat(cells) : " ".repeat(cells)).join(""));
266
274
  }
@@ -609,6 +617,42 @@ async function week(args) {
609
617
  }
610
618
  return 0;
611
619
  }
620
+ async function context(args) {
621
+ const { json } = parseFlags(args);
622
+ const [pillars, series] = await Promise.all([
623
+ request("/api/pillars"),
624
+ request("/api/campaigns")
625
+ ]);
626
+ if (json) {
627
+ out(JSON.stringify({ pillars: pillars.pillars, series: series.campaigns }, null, 2));
628
+ return 0;
629
+ }
630
+ out();
631
+ if (pillars.pillars.length) {
632
+ out(` ${bold2("What you post about")}`);
633
+ for (const p of pillars.pillars) {
634
+ out(` ${caramel(p.name)}${typeof p.weight === "number" ? dim2(` ${p.weight}%`) : ""}`);
635
+ }
636
+ } else {
637
+ out(` ${dim2("No content pillars yet. Set them in Settings.")}`);
638
+ }
639
+ out();
640
+ if (series.campaigns.length) {
641
+ out(` ${bold2("Series")}${dim2(" write into one with")} ${caramel('write --series "<name>"')}`);
642
+ for (const c of series.campaigns) {
643
+ const count = typeof c.postCount === "number" && c.postCount > 0 ? dim2(` ${c.postCount} ${c.postCount === 1 ? "post" : "posts"}`) : "";
644
+ out(` ${caramel(c.name)}${count}`);
645
+ if (c.brief)
646
+ out(` ${dim2(oneLine(c.brief, 64))}`);
647
+ if (c.goal)
648
+ out(` ${dim2(`goal: ${oneLine(c.goal, 58)}`)}`);
649
+ }
650
+ } else {
651
+ out(` ${dim2("No series running.")}`);
652
+ }
653
+ out();
654
+ return 0;
655
+ }
612
656
 
613
657
  // src/commands/read.ts
614
658
  function parseFlags2(args) {
@@ -680,20 +724,47 @@ function level(s, t) {
680
724
  const tri = up <= 1 ? up : 2 - up;
681
725
  return 0.18 + tri * 0.82;
682
726
  }
683
- function paintAt(level2, s) {
727
+ function rampAt(level2) {
684
728
  const from = [74, 58, 44];
685
729
  const to = [232, 201, 176];
686
730
  const c = (i) => Math.round(from[i] + (to[i] - from[i]) * level2);
687
- return paintRGB(c(0), c(1), c(2), s);
731
+ return [c(0), c(1), c(2)];
688
732
  }
689
733
  function frame(t) {
690
734
  const grid = logoGrid();
691
735
  const lit = new Map;
692
736
  for (const s of SPARKS)
693
737
  lit.set(`${s.row},${s.col}`, level(s, t));
694
- return grid.map((line, row) => line.split("").map((ch, col) => ch === "#" ? paintAt(lit.get(`${row},${col}`) ?? 0.18, "██") : " ").join(""));
738
+ const levelAt = (row, col) => {
739
+ if (row >= grid.length || grid[row][col] !== "#")
740
+ return null;
741
+ return lit.get(`${row},${col}`) ?? 0.18;
742
+ };
743
+ const rows = [];
744
+ for (let top = 0;top < grid.length; top += 2) {
745
+ let line = "";
746
+ for (let col = 0;col < grid[0].length; col++) {
747
+ const upper = levelAt(top, col);
748
+ const lower = levelAt(top + 1, col);
749
+ if (upper == null && lower == null) {
750
+ line += " ";
751
+ } else if (upper != null && lower == null) {
752
+ const [r, g, b] = rampAt(upper);
753
+ line += paintRGB(r, g, b, "▀");
754
+ } else if (upper == null && lower != null) {
755
+ const [r, g, b] = rampAt(lower);
756
+ line += paintRGB(r, g, b, "▄");
757
+ } else {
758
+ const [r, g, b] = rampAt(upper);
759
+ const [br, bg, bb] = rampAt(lower);
760
+ line += paintBgRGB(br, bg, bb, paintRGB(r, g, b, "▀"));
761
+ }
762
+ }
763
+ rows.push(line.trimEnd());
764
+ }
765
+ return rows;
695
766
  }
696
- var HEIGHT = 9;
767
+ var HEIGHT = 5;
697
768
 
698
769
  class Thinking {
699
770
  phases;
@@ -724,7 +795,7 @@ class Thinking {
724
795
  this.label = p.text;
725
796
  const rows = frame(t);
726
797
  const mid = Math.floor(rows.length / 2);
727
- const out2 = rows.map((r, i) => ` ${r}${i === mid ? ` \x1B[2m${this.label}\x1B[0m` : ""}`).join(`
798
+ const out2 = rows.map((r, i) => ` ${r}${i === mid ? ` \x1B[2m${this.label}\x1B[0m` : ""}`).join(`
728
799
  `);
729
800
  if (this.drawn)
730
801
  process.stderr.write(`\x1B[${HEIGHT}A`);
@@ -1078,6 +1149,23 @@ async function drop() {
1078
1149
  note(dim2("`write` to start something, or `drafts` to pick one up"));
1079
1150
  return 0;
1080
1151
  }
1152
+ async function unqueue(args) {
1153
+ const { json, rest } = parseFlags2(args);
1154
+ const ref = rest[0] ? resolveRef(rest[0]) : null;
1155
+ if (!ref) {
1156
+ fail("Which post?");
1157
+ note(dim2(`${CMD} unqueue <post-id> (\`${CMD} week\` shows what is queued)`));
1158
+ return 1;
1159
+ }
1160
+ const result = await request(`/api/linkedin/schedule/${encodeURIComponent(ref.id)}`, { method: "DELETE" });
1161
+ if (json) {
1162
+ out(JSON.stringify(result, null, 2));
1163
+ return 0;
1164
+ }
1165
+ ok("Pulled back out of the queue. It is a draft again.");
1166
+ note(dim2("`drafts` to find it, `queue` to send it back"));
1167
+ return 0;
1168
+ }
1081
1169
 
1082
1170
  // src/ui.ts
1083
1171
  var useColor2 = process.stdout.isTTY === true && !process.env.NO_COLOR && process.env.TERM !== "dumb";
@@ -1316,6 +1404,18 @@ var COMMANDS = {
1316
1404
  usage: "innernote week",
1317
1405
  detail: "Seven days across, so the gaps are visible before you read a word. Published is filled, scheduled is open."
1318
1406
  },
1407
+ unqueue: {
1408
+ summary: "pull a post back out of the queue before it goes live",
1409
+ usage: "innernote unqueue <post-id>",
1410
+ detail: "The undo for queue, while there is still time. The post returns to your drafts and nothing goes live. Once it has been published this refuses, because a live post cannot be unsent this way.",
1411
+ examples: ["innernote unqueue p17abc...", "innernote unqueue 3"]
1412
+ },
1413
+ context: {
1414
+ summary: "what you post about: your pillars and your series",
1415
+ usage: "innernote context",
1416
+ detail: "Your standing context: content pillars 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.",
1417
+ examples: ["innernote context", "innernote context --json"]
1418
+ },
1319
1419
  queue: {
1320
1420
  summary: "schedule a post into your next open slot",
1321
1421
  usage: "innernote queue <post-id>",
@@ -1433,7 +1533,7 @@ function suggest(state) {
1433
1533
  // package.json
1434
1534
  var package_default = {
1435
1535
  name: "innernote",
1436
- version: "0.2.0",
1536
+ version: "0.3.2",
1437
1537
  description: "Write LinkedIn posts in your voice, from the terminal.",
1438
1538
  type: "module",
1439
1539
  bin: {
@@ -1441,6 +1541,7 @@ var package_default = {
1441
1541
  },
1442
1542
  files: [
1443
1543
  "dist",
1544
+ "skills",
1444
1545
  "README.md"
1445
1546
  ],
1446
1547
  scripts: {
@@ -1654,6 +1755,18 @@ var COMMANDS2 = {
1654
1755
  usage: "innernote week",
1655
1756
  detail: "Seven days across, so the gaps are visible before you read a word. Published is filled, scheduled is open."
1656
1757
  },
1758
+ unqueue: {
1759
+ summary: "pull a post back out of the queue before it goes live",
1760
+ usage: "innernote unqueue <post-id>",
1761
+ detail: "The undo for queue, while there is still time. The post returns to your drafts and nothing goes live. Once it has been published this refuses, because a live post cannot be unsent this way.",
1762
+ examples: ["innernote unqueue p17abc...", "innernote unqueue 3"]
1763
+ },
1764
+ context: {
1765
+ summary: "what you post about: your pillars and your series",
1766
+ usage: "innernote context",
1767
+ detail: "Your standing context: content pillars 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.",
1768
+ examples: ["innernote context", "innernote context --json"]
1769
+ },
1657
1770
  queue: {
1658
1771
  summary: "schedule a post into your next open slot",
1659
1772
  usage: "innernote queue <post-id>",
@@ -1914,8 +2027,8 @@ function whatThereIs() {
1914
2027
  ];
1915
2028
  const groups = [
1916
2029
  ["Writing", ["write", "shape", "ask", "save", "capture"]],
1917
- ["Looking", ["show", "ideas", "drafts", "open", "week", "whoami"]],
1918
- ["Shipping", ["queue"]],
2030
+ ["Looking", ["show", "ideas", "drafts", "open", "week", "context", "whoami"]],
2031
+ ["Shipping", ["queue", "unqueue"]],
1919
2032
  ["Connection", ["login", "logout"]]
1920
2033
  ];
1921
2034
  for (const [title, cmds] of groups) {
@@ -1944,8 +2057,8 @@ function commandList() {
1944
2057
  const groups = [
1945
2058
  ["Getting connected", ["login", "logout", "whoami"]],
1946
2059
  ["Writing", ["write", "shape", "ask", "save", "capture"]],
1947
- ["Looking", ["show", "ideas", "drafts", "open", "week"]],
1948
- ["Shipping", ["queue"]]
2060
+ ["Looking", ["show", "ideas", "drafts", "open", "week", "context"]],
2061
+ ["Shipping", ["queue", "unqueue"]]
1949
2062
  ];
1950
2063
  const lines = [];
1951
2064
  for (const [title, names] of groups) {
@@ -1970,6 +2083,7 @@ var HELP = () => [
1970
2083
  ].join(`
1971
2084
  `);
1972
2085
  var COMMANDS3 = {
2086
+ context,
1973
2087
  login,
1974
2088
  logout: () => logout(),
1975
2089
  whoami,
@@ -1984,7 +2098,8 @@ var COMMANDS3 = {
1984
2098
  drafts,
1985
2099
  open,
1986
2100
  week,
1987
- queue
2101
+ queue,
2102
+ unqueue
1988
2103
  };
1989
2104
  async function main() {
1990
2105
  const [, , command, ...args] = process.argv;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "innernote",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "description": "Write LinkedIn posts in your voice, from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  },
9
9
  "files": [
10
10
  "dist",
11
+ "skills",
11
12
  "README.md"
12
13
  ],
13
14
  "scripts": {
@@ -0,0 +1,125 @@
1
+ ---
2
+ name: innernote-cli
3
+ description: Drive the innernote CLI (`npx innernote`) to help someone write, save and schedule LinkedIn posts in their own voice from the terminal. Use this whenever the user mentions innernote, asks to draft or queue a LinkedIn post from the command line, wants their posting week or captured ideas checked, or asks you to capture a thought for later, even if they do not name the CLI. Also use it before touching any `innernote` command in a script.
4
+ ---
5
+
6
+ # Driving the innernote CLI
7
+
8
+ innernote holds this person's writing voice: how they write, what they post
9
+ about, and their posting schedule. The CLI is a thin client over the same
10
+ service the app uses, so a draft made here sounds like THEM, not like you,
11
+ and everything you save appears in their app.
12
+
13
+ ## The contract, before any command
14
+
15
+ - **stdout is data, stderr is narration.** The post text, JSON and ids go to
16
+ stdout; spinners, notes and errors go to stderr. Pipe stdout with
17
+ confidence, and never parse the human-facing view: every read command takes
18
+ `--json`, which prints the API payload and nothing else.
19
+ - **Exit codes are the protocol.** `0` worked. `1` failed, reason on stderr.
20
+ `2` not connected: stop and ask the user to run `npx innernote login`.
21
+ `3` the account cannot do this right now: a paywall or a spent allowance,
22
+ never a bug. Only `1` is worth debugging.
23
+ - **A refusal is a sentence for the user, not an obstacle for you.** When a
24
+ command refuses (a limit, a missing cadence, no such post), the message is
25
+ already written for the person. Show it to them verbatim and stop. Do not
26
+ retry, and do not work around it.
27
+
28
+ ## Connecting
29
+
30
+ Check with `innernote whoami` (exit `2` means not connected). Connecting
31
+ needs a pairing code from the user's Settings page at
32
+ innernote.space/dashboard/settings; you cannot get one yourself. Ask them
33
+ for it, then run `innernote login THEIR-CODE`. The code works once and
34
+ expires in ten minutes. `INNERNOTE_TOKEN` in the environment overrides the
35
+ stored key, which is how CI and scripts authenticate.
36
+
37
+ ## Read before you write
38
+
39
+ Suggestions that ignore what this person posts about are generic advice,
40
+ which is the one thing innernote exists to prevent.
41
+
42
+ ```bash
43
+ innernote context --json # their content pillars and running series
44
+ innernote week --json # what is scheduled over the next seven days
45
+ innernote ideas --json # thoughts they captured and have not written
46
+ innernote drafts --json # saved posts, newest first, with ids
47
+ ```
48
+
49
+ Start with `context` and `week` when helping them decide what to write: an
50
+ empty week is a real answer, and a post should land inside a pillar or a
51
+ series they have committed to.
52
+
53
+ ## The writing loop
54
+
55
+ **Capture** the moment something is worth posting about later:
56
+
57
+ ```bash
58
+ innernote capture "their thought, in their words"
59
+ git log --oneline -20 | innernote capture # stdin works too
60
+ ```
61
+
62
+ Capture their framing, not your tidied summary. Their phrasing carries their
63
+ voice; a summary throws it away.
64
+
65
+ **Draft and keep** in one command:
66
+
67
+ ```bash
68
+ innernote write "their rough thought or topic, in their words" --save
69
+ innernote write "next part of the hiring story" --series "Build in Public" --save
70
+ ```
71
+
72
+ Pass their OWN WORDS as the argument, as fully as you have them. `write`
73
+ takes a whole messy thought, and any shaping you already know they want
74
+ (shorter, no list, warmer ending) belongs in that ask, phrased plainly.
75
+ `--series` takes the series NAME; a wrong name refuses with the real list,
76
+ so correct from that and retry once. The saved id is printed on stderr and
77
+ `innernote drafts --json` lists it first.
78
+
79
+ **Shaping an already-saved post is not yours to finish here.** Each CLI
80
+ invocation is a fresh process, so the flag form of `shape` transforms
81
+ stdin to stdout without saving:
82
+
83
+ ```bash
84
+ innernote shape shorter punchier < draft.txt # transformed text, NOT saved
85
+ ```
86
+
87
+ Use that for showing the person options. To revise and keep, fold the
88
+ direction into a fresh `write ... --save`, or hand them to the app or the
89
+ interactive session (`innernote` on its own), where reshaping a held draft
90
+ does persist.
91
+
92
+ ## Scheduling: the one action that reaches other people
93
+
94
+ `innernote queue <id>` schedules a saved post into their next open slot,
95
+ and it then goes live on LinkedIn on its own. Nobody presses anything
96
+ again, and a live post cannot be unsent.
97
+
98
+ So queue ONLY when the user has clearly told you to queue that specific
99
+ post. Never as a helpful next step, never to tidy up. If they have not said
100
+ so, tell them it is saved and stop. The undo exists while it is still
101
+ pending:
102
+
103
+ ```bash
104
+ innernote queue p17abc... # their explicit ask, and only then
105
+ innernote unqueue p17abc... # back to drafts, nothing goes out
106
+ ```
107
+
108
+ If `queue` exits `3`, their plan or allowance is the reason; show the
109
+ message, which says where to fix it, and leave the decision with them.
110
+
111
+ ## Worked example
112
+
113
+ The user says: "turn what I told you about interview homework into a post
114
+ for my build-in-public series, keep it short, and line it up for this week."
115
+
116
+ ```bash
117
+ innernote context --json # confirm the series name
118
+ innernote week --json # is there an open day?
119
+ innernote write "interview homework filters for free time, not talent. we swapped ours for a paid 40 minute working session and offer accepts went up. keep it short." --series "Build in Public" --save
120
+ innernote drafts --json # take the first id
121
+ innernote queue <that-id> # they said "line it up": that is the go-ahead
122
+ ```
123
+
124
+ Then tell them what was drafted, where it was queued, and that `unqueue`
125
+ takes it back any time before it goes live.