tokens-for-good 0.4.5 → 0.4.7

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 CHANGED
@@ -15,7 +15,7 @@ Works with Claude Code, OpenCode, Cursor, Windsurf, Devin, and Qwen Code as an M
15
15
 
16
16
  init is interactive: it asks for your API key, the cadence you want (daily / weekly / hourly / one-off), and then writes everything — MCP config, SessionStart hook, `/tfg` and `/tfg-schedule` skills, and your preference — in one shot.
17
17
 
18
- 3. **Open Claude Code.** Your first session acts on the cadence you picked automatically:
18
+ 3. **Open your AI coding tool.** Your first session acts on the cadence you picked automatically:
19
19
  - Scheduled → it sets up `/schedule` via the `/tfg-schedule` skill.
20
20
  - One-off → it kicks off a single research task via the `/tfg` skill.
21
21
 
package/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "tokens-for-good",
3
- "version": "0.4.5",
3
+ "version": "0.4.7",
4
4
  "type": "module",
5
5
  "description": "Donate your spare AI tokens to research nonprofits for Fierce Philanthropy",
6
+ "homepage": "https://tokensforgood.ai",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/Tokens-for-Good/tokens-for-good.git"
10
+ },
11
+ "bugs": {
12
+ "url": "https://github.com/Tokens-for-Good/tokens-for-good/issues"
13
+ },
6
14
  "bin": {
7
15
  "tokens-for-good": "src/cli.js"
8
16
  },
@@ -26,6 +34,9 @@
26
34
  "social-impact"
27
35
  ],
28
36
  "license": "MIT",
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
29
40
  "dependencies": {
30
41
  "@modelcontextprotocol/sdk": "^1.0.0",
31
42
  "prompts": "^2.4.2",
@@ -1,4 +1,4 @@
1
- # Step 3: Humanize — Claude Code Instructions
1
+ # Step 3: Humanize — Instructions
2
2
 
3
3
  ## Inputs
4
4
 
package/skills/tfg.md CHANGED
@@ -21,7 +21,7 @@ The user wants to complete one Tokens for Good research cycle.
21
21
 
22
22
  ## If something goes wrong
23
23
 
24
- - **`claim_org` returns an error about an API key** → run `npx tokens-for-good init` in terminal and restart Claude Code.
24
+ - **`claim_org` returns an error about an API key** → run `npx tokens-for-good init` in terminal and restart your AI tool.
25
25
  - **A citation fails verification** → fix it or remove the claim it supported. Don't submit reports with broken citations.
26
26
  - **User interrupts mid-research** → the claim will auto-expire on the server side. No cleanup needed.
27
27
 
package/src/cli.js CHANGED
@@ -1,57 +1,92 @@
1
- #!/usr/bin/env node
2
-
3
- // CLI entry point for tokens-for-good.
4
- // Usage:
5
- // npx tokens-for-good init Interactive first-time setup
6
- // npx tokens-for-good session-start-hook SessionStart hook (used by Claude Code)
7
- // npx tokens-for-good --mcp Start as MCP server (default)
8
- // npx tokens-for-good --status Show project status
9
- // npx tokens-for-good --impact Show your contribution stats
10
-
11
- const args = process.argv.slice(2);
12
- const first = args[0];
13
-
14
- if (first === 'init') {
15
- const { runInit } = await import('./init.js');
16
- await runInit();
17
- } else if (first === 'session-start-hook') {
18
- const { runSessionStartHook } = await import('./session-start-hook.js');
19
- runSessionStartHook();
20
- } else if (args.includes('--status') || first === 'status') {
21
- const { ApiClient } = await import('./api-client.js');
22
- try {
23
- const client = new ApiClient(process.env.TFG_API_KEY || 'public');
24
- const status = await client.getStatus();
25
- console.log('\nTokens for Good - Project Status\n');
26
- console.log(`Total orgs: ${status.total_orgs}`);
27
- console.log(`Pending research: ${status.pending_orgs}`);
28
- console.log(`Active contributors (7d): ${status.active_contributors_7d}`);
29
- console.log('\nQueue:');
30
- for (const [k, v] of Object.entries(status.queue || {})) {
31
- console.log(` ${k}: ${v}`);
32
- }
33
- console.log('\nTop Contributors:');
34
- (status.top_contributors || []).forEach((c, i) => {
35
- console.log(` ${i + 1}. @${c.github_handle} (${c.total_orgs} orgs, ${c.tier})`);
36
- });
37
- } catch (err) {
38
- console.error('Error:', err.message);
39
- }
40
- } else if (args.includes('--impact') || first === 'impact') {
41
- const { ApiClient } = await import('./api-client.js');
42
- try {
43
- const client = new ApiClient(process.env.TFG_API_KEY);
44
- const result = await client.getImpact();
45
- const c = result.contributor;
46
- console.log(`\nYour Impact (@${c.github_handle})\n`);
47
- console.log(`Tier: ${c.tier}`);
48
- console.log(`Orgs researched: ${c.total_orgs}`);
49
- console.log(`Acceptance rate: ${c.acceptance_rate}%`);
50
- console.log(`Automation: ${c.has_schedule ? 'Active' : 'Not set up'}`);
51
- } catch (err) {
52
- console.error('Error:', err.message);
53
- }
54
- } else {
55
- // Default: start MCP server
56
- await import('./mcp-server.js');
57
- }
1
+ #!/usr/bin/env node
2
+
3
+ // CLI entry point for tokens-for-good.
4
+ import { readFileSync } from 'node:fs';
5
+ import { fileURLToPath } from 'node:url';
6
+ import { dirname, join } from 'node:path';
7
+
8
+ const __dirname = dirname(fileURLToPath(import.meta.url));
9
+ const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
10
+
11
+ const HELP = `tokens-for-good v${pkg.version}
12
+ Donate your spare AI tokens to research nonprofits for Fierce Philanthropy.
13
+
14
+ Usage:
15
+ npx tokens-for-good <command> [options]
16
+
17
+ Commands:
18
+ init Interactive first-time setup (API key, causes, cadence)
19
+ session-start-hook Emit the SessionStart hook payload (used by your agent)
20
+ status Show project-wide research stats
21
+ impact Show your own contribution stats (needs TFG_API_KEY)
22
+
23
+ Options:
24
+ --status Same as the "status" command
25
+ --impact Same as the "impact" command
26
+ -v, --version Print the installed version
27
+ -h, --help Show this help
28
+
29
+ With no command (or --mcp), tokens-for-good starts as an MCP server over
30
+ stdio. That is how your editor's MCP client launches it.
31
+
32
+ Docs: https://github.com/Tokens-for-Good/tokens-for-good`;
33
+
34
+ const args = process.argv.slice(2);
35
+ const first = args[0];
36
+
37
+ if (args.includes('-v') || args.includes('--version')) {
38
+ console.log(pkg.version);
39
+ } else if (args.includes('-h') || args.includes('--help') || first === 'help') {
40
+ console.log(HELP);
41
+ } else if (first === 'init') {
42
+ const { runInit } = await import('./init.js');
43
+ await runInit();
44
+ } else if (first === 'session-start-hook') {
45
+ const { runSessionStartHook } = await import('./session-start-hook.js');
46
+ runSessionStartHook();
47
+ } else if (args.includes('--status') || first === 'status') {
48
+ const { ApiClient } = await import('./api-client.js');
49
+ try {
50
+ const client = new ApiClient(process.env.TFG_API_KEY || 'public');
51
+ const status = await client.getStatus();
52
+ const sys = status.system_stats || status;
53
+ console.log('\nTokens for Good - Project Status\n');
54
+ console.log(`Total orgs: ${sys.total_organizations ?? status.total_orgs ?? 'n/a'}`);
55
+ console.log(`Pending research: ${sys.pending_organizations ?? status.pending_orgs ?? 'n/a'}`);
56
+ console.log(`Active contributors (7d): ${sys.active_contributors_7_days ?? status.active_contributors_7d ?? 'n/a'}`);
57
+ console.log('\nQueue:');
58
+ for (const [k, v] of Object.entries(status.queue_status || status.queue || {})) {
59
+ console.log(` ${k}: ${v}`);
60
+ }
61
+ console.log('\nTop Contributors:');
62
+ (status.top_contributors || []).forEach((c, i) => {
63
+ const name = c.display_name || (c.github_handle ? '@' + c.github_handle : 'anonymous');
64
+ console.log(` ${c.rank ?? i + 1}. ${name} (${c.total_organizations ?? c.total_orgs} orgs, ${c.tier})`);
65
+ });
66
+ } catch (err) {
67
+ console.error('Error:', err.message);
68
+ }
69
+ } else if (args.includes('--impact') || first === 'impact') {
70
+ const { ApiClient } = await import('./api-client.js');
71
+ try {
72
+ const client = new ApiClient(process.env.TFG_API_KEY);
73
+ const result = await client.getImpact();
74
+ const c = result.contributor;
75
+ console.log(`\nYour Impact (@${c.github_handle})\n`);
76
+ console.log(`Tier: ${c.tier}`);
77
+ console.log(`Orgs researched: ${c.total_orgs}`);
78
+ console.log(`Acceptance rate: ${c.acceptance_rate}%`);
79
+ console.log(`Automation: ${c.has_schedule ? 'Active' : 'Not set up'}`);
80
+ } catch (err) {
81
+ console.error('Error:', err.message);
82
+ }
83
+ } else if (args.length === 0 || args.includes('--mcp')) {
84
+ // Default: start the MCP server. This is how the editor's MCP client launches us
85
+ // (init writes the config with the --mcp flag).
86
+ await import('./mcp-server.js');
87
+ } else {
88
+ // Unrecognized command/flag: show help instead of silently starting the server.
89
+ console.error(`Unknown command: ${args.join(' ')}\n`);
90
+ console.log(HELP);
91
+ process.exitCode = 1;
92
+ }
package/src/mcp-server.js CHANGED
@@ -17,7 +17,7 @@ const STATE_FILE = join(homedir(), '.tokens-for-good', 'state.json');
17
17
 
18
18
  const INIT_GUARD_MESSAGE = `Tokens for Good setup isn't complete on this machine yet.
19
19
 
20
- Tell the user to run this in their terminal (not in Claude), then restart Claude Code:
20
+ Tell the user to run this in their terminal (not here in the chat), then restart their AI tool:
21
21
 
22
22
  npx tokens-for-good init
23
23
 
@@ -50,7 +50,7 @@ const server = new McpServer({
50
50
 
51
51
  // --- No-key onboarding message ---
52
52
 
53
- const NO_KEY_INSTRUCTIONS = `The user wants to set up Tokens for Good. Tell them to run this in their terminal (not here in Claude), then restart Claude Code:
53
+ const NO_KEY_INSTRUCTIONS = `The user wants to set up Tokens for Good. Tell them to run this in their terminal (not here in the chat), then restart their AI tool:
54
54
 
55
55
  npx tokens-for-good init
56
56
 
@@ -60,7 +60,7 @@ The command walks them through everything in under a minute:
60
60
  3. Pick a cadence: **daily** (recommended), weekly, hourly, or one-off
61
61
  4. Confirm
62
62
 
63
- init writes everything — MCP config, SessionStart hook, /tfg and /tfg-schedule skills, and their recorded preference — in one shot. The first Claude Code session after init runs their chosen flow automatically.
63
+ init writes everything — MCP config, SessionStart hook, /tfg and /tfg-schedule skills, and their recorded preference — in one shot. The first session after init runs their chosen flow automatically.
64
64
 
65
65
  **What is Tokens for Good?** A way for developers to donate their spare AI subscription tokens to research nonprofit organizations for Fierce Philanthropy's social impact directory. Each org takes ~5 minutes and ~$0.20 in tokens. Contributors get credit on a public leaderboard.`;
66
66
 
@@ -225,12 +225,14 @@ server.tool('research_status', 'See the overall Tokens for Good project progress
225
225
  try {
226
226
  const clientForStatus = client || new ApiClient('dummy'); // Status is public
227
227
  const result = await clientForStatus.getStatus();
228
+ const sys = result.system_stats || result;
229
+ const queue = result.queue_status || result.queue || {};
228
230
  const topList = result.top_contributors?.map((c, i) =>
229
- `${i + 1}. @${c.github_handle} (${c.total_orgs} orgs, ${c.tier})`
231
+ `${c.rank ?? i + 1}. ${c.display_name || (c.github_handle ? '@' + c.github_handle : 'anonymous')} (${c.total_organizations ?? c.total_orgs} orgs, ${c.tier})`
230
232
  ).join('\n') || 'No contributors yet';
231
233
 
232
234
  return {
233
- content: [{ type: 'text', text: `Tokens for Good Progress:\n\nTotal orgs: ${result.total_orgs}\nPending research: ${result.pending_orgs}\nActive contributors (7d): ${result.active_contributors_7d}\n\nQueue:\n${Object.entries(result.queue || {}).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n\nTop Contributors:\n${topList}` }],
235
+ content: [{ type: 'text', text: `Tokens for Good Progress:\n\nTotal orgs: ${sys.total_organizations ?? result.total_orgs}\nPending research: ${sys.pending_organizations ?? result.pending_orgs}\nActive contributors (7d): ${sys.active_contributors_7_days ?? result.active_contributors_7d}\n\nQueue:\n${Object.entries(queue).map(([k, v]) => ` ${k}: ${v}`).join('\n')}\n\nTop Contributors:\n${topList}` }],
234
236
  };
235
237
  } catch (err) {
236
238
  return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
@@ -33,7 +33,7 @@ export function runSessionStartHook() {
33
33
  const daysSince = last ? Math.floor((Date.now() - last.getTime()) / 86_400_000) : Infinity;
34
34
  if (daysSince >= 7) {
35
35
  emit(
36
- "Tokens for Good: It's been a while say 'run /tfg' to research one org now, " +
36
+ "Tokens for Good: It's been a while. Say 'run /tfg' to research one org now, " +
37
37
  "or 'run /tfg-schedule daily' to automate it from here on."
38
38
  );
39
39
  }