tokens-for-good 0.4.0 → 0.4.2
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/package.json +1 -1
- package/src/cli.js +1 -1
- package/src/init.js +33 -3
- package/src/session-start-hook.js +24 -11
package/package.json
CHANGED
package/src/cli.js
CHANGED
package/src/init.js
CHANGED
|
@@ -7,6 +7,7 @@ import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs';
|
|
|
7
7
|
import { join, dirname } from 'path';
|
|
8
8
|
import { homedir } from 'os';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
|
+
import { spawnSync } from 'child_process';
|
|
10
11
|
import { detectPlatform } from './platform.js';
|
|
11
12
|
import { loadState, saveState } from './state.js';
|
|
12
13
|
|
|
@@ -238,15 +239,44 @@ function writeSessionStartHook() {
|
|
|
238
239
|
matcher: '',
|
|
239
240
|
hooks: [{
|
|
240
241
|
type: 'command',
|
|
241
|
-
command:
|
|
242
|
-
? 'cmd /c npx -y tokens-for-good session-start-hook'
|
|
243
|
-
: 'npx -y tokens-for-good session-start-hook',
|
|
242
|
+
command: hookCommand(),
|
|
244
243
|
}],
|
|
245
244
|
});
|
|
246
245
|
}
|
|
247
246
|
writeJson(abs, cfg);
|
|
248
247
|
}
|
|
249
248
|
|
|
249
|
+
// Claude Code runs SessionStart hooks under Git Bash on Windows with a
|
|
250
|
+
// stripped PATH that typically does not include C:\Program Files\nodejs,
|
|
251
|
+
// so a bare `npx` lookup fails silently. Resolve the absolute npx path at
|
|
252
|
+
// init time (when the user's full PATH is available) and bake it into the
|
|
253
|
+
// hook command so it works regardless of Claude Code's hook-runner PATH.
|
|
254
|
+
function hookCommand() {
|
|
255
|
+
if (!IS_WINDOWS) return 'npx -y tokens-for-good session-start-hook';
|
|
256
|
+
|
|
257
|
+
const npxPath = resolveWindowsNpxPath();
|
|
258
|
+
// Bash accepts double-quoted paths with spaces; escape backslashes for JSON.
|
|
259
|
+
return `"${npxPath}" -y tokens-for-good session-start-hook`;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function resolveWindowsNpxPath() {
|
|
263
|
+
// First try `where npx.cmd` — most reliable when PATH is correct.
|
|
264
|
+
try {
|
|
265
|
+
const r = spawnSync('where', ['npx.cmd'], { encoding: 'utf-8' });
|
|
266
|
+
if (r.status === 0) {
|
|
267
|
+
const first = r.stdout.trim().split(/\r?\n/)[0];
|
|
268
|
+
if (first && existsSync(first)) return first;
|
|
269
|
+
}
|
|
270
|
+
} catch { /* fall through */ }
|
|
271
|
+
|
|
272
|
+
// Fallback: npx.cmd usually sits alongside node.exe.
|
|
273
|
+
const alongside = join(dirname(process.execPath), 'npx.cmd');
|
|
274
|
+
if (existsSync(alongside)) return alongside;
|
|
275
|
+
|
|
276
|
+
// Last-resort guess — user's hook may need manual edit if this is wrong.
|
|
277
|
+
return 'C:\\Program Files\\nodejs\\npx.cmd';
|
|
278
|
+
}
|
|
279
|
+
|
|
250
280
|
function writeSkillFile(name) {
|
|
251
281
|
const src = join(PKG_ROOT, 'skills', `${name}.md`);
|
|
252
282
|
const dst = join(homedir(), '.claude', 'skills', name, 'SKILL.md');
|
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
// ~/.claude/settings.json on every Claude Code session start.
|
|
4
4
|
//
|
|
5
5
|
// Reads state.json and, if the user's init choice hasn't been acted on yet,
|
|
6
|
-
// emits
|
|
7
|
-
//
|
|
6
|
+
// emits a JSON payload on stdout in Claude Code's SessionStart-hook format
|
|
7
|
+
// so the message is injected into the session's context. Exits silently in
|
|
8
8
|
// the normal steady state so users aren't nudged forever.
|
|
9
|
+
//
|
|
10
|
+
// Docs: https://code.claude.com/docs/en/hooks.md (SessionStart section).
|
|
11
|
+
// Required output shape for SessionStart hooks:
|
|
12
|
+
// { "hookSpecificOutput": { "hookEventName": "SessionStart", "additionalContext": "<text>" } }
|
|
13
|
+
// Plain stdout is ignored by Claude Code, which is why this needs JSON.
|
|
9
14
|
import { loadState, isSnoozed } from './state.js';
|
|
10
15
|
|
|
11
16
|
export function runSessionStartHook() {
|
|
@@ -16,7 +21,7 @@ export function runSessionStartHook() {
|
|
|
16
21
|
|
|
17
22
|
// First session after init — emit the handoff instruction exactly once.
|
|
18
23
|
if (!state.first_setup_complete) {
|
|
19
|
-
|
|
24
|
+
emit(firstSessionMessage(state));
|
|
20
25
|
return;
|
|
21
26
|
}
|
|
22
27
|
|
|
@@ -27,37 +32,45 @@ export function runSessionStartHook() {
|
|
|
27
32
|
const last = state.last_contributed ? new Date(state.last_contributed) : null;
|
|
28
33
|
const daysSince = last ? Math.floor((Date.now() - last.getTime()) / 86_400_000) : Infinity;
|
|
29
34
|
if (daysSince >= 7) {
|
|
30
|
-
|
|
35
|
+
emit(
|
|
31
36
|
"Tokens for Good: It's been a while — say 'run /tfg' to research one org now, " +
|
|
32
|
-
"or 'run /tfg-schedule daily' to automate it from here on
|
|
37
|
+
"or 'run /tfg-schedule daily' to automate it from here on."
|
|
33
38
|
);
|
|
34
39
|
}
|
|
35
40
|
}
|
|
36
41
|
}
|
|
37
42
|
|
|
38
|
-
function
|
|
43
|
+
function emit(additionalContext) {
|
|
44
|
+
process.stdout.write(JSON.stringify({
|
|
45
|
+
hookSpecificOutput: {
|
|
46
|
+
hookEventName: 'SessionStart',
|
|
47
|
+
additionalContext,
|
|
48
|
+
},
|
|
49
|
+
}));
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function firstSessionMessage(state) {
|
|
39
53
|
const installedOn = state.installed_at
|
|
40
54
|
? new Date(state.installed_at).toISOString().slice(0, 10)
|
|
41
55
|
: 'recently';
|
|
42
56
|
|
|
43
57
|
if (state.intended_flow === 'scheduled') {
|
|
44
58
|
const freq = state.intended_frequency || 'daily';
|
|
45
|
-
|
|
59
|
+
return (
|
|
46
60
|
`Tokens for Good: The user ran \`npx tokens-for-good init\` on ${installedOn} ` +
|
|
47
61
|
`and chose scheduled research at ${freq} cadence. This is their first Claude Code ` +
|
|
48
62
|
`session since. Before any other task, invoke the /tfg-schedule skill with ` +
|
|
49
63
|
`frequency=${freq}. After /schedule confirms, the skill will call ` +
|
|
50
|
-
`mark_setup_complete on the TFG MCP to dismiss this prompt
|
|
64
|
+
`mark_setup_complete on the TFG MCP to dismiss this prompt.`
|
|
51
65
|
);
|
|
52
|
-
return;
|
|
53
66
|
}
|
|
54
67
|
|
|
55
68
|
// one_off flow
|
|
56
|
-
|
|
69
|
+
return (
|
|
57
70
|
`Tokens for Good: The user ran \`npx tokens-for-good init\` on ${installedOn} ` +
|
|
58
71
|
`and chose to do research manually (one-off, not scheduled). This is their first ` +
|
|
59
72
|
`Claude Code session since. Offer to start a research task now by invoking the ` +
|
|
60
73
|
`/tfg skill. After they submit their first report, the skill will call ` +
|
|
61
|
-
`mark_setup_complete on the TFG MCP to dismiss this prompt
|
|
74
|
+
`mark_setup_complete on the TFG MCP to dismiss this prompt.`
|
|
62
75
|
);
|
|
63
76
|
}
|