innernote 0.1.1 → 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 +165 -96
  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) {
@@ -344,6 +344,7 @@ async function login(args) {
344
344
  let code = args.find((a) => !a.startsWith("-"))?.trim();
345
345
  if (!code) {
346
346
  out(`Open ${bold2(config.apiUrl + PAIR_PAGE)} and copy your pairing code.`);
347
+ out(dim2(`New here? Sign in at ${config.apiUrl.replace(/^https?:\/\//, "")} first.`));
347
348
  out();
348
349
  if (!process.stdin.isTTY) {
349
350
  fail("No pairing code given, and there is no terminal to ask on.");
@@ -1121,6 +1122,9 @@ var dim4 = sgr2("2");
1121
1122
  var green4 = sgr2("32");
1122
1123
  var yellow4 = sgr2("33");
1123
1124
  var red4 = sgr2("31");
1125
+ function width2(s) {
1126
+ return s.replace(/\x1b\[[0-9;]*m/g, "").length;
1127
+ }
1124
1128
  var canAnimate2 = isTTY2 && process.env.TERM !== "dumb" && !process.env.CI;
1125
1129
  var MARK_GRID2 = [
1126
1130
  "....#....",
@@ -1159,44 +1163,44 @@ function wordmark2(name, gap = 1) {
1159
1163
  function widest2(rows) {
1160
1164
  return Math.max(...rows.map((r) => r.length));
1161
1165
  }
1162
- function lockup2(name = "innernote", tagline) {
1163
- const room = (process.stdout.columns ?? 80) - 5;
1164
- const mark = logo2(2);
1165
- 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);
1166
1171
  const word = wordmark2(name);
1167
1172
  if (word) {
1168
- const gap2 = 3;
1169
- 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) {
1170
1176
  const rows2 = mark.map((r, i) => {
1171
1177
  const right = word[i] ?? "";
1172
- 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);
1173
1179
  });
1174
- if (tagline)
1175
- rows2.push("", ` ${dim4(tagline)}`);
1180
+ const indent = " ".repeat(ink + gap);
1181
+ for (const line of subs)
1182
+ rows2.push(`${indent}${line}`);
1176
1183
  return rows2;
1177
1184
  }
1178
1185
  }
1179
- const gap = 3;
1180
- const beside = Math.max(name.length, tagline?.length ?? 0);
1181
- if (markWidth + gap + beside <= room) {
1182
- const mid = Math.floor(mark.length / 2);
1183
- return mark.map((r, i) => {
1184
- const left = caramel2(r.padEnd(markWidth)) + " ".repeat(gap);
1185
- if (i === mid)
1186
- return left + bold4(caramel2(name));
1187
- if (i === mid + 1 && tagline)
1188
- return left + dim4(tagline);
1189
- return caramel2(r.trimEnd());
1190
- });
1191
- }
1192
- const rows = mark.map((r) => caramel2(r.trimEnd()));
1193
- rows.push("", bold4(caramel2(name)));
1194
- if (tagline)
1195
- rows.push(dim4(tagline));
1196
- 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];
1197
1201
  }
1198
1202
  function banner2(tagline = "your voice, from the terminal") {
1199
- return lockup2("innernote", tagline).map((r) => " " + r).join(`
1203
+ return lockup2("innernote", dim4(tagline)).map((r) => " " + r).join(`
1200
1204
  `);
1201
1205
  }
1202
1206
 
@@ -1341,12 +1345,12 @@ function commandHelp(name) {
1341
1345
  return lines.join(`
1342
1346
  `);
1343
1347
  }
1344
- function wrap3(text, width2, indent) {
1348
+ function wrap3(text, width3, indent) {
1345
1349
  const words = text.split(/\s+/);
1346
1350
  const lines = [];
1347
1351
  let line = "";
1348
1352
  for (const w of words) {
1349
- if ((line + " " + w).trim().length > width2) {
1353
+ if ((line + " " + w).trim().length > width3) {
1350
1354
  lines.push(indent + line.trim());
1351
1355
  line = w;
1352
1356
  } else {
@@ -1368,65 +1372,68 @@ function margin(rows) {
1368
1372
  `);
1369
1373
  }
1370
1374
  function landing(state) {
1371
- const rows = ["", ...lockup("innernote"), ""];
1372
- if (state.name) {
1373
- rows.push(state.name + (state.plan ? dim2(` ${state.plan}`) : ""));
1374
- }
1375
- const sub = [];
1375
+ const small = [];
1376
+ if (state.plan)
1377
+ small.push(state.plan);
1376
1378
  if (state.powers)
1377
- sub.push(state.powers);
1378
- if (typeof state.expiresInDays === "number" && state.expiresInDays <= 14) {
1379
- sub.push(`key expires in ${state.expiresInDays} ${state.expiresInDays === 1 ? "day" : "days"}`);
1380
- }
1379
+ small.push(state.powers);
1381
1380
  if (state.version)
1382
- sub.push(`v${state.version}`);
1383
- if (sub.length)
1384
- rows.push(dim2(sub.join(" ")));
1385
- if (state.week?.length) {
1386
- 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"}`));
1387
1389
  }
1390
+ const rows = ["", ...lockup("innernote", sub), ""];
1391
+ if (state.week?.length)
1392
+ rows.push(...state.week, "");
1388
1393
  const counts = [];
1389
1394
  if (state.ideas)
1390
1395
  counts.push(`${state.ideas} ${state.ideas === 1 ? "idea" : "ideas"} waiting`);
1391
1396
  if (state.drafts)
1392
- counts.push(`${state.drafts} drafts`);
1397
+ counts.push(`${state.drafts} ${state.drafts === 1 ? "draft" : "drafts"}`);
1393
1398
  if (counts.length)
1394
- rows.push("", dim2(counts.join(" ")));
1399
+ rows.push(dim2(counts.join(" ")));
1395
1400
  if (state.next) {
1396
- 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)}`);
1397
1402
  }
1398
1403
  rows.push("");
1399
1404
  return margin(rows);
1400
1405
  }
1401
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}`));
1402
1411
  return margin([
1403
1412
  "",
1404
- ...lockup("innernote", "your voice, from the terminal"),
1405
- ...version ? [` ${dim2(`v${version}`)}`] : [],
1413
+ ...lockup("innernote", sub),
1406
1414
  "",
1407
- dim2("Write LinkedIn posts in your voice, without leaving here."),
1408
- "",
1409
- `${caramel(`${CMD} login`)}${pad("", Math.max(3, 20 - width(`${CMD} login`)))}${dim2("connect this machine")}`,
1410
- dim2(`Get a code at ${apiUrl.replace(/^https?:\/\//, "")}/dashboard/settings`),
1415
+ `${caramel(`${CMD} login`)} ${dim2("connect this machine")}`,
1416
+ dim2(`Get a code at ${host}/dashboard/settings`),
1417
+ `${dim2("No account yet?")} ${caramel(host)}`,
1411
1418
  ""
1412
1419
  ]);
1413
1420
  }
1414
1421
  function suggest(state) {
1415
1422
  if (state.scheduled === 0 && state.ideas > 0) {
1416
- return { command: `${CMD} ideas`, why: "nothing scheduled, and you have things saved" };
1423
+ return { command: "ideas", why: "nothing scheduled, and you have things saved" };
1417
1424
  }
1418
1425
  if (state.scheduled === 0) {
1419
- return { command: `${CMD} write`, why: "the week is empty" };
1426
+ return { command: "write", why: "the week is empty" };
1420
1427
  }
1421
1428
  if (state.ideas >= 5) {
1422
- return { command: `${CMD} ideas`, why: "your inbox is filling up" };
1429
+ return { command: "ideas", why: "your inbox is filling up" };
1423
1430
  }
1424
- return { command: `${CMD} write`, why: "start something" };
1431
+ return { command: "write", why: "start something" };
1425
1432
  }
1426
1433
  // package.json
1427
1434
  var package_default = {
1428
1435
  name: "innernote",
1429
- version: "0.1.1",
1436
+ version: "0.2.0",
1430
1437
  description: "Write LinkedIn posts in your voice, from the terminal.",
1431
1438
  type: "module",
1432
1439
  bin: {
@@ -1463,7 +1470,7 @@ function powers2(scopes) {
1463
1470
  return scopes[0];
1464
1471
  return `${scopes.slice(0, -1).join(", ")} and ${scopes[scopes.length - 1]}`;
1465
1472
  }
1466
- async function home() {
1473
+ async function home(inSession = false) {
1467
1474
  const config = await loadConfig();
1468
1475
  if (!config.token) {
1469
1476
  out();
@@ -1495,11 +1502,14 @@ async function home() {
1495
1502
  `).map((l) => l.replace(/^ {2}/, "")) : null,
1496
1503
  ideas: ideas2?.ideas.length,
1497
1504
  drafts: drafts2?.posts.length,
1498
- next: suggest({
1499
- scheduled,
1500
- ideas: ideas2?.ideas.length ?? 0,
1501
- drafts: drafts2?.posts.length ?? 0
1502
- })
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
+ })()
1503
1513
  }));
1504
1514
  out();
1505
1515
  return 0;
@@ -1508,6 +1518,30 @@ async function home() {
1508
1518
  // src/session.ts
1509
1519
  import { createInterface } from "node:readline";
1510
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
+
1511
1545
  // src/help.ts
1512
1546
  var COMMANDS2 = {
1513
1547
  login: {
@@ -1649,12 +1683,12 @@ function commandHelp2(name) {
1649
1683
  return lines.join(`
1650
1684
  `);
1651
1685
  }
1652
- function wrap4(text, width2, indent) {
1686
+ function wrap4(text, width3, indent) {
1653
1687
  const words = text.split(/\s+/);
1654
1688
  const lines = [];
1655
1689
  let line2 = "";
1656
1690
  for (const w of words) {
1657
- if ((line2 + " " + w).trim().length > width2) {
1691
+ if ((line2 + " " + w).trim().length > width3) {
1658
1692
  lines.push(indent + line2.trim());
1659
1693
  line2 = w;
1660
1694
  } else {
@@ -1726,6 +1760,40 @@ function indented(run) {
1726
1760
  };
1727
1761
  return run().finally(restore);
1728
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
+ }
1729
1797
  async function session(opts) {
1730
1798
  await opts.intro();
1731
1799
  const names = [...Object.keys(COMMANDS2), ...BUILTINS];
@@ -1743,7 +1811,8 @@ async function session(opts) {
1743
1811
  return [hits.length ? hits : names, line2];
1744
1812
  }
1745
1813
  });
1746
- 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.")}
1747
1816
  `);
1748
1817
  rl.prompt();
1749
1818
  return new Promise((resolve) => {
@@ -1924,7 +1993,7 @@ async function main() {
1924
1993
  return home();
1925
1994
  return session({
1926
1995
  intro: async () => {
1927
- await home();
1996
+ await home(true);
1928
1997
  },
1929
1998
  status: () => {
1930
1999
  const d = getDraft2();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "innernote",
3
- "version": "0.1.1",
3
+ "version": "0.2.0",
4
4
  "description": "Write LinkedIn posts in your voice, from the terminal.",
5
5
  "type": "module",
6
6
  "bin": {