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 +12 -1
- package/src/cli.js +92 -57
- package/src/mcp-server.js +4 -2
- package/src/session-start-hook.js +1 -1
package/package.json
CHANGED
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokens-for-good",
|
|
3
|
-
"version": "0.4.
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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}.
|
|
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(
|
|
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
|
|
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
|
}
|