@teeras/afterhours 0.1.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (84) hide show
  1. package/README.md +101 -15
  2. package/dist/activity.d.ts +35 -1
  3. package/dist/activity.d.ts.map +1 -1
  4. package/dist/activity.js +148 -1
  5. package/dist/activity.js.map +1 -1
  6. package/dist/autosync.d.ts +22 -0
  7. package/dist/autosync.d.ts.map +1 -0
  8. package/dist/autosync.js +85 -0
  9. package/dist/autosync.js.map +1 -0
  10. package/dist/cli.js +112 -9
  11. package/dist/cli.js.map +1 -1
  12. package/dist/cloud.d.ts +5 -0
  13. package/dist/cloud.d.ts.map +1 -1
  14. package/dist/cloud.js +10 -2
  15. package/dist/cloud.js.map +1 -1
  16. package/dist/collect.d.ts +3 -1
  17. package/dist/collect.d.ts.map +1 -1
  18. package/dist/collect.js +19 -7
  19. package/dist/collect.js.map +1 -1
  20. package/dist/detect.d.ts.map +1 -1
  21. package/dist/detect.js +35 -6
  22. package/dist/detect.js.map +1 -1
  23. package/dist/hooks.d.ts +29 -0
  24. package/dist/hooks.d.ts.map +1 -0
  25. package/dist/hooks.js +196 -0
  26. package/dist/hooks.js.map +1 -0
  27. package/dist/index.d.ts +9 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +8 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/install.d.ts +25 -0
  32. package/dist/install.d.ts.map +1 -0
  33. package/dist/install.js +155 -0
  34. package/dist/install.js.map +1 -0
  35. package/dist/moments.d.ts +21 -0
  36. package/dist/moments.d.ts.map +1 -0
  37. package/dist/moments.js +52 -0
  38. package/dist/moments.js.map +1 -0
  39. package/dist/off.d.ts +24 -0
  40. package/dist/off.d.ts.map +1 -0
  41. package/dist/off.js +36 -0
  42. package/dist/off.js.map +1 -0
  43. package/dist/privacy.d.ts +8 -0
  44. package/dist/privacy.d.ts.map +1 -1
  45. package/dist/privacy.js +27 -1
  46. package/dist/privacy.js.map +1 -1
  47. package/dist/qr.d.ts +29 -0
  48. package/dist/qr.d.ts.map +1 -0
  49. package/dist/qr.js +92 -0
  50. package/dist/qr.js.map +1 -0
  51. package/dist/render.d.ts.map +1 -1
  52. package/dist/render.js +61 -1
  53. package/dist/render.js.map +1 -1
  54. package/dist/report.d.ts.map +1 -1
  55. package/dist/report.js +115 -1
  56. package/dist/report.js.map +1 -1
  57. package/dist/runtime.d.ts +18 -0
  58. package/dist/runtime.d.ts.map +1 -0
  59. package/dist/runtime.js +70 -0
  60. package/dist/runtime.js.map +1 -0
  61. package/dist/secret.d.ts +9 -0
  62. package/dist/secret.d.ts.map +1 -0
  63. package/dist/secret.js +32 -0
  64. package/dist/secret.js.map +1 -0
  65. package/dist/setup.d.ts +18 -0
  66. package/dist/setup.d.ts.map +1 -0
  67. package/dist/setup.js +155 -0
  68. package/dist/setup.js.map +1 -0
  69. package/dist/sources/claude.d.ts.map +1 -1
  70. package/dist/sources/claude.js +162 -18
  71. package/dist/sources/claude.js.map +1 -1
  72. package/dist/sources/codex.d.ts.map +1 -1
  73. package/dist/sources/codex.js +240 -21
  74. package/dist/sources/codex.js.map +1 -1
  75. package/dist/sources/cursor.d.ts.map +1 -1
  76. package/dist/sources/cursor.js +1 -0
  77. package/dist/sources/cursor.js.map +1 -1
  78. package/dist/sources/gemini.d.ts.map +1 -1
  79. package/dist/sources/gemini.js +85 -8
  80. package/dist/sources/gemini.js.map +1 -1
  81. package/dist/types.d.ts +109 -6
  82. package/dist/types.d.ts.map +1 -1
  83. package/llms-install.md +29 -0
  84. package/package.json +3 -2
package/dist/privacy.js CHANGED
@@ -1,7 +1,33 @@
1
+ import { homedir } from "node:os";
2
+ import { sep } from "node:path";
3
+ /**
4
+ * Replace a leading home-directory prefix with `~`. Setup's `--json` output
5
+ * and error messages are read by a coding agent and often relayed verbatim
6
+ * into an LLM transcript sent to a third party — absolute paths there leak
7
+ * the operator's real username. Returns the path unchanged if it is not
8
+ * under `home` (no partial-segment matches, e.g. `/Users/alice2`).
9
+ */
10
+ export function tildePath(path, home = homedir()) {
11
+ if (path === home)
12
+ return "~";
13
+ const prefix = home.endsWith(sep) ? home : `${home}${sep}`;
14
+ return path.startsWith(prefix) ? `~${sep}${path.slice(prefix.length)}` : path;
15
+ }
1
16
  export const DATA_BOUNDARY = {
2
17
  mode: "local-first",
3
18
  sync: "off-until-user-connects",
4
- retained: ["hourly timestamps", "local date and hour", "event kinds", "session counts", "token counters"],
19
+ retained: [
20
+ "hourly timestamps",
21
+ "local date and hour",
22
+ "event kinds",
23
+ "session counts",
24
+ "token counters",
25
+ "content-free delegation episodes",
26
+ "measurement provenance and missingness",
27
+ "agent and model family",
28
+ "approval and lifecycle signals",
29
+ "reporting timezone",
30
+ ],
5
31
  neverRetained: [
6
32
  "prompt text",
7
33
  "source code",
@@ -1 +1 @@
1
- {"version":3,"file":"privacy.js","sourceRoot":"","sources":["../src/privacy.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,yBAAyB;IAC/B,QAAQ,EAAE,CAAC,mBAAmB,EAAE,qBAAqB,EAAE,aAAa,EAAE,gBAAgB,EAAE,gBAAgB,CAAC;IACzG,aAAa,EAAE;QACb,aAAa;QACb,aAAa;QACb,cAAc;QACd,kBAAkB;QAClB,qBAAqB;QACrB,YAAY;KACb;CACF,CAAC"}
1
+ {"version":3,"file":"privacy.js","sourceRoot":"","sources":["../src/privacy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAGhC;;;;;;GAMG;AACH,MAAM,UAAU,SAAS,CAAC,IAAY,EAAE,OAAe,OAAO,EAAE;IAC9D,IAAI,IAAI,KAAK,IAAI;QAAE,OAAO,GAAG,CAAC;IAC9B,MAAM,MAAM,GAAG,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,GAAG,EAAE,CAAC;IAC3D,OAAO,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAChF,CAAC;AAED,MAAM,CAAC,MAAM,aAAa,GAAiB;IACzC,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,yBAAyB;IAC/B,QAAQ,EAAE;QACR,mBAAmB;QACnB,qBAAqB;QACrB,aAAa;QACb,gBAAgB;QAChB,gBAAgB;QAChB,kCAAkC;QAClC,wCAAwC;QACxC,wBAAwB;QACxB,gCAAgC;QAChC,oBAAoB;KACrB;IACD,aAAa,EAAE;QACb,aAAa;QACb,aAAa;QACb,cAAc;QACd,kBAAkB;QAClB,qBAAqB;QACrB,YAAY;KACb;CACF,CAAC"}
package/dist/qr.d.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * A scannable QR pointing at the one-tap dashboard link. The URL is fixed, so
3
+ * the module matrix is generated once (offline, from the URL below) and
4
+ * embedded here rather than computed at runtime — this package carries zero
5
+ * dependencies, and a general-purpose QR encoder is not worth adding for one
6
+ * constant link.
7
+ *
8
+ * Version 2, error-correction level M, 25x25 modules. Verified against a real
9
+ * QR reader (zbarimg) two ways before landing: the raw matrix as an image,
10
+ * and — the exact thing this module produces — the half-block ASCII render
11
+ * rasterized back to pixels and re-decoded. See tests/qr.test.ts, which pins
12
+ * the verified render byte-for-byte.
13
+ */
14
+ export declare const TEERAS_APP_URL = "https://teeras.com/app";
15
+ /**
16
+ * Renders the fixed teeras.com/app QR as half-block terminal lines (▀ ▄ █),
17
+ * packing two module rows into one character row so modules read as roughly
18
+ * square in a typical monospace terminal. Includes the required quiet zone;
19
+ * deliberately makes no assumption about the terminal's light/dark theme —
20
+ * the render decodes under either color polarity.
21
+ */
22
+ export declare function renderQrLines(): string[];
23
+ /**
24
+ * The QR block for terminal output, captioned honestly, or just the bare
25
+ * URL when the output can't show block characters usefully (piped,
26
+ * redirected, or no TTY) — a QR that can't be scanned is worse than a link.
27
+ */
28
+ export declare function qrBlock(isTty: boolean): string[];
29
+ //# sourceMappingURL=qr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.d.ts","sourceRoot":"","sources":["../src/qr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,eAAO,MAAM,cAAc,2BAA2B,CAAC;AA2CvD;;;;;;GAMG;AACH,wBAAgB,aAAa,IAAI,MAAM,EAAE,CAexC;AAED;;;;GAIG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,EAAE,CAQhD"}
package/dist/qr.js ADDED
@@ -0,0 +1,92 @@
1
+ /**
2
+ * A scannable QR pointing at the one-tap dashboard link. The URL is fixed, so
3
+ * the module matrix is generated once (offline, from the URL below) and
4
+ * embedded here rather than computed at runtime — this package carries zero
5
+ * dependencies, and a general-purpose QR encoder is not worth adding for one
6
+ * constant link.
7
+ *
8
+ * Version 2, error-correction level M, 25x25 modules. Verified against a real
9
+ * QR reader (zbarimg) two ways before landing: the raw matrix as an image,
10
+ * and — the exact thing this module produces — the half-block ASCII render
11
+ * rasterized back to pixels and re-decoded. See tests/qr.test.ts, which pins
12
+ * the verified render byte-for-byte.
13
+ */
14
+ export const TEERAS_APP_URL = "https://teeras.com/app";
15
+ // One string per module row: "1" is a dark module, "0" is light.
16
+ const QR_MODULES = [
17
+ "1111111011011011101111111",
18
+ "1000001001001100101000001",
19
+ "1011101000111010001011101",
20
+ "1011101010001100101011101",
21
+ "1011101010011011101011101",
22
+ "1000001011011000101000001",
23
+ "1111111010101010101111111",
24
+ "0000000010100010100000000",
25
+ "1000101110100000011111001",
26
+ "0001100100010111100011010",
27
+ "1011011001110001100001100",
28
+ "0110110001111010000100110",
29
+ "1111011100001001011001111",
30
+ "1010010111100101000010010",
31
+ "0001001100100001000111100",
32
+ "0001110011001000100110110",
33
+ "1100001011010000111111100",
34
+ "0000000011001101100010000",
35
+ "1111111010111000101010000",
36
+ "1000001000010000100011111",
37
+ "1011101011001110111111111",
38
+ "1011101000000111011100111",
39
+ "1011101001100001101001010",
40
+ "1000001001101001010111110",
41
+ "1111111011111111000000111",
42
+ ];
43
+ const QUIET_SIDE = 4;
44
+ const QUIET_TOP = 4;
45
+ // One module more than the minimum on the bottom pads the row count to even,
46
+ // so each output row can pair two module rows into one half-block character.
47
+ const QUIET_BOTTOM = 5;
48
+ function isDark(row, col) {
49
+ if (row < 0 || row >= QR_MODULES.length)
50
+ return false;
51
+ const line = QR_MODULES[row];
52
+ return col >= 0 && col < line.length && line[col] === "1";
53
+ }
54
+ /**
55
+ * Renders the fixed teeras.com/app QR as half-block terminal lines (▀ ▄ █),
56
+ * packing two module rows into one character row so modules read as roughly
57
+ * square in a typical monospace terminal. Includes the required quiet zone;
58
+ * deliberately makes no assumption about the terminal's light/dark theme —
59
+ * the render decodes under either color polarity.
60
+ */
61
+ export function renderQrLines() {
62
+ const width = QR_MODULES.length + QUIET_SIDE * 2;
63
+ const totalRows = QR_MODULES.length + QUIET_TOP + QUIET_BOTTOM;
64
+ const lines = [];
65
+ for (let y = 0; y < totalRows; y += 2) {
66
+ let line = "";
67
+ for (let x = 0; x < width; x++) {
68
+ const col = x - QUIET_SIDE;
69
+ const top = isDark(y - QUIET_TOP, col);
70
+ const bottom = isDark(y + 1 - QUIET_TOP, col);
71
+ line += top && bottom ? "█" : top ? "▀" : bottom ? "▄" : " ";
72
+ }
73
+ lines.push(line);
74
+ }
75
+ return lines;
76
+ }
77
+ /**
78
+ * The QR block for terminal output, captioned honestly, or just the bare
79
+ * URL when the output can't show block characters usefully (piped,
80
+ * redirected, or no TTY) — a QR that can't be scanned is worse than a link.
81
+ */
82
+ export function qrBlock(isTty) {
83
+ if (!isTty)
84
+ return [` ${TEERAS_APP_URL}`];
85
+ return [
86
+ ...renderQrLines().map((line) => ` ${line}`),
87
+ "",
88
+ ` ${TEERAS_APP_URL}`,
89
+ " your week on your phone · iOS app coming soon",
90
+ ];
91
+ }
92
+ //# sourceMappingURL=qr.js.map
package/dist/qr.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qr.js","sourceRoot":"","sources":["../src/qr.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAC;AAEvD,iEAAiE;AACjE,MAAM,UAAU,GAAsB;IACpC,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;IAC3B,2BAA2B;CAC5B,CAAC;AAEF,MAAM,UAAU,GAAG,CAAC,CAAC;AACrB,MAAM,SAAS,GAAG,CAAC,CAAC;AACpB,6EAA6E;AAC7E,6EAA6E;AAC7E,MAAM,YAAY,GAAG,CAAC,CAAC;AAEvB,SAAS,MAAM,CAAC,GAAW,EAAE,GAAW;IACtC,IAAI,GAAG,GAAG,CAAC,IAAI,GAAG,IAAI,UAAU,CAAC,MAAM;QAAE,OAAO,KAAK,CAAC;IACtD,MAAM,IAAI,GAAG,UAAU,CAAC,GAAG,CAAE,CAAC;IAC9B,OAAO,GAAG,IAAI,CAAC,IAAI,GAAG,GAAG,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,GAAG,CAAC;AAC5D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,aAAa;IAC3B,MAAM,KAAK,GAAG,UAAU,CAAC,MAAM,GAAG,UAAU,GAAG,CAAC,CAAC;IACjD,MAAM,SAAS,GAAG,UAAU,CAAC,MAAM,GAAG,SAAS,GAAG,YAAY,CAAC;IAC/D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,SAAS,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,CAAC,EAAE,EAAE,CAAC;YAC/B,MAAM,GAAG,GAAG,CAAC,GAAG,UAAU,CAAC;YAC3B,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,GAAG,SAAS,EAAE,GAAG,CAAC,CAAC;YACvC,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,GAAG,CAAC,GAAG,SAAS,EAAE,GAAG,CAAC,CAAC;YAC9C,IAAI,IAAI,GAAG,IAAI,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;QAC/D,CAAC;QACD,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACnB,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,OAAO,CAAC,KAAc;IACpC,IAAI,CAAC,KAAK;QAAE,OAAO,CAAC,KAAK,cAAc,EAAE,CAAC,CAAC;IAC3C,OAAO;QACL,GAAG,aAAa,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,IAAI,EAAE,CAAC;QAC7C,EAAE;QACF,KAAK,cAAc,EAAE;QACrB,iDAAiD;KAClD,CAAC;AACJ,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAMvE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAiB3F"}
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAiEvE,wBAAgB,YAAY,CAAC,QAAQ,EAAE,kBAAkB,EAAE,MAAM,EAAE,gBAAgB,GAAG,MAAM,CAqB3F"}
package/dist/render.js CHANGED
@@ -1,15 +1,75 @@
1
1
  function hours(minutes) {
2
2
  return `${(minutes / 60).toFixed(1)}h`;
3
3
  }
4
+ function minutesLabel(minutes) {
5
+ return minutes >= 90 ? hours(minutes) : `${minutes}m`;
6
+ }
7
+ /** One fresh, honest line per day. Never guilt; good news is allowed to be good. */
8
+ function todayLines(report) {
9
+ const today = report.today;
10
+ const baseline = report.baselineLifeReturnedMinutes;
11
+ if (today.agentActiveMinutes === 0 && today.humanAttentionMinutes === 0) {
12
+ return [" today quiet so far — nothing has needed you."];
13
+ }
14
+ const returned = `+${hours(today.lifeReturnedMinutes)} time back (est.)`;
15
+ const compared = baseline == null
16
+ ? `${returned} · first measured day`
17
+ : `${returned} · typical day +${hours(baseline)}`;
18
+ const lines = [` today ${compared}`];
19
+ if (today.longestRunMinutes > 0 && today.longestRunSource) {
20
+ lines.push(` longest solo run ${minutesLabel(today.longestRunMinutes)} · ${today.longestRunSource}`);
21
+ }
22
+ return lines;
23
+ }
24
+ function msLabel(ms) {
25
+ return ms < 90_000 ? `${Math.round(ms / 1000)}s` : `${(ms / 60_000).toFixed(1)}m`;
26
+ }
27
+ /**
28
+ * Today's model responsiveness beside the person's own typical figures. Silent
29
+ * until today measured something — an empty speed line would only be noise.
30
+ */
31
+ function modelSpeedLines(report) {
32
+ const speed = report.modelSpeed;
33
+ const todayParts = [
34
+ speed.todayHumanWaitP50Ms != null ? `first reply ${msLabel(speed.todayHumanWaitP50Ms)}` : null,
35
+ speed.todayTurnDurationP50Ms != null ? `full turn ${msLabel(speed.todayTurnDurationP50Ms)}` : null,
36
+ ].filter((part) => part != null);
37
+ if (todayParts.length === 0)
38
+ return [];
39
+ const baselineParts = [
40
+ speed.baselineHumanWaitP50Ms != null ? msLabel(speed.baselineHumanWaitP50Ms) : null,
41
+ speed.baselineTurnDurationP50Ms != null ? msLabel(speed.baselineTurnDurationP50Ms) : null,
42
+ ].filter((part) => part != null);
43
+ const baseline = baselineParts.length > 0 ? ` · typical ${baselineParts.join(" · ")}` : "";
44
+ return [` model speed (est.) ${todayParts.join(" · ")}${baseline}`];
45
+ }
46
+ /**
47
+ * The other half of the returned-time fact. Agent work that overlaps compresses
48
+ * into fewer hours of the person's week rather than returning more of it, so
49
+ * the ratio is stated plainly instead of being folded into the time back.
50
+ * Silent below 1.1x — "1.0 agents at once" is noise, not news.
51
+ */
52
+ function parallelLine(report) {
53
+ const ratio = report.parallelRatio;
54
+ if (ratio == null || ratio < 1.1)
55
+ return [];
56
+ return [
57
+ ` agents at once ${ratio.toFixed(1)}× · that work filled ${hours(report.agentWallClockMinutes)} of your week`,
58
+ ];
59
+ }
4
60
  export function renderReport(snapshot, report) {
5
61
  const active = snapshot.sources.filter((source) => source.configured).map((source) => source.name);
6
62
  return [
7
63
  "",
8
64
  " teeras · afterhours",
9
65
  "",
10
- ` life returned +${hours(report.lifeReturnedMinutes)}`,
66
+ ...todayLines(report),
67
+ "",
68
+ ` time back · estimate +${hours(report.lifeReturnedMinutes)} this week`,
11
69
  ` human attention ${hours(report.humanAttentionMinutes)} estimated`,
12
70
  ` agent active time ${hours(report.agentActiveMinutes)} observed`,
71
+ ...parallelLine(report),
72
+ ...modelSpeedLines(report),
13
73
  ` nights protected ${report.protectedNights}/${report.observedNights}`,
14
74
  "",
15
75
  ` ${report.nextMove}`,
@@ -1 +1 @@
1
- {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAEA,SAAS,KAAK,CAAC,OAAe;IAC5B,OAAO,GAAG,CAAC,OAAO,GAAG,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC;AACzC,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,QAA4B,EAAE,MAAwB;IACjF,MAAM,MAAM,GAAG,QAAQ,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;IACnG,OAAO;QACL,EAAE;QACF,uBAAuB;QACvB,EAAE;QACF,0BAA0B,KAAK,CAAC,MAAM,CAAC,mBAAmB,CAAC,EAAE;QAC7D,yBAAyB,KAAK,CAAC,MAAM,CAAC,qBAAqB,CAAC,YAAY;QACxE,yBAAyB,KAAK,CAAC,MAAM,CAAC,kBAAkB,CAAC,WAAW;QACpE,yBAAyB,MAAM,CAAC,eAAe,IAAI,MAAM,CAAC,cAAc,EAAE;QAC1E,EAAE;QACF,KAAK,MAAM,CAAC,QAAQ,EAAE;QACtB,EAAE;QACF,sBAAsB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,+BAA+B,EAAE;QAC/F,sEAAsE;QACtE,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAkB,gBAAgB,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AASvF,wBAAgB,UAAU,CAAC,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,SAAK,GAAG,gBAAgB,CA+BxF"}
1
+ {"version":3,"file":"report.d.ts","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAEV,gBAAgB,EAChB,kBAAkB,EAMnB,MAAM,YAAY,CAAC;AAgIpB,wBAAgB,UAAU,CAAC,QAAQ,EAAE,kBAAkB,EAAE,QAAQ,SAAK,GAAG,gBAAgB,CAwCxF"}
package/dist/report.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { localDateOf } from "./activity.js";
1
2
  function localNight(bucket) {
2
3
  if (bucket.localHour >= 5)
3
4
  return bucket.localDate;
@@ -5,6 +6,112 @@ function localNight(bucket) {
5
6
  previous.setUTCDate(previous.getUTCDate() - 1);
6
7
  return previous.toISOString().slice(0, 10);
7
8
  }
9
+ /**
10
+ * Minutes of the clock covered, not minutes worked. Agents run in parallel —
11
+ * three sessions busy through the same hour are three hours of work but only
12
+ * one hour of the person's life, so each local hour contributes at most 60.
13
+ * Summing instead reported more time returned than the day contained.
14
+ */
15
+ function wallClockMinutes(buckets, pick) {
16
+ const perHour = new Map();
17
+ for (const bucket of buckets) {
18
+ const cell = `${bucket.localDate}T${bucket.localHour}`;
19
+ perHour.set(cell, (perHour.get(cell) ?? 0) + pick(bucket));
20
+ }
21
+ let total = 0;
22
+ for (const minutes of perHour.values())
23
+ total += Math.min(60, minutes);
24
+ return Math.round(total);
25
+ }
26
+ const agentMinutes = (bucket) => bucket.agentActiveMinutes;
27
+ const attentionMinutes = (bucket) => bucket.humanAttentionMinutes;
28
+ function lifeReturned(buckets) {
29
+ const agent = wallClockMinutes(buckets, agentMinutes);
30
+ const attention = wallClockMinutes(buckets, attentionMinutes);
31
+ return Math.max(0, agent - attention);
32
+ }
33
+ /**
34
+ * How many agents were working at once, on average, across the hours that had
35
+ * any agent activity. Reported beside the returned time because it is the
36
+ * other half of the same fact: the work compressed, the day did not stretch.
37
+ */
38
+ function parallelRatio(worked, wallClock) {
39
+ return wallClock > 0 ? worked / wallClock : null;
40
+ }
41
+ function median(values) {
42
+ if (values.length === 0)
43
+ return null;
44
+ const sorted = [...values].sort((a, b) => a - b);
45
+ const middle = Math.floor(sorted.length / 2);
46
+ return sorted.length % 2 === 1 ? sorted[middle] : Math.round((sorted[middle - 1] + sorted[middle]) / 2);
47
+ }
48
+ function todayFrom(snapshot) {
49
+ const localDate = localDateOf(Date.parse(snapshot.until));
50
+ const byDate = new Map();
51
+ for (const bucket of snapshot.sources.flatMap((source) => source.buckets)) {
52
+ const day = byDate.get(bucket.localDate) ?? [];
53
+ day.push(bucket);
54
+ byDate.set(bucket.localDate, day);
55
+ }
56
+ const todayBuckets = byDate.get(localDate) ?? [];
57
+ let longestRunMinutes = 0;
58
+ let longestRunSource = null;
59
+ for (const source of snapshot.sources) {
60
+ for (const episode of source.episodes) {
61
+ if (episode.localDate !== localDate)
62
+ continue;
63
+ if (episode.longestAutonomousRunMinutes > longestRunMinutes) {
64
+ longestRunMinutes = episode.longestAutonomousRunMinutes;
65
+ longestRunSource = source.name;
66
+ }
67
+ }
68
+ }
69
+ return {
70
+ today: {
71
+ localDate,
72
+ lifeReturnedMinutes: lifeReturned(todayBuckets),
73
+ agentActiveMinutes: Math.round(todayBuckets.reduce((sum, bucket) => sum + bucket.agentActiveMinutes, 0)),
74
+ humanAttentionMinutes: Math.round(todayBuckets.reduce((sum, bucket) => sum + bucket.humanAttentionMinutes, 0)),
75
+ longestRunMinutes: Math.round(longestRunMinutes),
76
+ longestRunSource,
77
+ },
78
+ baseline: median([...byDate.entries()]
79
+ .filter(([date]) => date !== localDate)
80
+ .map(([, buckets]) => lifeReturned(buckets))),
81
+ };
82
+ }
83
+ /**
84
+ * Median of episode medians, weighted by how many turns each episode measured.
85
+ * True percentiles cannot be rebuilt from aggregates, so the summary leans on
86
+ * the episodes that actually saw the most turns.
87
+ */
88
+ function weightedMedian(stats) {
89
+ const clean = stats.filter((item) => item.samples > 0).sort((a, b) => a.p50Ms - b.p50Ms);
90
+ const total = clean.reduce((sum, item) => sum + item.samples, 0);
91
+ if (total === 0)
92
+ return null;
93
+ let cumulative = 0;
94
+ for (const item of clean) {
95
+ cumulative += item.samples;
96
+ if (cumulative >= total / 2)
97
+ return item.p50Ms;
98
+ }
99
+ return clean.at(-1).p50Ms;
100
+ }
101
+ function modelSpeedFrom(snapshot, today) {
102
+ const episodes = snapshot.sources.flatMap((source) => source.episodes);
103
+ const summarize = (match, read) => weightedMedian(episodes.filter(match)
104
+ .map((episode) => read(episode.latency))
105
+ .filter((stats) => stats != null));
106
+ const isToday = (episode) => episode.localDate === today;
107
+ const isBaseline = (episode) => episode.localDate !== today;
108
+ return {
109
+ todayHumanWaitP50Ms: summarize(isToday, (latency) => latency.humanWait),
110
+ todayTurnDurationP50Ms: summarize(isToday, (latency) => latency.turnDuration),
111
+ baselineHumanWaitP50Ms: summarize(isBaseline, (latency) => latency.humanWait),
112
+ baselineTurnDurationP50Ms: summarize(isBaseline, (latency) => latency.turnDuration),
113
+ };
114
+ }
8
115
  export function reportFrom(snapshot, stopHour = 22) {
9
116
  const buckets = snapshot.sources.flatMap((source) => source.buckets);
10
117
  const agentActiveMinutes = Math.round(buckets.reduce((sum, bucket) => sum + bucket.agentActiveMinutes, 0));
@@ -21,15 +128,22 @@ export function reportFrom(snapshot, stopHour = 22) {
21
128
  const nextMove = afterHoursCheckIns > 0
22
129
  ? `You checked in ${afterHoursCheckIns} time${afterHoursCheckIns === 1 ? "" : "s"} after ${stopHour}:00. Let the agents finish without you tonight.`
23
130
  : "Your stop time held. Keep the boundary and let the agents carry the night shift.";
131
+ const { today, baseline } = todayFrom(snapshot);
132
+ const agentWallClockMinutes = wallClockMinutes(buckets, agentMinutes);
24
133
  return {
25
- lifeReturnedMinutes: Math.max(0, agentActiveMinutes - humanAttentionMinutes),
134
+ lifeReturnedMinutes: lifeReturned(buckets),
26
135
  agentActiveMinutes,
136
+ agentWallClockMinutes,
137
+ parallelRatio: parallelRatio(agentActiveMinutes, agentWallClockMinutes),
27
138
  humanAttentionMinutes,
28
139
  afterHoursCheckIns,
29
140
  protectedNights,
30
141
  observedNights,
31
142
  agentTurns,
32
143
  humanCheckIns,
144
+ today,
145
+ baselineLifeReturnedMinutes: baseline,
146
+ modelSpeed: modelSpeedFrom(snapshot, today.localDate),
33
147
  nextMove,
34
148
  };
35
149
  }
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Hooks fire long after the terminal that ran `npx @teeras/afterhours setup`
3
+ * is gone, and an npx-only user never gets a `trs` on PATH. So setup keeps its
4
+ * own copy of the runtime here and points the hooks at an absolute path.
5
+ */
6
+ export declare function runtimeBinDir(home?: string): string;
7
+ /**
8
+ * Copy the running runtime into `<home>/.teeras/afterhours/bin` and return the
9
+ * absolute path of its `cli.js`. Re-running refreshes the copy in place, so an
10
+ * upgrade is a plain `npx @teeras/afterhours setup` away. Throws instead of
11
+ * leaving hooks pointed at a command that cannot run.
12
+ */
13
+ export declare function installRuntime(home?: string, sourceDir?: string): string;
14
+ /** `trs off`'s counterpart to `installRuntime`: deletes the durable copy. */
15
+ export declare function removeRuntime(home?: string): boolean;
16
+ /** Absolute, quoted, and PATH-free: the hook keeps working after npx exits. */
17
+ export declare function hookCommand(cliPath: string, codex?: boolean): string;
18
+ //# sourceMappingURL=runtime.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAKA;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,IAAI,SAAY,GAAG,MAAM,CAEtD;AAqBD;;;;;GAKG;AACH,wBAAgB,cAAc,CAC5B,IAAI,SAAY,EAChB,SAAS,SAA0C,GAClD,MAAM,CAsBR;AAED,6EAA6E;AAC7E,wBAAgB,aAAa,CAAC,IAAI,SAAY,GAAG,OAAO,CAKvD;AAED,+EAA+E;AAC/E,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,UAAQ,GAAG,MAAM,CAElE"}
@@ -0,0 +1,70 @@
1
+ import { chmodSync, copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ /**
6
+ * Hooks fire long after the terminal that ran `npx @teeras/afterhours setup`
7
+ * is gone, and an npx-only user never gets a `trs` on PATH. So setup keeps its
8
+ * own copy of the runtime here and points the hooks at an absolute path.
9
+ */
10
+ export function runtimeBinDir(home = homedir()) {
11
+ return join(home, ".teeras", "afterhours", "bin");
12
+ }
13
+ function copyJsTree(sourceDir, targetDir) {
14
+ let copied = 0;
15
+ for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
16
+ if (entry.isSymbolicLink())
17
+ continue;
18
+ const from = join(sourceDir, entry.name);
19
+ const to = join(targetDir, entry.name);
20
+ if (entry.isDirectory()) {
21
+ mkdirSync(to, { recursive: true });
22
+ copied += copyJsTree(from, to);
23
+ continue;
24
+ }
25
+ // Only executable modules travel: no maps, no declarations, no data.
26
+ if (!entry.isFile() || !entry.name.endsWith(".js"))
27
+ continue;
28
+ copyFileSync(from, to);
29
+ copied += 1;
30
+ }
31
+ return copied;
32
+ }
33
+ /**
34
+ * Copy the running runtime into `<home>/.teeras/afterhours/bin` and return the
35
+ * absolute path of its `cli.js`. Re-running refreshes the copy in place, so an
36
+ * upgrade is a plain `npx @teeras/afterhours setup` away. Throws instead of
37
+ * leaving hooks pointed at a command that cannot run.
38
+ */
39
+ export function installRuntime(home = homedir(), sourceDir = dirname(fileURLToPath(import.meta.url))) {
40
+ const hasCli = readdirSync(sourceDir, { withFileTypes: true })
41
+ .some((entry) => entry.isFile() && entry.name === "cli.js");
42
+ if (!hasCli) {
43
+ throw new Error("no cli.js in the package being installed from — the runtime is incomplete. Reinstall with `npx @teeras/afterhours@latest setup`.");
44
+ }
45
+ const target = runtimeBinDir(home);
46
+ mkdirSync(target, { recursive: true, mode: 0o700 });
47
+ chmodSync(target, 0o700);
48
+ const copied = copyJsTree(sourceDir, target);
49
+ if (copied === 0) {
50
+ throw new Error("no runtime files in the package being installed from — nothing to install. Reinstall with `npx @teeras/afterhours@latest setup`.");
51
+ }
52
+ // Outside the npm package the `.js` files lose their module type. Node 20
53
+ // has no ESM auto-detection, so without this marker the hook would die with
54
+ // "Cannot use import statement outside a module" on supported versions.
55
+ writeFileSync(join(target, "package.json"), `${JSON.stringify({ type: "module", private: true }, null, 2)}\n`);
56
+ return join(target, "cli.js");
57
+ }
58
+ /** `trs off`'s counterpart to `installRuntime`: deletes the durable copy. */
59
+ export function removeRuntime(home = homedir()) {
60
+ const target = runtimeBinDir(home);
61
+ if (!existsSync(target))
62
+ return false;
63
+ rmSync(target, { recursive: true, force: true });
64
+ return true;
65
+ }
66
+ /** Absolute, quoted, and PATH-free: the hook keeps working after npx exits. */
67
+ export function hookCommand(cliPath, codex = false) {
68
+ return `"${process.execPath}" "${cliPath}" hook${codex ? " codex" : ""}`;
69
+ }
70
+ //# sourceMappingURL=runtime.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export declare function episodeSecretPath(home?: string): string;
2
+ /**
3
+ * Stable per-device secret for opaque episode ids. Device tokens rotate on
4
+ * expiry and re-login; keying the HMAC with a token would re-key every episode
5
+ * id, duplicating canonical rows server-side and orphaning hook-journal events.
6
+ * This secret never leaves the device and survives login cycles.
7
+ */
8
+ export declare function ensureEpisodeSecret(home?: string): string;
9
+ //# sourceMappingURL=secret.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret.d.ts","sourceRoot":"","sources":["../src/secret.ts"],"names":[],"mappings":"AAOA,wBAAgB,iBAAiB,CAAC,IAAI,SAAY,GAAG,MAAM,CAE1D;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,SAAY,GAAG,MAAM,CAc5D"}
package/dist/secret.js ADDED
@@ -0,0 +1,32 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { chmodSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { dirname, join } from "node:path";
5
+ const SECRET_PATTERN = /^[a-f0-9]{64}$/;
6
+ export function episodeSecretPath(home = homedir()) {
7
+ return join(home, ".teeras", "afterhours", "episode-secret");
8
+ }
9
+ /**
10
+ * Stable per-device secret for opaque episode ids. Device tokens rotate on
11
+ * expiry and re-login; keying the HMAC with a token would re-key every episode
12
+ * id, duplicating canonical rows server-side and orphaning hook-journal events.
13
+ * This secret never leaves the device and survives login cycles.
14
+ */
15
+ export function ensureEpisodeSecret(home = homedir()) {
16
+ const path = episodeSecretPath(home);
17
+ try {
18
+ const existing = readFileSync(path, "utf8").trim();
19
+ if (SECRET_PATTERN.test(existing))
20
+ return existing;
21
+ }
22
+ catch {
23
+ // Missing or unreadable: mint a fresh secret below.
24
+ }
25
+ const secret = randomBytes(32).toString("hex");
26
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
27
+ chmodSync(dirname(path), 0o700);
28
+ writeFileSync(path, `${secret}\n`, { mode: 0o600 });
29
+ chmodSync(path, 0o600);
30
+ return secret;
31
+ }
32
+ //# sourceMappingURL=secret.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secret.js","sourceRoot":"","sources":["../src/secret.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAC1C,OAAO,EAAE,SAAS,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,cAAc,GAAG,gBAAgB,CAAC;AAExC,MAAM,UAAU,iBAAiB,CAAC,IAAI,GAAG,OAAO,EAAE;IAChD,OAAO,IAAI,CAAC,IAAI,EAAE,SAAS,EAAE,YAAY,EAAE,gBAAgB,CAAC,CAAC;AAC/D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAI,GAAG,OAAO,EAAE;IAClD,MAAM,IAAI,GAAG,iBAAiB,CAAC,IAAI,CAAC,CAAC;IACrC,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;QACnD,IAAI,cAAc,CAAC,IAAI,CAAC,QAAQ,CAAC;YAAE,OAAO,QAAQ,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,oDAAoD;IACtD,CAAC;IACD,MAAM,MAAM,GAAG,WAAW,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC/C,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC3D,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,KAAK,CAAC,CAAC;IAChC,aAAa,CAAC,IAAI,EAAE,GAAG,MAAM,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACpD,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACvB,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,18 @@
1
+ export interface SetupOptions {
2
+ json?: boolean;
3
+ home?: string;
4
+ fetcher?: typeof fetch;
5
+ sleep?: (ms: number) => Promise<void>;
6
+ out?: (text: string) => void;
7
+ openUri?: (uri: string) => void;
8
+ /** Directory holding the runtime to copy. Defaults to the running dist. */
9
+ runtimeSourceDir?: string;
10
+ /** Whether the terminal can show QR art. Defaults to `process.stdout.isTTY`. */
11
+ isTty?: boolean;
12
+ }
13
+ /**
14
+ * The text output is written to be quoted verbatim by a coding agent to its
15
+ * user. Keep every line meaningful when read on a phone, out of context.
16
+ */
17
+ export declare function runSetup(options?: SetupOptions): Promise<void>;
18
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAsBA,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,OAAO,CAAC,EAAE,OAAO,KAAK,CAAC;IACvB,KAAK,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACtC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7B,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAChC,2EAA2E;IAC3E,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,gFAAgF;IAChF,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAgCD;;;GAGG;AACH,wBAAsB,QAAQ,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAkHxE"}