worktrust 0.5.0 → 0.6.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.
- package/README.md +4 -0
- package/log-session.mjs +7 -2
- package/package.json +1 -1
- package/worktrust.mjs +37 -6
package/README.md
CHANGED
|
@@ -32,8 +32,12 @@ curl -fsSO https://app.worktrust.io/counter/worktrust.mjs && node worktrust.mjs
|
|
|
32
32
|
3. **The terminal finishes by itself.** It keeps the key in its private file and gives every
|
|
33
33
|
AI app here the WorkTrust door, then installs the session hook. Quit your AI apps and open them
|
|
34
34
|
again.
|
|
35
|
+
4. **Earlier work only if you say yes.** It counts the Claude Code and Codex sessions already on
|
|
36
|
+
this computer and asks `Send these as history? [y/N]`. Enter sends nothing. What goes is hours
|
|
37
|
+
and tokens per day, never text, and WorkTrust shows it as earlier work, never as verified hours.
|
|
35
38
|
|
|
36
39
|
```
|
|
40
|
+
npx worktrust history send this computer's earlier sessions later (asks first)
|
|
37
41
|
npx worktrust status what is coupled here
|
|
38
42
|
npx worktrust disconnect take it out again (shows the plan, asks first)
|
|
39
43
|
```
|
package/log-session.mjs
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
* node log-session.mjs --uninstall [--purge] take it out again (--purge also deletes the copies)
|
|
24
24
|
* node log-session.mjs --dry-run show what the current transcripts would send
|
|
25
25
|
* node log-session.mjs --history [--dry-run] send the months already on this machine, as HISTORY
|
|
26
|
+
* node log-session.mjs --history --summary one JSON line: sessions, hours, first and last month (sends nothing)
|
|
26
27
|
* (as a hook) reads the hook's JSON on stdin, logs, exits
|
|
27
28
|
* (from Codex) notify = ["node", "~/.worktrust/log-session.mjs", "--codex-notify"]
|
|
28
29
|
* in ~/.codex/config.toml: Codex calls it at every turn's end and
|
|
@@ -587,6 +588,7 @@ function historyEntries() {
|
|
|
587
588
|
from: messages[0].at, to: last,
|
|
588
589
|
});
|
|
589
590
|
if (CODEX_ROLLOUT.test(file)) entrySource.set(entry, "codex");
|
|
591
|
+
entryFile.set(entry, file);
|
|
590
592
|
entries.push(entry);
|
|
591
593
|
void day;
|
|
592
594
|
}
|
|
@@ -765,12 +767,15 @@ const door = coupling();
|
|
|
765
767
|
const codexDoor = codexCoupling();
|
|
766
768
|
const doorFor = (entry) => (String(entry?.id ?? "").startsWith("codex:") ? codexDoor ?? door : door);
|
|
767
769
|
const entrySource = new WeakMap(); // a history entry → "codex" when its rollout was Codex's; sendHistory routes by it
|
|
770
|
+
const entryFile = new WeakMap(); // a history entry → its transcript, so `--summary` can count sessions
|
|
768
771
|
if (!door && !codexDoor && !DRY) { process.exit(0); } // Not coupled on this machine: nothing to do, quietly.
|
|
769
772
|
|
|
770
773
|
if (flag("history")) {
|
|
771
774
|
const entries = historyEntries();
|
|
772
775
|
const hours = entries.reduce((sum, entry) => sum + (entry.seconds ?? 0), 0) / 3600;
|
|
773
776
|
const months = [...new Set(entries.map((entry) => entry.at.slice(0, 7)))].sort();
|
|
777
|
+
// ONE LINE FOR `npx worktrust` TO ASK WITH (2026-10-03): sessions, hours, first and last month; nothing sent.
|
|
778
|
+
if (flag("summary")) { console.log(JSON.stringify({ sessions: new Set(entries.map((entry) => entryFile.get(entry))).size, hours: Math.round(hours * 10) / 10, first: months[0] ?? null, last: months.at(-1) ?? null })); process.exit(0); }
|
|
774
779
|
console.log(`${entries.length} day-stretches · ${hours.toFixed(1)} measured hours · ${months.join(", ")}`);
|
|
775
780
|
if (DRY) {
|
|
776
781
|
// One example in full: the shape is the argument for trusting it, and a summary hides it.
|
|
@@ -778,8 +783,8 @@ if (flag("history")) {
|
|
|
778
783
|
console.log("\ndry run — nothing sent. Add --history without --dry-run to import them.");
|
|
779
784
|
process.exit(0);
|
|
780
785
|
}
|
|
781
|
-
|
|
782
|
-
process.exit(0);
|
|
786
|
+
// A batch the door refused is a failure the caller must see, not a quiet zero.
|
|
787
|
+
process.exit((await sendHistory(door, entries, true)) === entries.length ? 0 : 1);
|
|
783
788
|
}
|
|
784
789
|
|
|
785
790
|
const input = hookInput();
|
package/package.json
CHANGED
package/worktrust.mjs
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
* npx worktrust --yes no question (for scripts); the computer is still approved in the app
|
|
9
9
|
* npx worktrust --device a computer without a browser (SSH): type a code on another device
|
|
10
10
|
* npx worktrust connect couple again, also on a computer already coupled
|
|
11
|
+
* npx worktrust history send this computer's earlier sessions as history (asks first)
|
|
11
12
|
* npx worktrust disconnect show what comes out, ask, take it out again
|
|
12
13
|
* npx worktrust status what is coupled here
|
|
13
14
|
*
|
|
@@ -95,9 +96,9 @@ async function scripts() {
|
|
|
95
96
|
}
|
|
96
97
|
|
|
97
98
|
/** Run a script; its output is shown, or collected for the plan. The key travels in the environment only. */
|
|
98
|
-
function run(path, scriptArgs, token, collect = false) {
|
|
99
|
+
function run(path, scriptArgs, token, collect = false, extraEnv = {}) {
|
|
99
100
|
return new Promise((resolve) => {
|
|
100
|
-
const child = spawn(process.execPath, [path, ...scriptArgs], { stdio: collect ? ["ignore", "pipe", "pipe"] : "inherit", env: { ...process.env, WORKTRUST_MCP_URL: MCP, WORKTRUST_MCP_TOKEN: token, WORKTRUST_NODE: NODE } });
|
|
101
|
+
const child = spawn(process.execPath, [path, ...scriptArgs], { stdio: collect ? ["ignore", "pipe", "pipe"] : "inherit", env: { ...process.env, WORKTRUST_MCP_URL: MCP, WORKTRUST_MCP_TOKEN: token, WORKTRUST_NODE: NODE, ...extraEnv } });
|
|
101
102
|
let out = "";
|
|
102
103
|
child.stdout?.on("data", (chunk) => { out += chunk; });
|
|
103
104
|
child.stderr?.on("data", (chunk) => { out += chunk; });
|
|
@@ -105,13 +106,14 @@ function run(path, scriptArgs, token, collect = false) {
|
|
|
105
106
|
});
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
/** `yes` is the answer Enter gives, and the one `--yes` gives: a question whose default is No stays No under --yes. */
|
|
110
|
+
async function ask(question, yes = true) {
|
|
111
|
+
if (has("yes")) return yes;
|
|
110
112
|
if (!process.stdin.isTTY) fail("not a terminal: nothing was changed. Run it in a terminal, or add --yes to agree in advance.");
|
|
111
113
|
const prompt = createInterface({ input: process.stdin, output: process.stdout });
|
|
112
|
-
const answer = (await prompt.question(` ${question} [Y/n] `)).trim().toLowerCase();
|
|
114
|
+
const answer = (await prompt.question(` ${question} ${yes ? "[Y/n]" : "[y/N]"} `)).trim().toLowerCase();
|
|
113
115
|
prompt.close();
|
|
114
|
-
return answer === ""
|
|
116
|
+
return answer === "" ? yes : answer === "y" || answer === "yes";
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
function openBrowser(url) {
|
|
@@ -398,11 +400,38 @@ async function connect() {
|
|
|
398
400
|
say();
|
|
399
401
|
const hookArgs = ["--install", ...(BUNDLED ? ["--counter", paths["count-behaviour.mjs"]] : []), ...(direct ? [] : ["--via", STABLE])];
|
|
400
402
|
if ((await run(paths["log-session.mjs"], hookArgs, token)).code !== 0) fail("installing the session hook failed; the output above says where.");
|
|
403
|
+
await offerHistory(paths, token, device ? device.privateKey.export({ format: "pem", type: "pkcs8" }) : null);
|
|
401
404
|
say();
|
|
402
405
|
say(" Done. Quit your AI apps completely and open them again. This computer appears in WorkTrust");
|
|
403
406
|
say(" under Sources → Devices once one of them has spoken.");
|
|
404
407
|
}
|
|
405
408
|
|
|
409
|
+
/**
|
|
410
|
+
* EARLIER WORK IS SENT ONLY ON A YES (owner, 2026-10-03). After coupling, or as `npx worktrust history`:
|
|
411
|
+
* what this computer holds is counted here and shown, and nothing leaves before the person types y.
|
|
412
|
+
* Enter, --yes and a closed terminal all mean No; `--history` says yes in advance. What goes is hours
|
|
413
|
+
* and tokens per day, filed as history (never verified hours, never text).
|
|
414
|
+
*/
|
|
415
|
+
async function offerHistory(paths, token, devicePem, url = MCP) {
|
|
416
|
+
const env = { WORKTRUST_MCP_URL: url, ...(devicePem ? { WORKTRUST_DEVICE_KEY: devicePem } : {}) };
|
|
417
|
+
const counted = await run(paths["log-session.mjs"], ["--history", "--dry-run", "--summary"], token, true, env);
|
|
418
|
+
let found = null;
|
|
419
|
+
try { found = JSON.parse(counted.out.trim().split("\n").pop()); } catch { /* no line: nothing to offer */ }
|
|
420
|
+
say();
|
|
421
|
+
if (!found?.sessions) { say(" No earlier Claude Code or Codex sessions on this computer to send."); return; }
|
|
422
|
+
say(` This computer holds ${found.sessions} earlier Claude Code / Codex sessions · ${found.hours} measured hours · ${found.first} … ${found.last}.`);
|
|
423
|
+
if (has("dry-run")) { say(" Dry run: nothing was sent."); return; }
|
|
424
|
+
if (!has("history") && !(await ask("Send these as history (hours and tokens per day; never text)?", false))) { say(" Not sent. Send them later with: npx worktrust history"); return; }
|
|
425
|
+
const sent = await run(paths["log-session.mjs"], ["--history"], token, true, env);
|
|
426
|
+
say(sent.code === 0 ? " ✓ Sent as history. It shows in WorkTrust as earlier work, never as verified hours." : " Not all of it arrived. Run npx worktrust history again: a line already received is kept once.");
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
async function history() {
|
|
430
|
+
const key = await freshKey();
|
|
431
|
+
if (!key) fail("this computer is not coupled through the key file. Run npx worktrust first.");
|
|
432
|
+
await offerHistory(await scripts(), key.token, key.device ?? null, key.url);
|
|
433
|
+
}
|
|
434
|
+
|
|
406
435
|
async function disconnect() {
|
|
407
436
|
say();
|
|
408
437
|
say(` WorkTrust · disconnect ${hostname().replace(/\.local$/i, "")}`);
|
|
@@ -454,6 +483,7 @@ function help() {
|
|
|
454
483
|
say(" --name <name> the name this computer gets (default: its host name)");
|
|
455
484
|
say(" --direct put the key in each app's settings instead of the key file");
|
|
456
485
|
say(" worktrust connect couple again, also when this computer is already coupled");
|
|
486
|
+
say(" worktrust history send this computer's earlier sessions as history (asks first)");
|
|
457
487
|
say(" worktrust disconnect take WorkTrust out of every AI app here (asks first)");
|
|
458
488
|
say(" worktrust status what is coupled here");
|
|
459
489
|
}
|
|
@@ -465,6 +495,7 @@ if (command === "default") { if (keyStore.load()) { status(); say(); say(" This
|
|
|
465
495
|
else if (command === "mcp") await bridge();
|
|
466
496
|
else if (command === "hook") await hook();
|
|
467
497
|
else if (command === "connect") await connect();
|
|
498
|
+
else if (command === "history") await history();
|
|
468
499
|
else if (command === "disconnect") await disconnect();
|
|
469
500
|
else if (command === "status") status();
|
|
470
501
|
else help();
|