tokens-for-good 0.4.6 → 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/package.json CHANGED
@@ -1,8 +1,16 @@
1
1
  {
2
2
  "name": "tokens-for-good",
3
- "version": "0.4.6",
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",
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
@@ -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
  }