@teeras/afterhours 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 (84) hide show
  1. package/README.md +101 -15
  2. package/dist/activity.d.ts +15 -1
  3. package/dist/activity.d.ts.map +1 -1
  4. package/dist/activity.js +76 -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 +8 -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 +26 -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 +38 -1
  53. package/dist/render.js.map +1 -1
  54. package/dist/report.d.ts.map +1 -1
  55. package/dist/report.js +82 -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 +141 -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 +209 -18
  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 +73 -8
  80. package/dist/sources/gemini.js.map +1 -1
  81. package/dist/types.d.ts +61 -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/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,80 @@ 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
+ }
8
83
  export function reportFrom(snapshot, stopHour = 22) {
9
84
  const buckets = snapshot.sources.flatMap((source) => source.buckets);
10
85
  const agentActiveMinutes = Math.round(buckets.reduce((sum, bucket) => sum + bucket.agentActiveMinutes, 0));
@@ -21,15 +96,21 @@ export function reportFrom(snapshot, stopHour = 22) {
21
96
  const nextMove = afterHoursCheckIns > 0
22
97
  ? `You checked in ${afterHoursCheckIns} time${afterHoursCheckIns === 1 ? "" : "s"} after ${stopHour}:00. Let the agents finish without you tonight.`
23
98
  : "Your stop time held. Keep the boundary and let the agents carry the night shift.";
99
+ const { today, baseline } = todayFrom(snapshot);
100
+ const agentWallClockMinutes = wallClockMinutes(buckets, agentMinutes);
24
101
  return {
25
- lifeReturnedMinutes: Math.max(0, agentActiveMinutes - humanAttentionMinutes),
102
+ lifeReturnedMinutes: lifeReturned(buckets),
26
103
  agentActiveMinutes,
104
+ agentWallClockMinutes,
105
+ parallelRatio: parallelRatio(agentActiveMinutes, agentWallClockMinutes),
27
106
  humanAttentionMinutes,
28
107
  afterHoursCheckIns,
29
108
  protectedNights,
30
109
  observedNights,
31
110
  agentTurns,
32
111
  humanCheckIns,
112
+ today,
113
+ baselineLifeReturnedMinutes: baseline,
33
114
  nextMove,
34
115
  };
35
116
  }
@@ -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"}
package/dist/setup.js ADDED
@@ -0,0 +1,155 @@
1
+ import { homedir } from "node:os";
2
+ import { ConnectionExpiredError, openVerificationUri, pollDeviceLogin, readCredentials, removeCredentials, startDeviceLogin, syncSnapshot, teerasBaseUrl, writeCredentials, } from "./cloud.js";
3
+ import { collectAfterhours } from "./collect.js";
4
+ import { installHooks } from "./install.js";
5
+ import { tildePath } from "./privacy.js";
6
+ import { qrBlock } from "./qr.js";
7
+ import { renderReport } from "./render.js";
8
+ import { reportFrom } from "./report.js";
9
+ import { hookCommand, installRuntime } from "./runtime.js";
10
+ import { markSyncSuccess } from "./autosync.js";
11
+ /**
12
+ * "Connected" used to mean three different things at once here — account
13
+ * signed in, hooks installed, an agent detected — which made the screen
14
+ * unreadable at a glance. The account gets its own word ("signed in"); an
15
+ * agent is `measuring` once its hooks are in place, or `not found` if the
16
+ * agent itself never showed up. One agent per line: a separator between
17
+ * agents reads as another state when a state is two words.
18
+ *
19
+ * The `agents` label is printed once and the rest of the block indented under
20
+ * it, so the column reads like the single `hooks`/`sync` rows around it
21
+ * rather than like a repeated key.
22
+ */
23
+ function hookLine(results) {
24
+ const label = (result) => result.agent === "claude-code" ? "Claude Code" : "Codex";
25
+ const state = (result) => {
26
+ switch (result.action) {
27
+ case "skipped":
28
+ return "not found";
29
+ case "installed":
30
+ case "already-installed":
31
+ return "measuring";
32
+ case "unreadable":
33
+ return "settings file unreadable, left untouched";
34
+ }
35
+ };
36
+ return results.map((result, index) => ` ${index === 0 ? "agents" : " "} ${label(result).padEnd(12)}${state(result)}`);
37
+ }
38
+ /**
39
+ * The text output is written to be quoted verbatim by a coding agent to its
40
+ * user. Keep every line meaningful when read on a phone, out of context.
41
+ */
42
+ export async function runSetup(options = {}) {
43
+ const home = options.home ?? homedir();
44
+ const json = options.json === true;
45
+ const out = options.out ?? ((text) => process.stdout.write(text));
46
+ const openUri = options.openUri ?? openVerificationUri;
47
+ const isTty = options.isTty ?? process.stdout.isTTY === true;
48
+ // Hooks must survive the npx temp directory, so the runtime is copied into
49
+ // the home first and the fragments point at that absolute path. A hook we
50
+ // cannot run is silent, missing measurement — refuse rather than report it.
51
+ let cliPath;
52
+ try {
53
+ cliPath = installRuntime(home, options.runtimeSourceDir);
54
+ }
55
+ catch (error) {
56
+ // The reason has to survive — "check that your home directory is
57
+ // writable" is wrong guidance for a full disk, and a bare failure with no
58
+ // reason at all is unfixable. `error.code` (EACCES/ENOSPC/EROFS) is both
59
+ // the most diagnostic part and the part that carries no path; when there
60
+ // is no code, the message goes through `tildePath` so a home prefix
61
+ // cannot ride along into a transcript.
62
+ const code = error?.code;
63
+ const reason = typeof code === "string" && code.length > 0
64
+ ? code
65
+ : tildePath(error instanceof Error ? error.message : String(error), home);
66
+ throw new Error(`could not install the local hook command (${reason}). Nothing was changed. EACCES or EROFS means ~/.teeras is not writable; ENOSPC means the disk is full. Clear that, then run \`npx @teeras/afterhours@latest setup\` again.`);
67
+ }
68
+ const hooks = installHooks(home, {
69
+ claude: hookCommand(cliPath),
70
+ codex: hookCommand(cliPath, true),
71
+ });
72
+ if (json) {
73
+ const redacted = hooks.map((result) => ({ ...result, path: tildePath(result.path, home) }));
74
+ out(`${JSON.stringify({ event: "hooks", results: redacted })}\n`);
75
+ }
76
+ // Collected once and reused for both the sync payload and the closing
77
+ // report — the same counts back the ask and the payoff in one breath.
78
+ const snapshot = collectAfterhours({ home });
79
+ let credentials = readCredentials(home);
80
+ let alreadyConnected = false;
81
+ if (credentials) {
82
+ try {
83
+ await syncSnapshot(snapshot, credentials, options.fetcher);
84
+ alreadyConnected = true;
85
+ }
86
+ catch (error) {
87
+ if (!(error instanceof ConnectionExpiredError))
88
+ throw error;
89
+ // The server rejected a token that still looks valid locally. Clear it
90
+ // and reconnect in this same run — setup must never dead-end on stale
91
+ // state or bounce the user to another command.
92
+ removeCredentials(home);
93
+ credentials = null;
94
+ if (json)
95
+ out(`${JSON.stringify({ event: "reconnect", reason: "connection expired" })}\n`);
96
+ else
97
+ out("\n your previous connection expired — reconnecting…\n");
98
+ }
99
+ }
100
+ if (!credentials) {
101
+ const baseUrl = teerasBaseUrl();
102
+ const grant = await startDeviceLogin(baseUrl, options.fetcher);
103
+ if (json) {
104
+ out(`${JSON.stringify({ event: "code", userCode: grant.userCode, verificationUri: grant.verificationUri, expiresIn: grant.expiresIn })}\n`);
105
+ }
106
+ else {
107
+ const host = new URL(grant.verificationUri).host;
108
+ out([
109
+ "",
110
+ " teeras · one tap to connect",
111
+ "",
112
+ " On this computer: open",
113
+ ` ${grant.verificationUri}`,
114
+ " then approve. (Sign in with Google if asked.)",
115
+ "",
116
+ ` On your phone instead: go to ${host}/connect and enter ${grant.userCode}`,
117
+ "",
118
+ " While you work, agent activity counts (never content) sync to your Teeras account.",
119
+ " Waiting here while you continue…",
120
+ "",
121
+ ].join("\n"));
122
+ openUri(grant.verificationUri);
123
+ }
124
+ const token = await pollDeviceLogin(baseUrl, grant, { fetcher: options.fetcher, sleep: options.sleep });
125
+ credentials = {
126
+ baseUrl,
127
+ accessToken: token.accessToken,
128
+ deviceId: token.deviceId,
129
+ connectedAt: new Date().toISOString(),
130
+ expiresAt: token.expiresAt,
131
+ };
132
+ writeCredentials(credentials, home);
133
+ await syncSnapshot(snapshot, credentials, options.fetcher);
134
+ }
135
+ markSyncSuccess(home);
136
+ if (json) {
137
+ out(`${JSON.stringify({ event: "connected", alreadyConnected, deviceId: credentials.deviceId, firstSync: !alreadyConnected })}\n`);
138
+ return;
139
+ }
140
+ // The payoff, not just a status line: the same week report `trs week`
141
+ // prints, so the value lands in the same breath as the ask.
142
+ out(renderReport(snapshot, reportFrom(snapshot)));
143
+ out([
144
+ "",
145
+ alreadyConnected ? " ✓ signed in — refreshed your setup" : " ✓ signed in",
146
+ "",
147
+ ...hookLine(hooks),
148
+ " hooks load when an agent starts, so measuring begins with your next session — not this one.",
149
+ " sync your counts-only week is on web and mobile · updates while you work",
150
+ "",
151
+ ...qrBlock(isTty),
152
+ "",
153
+ ].join("\n"));
154
+ }
155
+ //# sourceMappingURL=setup.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.js","sourceRoot":"","sources":["../src/setup.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EACL,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,EACf,eAAe,EACf,iBAAiB,EACjB,gBAAgB,EAChB,YAAY,EACZ,aAAa,EACb,gBAAgB,GAEjB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACjD,OAAO,EAAE,YAAY,EAA0B,MAAM,cAAc,CAAC;AACpE,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAehD;;;;;;;;;;;GAWG;AACH,SAAS,QAAQ,CAAC,OAA4B;IAC5C,MAAM,KAAK,GAAG,CAAC,MAAyB,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,KAAK,aAAa,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC;IACtG,MAAM,KAAK,GAAG,CAAC,MAAyB,EAAU,EAAE;QAClD,QAAQ,MAAM,CAAC,MAAM,EAAE,CAAC;YACtB,KAAK,SAAS;gBACZ,OAAO,WAAW,CAAC;YACrB,KAAK,WAAW,CAAC;YACjB,KAAK,mBAAmB;gBACtB,OAAO,WAAW,CAAC;YACrB,KAAK,YAAY;gBACf,OAAO,0CAA0C,CAAC;QACtD,CAAC;IACH,CAAC,CAAC;IACF,OAAO,OAAO,CAAC,GAAG,CAChB,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,QAAQ,KAAK,KAAK,CAAC,MAAM,CAAC,CAAC,MAAM,CAAC,EAAE,CAAC,GAAG,KAAK,CAAC,MAAM,CAAC,EAAE,CACzG,CAAC;AACJ,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAAC,UAAwB,EAAE;IACvD,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,OAAO,EAAE,CAAC;IACvC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,KAAK,IAAI,CAAC;IACnC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,CAAC,CAAC,IAAY,EAAE,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC;IAC1E,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,IAAI,mBAAmB,CAAC;IACvD,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC,KAAK,KAAK,IAAI,CAAC;IAE7D,2EAA2E;IAC3E,0EAA0E;IAC1E,4EAA4E;IAC5E,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,cAAc,CAAC,IAAI,EAAE,OAAO,CAAC,gBAAgB,CAAC,CAAC;IAC3D,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iEAAiE;QACjE,0EAA0E;QAC1E,yEAAyE;QACzE,yEAAyE;QACzE,oEAAoE;QACpE,uCAAuC;QACvC,MAAM,IAAI,GAAI,KAAsC,EAAE,IAAI,CAAC;QAC3D,MAAM,MAAM,GAAG,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,MAAM,GAAG,CAAC;YACxD,CAAC,CAAC,IAAI;YACN,CAAC,CAAC,SAAS,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC,CAAC;QAC5E,MAAM,IAAI,KAAK,CACb,6CAA6C,MAAM,6KAA6K,CACjO,CAAC;IACJ,CAAC;IAED,MAAM,KAAK,GAAG,YAAY,CAAC,IAAI,EAAE;QAC/B,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC;QAC5B,KAAK,EAAE,WAAW,CAAC,OAAO,EAAE,IAAI,CAAC;KAClC,CAAC,CAAC;IACH,IAAI,IAAI,EAAE,CAAC;QACT,MAAM,QAAQ,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,MAAM,EAAE,IAAI,EAAE,SAAS,CAAC,MAAM,CAAC,IAAI,EAAE,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;QAC5F,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC,IAAI,CAAC,CAAC;IACpE,CAAC;IAED,sEAAsE;IACtE,sEAAsE;IACtE,MAAM,QAAQ,GAAG,iBAAiB,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7C,IAAI,WAAW,GAA4B,eAAe,CAAC,IAAI,CAAC,CAAC;IACjE,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAC7B,IAAI,WAAW,EAAE,CAAC;QAChB,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;YAC3D,gBAAgB,GAAG,IAAI,CAAC;QAC1B,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,CAAC,KAAK,YAAY,sBAAsB,CAAC;gBAAE,MAAM,KAAK,CAAC;YAC5D,uEAAuE;YACvE,sEAAsE;YACtE,+CAA+C;YAC/C,iBAAiB,CAAC,IAAI,CAAC,CAAC;YACxB,WAAW,GAAG,IAAI,CAAC;YACnB,IAAI,IAAI;gBAAE,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,oBAAoB,EAAE,CAAC,IAAI,CAAC,CAAC;;gBACtF,GAAG,CAAC,wDAAwD,CAAC,CAAC;QACrE,CAAC;IACH,CAAC;IACD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,MAAM,OAAO,GAAG,aAAa,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;QAC/D,IAAI,IAAI,EAAE,CAAC;YACT,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,CAAC,QAAQ,EAAE,eAAe,EAAE,KAAK,CAAC,eAAe,EAAE,SAAS,EAAE,KAAK,CAAC,SAAS,EAAE,CAAC,IAAI,CAAC,CAAC;QAC9I,CAAC;aAAM,CAAC;YACN,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,IAAI,CAAC;YACjD,GAAG,CAAC;gBACF,EAAE;gBACF,+BAA+B;gBAC/B,EAAE;gBACF,0BAA0B;gBAC1B,KAAK,KAAK,CAAC,eAAe,EAAE;gBAC5B,iDAAiD;gBACjD,EAAE;gBACF,mCAAmC,IAAI,wBAAwB,KAAK,CAAC,QAAQ,EAAE;gBAC/E,EAAE;gBACF,sFAAsF;gBACtF,oCAAoC;gBACpC,EAAE;aACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;YACd,OAAO,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC;QACjC,CAAC;QACD,MAAM,KAAK,GAAG,MAAM,eAAe,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,OAAO,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACxG,WAAW,GAAG;YACZ,OAAO;YACP,WAAW,EAAE,KAAK,CAAC,WAAW;YAC9B,QAAQ,EAAE,KAAK,CAAC,QAAQ;YACxB,WAAW,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACrC,SAAS,EAAE,KAAK,CAAC,SAAS;SAC3B,CAAC;QACF,gBAAgB,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;QACpC,MAAM,YAAY,CAAC,QAAQ,EAAE,WAAW,EAAE,OAAO,CAAC,OAAO,CAAC,CAAC;IAC7D,CAAC;IACD,eAAe,CAAC,IAAI,CAAC,CAAC;IAEtB,IAAI,IAAI,EAAE,CAAC;QACT,GAAG,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,gBAAgB,EAAE,QAAQ,EAAE,WAAW,CAAC,QAAQ,EAAE,SAAS,EAAE,CAAC,gBAAgB,EAAE,CAAC,IAAI,CAAC,CAAC;QACnI,OAAO;IACT,CAAC;IAED,sEAAsE;IACtE,4DAA4D;IAC5D,GAAG,CAAC,YAAY,CAAC,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAClD,GAAG,CAAC;QACF,EAAE;QACF,gBAAgB,CAAC,CAAC,CAAC,sCAAsC,CAAC,CAAC,CAAC,eAAe;QAC3E,EAAE;QACF,GAAG,QAAQ,CAAC,KAAK,CAAC;QAClB,+FAA+F;QAC/F,+EAA+E;QAC/E,EAAE;QACF,GAAG,OAAO,CAAC,KAAK,CAAC;QACjB,EAAE;KACH,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;AAChB,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/sources/claude.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAUjD,eAAO,MAAM,YAAY,EAAE,aA2C1B,CAAC"}
1
+ {"version":3,"file":"claude.d.ts","sourceRoot":"","sources":["../../src/sources/claude.ts"],"names":[],"mappings":"AAgBA,OAAO,KAAK,EAAE,aAAa,EAAe,MAAM,aAAa,CAAC;AAiB9D,eAAO,MAAM,YAAY,EAAE,aA+J1B,CAAC"}