innernote 0.1.2 → 0.2.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 (2) hide show
  1. package/dist/index.js +164 -98
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -286,44 +286,44 @@ function wordmark(name, gap = 1) {
286
286
  function widest(rows) {
287
287
  return Math.max(...rows.map((r) => r.length));
288
288
  }
289
- function lockup(name = "innernote", tagline) {
290
- const room = (process.stdout.columns ?? 80) - 5;
291
- const mark = logo(2);
292
- const markWidth = widest(mark);
289
+ function lockup(name = "innernote", sub) {
290
+ const room = (process.stdout.columns || 80) - 5;
291
+ const subs = sub == null ? [] : Array.isArray(sub) ? sub : [sub];
292
+ const mark = logo(2).map((r) => r.trimEnd());
293
+ const ink = widest(mark);
293
294
  const word = wordmark(name);
294
295
  if (word) {
295
- const gap2 = 3;
296
- if (markWidth + gap2 + widest(word) <= room) {
296
+ const gap = 2;
297
+ const wide = Math.max(widest(word), ...subs.map((s) => width(s)));
298
+ if (ink + gap + wide <= room) {
297
299
  const rows2 = mark.map((r, i) => {
298
300
  const right = word[i] ?? "";
299
- return right ? `${caramel(r.padEnd(markWidth))}${" ".repeat(gap2)}${bold2(caramel(right))}` : caramel(r.trimEnd());
301
+ return right ? `${caramel(r.padEnd(ink))}${" ".repeat(gap)}${bold2(caramel(right))}` : caramel(r);
300
302
  });
301
- if (tagline)
302
- rows2.push("", ` ${dim2(tagline)}`);
303
+ const indent = " ".repeat(ink + gap);
304
+ for (const line of subs)
305
+ rows2.push(`${indent}${line}`);
303
306
  return rows2;
304
307
  }
305
308
  }
306
- const gap = 3;
307
- const beside = Math.max(name.length, tagline?.length ?? 0);
308
- if (markWidth + gap + beside <= room) {
309
- const mid = Math.floor(mark.length / 2);
310
- return mark.map((r, i) => {
311
- const left = caramel(r.padEnd(markWidth)) + " ".repeat(gap);
312
- if (i === mid)
313
- return left + bold2(caramel(name));
314
- if (i === mid + 1 && tagline)
315
- return left + dim2(tagline);
316
- return caramel(r.trimEnd());
317
- });
318
- }
319
- const rows = mark.map((r) => caramel(r.trimEnd()));
320
- rows.push("", bold2(caramel(name)));
321
- if (tagline)
322
- rows.push(dim2(tagline));
323
- return rows;
309
+ const mid = Math.floor(mark.length / 2);
310
+ const below = [];
311
+ const rows = mark.map((r, i) => {
312
+ const left = caramel(r.padEnd(ink)) + " ";
313
+ if (i === mid)
314
+ return left + bold2(caramel(name));
315
+ const sub2 = subs[i - mid - 1];
316
+ if (i > mid && sub2 !== undefined) {
317
+ if (ink + 3 + width(sub2) <= room)
318
+ return left + sub2;
319
+ below.push(sub2);
320
+ }
321
+ return caramel(r);
322
+ });
323
+ return [...rows, ...below];
324
324
  }
325
325
  function banner(tagline = "your voice, from the terminal") {
326
- return lockup("innernote", tagline).map((r) => " " + r).join(`
326
+ return lockup("innernote", dim2(tagline)).map((r) => " " + r).join(`
327
327
  `);
328
328
  }
329
329
  function bar(score, width2 = 10) {
@@ -1122,6 +1122,9 @@ var dim4 = sgr2("2");
1122
1122
  var green4 = sgr2("32");
1123
1123
  var yellow4 = sgr2("33");
1124
1124
  var red4 = sgr2("31");
1125
+ function width2(s) {
1126
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
1127
+ }
1125
1128
  var canAnimate2 = isTTY2 && process.env.TERM !== "dumb" && !process.env.CI;
1126
1129
  var MARK_GRID2 = [
1127
1130
  "....#....",
@@ -1160,44 +1163,44 @@ function wordmark2(name, gap = 1) {
1160
1163
  function widest2(rows) {
1161
1164
  return Math.max(...rows.map((r) => r.length));
1162
1165
  }
1163
- function lockup2(name = "innernote", tagline) {
1164
- const room = (process.stdout.columns ?? 80) - 5;
1165
- const mark = logo2(2);
1166
- const markWidth = widest2(mark);
1166
+ function lockup2(name = "innernote", sub) {
1167
+ const room = (process.stdout.columns || 80) - 5;
1168
+ const subs = sub == null ? [] : Array.isArray(sub) ? sub : [sub];
1169
+ const mark = logo2(2).map((r) => r.trimEnd());
1170
+ const ink = widest2(mark);
1167
1171
  const word = wordmark2(name);
1168
1172
  if (word) {
1169
- const gap2 = 3;
1170
- if (markWidth + gap2 + widest2(word) <= room) {
1173
+ const gap = 2;
1174
+ const wide = Math.max(widest2(word), ...subs.map((s) => width2(s)));
1175
+ if (ink + gap + wide <= room) {
1171
1176
  const rows2 = mark.map((r, i) => {
1172
1177
  const right = word[i] ?? "";
1173
- return right ? `${caramel2(r.padEnd(markWidth))}${" ".repeat(gap2)}${bold4(caramel2(right))}` : caramel2(r.trimEnd());
1178
+ return right ? `${caramel2(r.padEnd(ink))}${" ".repeat(gap)}${bold4(caramel2(right))}` : caramel2(r);
1174
1179
  });
1175
- if (tagline)
1176
- rows2.push("", ` ${dim4(tagline)}`);
1180
+ const indent = " ".repeat(ink + gap);
1181
+ for (const line of subs)
1182
+ rows2.push(`${indent}${line}`);
1177
1183
  return rows2;
1178
1184
  }
1179
1185
  }
1180
- const gap = 3;
1181
- const beside = Math.max(name.length, tagline?.length ?? 0);
1182
- if (markWidth + gap + beside <= room) {
1183
- const mid = Math.floor(mark.length / 2);
1184
- return mark.map((r, i) => {
1185
- const left = caramel2(r.padEnd(markWidth)) + " ".repeat(gap);
1186
- if (i === mid)
1187
- return left + bold4(caramel2(name));
1188
- if (i === mid + 1 && tagline)
1189
- return left + dim4(tagline);
1190
- return caramel2(r.trimEnd());
1191
- });
1192
- }
1193
- const rows = mark.map((r) => caramel2(r.trimEnd()));
1194
- rows.push("", bold4(caramel2(name)));
1195
- if (tagline)
1196
- rows.push(dim4(tagline));
1197
- return rows;
1186
+ const mid = Math.floor(mark.length / 2);
1187
+ const below = [];
1188
+ const rows = mark.map((r, i) => {
1189
+ const left = caramel2(r.padEnd(ink)) + " ";
1190
+ if (i === mid)
1191
+ return left + bold4(caramel2(name));
1192
+ const sub2 = subs[i - mid - 1];
1193
+ if (i > mid && sub2 !== undefined) {
1194
+ if (ink + 3 + width2(sub2) <= room)
1195
+ return left + sub2;
1196
+ below.push(sub2);
1197
+ }
1198
+ return caramel2(r);
1199
+ });
1200
+ return [...rows, ...below];
1198
1201
  }
1199
1202
  function banner2(tagline = "your voice, from the terminal") {
1200
- return lockup2("innernote", tagline).map((r) => " " + r).join(`
1203
+ return lockup2("innernote", dim4(tagline)).map((r) => " " + r).join(`
1201
1204
  `);
1202
1205
  }
1203
1206
 
@@ -1342,12 +1345,12 @@ function commandHelp(name) {
1342
1345
  return lines.join(`
1343
1346
  `);
1344
1347
  }
1345
- function wrap3(text, width2, indent) {
1348
+ function wrap3(text, width3, indent) {
1346
1349
  const words = text.split(/\s+/);
1347
1350
  const lines = [];
1348
1351
  let line = "";
1349
1352
  for (const w of words) {
1350
- if ((line + " " + w).trim().length > width2) {
1353
+ if ((line + " " + w).trim().length > width3) {
1351
1354
  lines.push(indent + line.trim());
1352
1355
  line = w;
1353
1356
  } else {
@@ -1369,67 +1372,68 @@ function margin(rows) {
1369
1372
  `);
1370
1373
  }
1371
1374
  function landing(state) {
1372
- const rows = ["", ...lockup("innernote"), ""];
1373
- if (state.name) {
1374
- rows.push(state.name + (state.plan ? dim2(` ${state.plan}`) : ""));
1375
- }
1376
- const sub = [];
1375
+ const small = [];
1376
+ if (state.plan)
1377
+ small.push(state.plan);
1377
1378
  if (state.powers)
1378
- sub.push(state.powers);
1379
- if (typeof state.expiresInDays === "number" && state.expiresInDays <= 14) {
1380
- sub.push(`key expires in ${state.expiresInDays} ${state.expiresInDays === 1 ? "day" : "days"}`);
1381
- }
1379
+ small.push(state.powers);
1382
1380
  if (state.version)
1383
- sub.push(`v${state.version}`);
1384
- if (sub.length)
1385
- rows.push(dim2(sub.join(" ")));
1386
- if (state.week?.length) {
1387
- rows.push("", ...state.week);
1381
+ small.push(`v${state.version}`);
1382
+ const sub = [];
1383
+ if (state.name)
1384
+ sub.push(`${bold2(state.name)} ${dim2(small.join(" "))}`);
1385
+ else if (small.length)
1386
+ sub.push(dim2(small.join(" ")));
1387
+ if (typeof state.expiresInDays === "number" && state.expiresInDays <= 14) {
1388
+ sub.push(yellow2(`key expires in ${state.expiresInDays} ${state.expiresInDays === 1 ? "day" : "days"}`));
1388
1389
  }
1390
+ const rows = ["", ...lockup("innernote", sub), ""];
1391
+ if (state.week?.length)
1392
+ rows.push(...state.week, "");
1389
1393
  const counts = [];
1390
1394
  if (state.ideas)
1391
1395
  counts.push(`${state.ideas} ${state.ideas === 1 ? "idea" : "ideas"} waiting`);
1392
1396
  if (state.drafts)
1393
- counts.push(`${state.drafts} drafts`);
1397
+ counts.push(`${state.drafts} ${state.drafts === 1 ? "draft" : "drafts"}`);
1394
1398
  if (counts.length)
1395
- rows.push("", dim2(counts.join(" ")));
1399
+ rows.push(dim2(counts.join(" ")));
1396
1400
  if (state.next) {
1397
- rows.push("", `${caramel(state.next.command)}${pad("", Math.max(1, 20 - width(state.next.command)))}${dim2(state.next.why)}`);
1401
+ rows.push(`${caramel(state.next.command)} ${dim2(state.next.why)}`);
1398
1402
  }
1399
1403
  rows.push("");
1400
1404
  return margin(rows);
1401
1405
  }
1402
1406
  function landingDisconnected(apiUrl, version) {
1407
+ const host = apiUrl.replace(/^https?:\/\//, "");
1408
+ const sub = [dim2("LinkedIn posts in your voice, from the terminal")];
1409
+ if (version)
1410
+ sub.push(dim2(`v${version}`));
1403
1411
  return margin([
1404
1412
  "",
1405
- ...lockup("innernote", "your voice, from the terminal"),
1406
- ...version ? [` ${dim2(`v${version}`)}`] : [],
1407
- "",
1408
- dim2("Write LinkedIn posts in your voice, without leaving here."),
1409
- "",
1410
- `${caramel(`${CMD} login`)}${pad("", Math.max(3, 20 - width(`${CMD} login`)))}${dim2("connect this machine")}`,
1411
- dim2(`Get a code at ${apiUrl.replace(/^https?:\/\//, "")}/dashboard/settings`),
1413
+ ...lockup("innernote", sub),
1412
1414
  "",
1413
- `${dim2("No account yet?")} ${caramel(apiUrl.replace(/^https?:\/\//, ""))}`,
1415
+ `${caramel(`${CMD} login`)} ${dim2("connect this machine")}`,
1416
+ dim2(`Get a code at ${host}/dashboard/settings`),
1417
+ `${dim2("No account yet?")} ${caramel(host)}`,
1414
1418
  ""
1415
1419
  ]);
1416
1420
  }
1417
1421
  function suggest(state) {
1418
1422
  if (state.scheduled === 0 && state.ideas > 0) {
1419
- return { command: `${CMD} ideas`, why: "nothing scheduled, and you have things saved" };
1423
+ return { command: "ideas", why: "nothing scheduled, and you have things saved" };
1420
1424
  }
1421
1425
  if (state.scheduled === 0) {
1422
- return { command: `${CMD} write`, why: "the week is empty" };
1426
+ return { command: "write", why: "the week is empty" };
1423
1427
  }
1424
1428
  if (state.ideas >= 5) {
1425
- return { command: `${CMD} ideas`, why: "your inbox is filling up" };
1429
+ return { command: "ideas", why: "your inbox is filling up" };
1426
1430
  }
1427
- return { command: `${CMD} write`, why: "start something" };
1431
+ return { command: "write", why: "start something" };
1428
1432
  }
1429
1433
  // package.json
1430
1434
  var package_default = {
1431
1435
  name: "innernote",
1432
- version: "0.1.2",
1436
+ version: "0.2.0",
1433
1437
  description: "Write LinkedIn posts in your voice, from the terminal.",
1434
1438
  type: "module",
1435
1439
  bin: {
@@ -1466,7 +1470,7 @@ function powers2(scopes) {
1466
1470
  return scopes[0];
1467
1471
  return `${scopes.slice(0, -1).join(", ")} and ${scopes[scopes.length - 1]}`;
1468
1472
  }
1469
- async function home() {
1473
+ async function home(inSession = false) {
1470
1474
  const config = await loadConfig();
1471
1475
  if (!config.token) {
1472
1476
  out();
@@ -1498,11 +1502,14 @@ async function home() {
1498
1502
  `).map((l) => l.replace(/^ {2}/, "")) : null,
1499
1503
  ideas: ideas2?.ideas.length,
1500
1504
  drafts: drafts2?.posts.length,
1501
- next: suggest({
1502
- scheduled,
1503
- ideas: ideas2?.ideas.length ?? 0,
1504
- drafts: drafts2?.posts.length ?? 0
1505
- })
1505
+ next: (() => {
1506
+ const n = suggest({
1507
+ scheduled,
1508
+ ideas: ideas2?.ideas.length ?? 0,
1509
+ drafts: drafts2?.posts.length ?? 0
1510
+ });
1511
+ return inSession ? n : { ...n, command: `${CMD} ${n.command}` };
1512
+ })()
1506
1513
  }));
1507
1514
  out();
1508
1515
  return 0;
@@ -1511,6 +1518,30 @@ async function home() {
1511
1518
  // src/session.ts
1512
1519
  import { createInterface } from "node:readline";
1513
1520
 
1521
+ // src/ghost.ts
1522
+ function ghostFor(line2, cursor, history, names) {
1523
+ if (!line2 || cursor !== line2.length)
1524
+ return null;
1525
+ if (line2.startsWith(" "))
1526
+ return null;
1527
+ const fromHistory = history.find((h) => h.startsWith(line2) && h.length > line2.length);
1528
+ if (fromHistory)
1529
+ return fromHistory.slice(line2.length);
1530
+ if (!line2.includes(" ")) {
1531
+ const name = names.find((n) => n.startsWith(line2) && n.length > line2.length);
1532
+ return name ? name.slice(line2.length) : null;
1533
+ }
1534
+ if (line2.startsWith("help ")) {
1535
+ const arg = line2.slice(5);
1536
+ if (arg && !arg.includes(" ")) {
1537
+ const name = names.find((n) => n.startsWith(arg) && n.length > arg.length);
1538
+ if (name)
1539
+ return name.slice(arg.length);
1540
+ }
1541
+ }
1542
+ return null;
1543
+ }
1544
+
1514
1545
  // src/help.ts
1515
1546
  var COMMANDS2 = {
1516
1547
  login: {
@@ -1652,12 +1683,12 @@ function commandHelp2(name) {
1652
1683
  return lines.join(`
1653
1684
  `);
1654
1685
  }
1655
- function wrap4(text, width2, indent) {
1686
+ function wrap4(text, width3, indent) {
1656
1687
  const words = text.split(/\s+/);
1657
1688
  const lines = [];
1658
1689
  let line2 = "";
1659
1690
  for (const w of words) {
1660
- if ((line2 + " " + w).trim().length > width2) {
1691
+ if ((line2 + " " + w).trim().length > width3) {
1661
1692
  lines.push(indent + line2.trim());
1662
1693
  line2 = w;
1663
1694
  } else {
@@ -1729,6 +1760,40 @@ function indented(run) {
1729
1760
  };
1730
1761
  return run().finally(restore);
1731
1762
  }
1763
+ function armGhost(rl, names, promptWidth) {
1764
+ if (!process.stdin.isTTY || !process.stdout.isTTY)
1765
+ return;
1766
+ if (dim2("x") === "x")
1767
+ return;
1768
+ const inner = rl;
1769
+ let shown = null;
1770
+ const clear = () => {
1771
+ if (shown == null)
1772
+ return;
1773
+ process.stdout.write("\x1B[K");
1774
+ shown = null;
1775
+ };
1776
+ const draw = () => {
1777
+ if (process.stdin.isPaused())
1778
+ return;
1779
+ const rest = ghostFor(inner.line ?? "", inner.cursor ?? 0, inner.history ?? [], names);
1780
+ if (!rest)
1781
+ return;
1782
+ const cols = process.stdout.columns || 80;
1783
+ if (promptWidth() + (inner.line?.length ?? 0) + rest.length >= cols - 1)
1784
+ return;
1785
+ process.stdout.write(`${dim2(rest)}\x1B[${rest.length}D`);
1786
+ shown = rest;
1787
+ };
1788
+ process.stdin.prependListener("keypress", (_ch, key) => {
1789
+ const offered = shown;
1790
+ clear();
1791
+ if (key?.name === "right" && offered && inner.cursor === inner.line.length) {
1792
+ rl.write(offered);
1793
+ }
1794
+ setImmediate(draw);
1795
+ });
1796
+ }
1732
1797
  async function session(opts) {
1733
1798
  await opts.intro();
1734
1799
  const names = [...Object.keys(COMMANDS2), ...BUILTINS];
@@ -1746,7 +1811,8 @@ async function session(opts) {
1746
1811
  return [hits.length ? hits : names, line2];
1747
1812
  }
1748
1813
  });
1749
- console.log(` ${dim2("Type a command, or")} ${caramel("help")}${dim2(". Ctrl-C twice to leave.")}
1814
+ armGhost(rl, names, () => width(promptFor()));
1815
+ console.log(` ${dim2("Type a command, or")} ${caramel("help")}${dim2(". Right arrow takes the hint. Ctrl-C twice to leave.")}
1750
1816
  `);
1751
1817
  rl.prompt();
1752
1818
  return new Promise((resolve) => {
@@ -1927,7 +1993,7 @@ async function main() {
1927
1993
  return home();
1928
1994
  return session({
1929
1995
  intro: async () => {
1930
- await home();
1996
+ await home(true);
1931
1997
  },
1932
1998
  status: () => {
1933
1999
  const d = getDraft2();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "innernote",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Write LinkedIn posts in your voice, from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {