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 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://fierce-philanthropy-directory.laravel.cloud)'s social impact directory. Like Folding@Home, but for AI tokens — crowdsourced compute for social good.
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 Devin as an MCP server.
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 [fierce-philanthropy-directory.laravel.cloud/contribute](https://fierce-philanthropy-directory.laravel.cloud/contribute) (GitHub OAuth, free) and copy your API key.
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 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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tokens-for-good",
3
- "version": "0.4.3",
3
+ "version": "0.4.6",
4
4
  "type": "module",
5
5
  "description": "Donate your spare AI tokens to research nonprofits for Fierce Philanthropy",
6
6
  "bin": {
@@ -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/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://fierce-philanthropy-directory.laravel.cloud/api';
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://fierce-philanthropy-directory.laravel.cloud/contribute');
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
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
 
3
3
  // CLI entry point for tokens-for-good.
4
4
  // Usage:
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://fierce-philanthropy-directory.laravel.cloud/contribute):',
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
- const abs = (() => {
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() { return homeRelative(join(homedir(), '.claude', 'settings.json')); }
170
- function skillPath(name) { return homeRelative(join(homedir(), '.claude', 'skills', name, 'SKILL.md')); }
171
- function statePath() { return homeRelative(join(homedir(), '.tokens-for-good', 'state.json')); }
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
- const cron = freq === 'hourly' ? '0 * * * *' : freq === 'weekly' ? '0 2 * * 1' : '0 2 * * *';
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
+ }