tokenmaxxing 0.19.0 → 0.21.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/DESIGN.md +34 -23
- package/README.md +4 -4
- package/package.json +1 -1
- package/src/cli/add.ts +1 -0
- package/src/cli/auth.ts +25 -14
- package/src/cli/check.ts +3 -2
- package/src/cli/codexadd.ts +44 -40
- package/src/cli/codexinit.ts +59 -12
- package/src/cli/codexswitch.ts +15 -1
- package/src/cli/config.ts +10 -1
- package/src/cli/doctor.ts +3 -3
- package/src/cli/init.ts +54 -32
- package/src/cli/onboard.ts +62 -45
- package/src/cli/render.ts +0 -16
- package/src/cli/rm.ts +40 -2
- package/src/cli/serve.ts +650 -78
- package/src/cli/status.ts +69 -23
- package/src/cli/switch.ts +54 -19
- package/src/entries/codexstophook.ts +123 -4
- package/src/entries/codexsupervisor.ts +87 -13
- package/src/entries/sessionstart.ts +1 -1
- package/src/entries/statusline.ts +56 -20
- package/src/entries/stophook.ts +23 -9
- package/src/entries/supervisor.ts +134 -18
- package/src/lib/atomic.ts +28 -6
- package/src/lib/claudebin.ts +2 -2
- package/src/lib/claudejson.ts +5 -5
- package/src/lib/claudelock.ts +112 -37
- package/src/lib/codexauth.ts +10 -2
- package/src/lib/codexbin.ts +1 -1
- package/src/lib/codexdecide.ts +149 -19
- package/src/lib/codexpick.ts +17 -6
- package/src/lib/codexpresence.ts +59 -21
- package/src/lib/codexsample.ts +17 -8
- package/src/lib/codexswap.ts +10 -1
- package/src/lib/credstore.ts +6 -2
- package/src/lib/decide.ts +114 -42
- package/src/lib/install.ts +125 -17
- package/src/lib/keychain.ts +41 -15
- package/src/lib/lock.ts +57 -35
- package/src/lib/log.ts +36 -7
- package/src/lib/oauth.ts +18 -11
- package/src/lib/paths.ts +17 -11
- package/src/lib/picker.ts +11 -3
- package/src/lib/proc.ts +37 -0
- package/src/lib/sample.ts +91 -31
- package/src/lib/sessions.ts +23 -1
- package/src/lib/settings.ts +59 -18
- package/src/lib/slackbridge.ts +583 -76
- package/src/lib/slackstate.ts +159 -12
- package/src/lib/slackstream.ts +127 -21
- package/src/lib/state.ts +131 -35
- package/src/lib/swap.ts +109 -47
- package/src/lib/types.ts +79 -37
- package/src/lib/usage.ts +114 -16
- package/src/main.ts +61 -7
- package/src/serve-plugin/.claude-plugin/plugin.json +4 -0
- package/src/serve-plugin/skills/ask-the-user/SKILL.md +36 -0
- package/src/serve-plugin/skills/serve-session/SKILL.md +50 -0
package/src/lib/settings.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
// are ours outright - tokenmaxxing renders them natively, so any other
|
|
5
5
|
// statusLine command is replaced.
|
|
6
6
|
|
|
7
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
8
8
|
import { join } from "node:path";
|
|
9
9
|
import { z } from "zod";
|
|
10
10
|
import { paths } from "./paths.ts";
|
|
@@ -39,11 +39,15 @@ function readSettings(): Settings {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function writeSettings(s: Settings): void {
|
|
42
|
-
|
|
42
|
+
// Preserve the user's mode: settings.json can carry an env block with
|
|
43
|
+
// credentials, and the atomic rename would otherwise widen a 0600 file to
|
|
44
|
+
// world-readable. A brand-new file starts at the conservative 0600.
|
|
45
|
+
const mode = existsSync(paths.claudeSettings) ? statSync(paths.claudeSettings).mode & 0o777 : 0o600;
|
|
46
|
+
writeFileAtomic(paths.claudeSettings, JSON.stringify(s, null, 2) + "\n", mode);
|
|
43
47
|
}
|
|
44
48
|
|
|
45
49
|
/** True if a hook/statusline command string is one tokenmaxxing installed. */
|
|
46
|
-
|
|
50
|
+
function isOurCommand(cmd: string | undefined): boolean {
|
|
47
51
|
if (!cmd) return false;
|
|
48
52
|
return (
|
|
49
53
|
cmd.includes(SUBCMD.statusline) ||
|
|
@@ -55,36 +59,69 @@ export function isOurCommand(cmd: string | undefined): boolean {
|
|
|
55
59
|
);
|
|
56
60
|
}
|
|
57
61
|
|
|
62
|
+
/** The exact command string installSettings writes for a subcommand; doctor
|
|
63
|
+
* compares against it verbatim, so a green check proves the canonical entry. */
|
|
64
|
+
function ourCommand(sub: string): string {
|
|
65
|
+
return `${JSON.stringify(installedBin())} ${sub}`;
|
|
66
|
+
}
|
|
67
|
+
|
|
58
68
|
function ourHookGroup(sub: string): HookGroup {
|
|
59
|
-
return { hooks: [{ type: "command", command:
|
|
69
|
+
return { hooks: [{ type: "command", command: ourCommand(sub) }] };
|
|
60
70
|
}
|
|
61
71
|
|
|
62
72
|
function appendHook(s: Settings, event: string, sub: string): void {
|
|
63
73
|
s.hooks ??= {};
|
|
64
74
|
s.hooks[event] ??= [];
|
|
65
75
|
const arr = s.hooks[event]!;
|
|
66
|
-
const present = arr.some((g) => g.hooks
|
|
76
|
+
const present = arr.some((g) => g.hooks.some((h) => h.command === ourCommand(sub)));
|
|
67
77
|
if (!present) arr.push(ourHookGroup(sub));
|
|
68
78
|
}
|
|
69
79
|
|
|
80
|
+
/** True only for a command tokenmaxxing itself wrote - the exact historical
|
|
81
|
+
* shape `"<...>/tokenmaxxing" <sub>` at ANY install path (so stale
|
|
82
|
+
* pre-relocation entries match too). A foreign command that merely mentions
|
|
83
|
+
* the subcommand or the path as text is NOT ours and must survive removal. */
|
|
84
|
+
/** Structural ownership: exactly `"<path>/tokenmaxxing" <sub>`. Exported for
|
|
85
|
+
* the codex hooks.json installer, whose old includes()-based match deleted
|
|
86
|
+
* foreign hooks sharing a group and misclassified commands merely mentioning
|
|
87
|
+
* the subcommand (closing-review catch; the same class settings.ts's own
|
|
88
|
+
* removeHook was fixed for in PR #31). */
|
|
89
|
+
export function isOurHookCommand(cmd: string, sub: string): boolean {
|
|
90
|
+
if (!cmd.endsWith(` ${sub}`)) return false;
|
|
91
|
+
const quotedPath = cmd.slice(0, cmd.length - (sub.length + 1));
|
|
92
|
+
if (!quotedPath.startsWith('"') || !quotedPath.endsWith('"')) return false;
|
|
93
|
+
let path: unknown;
|
|
94
|
+
try {
|
|
95
|
+
path = JSON.parse(quotedPath);
|
|
96
|
+
} catch {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
const parsed = z.string().safeParse(path);
|
|
100
|
+
return parsed.success && parsed.data.endsWith("/tokenmaxxing");
|
|
101
|
+
}
|
|
102
|
+
|
|
70
103
|
function removeHook(s: Settings, event: string, sub: string): void {
|
|
71
104
|
const arr = s.hooks?.[event];
|
|
72
105
|
if (!arr) return;
|
|
73
|
-
|
|
106
|
+
// Strip only VERIFIED tokenmaxxing-owned entries from WITHIN each group:
|
|
107
|
+
// foreign hooks sharing a group - or merely mentioning our strings - survive
|
|
108
|
+
// (review catches, PR #31), and a group is dropped only once it is empty.
|
|
109
|
+
for (const g of arr) g.hooks = g.hooks.filter((h) => !isOurHookCommand(h.command, sub));
|
|
110
|
+
s.hooks![event] = arr.filter((g) => g.hooks.length > 0);
|
|
74
111
|
if (s.hooks![event]!.length === 0) delete s.hooks![event];
|
|
75
112
|
}
|
|
76
113
|
|
|
77
|
-
/** Install the entries: take both statusLine slots, append our hooks.
|
|
114
|
+
/** Install the entries: take both statusLine slots, append our hooks. Stale
|
|
115
|
+
* same-subcommand hooks from an OLD install path are dropped first, so a
|
|
116
|
+
* TOKENMAXXING_HOME relocation rewrites the entries instead of leaving dead
|
|
117
|
+
* paths that read as installed (relocation residue is how the supervisor
|
|
118
|
+
* recursion incident started). Foreign hooks are untouched. */
|
|
78
119
|
export function installSettings(): void {
|
|
79
120
|
const s = readSettings();
|
|
80
|
-
s.statusLine = {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
s.subagentStatusLine = {
|
|
85
|
-
type: "command",
|
|
86
|
-
command: `${JSON.stringify(installedBin())} ${SUBCMD.subagentStatusline}`,
|
|
87
|
-
};
|
|
121
|
+
s.statusLine = { type: "command", command: ourCommand(SUBCMD.statusline) };
|
|
122
|
+
s.subagentStatusLine = { type: "command", command: ourCommand(SUBCMD.subagentStatusline) };
|
|
123
|
+
removeHook(s, "Stop", SUBCMD.stop);
|
|
124
|
+
removeHook(s, "SessionStart", SUBCMD.sessionStart);
|
|
88
125
|
appendHook(s, "Stop", SUBCMD.stop);
|
|
89
126
|
appendHook(s, "SessionStart", SUBCMD.sessionStart);
|
|
90
127
|
writeSettings(s);
|
|
@@ -110,11 +147,15 @@ export type SettingsCheck = z.infer<typeof SettingsCheckSchema>;
|
|
|
110
147
|
|
|
111
148
|
export function checkSettings(): SettingsCheck {
|
|
112
149
|
const s = readSettings();
|
|
150
|
+
// Exact-command identity (review catch, PR #31): only the verbatim string
|
|
151
|
+
// installSettings writes counts as installed. A hook pointing at an OLD
|
|
152
|
+
// install path reads as broken, and a foreign command that merely mentions
|
|
153
|
+
// the path or subcommand as text never green-lights.
|
|
113
154
|
const has = (event: string, sub: string) =>
|
|
114
|
-
!!s.hooks?.[event]?.some((g) => g.hooks
|
|
155
|
+
!!s.hooks?.[event]?.some((g) => g.hooks.some((h) => h.command === ourCommand(sub)));
|
|
115
156
|
return {
|
|
116
|
-
statusLineOk:
|
|
117
|
-
subagentStatusLineOk:
|
|
157
|
+
statusLineOk: s.statusLine?.command === ourCommand(SUBCMD.statusline),
|
|
158
|
+
subagentStatusLineOk: s.subagentStatusLine?.command === ourCommand(SUBCMD.subagentStatusline),
|
|
118
159
|
stopOk: has("Stop", SUBCMD.stop),
|
|
119
160
|
sessionStartOk: has("SessionStart", SUBCMD.sessionStart),
|
|
120
161
|
};
|