tokens-for-good 0.4.3 → 0.4.6
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 +5 -4
- package/package.json +1 -1
- package/pipeline/03-humanize/PROMPT.md +1 -1
- package/skills/tfg.md +1 -1
- package/src/api-client.js +9 -2
- package/src/cli.js +1 -1
- package/src/init.js +44 -21
- package/src/mcp-server.js +356 -356
- package/src/platform.js +15 -2
- package/src/state.js +5 -0
- package/pipeline/_archive/2026-05-07-pre-todd-v2/01-research-PROMPT.md +0 -170
- package/pipeline/_archive/2026-05-07-pre-todd-v2/02-verify-PROMPT.md +0 -69
- package/pipeline/_archive/2026-05-07-pre-todd-v2/03-humanize-PROMPT.md +0 -143
- package/pipeline/_archive/2026-05-07-pre-todd-v2/04-peer-review-PROMPT.md +0 -93
- package/pipeline/_archive/2026-05-07-pre-todd-v2/README.md +0 -11
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# Tokens for Good
|
|
2
2
|
|
|
3
|
-
Donate your spare AI tokens to research nonprofit organizations for [Fierce Philanthropy](https://
|
|
3
|
+
Donate your spare AI tokens to research nonprofit organizations for [Fierce Philanthropy](https://tokensforgood.ai)'s social impact directory. Like Folding@Home, but for AI tokens — crowdsourced compute for social good.
|
|
4
4
|
|
|
5
|
-
Works with Claude Code, OpenCode, Cursor, Windsurf, and
|
|
5
|
+
Works with Claude Code, OpenCode, Cursor, Windsurf, Devin, and Qwen Code as an MCP server.
|
|
6
6
|
|
|
7
7
|
## Quickstart
|
|
8
8
|
|
|
9
|
-
1. **Sign up** at [
|
|
9
|
+
1. **Sign up** at [tokensforgood.ai/contribute](https://tokensforgood.ai/contribute) (GitHub OAuth, free) and copy your API key.
|
|
10
10
|
2. **Run init in your terminal:**
|
|
11
11
|
|
|
12
12
|
```bash
|
|
@@ -15,7 +15,7 @@ Works with Claude Code, OpenCode, Cursor, Windsurf, and Devin as an MCP server.
|
|
|
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
|
|
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
|
|
|
@@ -58,6 +58,7 @@ Once installed, these are available to your AI via the MCP server:
|
|
|
58
58
|
## Non-Claude-Code platforms
|
|
59
59
|
|
|
60
60
|
- **OpenCode** — `init` writes `~/.config/opencode/opencode.json` and prints a cron line you can paste into `crontab -e`.
|
|
61
|
+
- **Qwen Code** — `init` writes `~/.qwen/settings.json` (preserving other keys) plus a `/tfg` slash command at `~/.qwen/commands/tfg.md`. For recurring runs, enable Qwen Code's experimental cron (`QWEN_CODE_ENABLE_CRON=1`) or use a system cron line.
|
|
61
62
|
- **Cursor / Windsurf / Devin** — `init` writes the MCP config; automation requires platform-native scheduling.
|
|
62
63
|
|
|
63
64
|
## Contributing
|
package/package.json
CHANGED
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
|
|
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/api-client.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
// HTTP client for the Fierce Philanthropy coordination API
|
|
2
2
|
|
|
3
|
-
const BASE_URL = process.env.FIERCE_API_URL || 'https://
|
|
3
|
+
const BASE_URL = process.env.FIERCE_API_URL || 'https://tokensforgood.ai/api';
|
|
4
4
|
|
|
5
5
|
export class ApiClient {
|
|
6
6
|
constructor(apiKey) {
|
|
7
7
|
this.apiKey = apiKey;
|
|
8
8
|
if (!apiKey) {
|
|
9
|
-
throw new Error('TFG_API_KEY environment variable is required. Get your key at https://
|
|
9
|
+
throw new Error('TFG_API_KEY environment variable is required. Get your key at https://tokensforgood.ai/contribute');
|
|
10
10
|
}
|
|
11
11
|
}
|
|
12
12
|
|
|
@@ -86,4 +86,11 @@ export class ApiClient {
|
|
|
86
86
|
return this.request('GET', '/research/impact');
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
async getNextAction() {
|
|
90
|
+
return this.request('GET', '/research/next-action');
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async enableSchedule() {
|
|
94
|
+
return this.request('POST', '/research/enable-schedule');
|
|
95
|
+
}
|
|
89
96
|
}
|
package/src/cli.js
CHANGED
package/src/init.js
CHANGED
|
@@ -28,6 +28,7 @@ const PLATFORM_CHOICES = [
|
|
|
28
28
|
{ title: 'Cursor', value: 'cursor' },
|
|
29
29
|
{ title: 'Windsurf', value: 'windsurf' },
|
|
30
30
|
{ title: 'Devin', value: 'devin' },
|
|
31
|
+
{ title: 'Qwen Code', value: 'qwen-code' },
|
|
31
32
|
];
|
|
32
33
|
|
|
33
34
|
const onCancel = () => {
|
|
@@ -57,7 +58,7 @@ export async function runInit() {
|
|
|
57
58
|
const { apiKey } = await prompts({
|
|
58
59
|
type: 'password',
|
|
59
60
|
name: 'apiKey',
|
|
60
|
-
message: 'Paste your TFG API key (get one at https://
|
|
61
|
+
message: 'Paste your TFG API key (get one at https://tokensforgood.ai/contribute):',
|
|
61
62
|
validate: v => /^tfg_(live|test)_/.test((v || '').trim()) || 'Key should start with tfg_live_ or tfg_test_',
|
|
62
63
|
}, { onCancel });
|
|
63
64
|
|
|
@@ -110,6 +111,9 @@ export async function runInit() {
|
|
|
110
111
|
console.log(`✓ ${plans[2].label}`);
|
|
111
112
|
writeSkillFile('tfg');
|
|
112
113
|
console.log(`✓ ${plans[3].label}`);
|
|
114
|
+
} else if (platform === 'qwen-code') {
|
|
115
|
+
writeQwenCommand('tfg');
|
|
116
|
+
console.log(`✓ ${plans[1].label}`);
|
|
113
117
|
}
|
|
114
118
|
|
|
115
119
|
saveState({
|
|
@@ -137,6 +141,8 @@ function planWrites(platform) {
|
|
|
137
141
|
plans.push({ label: `${settingsPath()} (SessionStart hook)` });
|
|
138
142
|
plans.push({ label: `${skillPath('tfg-schedule')} (/tfg-schedule skill)` });
|
|
139
143
|
plans.push({ label: `${skillPath('tfg')} (/tfg skill)` });
|
|
144
|
+
} else if (platform === 'qwen-code') {
|
|
145
|
+
plans.push({ label: `${qwenCommandPath('tfg')} (/tfg slash command)` });
|
|
140
146
|
}
|
|
141
147
|
plans.push({ label: `${statePath()} (recorded choice)` });
|
|
142
148
|
return plans;
|
|
@@ -149,26 +155,13 @@ function homeRelative(abs) {
|
|
|
149
155
|
}
|
|
150
156
|
|
|
151
157
|
function mcpConfigPath(platform) {
|
|
152
|
-
|
|
153
|
-
switch (platform) {
|
|
154
|
-
case 'opencode':
|
|
155
|
-
return join(homedir(), '.config', 'opencode', 'opencode.json');
|
|
156
|
-
case 'cursor':
|
|
157
|
-
return join(process.cwd(), '.cursor', 'mcp.json');
|
|
158
|
-
case 'windsurf':
|
|
159
|
-
return join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
160
|
-
case 'devin':
|
|
161
|
-
case 'claude-code':
|
|
162
|
-
default:
|
|
163
|
-
return join(homedir(), '.mcp.json');
|
|
164
|
-
}
|
|
165
|
-
})();
|
|
166
|
-
return homeRelative(abs);
|
|
158
|
+
return homeRelative(absoluteMcpPath(platform));
|
|
167
159
|
}
|
|
168
160
|
|
|
169
|
-
function settingsPath()
|
|
170
|
-
function skillPath(name)
|
|
171
|
-
function
|
|
161
|
+
function settingsPath() { return homeRelative(join(homedir(), '.claude', 'settings.json')); }
|
|
162
|
+
function skillPath(name) { return homeRelative(join(homedir(), '.claude', 'skills', name, 'SKILL.md')); }
|
|
163
|
+
function qwenCommandPath(name) { return homeRelative(join(homedir(), '.qwen', 'commands', `${name}.md`)); }
|
|
164
|
+
function statePath() { return homeRelative(join(homedir(), '.tokens-for-good', 'state.json')); }
|
|
172
165
|
|
|
173
166
|
// --- File writers ---
|
|
174
167
|
|
|
@@ -228,6 +221,8 @@ function absoluteMcpPath(platform) {
|
|
|
228
221
|
return join(process.cwd(), '.cursor', 'mcp.json');
|
|
229
222
|
case 'windsurf':
|
|
230
223
|
return join(homedir(), '.codeium', 'windsurf', 'mcp_config.json');
|
|
224
|
+
case 'qwen-code':
|
|
225
|
+
return join(homedir(), '.qwen', 'settings.json');
|
|
231
226
|
case 'devin':
|
|
232
227
|
case 'claude-code':
|
|
233
228
|
default:
|
|
@@ -293,6 +288,16 @@ function writeSkillFile(name) {
|
|
|
293
288
|
writeFileSync(dst, readFileSync(src, 'utf-8'), 'utf-8');
|
|
294
289
|
}
|
|
295
290
|
|
|
291
|
+
// Qwen Code uses Gemini CLI's custom-command format: one .md per command at
|
|
292
|
+
// ~/.qwen/commands/<name>.md. YAML frontmatter is supported; the body is the
|
|
293
|
+
// command prompt. Our existing skill body works as-is.
|
|
294
|
+
function writeQwenCommand(name) {
|
|
295
|
+
const src = join(PKG_ROOT, 'skills', `${name}.md`);
|
|
296
|
+
const dst = join(homedir(), '.qwen', 'commands', `${name}.md`);
|
|
297
|
+
ensureDir(dst);
|
|
298
|
+
writeFileSync(dst, readFileSync(src, 'utf-8'), 'utf-8');
|
|
299
|
+
}
|
|
300
|
+
|
|
296
301
|
// --- Closing guidance ---
|
|
297
302
|
|
|
298
303
|
function printClosingGuidance(platform, flow, freq) {
|
|
@@ -309,13 +314,25 @@ function printClosingGuidance(platform, flow, freq) {
|
|
|
309
314
|
if (flow === 'scheduled') {
|
|
310
315
|
console.log(`MCP config written to ${mcpConfigPath('opencode')}.\n`);
|
|
311
316
|
console.log('To run on a schedule, add this to your crontab (crontab -e):');
|
|
312
|
-
|
|
313
|
-
console.log(` ${cron} cd /path/to/workspace && opencode run "Research a nonprofit org for Fierce Philanthropy using the tokens-for-good MCP tools."\n`);
|
|
317
|
+
console.log(` ${cronExpression(freq)} cd /path/to/workspace && opencode run "Research a nonprofit org for Fierce Philanthropy using the tokens-for-good MCP tools."\n`);
|
|
314
318
|
} else {
|
|
315
319
|
console.log('MCP config written. In OpenCode run: "Research a nonprofit org for Fierce Philanthropy."\n');
|
|
316
320
|
}
|
|
317
321
|
return;
|
|
318
322
|
}
|
|
323
|
+
if (platform === 'qwen-code') {
|
|
324
|
+
console.log(`MCP config written to ${mcpConfigPath('qwen-code')}.`);
|
|
325
|
+
console.log(`Slash command written to ${qwenCommandPath('tfg')}.`);
|
|
326
|
+
console.log('Restart Qwen Code, then run `/tfg` to research one org.');
|
|
327
|
+
if (flow === 'scheduled') {
|
|
328
|
+
console.log('\nFor recurring runs, either:');
|
|
329
|
+
console.log(' • Enable Qwen Code\'s experimental cron (set QWEN_CODE_ENABLE_CRON=1 and use the Cron tool / /loop), or');
|
|
330
|
+
console.log(' • Add a system cron line (crontab -e):');
|
|
331
|
+
console.log(` ${cronExpression(freq)} cd /path/to/workspace && qwen --prompt "Run /tfg"`);
|
|
332
|
+
}
|
|
333
|
+
console.log('');
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
319
336
|
// cursor, windsurf, devin
|
|
320
337
|
console.log(`MCP config written to ${mcpConfigPath(platform)}.`);
|
|
321
338
|
console.log(`Restart ${platform} and run: "Research a nonprofit org for Fierce Philanthropy."`);
|
|
@@ -324,3 +341,9 @@ function printClosingGuidance(platform, flow, freq) {
|
|
|
324
341
|
}
|
|
325
342
|
console.log('');
|
|
326
343
|
}
|
|
344
|
+
|
|
345
|
+
function cronExpression(freq) {
|
|
346
|
+
if (freq === 'hourly') return '0 * * * *';
|
|
347
|
+
if (freq === 'weekly') return '0 2 * * 1';
|
|
348
|
+
return '0 2 * * *';
|
|
349
|
+
}
|