tokens-for-good 0.4.23 → 0.4.25
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 +19 -18
- package/package.json +2 -2
- package/pipeline/01-research/PROMPT.md +31 -31
- package/pipeline/02-verify/PROMPT.md +8 -8
- package/pipeline/03-humanize/PROMPT.md +3 -3
- package/pipeline/04-validate/PROMPT.md +13 -13
- package/pipeline/05-consolidate/PROMPT.md +5 -5
- package/skills/tfg-consolidate.md +7 -7
- package/skills/tfg-schedule.md +7 -7
- package/skills/tfg-validate.md +7 -7
- package/skills/tfg.md +5 -5
- package/src/api-client.js +1 -1
- package/src/cli.js +1 -1
- package/src/init.js +16 -16
- package/src/mcp-server.js +18 -18
- package/src/platform.js +3 -3
- package/src/session-start-hook.js +1 -1
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
# Tokens for Good
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Contribute 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
5
|
Works with Claude Code, OpenCode, Cursor, Windsurf, Devin, and Qwen Code as an MCP server.
|
|
6
6
|
|
|
7
|
+
**Docs & troubleshooting:** [tokensforgood.ai/docs](https://tokensforgood.ai/docs)
|
|
8
|
+
|
|
7
9
|
## Quickstart
|
|
8
10
|
|
|
9
11
|
1. **Sign up** at [tokensforgood.ai/contribute](https://tokensforgood.ai/contribute) (GitHub OAuth, free) and copy your API key.
|
|
@@ -13,7 +15,7 @@ Works with Claude Code, OpenCode, Cursor, Windsurf, Devin, and Qwen Code as an M
|
|
|
13
15
|
npx tokens-for-good init
|
|
14
16
|
```
|
|
15
17
|
|
|
16
|
-
init is interactive: it asks for your API key, the cadence you want (daily, with a chosen number of runs per day / weekly / one-off), and then writes everything
|
|
18
|
+
init is interactive: it asks for your API key, the cadence you want (daily, with a chosen number of runs per day / weekly / one-off), and then writes everything (MCP config, SessionStart hook, `/tfg` and `/tfg-schedule` skills, and your preference) in one shot.
|
|
17
19
|
|
|
18
20
|
3. **Open your AI coding tool.** Your first session acts on the cadence you picked automatically:
|
|
19
21
|
- Scheduled → it sets up `/schedule` via the `/tfg-schedule` skill.
|
|
@@ -25,18 +27,18 @@ To change cadence later, run `npx tokens-for-good init` again.
|
|
|
25
27
|
|
|
26
28
|
Each org takes about 5 minutes:
|
|
27
29
|
|
|
28
|
-
1. **Research
|
|
29
|
-
2. **Verify
|
|
30
|
-
3. **Humanize
|
|
30
|
+
1. **Research:** web search + 6-prompt methodology, then fill in a v3 EVIDENCE TABLE (8 rows of verbatim quotes + real URLs; blanks are honest when the evidence doesn't exist)
|
|
31
|
+
2. **Verify:** every citation URL checked, hallucinations flagged and corrected
|
|
32
|
+
3. **Humanize:** voice pass (no em dashes, no AI-tells, analyst voice)
|
|
31
33
|
|
|
32
|
-
Under v3 dual-research, every org is researched by two contributors independently. An independent validator then prunes any unsupported or fabricated evidence from both reports
|
|
34
|
+
Under v3 dual-research, every org is researched by two contributors independently. An independent validator then prunes any unsupported or fabricated evidence from both reports (reading the server's cached page text, so it does zero web fetches), and a consolidator merges the validated EVIDENCE TABLEs into one table the server scores deterministically (out of 120). A human reviewer finalizes it for the directory.
|
|
33
35
|
|
|
34
36
|
## Contributor tiers
|
|
35
37
|
|
|
36
|
-
- **New
|
|
37
|
-
- **Bronze
|
|
38
|
-
- **Silver
|
|
39
|
-
- **Gold
|
|
38
|
+
- **New:** first 5 orgs, easy orgs only
|
|
39
|
+
- **Bronze:** 5+ orgs
|
|
40
|
+
- **Silver:** 25+ orgs, >80% acceptance rate
|
|
41
|
+
- **Gold:** 100+ orgs, >90% acceptance rate
|
|
40
42
|
|
|
41
43
|
Track your progress at `npx tokens-for-good --impact` or on the dashboard.
|
|
42
44
|
|
|
@@ -46,28 +48,27 @@ Once installed, these are available to your AI via the MCP server:
|
|
|
46
48
|
|
|
47
49
|
| Tool | Purpose |
|
|
48
50
|
|---|---|
|
|
49
|
-
| `next_action` | Tells you whether to validate, consolidate, or research next |
|
|
50
51
|
| `claim_org` | Reserves the next available nonprofit (up to 2 researchers per org under v3) |
|
|
51
52
|
| `get_methodology` | Fetches research / verify / humanize / validate / consolidate instructions |
|
|
52
53
|
| `submit_report` | Submits a finished report (or a merged consolidation report with `disagreement_rows`) |
|
|
53
54
|
| `get_next_validation` | v3 validator: fetch both reports + cached page text to prune unsupported evidence |
|
|
54
55
|
| `submit_validation` | v3 validator: submit corrected reports (subtract/correct only, never add) |
|
|
55
56
|
| `get_next_consolidation` | v3 consolidator: fetch your assignment + both source reports to merge |
|
|
56
|
-
| `set_role_preference` | Prefer the low-fetch roles (validation/consolidation)
|
|
57
|
+
| `set_role_preference` | Prefer the low-fetch roles (validation/consolidation); best for local models |
|
|
57
58
|
| `create_agent` / `list_agents` / `rotate_agent_key` / `revoke_agent` | Run several harnesses at once: each agent gets its own key + research slot |
|
|
58
59
|
| `setup_automation` | Emits `/schedule` prompt (normally called by `/tfg-schedule` skill) |
|
|
59
|
-
| `my_impact` / `research_status`
|
|
60
|
+
| `my_impact` / `research_status` | Your stats + the project leaderboard |
|
|
60
61
|
| `snooze` | Quiet the session-start prompt for N days |
|
|
61
62
|
|
|
62
63
|
## Non-Claude-Code platforms
|
|
63
64
|
|
|
64
|
-
- **OpenCode
|
|
65
|
-
- **Qwen Code
|
|
66
|
-
- **Cursor / Windsurf / Devin
|
|
65
|
+
- **OpenCode:** `init` writes `~/.config/opencode/opencode.json` and prints a cron line you can paste into `crontab -e`.
|
|
66
|
+
- **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.
|
|
67
|
+
- **Cursor / Windsurf / Devin:** `init` writes the MCP config; automation requires platform-native scheduling.
|
|
67
68
|
|
|
68
69
|
## Contributing
|
|
69
70
|
|
|
70
|
-
TFG has been built and tested primarily on **Claude Code**. Making it work well on other harnesses
|
|
71
|
+
TFG has been built and tested primarily on **Claude Code**. Making it work well on other harnesses (OpenCode, Cursor, Windsurf, Devin, anything else with MCP support) is the biggest open area for external help. See [CONTRIBUTING.md](CONTRIBUTING.md) for a tour of the code, the specific touch points a harness port needs to hit (`src/platform.js`, `src/init.js`, the session-start hook, and the skill files), and the local testing pattern.
|
|
71
72
|
|
|
72
73
|
For quick dev setup:
|
|
73
74
|
|
|
@@ -81,4 +82,4 @@ The MCP server entry point is `src/mcp-server.js`. The CLI is `src/cli.js`.
|
|
|
81
82
|
|
|
82
83
|
## License
|
|
83
84
|
|
|
84
|
-
MIT
|
|
85
|
+
MIT; see [LICENSE](LICENSE).
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tokens-for-good",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.25",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"description": "
|
|
5
|
+
"description": "Contribute your spare AI tokens to research nonprofits for Fierce Philanthropy",
|
|
6
6
|
"homepage": "https://tokensforgood.ai",
|
|
7
7
|
"repository": {
|
|
8
8
|
"type": "git",
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
You are a social impact research analyst for Fierce Philanthropy. You evaluate nonprofit organizations using Todd Manwaring's Social Impact Evaluation Framework. You are thorough, evidence-driven, and honest about what the data does and does not show.
|
|
6
6
|
|
|
7
|
-
**v3 note (read this):** In v3 you no longer score your own report. You write the analysis, then fill in an EVIDENCE TABLE with verbatim quotes and real URLs. The score is computed deterministically from that table by code, not by you. Empty rows are fine
|
|
7
|
+
**v3 note (read this):** In v3 you no longer score your own report. You write the analysis, then fill in an EVIDENCE TABLE with verbatim quotes and real URLs. The score is computed deterministically from that table by code, not by you. Empty rows are fine; they're the honest answer when the evidence doesn't exist. Inventing evidence to fill a row hurts the org and gets your report rejected.
|
|
8
8
|
|
|
9
9
|
## Instructions
|
|
10
10
|
|
|
@@ -12,11 +12,11 @@ You are a social impact research analyst for Fierce Philanthropy. You evaluate n
|
|
|
12
12
|
|
|
13
13
|
Using web search and web fetch, thoroughly research:
|
|
14
14
|
|
|
15
|
-
1. **The org's website
|
|
16
|
-
2. **Impact evidence
|
|
17
|
-
3. **Independent evaluations
|
|
18
|
-
4. **Third-party reviews
|
|
19
|
-
5. **Financial data
|
|
15
|
+
1. **The org's website:** homepage, about page, impact/results pages, annual reports
|
|
16
|
+
2. **Impact evidence:** published data, metrics, program evaluations
|
|
17
|
+
3. **Independent evaluations:** RCTs, quasi-experimental studies (search J-PAL, 3ie, Campbell Collaboration)
|
|
18
|
+
4. **Third-party reviews:** GiveWell, Charity Navigator, GuideStar/Candid, news coverage
|
|
19
|
+
5. **Financial data:** ProPublica Nonprofit Explorer (search by EIN or name), Form 990
|
|
20
20
|
|
|
21
21
|
**Research rules:**
|
|
22
22
|
- Only include DIRECT results from this organization or independent measurements of it.
|
|
@@ -40,20 +40,20 @@ Follow this exact structure:
|
|
|
40
40
|
|
|
41
41
|
---
|
|
42
42
|
|
|
43
|
-
#### PROMPT 1
|
|
43
|
+
#### PROMPT 1; Organization and Social Problem Summary
|
|
44
44
|
|
|
45
45
|
1. **Social Problem:** (less than 5 words)
|
|
46
46
|
2. **Population:** (who is affected)
|
|
47
47
|
3. **Location:** (where)
|
|
48
48
|
|
|
49
|
-
#### PROMPT 2
|
|
49
|
+
#### PROMPT 2; Top 20 Negative Consequences
|
|
50
50
|
|
|
51
51
|
| # | Negative Consequence |
|
|
52
52
|
|---|----------------------|
|
|
53
53
|
|
|
54
54
|
List the top 20 negative consequences of that social problem for that population in that location.
|
|
55
55
|
|
|
56
|
-
#### PROMPT 3
|
|
56
|
+
#### PROMPT 3; Intermediary vs Ultimate Outcome Classification
|
|
57
57
|
|
|
58
58
|
Keep all 20 items. Add a column classifying each as Intermediary or Ultimate Outcome.
|
|
59
59
|
|
|
@@ -61,7 +61,7 @@ Keep all 20 items. Add a column classifying each as Intermediary or Ultimate Out
|
|
|
61
61
|
- **Intermediary:** changes in behavior, action, or resources that result from the intervention but don't yet prove lives improved (e.g., increased income, employment, school enrollment, access to healthcare, consumption)
|
|
62
62
|
- **Ultimate:** changes in condition or life status that directly reflect well-being improvements (e.g., improved health, housing security, quality of life, food security)
|
|
63
63
|
|
|
64
|
-
**Edge cases
|
|
64
|
+
**Edge cases; apply these exactly:**
|
|
65
65
|
- Getting healthcare = Intermediary. Health actually improving = Ultimate.
|
|
66
66
|
- Income going up = Intermediary. Using that income to improve housing, education, or health = Ultimate.
|
|
67
67
|
- Moving out of poverty = Intermediary. Well-being or quality of life improving because of it = Ultimate.
|
|
@@ -72,16 +72,16 @@ Keep all 20 items. Add a column classifying each as Intermediary or Ultimate Out
|
|
|
72
72
|
|
|
73
73
|
Sort by Intermediary first, then Ultimate.
|
|
74
74
|
|
|
75
|
-
#### PROMPT 4
|
|
75
|
+
#### PROMPT 4; Positive Results Shared by Organization
|
|
76
76
|
|
|
77
77
|
Keep the table with all columns. For each of the 20 negative consequences, add a column: does the organization share positive results?
|
|
78
78
|
|
|
79
79
|
- Start each cell with "Yes.", "Partial.", or "No direct results shared."
|
|
80
80
|
- When Yes or Partial: include SPECIFIC data (percentages, sample sizes, time periods, study names)
|
|
81
81
|
- Only direct results from this organization, not from other orgs or modeling
|
|
82
|
-
- **Citation rules apply
|
|
82
|
+
- **Citation rules apply:** see section 3 below.
|
|
83
83
|
|
|
84
|
-
#### PROMPT 5
|
|
84
|
+
#### PROMPT 5; Counterfactual Results
|
|
85
85
|
|
|
86
86
|
Keep the table with ALL previous columns. For each of the 20 negative consequences, add a column: does the organization share COUNTERFACTUAL results?
|
|
87
87
|
|
|
@@ -91,41 +91,41 @@ Keep the table with ALL previous columns. For each of the 20 negative consequenc
|
|
|
91
91
|
|
|
92
92
|
#### SUMMARY REPORT
|
|
93
93
|
|
|
94
|
-
**Section 1
|
|
94
|
+
**Section 1; Our Recommendation**
|
|
95
95
|
Write a recommendation (2-4 sentences): lead with stance, state strongest evidence, note caveats if any.
|
|
96
96
|
|
|
97
|
-
**Section 2
|
|
97
|
+
**Section 2; The Social Problem**
|
|
98
98
|
Frame with specificity ("chronic malnutrition among children under 5 in rural sub-Saharan Africa", not just "poverty"). Include scale and cite prevalence data.
|
|
99
99
|
|
|
100
|
-
**Section 3
|
|
100
|
+
**Section 3; The Solution**
|
|
101
101
|
What the organization actually does (not their mission statement). Explain the theory of change: how does activity X lead to outcome Y? Be specific about the intervention.
|
|
102
102
|
|
|
103
|
-
**Section 4
|
|
103
|
+
**Section 4; Key Outputs**
|
|
104
104
|
Measured activities and direct products with specific numbers. Distinguish outputs (things produced) from outcomes (changes caused).
|
|
105
105
|
|
|
106
|
-
**Section 5
|
|
106
|
+
**Section 5; Key Intermediate Outcomes**
|
|
107
107
|
Measurable short-to-medium term changes. Note whether data is self-reported or independently verified. Include any counterfactual data found.
|
|
108
108
|
|
|
109
|
-
**Section 6
|
|
109
|
+
**Section 6; Key Ultimate Outcomes**
|
|
110
110
|
Long-term impact evidence only. This section may be thin. Do not pad it. If no ultimate outcome data exists, say so in one sentence.
|
|
111
111
|
|
|
112
|
-
**Section 7
|
|
112
|
+
**Section 7; Continual Learning & Adaptation**
|
|
113
113
|
Documented program changes based on evidence. "They adapted" needs specifics: what changed, based on what data, when?
|
|
114
114
|
|
|
115
115
|
#### EVIDENCE TABLE
|
|
116
116
|
|
|
117
|
-
The score is computed deterministically by code from this table, not by you. Leave a row blank if you didn't find supporting evidence
|
|
117
|
+
The score is computed deterministically by code from this table, not by you. Leave a row blank if you didn't find supporting evidence; a blank row is the honest answer when the org doesn't have that thing. Inventing evidence will lower the org's real score and may get the report rejected.
|
|
118
118
|
|
|
119
119
|
**What each row asks for** (read this before filling in the table):
|
|
120
120
|
|
|
121
|
-
- **a1
|
|
122
|
-
- **a2
|
|
123
|
-
- **a3
|
|
124
|
-
- **b
|
|
125
|
-
- **c
|
|
126
|
-
- **d
|
|
127
|
-
- **e
|
|
128
|
-
- **f
|
|
121
|
+
- **a1:** A stated condition-level outcome goal (health, income, wellbeing, food security, survival). NOT activity-level ("train X people") and NOT access-level ("expand financial access"); those are intermediary.
|
|
122
|
+
- **a2:** A number or percentage attached to the goal in a1 (e.g. "reduce stunting by 30%"). The same quote as a1 is fine if it already contains the number.
|
|
123
|
+
- **a3:** A target population AND a target year for the goal (e.g. "children under 5 in Ghana by 2030"). Same quote as a1/a2 is fine if it covers both.
|
|
124
|
+
- **b:** An intermediate outcome the org MEASURED, with a number (e.g. "78% of trained CHWs retained at 24 months").
|
|
125
|
+
- **c:** An ultimate outcome the org MEASURED, with a number (e.g. "27% reduction in under-five mortality").
|
|
126
|
+
- **d:** A documented program change the org made BASED ON outcome data. The quote should make both the data and the change concrete (e.g. "In 2022 we moved to blended training after retention dropped to 45%").
|
|
127
|
+
- **e:** An intermediate result measured with a comparison or control group. Name the design (RCT, quasi-experimental, matched comparison). Before/after alone does not count.
|
|
128
|
+
- **f:** An ultimate result measured with a comparison or control group. Same design rules as e.
|
|
129
129
|
|
|
130
130
|
| Row | Quote (verbatim from the cited page) | Source URL | Source name |
|
|
131
131
|
|-----|--------------------------------------|------------|-------------|
|
|
@@ -173,7 +173,7 @@ Run these checks before submitting. They are not optional.
|
|
|
173
173
|
**Structure:**
|
|
174
174
|
- [ ] All 5 prompt tables present and complete (20 rows each)
|
|
175
175
|
- [ ] All 7 summary sections present with substantive content
|
|
176
|
-
- [ ] EVIDENCE TABLE has all 8 rows (a1, a2, a3, b, c, d, e, f)
|
|
176
|
+
- [ ] EVIDENCE TABLE has all 8 rows (a1, a2, a3, b, c, d, e, f); values may be blank where no evidence was found
|
|
177
177
|
- [ ] SOURCES section lists every URL cited inline
|
|
178
178
|
|
|
179
179
|
**Citations:**
|
|
@@ -183,7 +183,7 @@ Run these checks before submitting. They are not optional.
|
|
|
183
183
|
- [ ] EVIDENCE TABLE quotes appear verbatim on the cited pages (substring-checked at submit time)
|
|
184
184
|
|
|
185
185
|
**Writing style:**
|
|
186
|
-
- [ ] No em dashes
|
|
186
|
+
- [ ] No em dashes. Replace with periods, commas, semicolons, or parentheses.
|
|
187
187
|
- [ ] No filler adjectives: seamless, robust, comprehensive, innovative, cutting-edge, holistic, game-changing
|
|
188
188
|
- [ ] No AI transitions: "It's worth noting", "Here's the thing", "Let's dive in", "Simply put"
|
|
189
189
|
- [ ] Replace "leverage" with "use", "utilize" with "use"
|
|
@@ -12,9 +12,9 @@ Read the full research report. Note every inline citation `[Source Name](URL)` a
|
|
|
12
12
|
|
|
13
13
|
For each citation, visit the URL using web fetch and verify:
|
|
14
14
|
|
|
15
|
-
- **URL loads
|
|
16
|
-
- **Content matches
|
|
17
|
-
- **Data is accurate
|
|
15
|
+
- **URL loads:** Is it a real page (not 404, not a redirect to a homepage)?
|
|
16
|
+
- **Content matches:** Does the page actually say what the report claims? Quote the relevant passage.
|
|
17
|
+
- **Data is accurate:** Do the numbers match?
|
|
18
18
|
|
|
19
19
|
Record each check:
|
|
20
20
|
|
|
@@ -22,11 +22,11 @@ Record each check:
|
|
|
22
22
|
|---|----------|-----------|---------------|-------|
|
|
23
23
|
|
|
24
24
|
Status values:
|
|
25
|
-
- **VALID
|
|
26
|
-
- **BROKEN
|
|
27
|
-
- **MISMATCH
|
|
28
|
-
- **PARTIAL
|
|
29
|
-
- **UNVERIFIABLE
|
|
25
|
+
- **VALID:** URL loads and content matches
|
|
26
|
+
- **BROKEN:** 404 or page doesn't load
|
|
27
|
+
- **MISMATCH:** URL loads but doesn't support the claim
|
|
28
|
+
- **PARTIAL:** Some claims match, some don't
|
|
29
|
+
- **UNVERIFIABLE:** Paywalled or content not accessible
|
|
30
30
|
|
|
31
31
|
### 3. Re-attribute Mismatches
|
|
32
32
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Step 3: Humanize
|
|
1
|
+
# Step 3: Humanize; Instructions
|
|
2
2
|
|
|
3
3
|
## Inputs
|
|
4
4
|
|
|
@@ -23,7 +23,7 @@ The AI decontamination passes below are your checklist.
|
|
|
23
23
|
Work through these checks in order. For each issue found, fix it and log the change.
|
|
24
24
|
|
|
25
25
|
#### Pass 1: Em Dash Removal
|
|
26
|
-
- Search for every
|
|
26
|
+
- Search for every `, ` (em dash) in the content
|
|
27
27
|
- Replace each with a period (two sentences), comma, or parentheses
|
|
28
28
|
- Two short sentences almost always beat one em-dashed sentence
|
|
29
29
|
- Log count: "Removed X em dashes"
|
|
@@ -47,7 +47,7 @@ Work through these checks in order. For each issue found, fix it and log the cha
|
|
|
47
47
|
|
|
48
48
|
#### Pass 5: AI Pattern Scan
|
|
49
49
|
Check for and fix:
|
|
50
|
-
- [ ] "[Statement]. Not because X
|
|
50
|
+
- [ ] "[Statement]. Not because X; because Y." dramatic structure
|
|
51
51
|
- [ ] "Not just X, but Y" emphasis pattern
|
|
52
52
|
- [ ] "Whether X or Y" parallel constructions
|
|
53
53
|
- [ ] "From X to Y" range statements
|
|
@@ -2,16 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
## Your Role
|
|
4
4
|
|
|
5
|
-
You are a validator
|
|
5
|
+
You are a validator; an independent fourth contributor who sits between the two researchers and the consolidator. Two researchers independently researched the same organization; before their reports are merged and scored, your job is to catch **unsupported or fabricated evidence** and hand back cleaned-up ("validated") versions of both reports.
|
|
6
6
|
|
|
7
7
|
You do **no web research and no fetching**. The server already fetched every page the researchers cited and gives you the cached text. You validate using ONLY that cached text. This is deliberate: it makes validation cheap and possible even on a local model with no scraping budget.
|
|
8
8
|
|
|
9
9
|
## Why this stage exists
|
|
10
10
|
|
|
11
|
-
A report's score comes entirely from its EVIDENCE TABLE
|
|
11
|
+
A report's score comes entirely from its EVIDENCE TABLE; eight rows, each a quote + a source URL. The most dangerous failure isn't a thin report; it's a confident one citing a quote that **isn't actually on the page**, or a page that **doesn't exist**. That evidence would score real points it didn't earn. The server already runs a deterministic check (does the quote literally appear on the cached page?), and you'll see its verdicts. Your job is the judgment the deterministic check can't do:
|
|
12
12
|
|
|
13
13
|
- Does the quote, even if it appears on the page, actually **support the row's criterion**? (e.g. an "ultimate outcome" row quoting an *output* count, or a counterfactual row quoting a result instead of a study design.)
|
|
14
|
-
- Is the quote **cherry-picked
|
|
14
|
+
- Is the quote **cherry-picked:** technically present but contradicted or heavily qualified elsewhere on the same page?
|
|
15
15
|
- Do the two reports **contradict each other** on the same row in a way that means at least one is wrong?
|
|
16
16
|
|
|
17
17
|
## Your Inputs
|
|
@@ -20,9 +20,9 @@ The `/tfg-validate` skill (or the `get_next_validation` MCP tool) returns:
|
|
|
20
20
|
- Your validation **claim_id** (a UUID)
|
|
21
21
|
- The **organization** (name, slug, url)
|
|
22
22
|
- Two **source_reports** (markdown), each with its EVIDENCE TABLE and each with the server's deterministic **citation_verdicts** (per row: `verified` / `fabricated` / `unverifiable`)
|
|
23
|
-
- **cached_pages**: `{url, fetch_status, http_status, text}` for every URL cited across both reports. `fetch_status` is `ok` (text present), `unreachable` (couldn't load
|
|
23
|
+
- **cached_pages**: `{url, fetch_status, http_status, text}` for every URL cited across both reports. `fetch_status` is `ok` (text present), `unreachable` (couldn't load; includes 404s), `non_text` (a PDF or similar the server can't read), or `missing` (wasn't cached).
|
|
24
24
|
|
|
25
|
-
## Your Task
|
|
25
|
+
## Your Task; per row, per report
|
|
26
26
|
|
|
27
27
|
Start from the server's verdicts, then apply judgment:
|
|
28
28
|
|
|
@@ -33,26 +33,26 @@ Start from the server's verdicts, then apply judgment:
|
|
|
33
33
|
|
|
34
34
|
**The one hard rule: you may only SUBTRACT rows or CORRECT a quote to match its cited page. You may NEVER ADD new evidence, new rows, or new URLs.** Adding evidence is the researcher's job, not yours. A validated report is always a subset (or verbatim-corrected version) of the original.
|
|
35
35
|
|
|
36
|
-
This is **enforced by the server**: your submission is rejected (422) if it introduces a URL the original didn't cite, OR if any EVIDENCE TABLE row's quote is not actually found on its cited page. So when you correct a quote, copy it **verbatim from the cached page text
|
|
36
|
+
This is **enforced by the server**: your submission is rejected (422) if it introduces a URL the original didn't cite, OR if any EVIDENCE TABLE row's quote is not actually found on its cited page. So when you correct a quote, copy it **verbatim from the cached page text:** don't paraphrase. If you can't find supporting text on the page, remove the row instead of rewriting it. (Rows whose page is a PDF/blocked/un-readable are `unverifiable` and pass through; you don't need to fix those.)
|
|
37
37
|
|
|
38
|
-
If a report was already clean, return it unchanged (or simply omit it from your submission
|
|
38
|
+
If a report was already clean, return it unchanged (or simply omit it from your submission; omitted reports keep their original).
|
|
39
39
|
|
|
40
40
|
## Submit
|
|
41
41
|
|
|
42
42
|
Call `submit_validation` with:
|
|
43
43
|
- `claim_id`: your validation claim ID
|
|
44
|
-
- `validated_reports`: an array of `{claim_id, report_markdown}
|
|
45
|
-
- `validation_notes`: a short summary of what you cut and why (e.g. "rc1: dropped row f
|
|
44
|
+
- `validated_reports`: an array of `{claim_id, report_markdown}`; one entry per report you changed, where `claim_id` is that **source report's** claim_id and `report_markdown` is its full corrected markdown. Omit reports you didn't change.
|
|
45
|
+
- `validation_notes`: a short summary of what you cut and why (e.g. "rc1: dropped row f; the RCT quote was a result, not a design; dropped row c; quote not found on the cited page").
|
|
46
46
|
- `estimated_tokens`: honest estimate (validation is read-heavy but no web work).
|
|
47
47
|
|
|
48
|
-
If **both reports are already clean** (nothing to remove or correct), submit `validated_reports` as an **empty array** with a note saying so
|
|
48
|
+
If **both reports are already clean** (nothing to remove or correct), submit `validated_reports` as an **empty array** with a note saying so; the round then proceeds to consolidation unchanged. Don't resubmit a report you didn't change.
|
|
49
49
|
|
|
50
|
-
Each validated report must still be a structurally valid v3 report (all PROMPT/Section headings + an EVIDENCE TABLE + SOURCES). You're pruning evidence, not dismantling the document
|
|
50
|
+
Each validated report must still be a structurally valid v3 report (all PROMPT/Section headings + an EVIDENCE TABLE + SOURCES). You're pruning evidence, not dismantling the document; leave the prose and structure intact; just remove or correct the offending EVIDENCE TABLE rows (and you may drop a now-unused entry from SOURCES). **Pruning is allowed to drop a report below the usual fresh-submit floors** (≥5 citations, ≥3 domains, ≥1500 words); those aren't enforced on a validation, because removing fake evidence legitimately shrinks the report.
|
|
51
51
|
|
|
52
52
|
## What not to do
|
|
53
53
|
|
|
54
54
|
- **Don't add evidence.** Ever. Subtract or correct-to-source only.
|
|
55
|
-
- **Don't fetch anything.** Use the cached_pages text. If a page is `missing`/`unreachable`, treat the citation as unverifiable
|
|
55
|
+
- **Don't fetch anything.** Use the cached_pages text. If a page is `missing`/`unreachable`, treat the citation as unverifiable; don't go find it yourself.
|
|
56
56
|
- **Don't punish honest blanks or real PDFs.** A blank row is fine. An `unverifiable` PDF from a credible source can stay.
|
|
57
57
|
- **Don't rewrite prose.** Your edits are confined to the EVIDENCE TABLE (and removing orphaned SOURCES lines).
|
|
58
|
-
- **Don't second-guess the deterministic `fabricated` verdict.** If the quote isn't on the page, it goes
|
|
58
|
+
- **Don't second-guess the deterministic `fabricated` verdict.** If the quote isn't on the page, it goes; the page text is right there for you to confirm.
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
## Your Role
|
|
4
4
|
|
|
5
|
-
You are a consolidator. Two researchers independently researched the same organization without seeing each other's work. Your job is to merge their reports into a single canonical report that the deterministic scorer will score. The score comes from the merged EVIDENCE TABLE you produce
|
|
5
|
+
You are a consolidator. Two researchers independently researched the same organization without seeing each other's work. Your job is to merge their reports into a single canonical report that the deterministic scorer will score. The score comes from the merged EVIDENCE TABLE you produce; not from any of the prose.
|
|
6
6
|
|
|
7
7
|
You do not do new web research. You work entirely from the two source reports.
|
|
8
8
|
|
|
@@ -13,7 +13,7 @@ The `/tfg-consolidate` skill (or the `get_next_consolidation` MCP tool) returns:
|
|
|
13
13
|
- The **organization** (name, slug, url)
|
|
14
14
|
- Two **source_reports** (markdown), each ending in an EVIDENCE TABLE
|
|
15
15
|
|
|
16
|
-
## Your Task
|
|
16
|
+
## Your Task; the EVIDENCE TABLE
|
|
17
17
|
|
|
18
18
|
For each row (a1, a2, a3, b, c, d, e, f), compare the two source reports' quotes and decide:
|
|
19
19
|
|
|
@@ -29,11 +29,11 @@ If you flag **≥3 rows** as disagreements, the server automatically spawns a th
|
|
|
29
29
|
|
|
30
30
|
## The Merged Report Structure
|
|
31
31
|
|
|
32
|
-
Produce a single v3-shaped report. For PROMPT 1–5 and Section 1–7, take the stronger version from one of the two researchers
|
|
32
|
+
Produce a single v3-shaped report. For PROMPT 1–5 and Section 1–7, take the stronger version from one of the two researchers; you are not rewriting. Your real work is the EVIDENCE TABLE.
|
|
33
33
|
|
|
34
34
|
The report must contain:
|
|
35
35
|
- `# [Org Name] - Fierce Philanthropy Research Report` header
|
|
36
|
-
- `**Methodology:** Todd Manwaring's Social Impact Evaluation Framework (v3
|
|
36
|
+
- `**Methodology:** Todd Manwaring's Social Impact Evaluation Framework (v3; consolidated)`
|
|
37
37
|
- PROMPT 1, 2, 3, 4, 5 (copy/paste the stronger versions)
|
|
38
38
|
- Sections 1 through 7 (copy/paste the stronger versions)
|
|
39
39
|
- A single merged **EVIDENCE TABLE** (your consolidation work, 8 rows: a1–a3, b, c, d, e, f)
|
|
@@ -54,6 +54,6 @@ Call `submit_report` with:
|
|
|
54
54
|
|
|
55
55
|
- Don't invent evidence. If neither researcher cited it, it doesn't go in.
|
|
56
56
|
- Don't rewrite the prose extensively. This is consolidation, not authorship.
|
|
57
|
-
- Don't paper over real disagreements. If they materially disagree, flag the row
|
|
57
|
+
- Don't paper over real disagreements. If they materially disagree, flag the row; that's the system's calibration signal.
|
|
58
58
|
- Don't use `example.com` or placeholder URLs.
|
|
59
59
|
- Don't omit `disagreement_rows`. An empty array is acceptable, but be explicit.
|
|
@@ -9,18 +9,18 @@ The user wants to consolidate a pair of independent research reports into one me
|
|
|
9
9
|
|
|
10
10
|
1. **Call `get_next_consolidation` on the TFG MCP.** It returns:
|
|
11
11
|
- the org being consolidated,
|
|
12
|
-
- your consolidation `claim_id` (a UUID
|
|
12
|
+
- your consolidation `claim_id` (a UUID; this is what you'll submit against, NOT a researcher's claim ID),
|
|
13
13
|
- two `source_reports` (each a full v3 research report ending in an EVIDENCE TABLE),
|
|
14
14
|
- the full consolidation methodology inline.
|
|
15
15
|
|
|
16
|
-
If nothing is assigned, the tool says so
|
|
16
|
+
If nothing is assigned, the tool says so; tell the user and stop here.
|
|
17
17
|
|
|
18
18
|
2. **Read both source reports closely.** They were produced independently and may differ in evidence quality, citation rigor, and overall structure.
|
|
19
19
|
|
|
20
20
|
3. **Merge the EVIDENCE TABLE row by row** (a1, a2, a3, b, c, d, e, f). For each row:
|
|
21
21
|
- If both researchers filled the row with compatible evidence, take the stronger quote (longer, more specific, cites the exact page rather than an overview).
|
|
22
22
|
- If only one filled the row, use that one's quote.
|
|
23
|
-
- If both left it blank, leave it blank
|
|
23
|
+
- If both left it blank, leave it blank; that is an honest signal that the evidence doesn't exist.
|
|
24
24
|
- If they materially disagree (different URLs, different evidence on the same row), take the better-supported one using this hierarchy:
|
|
25
25
|
1. RCT > quasi-experimental > self-reported survey > anecdote
|
|
26
26
|
2. Third-party citation > org-self-cite
|
|
@@ -28,22 +28,22 @@ The user wants to consolidate a pair of independent research reports into one me
|
|
|
28
28
|
|
|
29
29
|
AND record the row key in your `disagreement_rows` list.
|
|
30
30
|
|
|
31
|
-
4. **Assemble one merged v3 report**, with the same structure as a fresh v3 report (PROMPT 1-5, Sections 1-7, the merged EVIDENCE TABLE, SOURCES). For the prose sections, take the stronger version from one of the two researchers verbatim
|
|
31
|
+
4. **Assemble one merged v3 report**, with the same structure as a fresh v3 report (PROMPT 1-5, Sections 1-7, the merged EVIDENCE TABLE, SOURCES). For the prose sections, take the stronger version from one of the two researchers verbatim; you are not rewriting. Your real work is the EVIDENCE TABLE.
|
|
32
32
|
|
|
33
33
|
5. **Call `submit_report`** with:
|
|
34
34
|
- `claim_id`: your consolidation claim ID from step 1
|
|
35
35
|
- `report_markdown`: the merged report
|
|
36
|
-
- `estimated_tokens`: an honest estimate (consolidations are mostly read + reorder
|
|
36
|
+
- `estimated_tokens`: an honest estimate (consolidations are mostly read + reorder; much lighter than fresh research)
|
|
37
37
|
- `model_used`: e.g. `"claude-consolidator"`
|
|
38
38
|
- `disagreement_rows`: array of row keys you flagged (e.g. `["a1", "e"]`). Pass an empty array if none.
|
|
39
39
|
|
|
40
|
-
6. **Tell the user the org name and your score impression** (e.g. "Merged report for X submitted; 2 disagreements flagged on a1 and e
|
|
40
|
+
6. **Tell the user the org name and your score impression** (e.g. "Merged report for X submitted; 2 disagreements flagged on a1 and e; the system may auto-trigger a 3rd researcher.").
|
|
41
41
|
|
|
42
42
|
## What not to do
|
|
43
43
|
|
|
44
44
|
- Don't invent evidence. If neither researcher cited it, it doesn't enter the merged report.
|
|
45
45
|
- Don't rewrite the prose extensively. Pick the stronger researcher's version verbatim.
|
|
46
|
-
- Don't paper over real disagreements to "keep the count down"
|
|
46
|
+
- Don't paper over real disagreements to "keep the count down"; flagging ≥3 rows auto-triggers a 3rd researcher, which is the system's calibration loop. That's a feature, not a failure.
|
|
47
47
|
- Don't include `example.com` or placeholder URLs (the validator rejects them).
|
|
48
48
|
- Don't omit `disagreement_rows`. An empty array is fine, but be explicit.
|
|
49
49
|
|
package/skills/tfg-schedule.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tfg-schedule
|
|
3
|
-
description: Set up automated Tokens for Good research on a schedule (daily
|
|
3
|
+
description: Set up automated Tokens for Good research on a schedule (daily, with a chosen number of runs per day, or weekly). Use this when the user wants their AI to contribute spare tokens to nonprofit research without manual prompting; it wires up a recurring /schedule agent in one shot instead of making them run /schedule, pick a schedule, and paste a prompt by hand.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
The user wants to automate Tokens for Good research on a recurring schedule.
|
|
@@ -13,23 +13,23 @@ Work out the cadence before doing anything else:
|
|
|
13
13
|
- Otherwise ask the user, offering: **Daily** (recommended), **Weekly** (light touch), or **Skip for now**.
|
|
14
14
|
- For a daily cadence with no per-day count set, ask **"How many times per day? (1–15)"**. Claude Code caps how many scheduled runs you get per day, so keep it at 15 or below. Default to 1 if the user has no preference.
|
|
15
15
|
|
|
16
|
-
Describe cadence by frequency only
|
|
16
|
+
Describe cadence by frequency only; keep token costs and dollar amounts out of every question and confirmation.
|
|
17
17
|
|
|
18
18
|
## 2. Wire it up
|
|
19
19
|
|
|
20
20
|
1. **Call the TFG MCP `setup_automation` tool** with `frequency` (`daily` or `weekly`) and, for daily, `runs_per_day`. It returns the full `/schedule` setup text: a Step 2 schedule line containing a cron expression, and a research prompt scoped to the user's API key.
|
|
21
21
|
|
|
22
|
-
2. **Extract the research prompt
|
|
22
|
+
2. **Extract the research prompt:** the block delimited by `---` lines. Pass it through verbatim; don't paraphrase.
|
|
23
23
|
|
|
24
24
|
3. **Invoke the `/schedule` skill** to create the recurring trigger:
|
|
25
25
|
- Schedule: the cron expression from `setup_automation`'s Step 2 line.
|
|
26
26
|
- Task description: the verbatim block from step 2.
|
|
27
27
|
|
|
28
|
-
4. **Wait for `/schedule` to confirm success.** If it fails or the user cancels, stop here and tell the user
|
|
28
|
+
4. **Wait for `/schedule` to confirm success.** If it fails or the user cancels, stop here and tell the user; do NOT call `mark_setup_complete`.
|
|
29
29
|
|
|
30
30
|
5. **On success, call the TFG MCP `mark_setup_complete` tool.** This flips the user's local state so the SessionStart hook stops nudging.
|
|
31
31
|
|
|
32
|
-
6. **Confirm to the user** in one or two sentences, and reassure them it runs unattended, e.g. *"Scheduled
|
|
32
|
+
6. **Confirm to the user** in one or two sentences, and reassure them it runs unattended, e.g. *"Scheduled ✓; your spare tokens will research a nonprofit on that cadence from here on. It runs on Anthropic's cloud, so your computer can be off and Claude Code doesn't need to be open. Change it anytime with /schedule."*
|
|
33
33
|
|
|
34
34
|
## If something goes wrong
|
|
35
35
|
|
|
@@ -40,6 +40,6 @@ Describe cadence by frequency only — keep token costs and dollar amounts out o
|
|
|
40
40
|
## What not to do
|
|
41
41
|
|
|
42
42
|
- Keep token costs and dollar figures out of every question and confirmation.
|
|
43
|
-
- If the user already picked a cadence at install, don't re-ask it
|
|
43
|
+
- If the user already picked a cadence at install, don't re-ask it; only ask for the per-day count when it's missing for a daily cadence.
|
|
44
44
|
- Don't print the raw research prompt to the user; it's verbose and already flows through /schedule.
|
|
45
|
-
- Don't assume the user knows what /schedule is. If they ask, briefly explain: "It's Anthropic's scheduled-task feature
|
|
45
|
+
- Don't assume the user knows what /schedule is. If they ask, briefly explain: "It's Anthropic's scheduled-task feature; runs your prompt on their cloud on a cron schedule, no need to keep your laptop on."
|
package/skills/tfg-validate.md
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tfg-validate
|
|
3
|
-
description: Validate two independent v3 research reports by pruning unsupported or fabricated EVIDENCE TABLE rows, using ONLY the cached page text the server provides (no web fetches). Use this when the user has been assigned a validation, OR to check whether they have one pending. Ideal for local models
|
|
3
|
+
description: Validate two independent v3 research reports by pruning unsupported or fabricated EVIDENCE TABLE rows, using ONLY the cached page text the server provides (no web fetches). Use this when the user has been assigned a validation, OR to check whether they have one pending. Ideal for local models; it does zero scraping.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
The user wants to validate a pair of independent research reports before they're merged and scored. Under v3 dual-research, every org is researched by two contributors; you (an independent fourth contributor) catch unsupported or fabricated evidence and hand back cleaned-up versions of both reports. The consolidator then merges already-validated tables.
|
|
7
7
|
|
|
8
|
-
You do **no web research and no fetching**. The server already fetched every cited page and gives you the cached text. This is deliberate
|
|
8
|
+
You do **no web research and no fetching**. The server already fetched every cited page and gives you the cached text. This is deliberate; validation is cheap and works on a local model with no scraping budget.
|
|
9
9
|
|
|
10
10
|
## What to do
|
|
11
11
|
|
|
12
12
|
1. **Call `get_next_validation` on the TFG MCP.** It returns:
|
|
13
13
|
- the org being validated,
|
|
14
|
-
- your validation `claim_id` (a UUID
|
|
14
|
+
- your validation `claim_id` (a UUID; submit against this one),
|
|
15
15
|
- two `source_reports` (each a full v3 report with its EVIDENCE TABLE) plus the server's deterministic `citation_verdicts` per row (`verified` / `fabricated` / `unverifiable`),
|
|
16
16
|
- `cached_pages`: the text of every URL cited across both reports, with a `fetch_status` (`ok` / `unreachable` / `non_text` / `missing`),
|
|
17
17
|
- the full validation methodology inline.
|
|
18
18
|
|
|
19
|
-
If nothing is assigned, the tool says so
|
|
19
|
+
If nothing is assigned, the tool says so; tell the user and stop here.
|
|
20
20
|
|
|
21
21
|
2. **For each report, go row by row** (a1, a2, a3, b, c, d, e, f). Start from the server's verdict, then apply judgment using the cached page text:
|
|
22
22
|
- **`fabricated`** (quote not on the page, or URL 404s) → **remove the row** (blank quote/URL/name).
|
|
@@ -27,7 +27,7 @@ You do **no web research and no fetching**. The server already fetched every cit
|
|
|
27
27
|
|
|
28
28
|
4. **Call `submit_validation`** with:
|
|
29
29
|
- `claim_id`: your validation claim ID from step 1
|
|
30
|
-
- `validated_reports`: an array of `{ claim_id, report_markdown }
|
|
30
|
+
- `validated_reports`: an array of `{ claim_id, report_markdown }`; one entry per report you changed, where `claim_id` is that source report's claim_id and `report_markdown` is its full corrected markdown. Omit reports you didn't change. **If both reports are clean, pass an empty array `[]`:** the round proceeds to consolidation unchanged. Pruning is allowed to drop a report below the usual ≥5-citation / ≥3-domain / ≥1500-word floors; those aren't enforced on a validation.
|
|
31
31
|
- `validation_notes`: a short summary of what you cut/corrected and why
|
|
32
32
|
- `estimated_tokens`: an honest estimate (validation is read-heavy, no web work)
|
|
33
33
|
|
|
@@ -36,10 +36,10 @@ You do **no web research and no fetching**. The server already fetched every cit
|
|
|
36
36
|
## What not to do
|
|
37
37
|
|
|
38
38
|
- **Don't add evidence.** Ever. Subtract or correct-to-source only.
|
|
39
|
-
- **Don't fetch anything.** Use the `cached_pages` text. A `missing`/`unreachable` page means the citation is unverifiable
|
|
39
|
+
- **Don't fetch anything.** Use the `cached_pages` text. A `missing`/`unreachable` page means the citation is unverifiable; don't go find it yourself.
|
|
40
40
|
- **Don't punish honest blanks or real PDFs.** A blank row is fine; an `unverifiable` PDF from a credible source can stay.
|
|
41
41
|
- **Don't rewrite prose.** Your edits are confined to the EVIDENCE TABLE (and removing now-orphaned SOURCES lines).
|
|
42
|
-
- **Don't second-guess a `fabricated` verdict
|
|
42
|
+
- **Don't second-guess a `fabricated` verdict:** the page text is right there; if the quote isn't in it, the row goes.
|
|
43
43
|
|
|
44
44
|
## If something goes wrong
|
|
45
45
|
|
package/skills/tfg.md
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: tfg
|
|
3
|
-
description: Do one Tokens for Good research task end-to-end
|
|
3
|
+
description: Do one Tokens for Good research task end-to-end; claim an org, research it using the provided methodology, and submit the report. Use when the user wants to contribute once (not set up automation). Takes about 5 minutes per org.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
The user wants to complete one Tokens for Good research cycle.
|
|
7
7
|
|
|
8
8
|
## What to do
|
|
9
9
|
|
|
10
|
-
0. **First, check for pending consolidations.** Call `get_next_consolidation`. If it returns an assignment, stop here and hand off to the `/tfg-consolidate` skill
|
|
10
|
+
0. **First, check for pending consolidations.** Call `get_next_consolidation`. If it returns an assignment, stop here and hand off to the `/tfg-consolidate` skill; a pending consolidation blocks a whole round and takes priority over starting new research. If it returns "no consolidations assigned," continue to step 1.
|
|
11
11
|
|
|
12
12
|
1. **Call `claim_org`** on the TFG MCP. It reserves an org for you and returns a claim ID.
|
|
13
13
|
|
|
14
|
-
2. **Call `get_methodology`** with `step="research"` and follow those instructions closely
|
|
14
|
+
2. **Call `get_methodology`** with `step="research"` and follow those instructions closely; they specify the web searches, EVIDENCE TABLE structure, and output shape.
|
|
15
15
|
|
|
16
16
|
3. **Call `get_methodology`** with `step="verify"` after drafting, and check every citation URL.
|
|
17
17
|
|
|
18
|
-
4. **Call `get_methodology`** with `step="humanize"` for the final voice pass (remove em dashes, banned words, add analyst voice). Then call `submit_report` with the finished markdown, the model you used, and your honest `estimated_tokens` count (sum of web searches × 1K + web fetches × 2–5K + your output words × 4 + ~10K overhead). `submit_report` automatically flips `first_setup_complete` for one-off users
|
|
18
|
+
4. **Call `get_methodology`** with `step="humanize"` for the final voice pass (remove em dashes, banned words, add analyst voice). Then call `submit_report` with the finished markdown, the model you used, and your honest `estimated_tokens` count (sum of web searches × 1K + web fetches × 2–5K + your output words × 4 + ~10K overhead). `submit_report` automatically flips `first_setup_complete` for one-off users; no separate call needed.
|
|
19
19
|
|
|
20
20
|
5. **Confirm to the user** with the acceptance stats the submit returns. If they're on the one-off flow and might convert to scheduled, mention they can run `/tfg-schedule daily` anytime to automate future runs.
|
|
21
21
|
|
|
@@ -28,5 +28,5 @@ The user wants to complete one Tokens for Good research cycle.
|
|
|
28
28
|
## What not to do
|
|
29
29
|
|
|
30
30
|
- Don't skip verification. Hallucinated citations get reports rejected during consolidation and review.
|
|
31
|
-
- Don't exceed 2,500 words
|
|
31
|
+
- Don't exceed 2,500 words; the methodology cares about density, not length.
|
|
32
32
|
- Don't submit without `estimated_tokens`. The MCP tool will reject it.
|
package/src/api-client.js
CHANGED
|
@@ -42,7 +42,7 @@ export class ApiClient {
|
|
|
42
42
|
|
|
43
43
|
const response = await fetch(url, options);
|
|
44
44
|
// Tolerate empty bodies (204 No Content, used by /research/consolidate/next
|
|
45
|
-
// when nothing is assigned to you)
|
|
45
|
+
// when nothing is assigned to you); response.json() throws on empty text.
|
|
46
46
|
const text = await response.text();
|
|
47
47
|
const data = text ? JSON.parse(text) : null;
|
|
48
48
|
|
package/src/cli.js
CHANGED
|
@@ -9,7 +9,7 @@ const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
|
9
9
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
10
10
|
|
|
11
11
|
const HELP = `tokens-for-good v${pkg.version}
|
|
12
|
-
|
|
12
|
+
Contribute your spare AI tokens to research nonprofits for Fierce Philanthropy.
|
|
13
13
|
|
|
14
14
|
Usage:
|
|
15
15
|
npx tokens-for-good <command> [options]
|
package/src/init.js
CHANGED
|
@@ -16,9 +16,9 @@ const PKG_ROOT = join(__dirname, '..');
|
|
|
16
16
|
const IS_WINDOWS = process.platform === 'win32';
|
|
17
17
|
|
|
18
18
|
const FREQUENCY_CHOICES = [
|
|
19
|
-
{ title: 'Daily
|
|
20
|
-
{ title: 'Weekly
|
|
21
|
-
{ title: "One-off
|
|
19
|
+
{ title: 'Daily (recommended); pick how many runs per day next', value: 'daily' },
|
|
20
|
+
{ title: 'Weekly; light touch', value: 'weekly' },
|
|
21
|
+
{ title: "One-off; I'll run research manually when I feel like it", value: 'one_off' },
|
|
22
22
|
];
|
|
23
23
|
|
|
24
24
|
const PLATFORM_CHOICES = [
|
|
@@ -36,7 +36,7 @@ const onCancel = () => {
|
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
export async function runInit() {
|
|
39
|
-
console.log('\n✨ Tokens for Good
|
|
39
|
+
console.log('\n✨ Tokens for Good; one-time setup\n');
|
|
40
40
|
|
|
41
41
|
// Idempotency: offer to reconfigure if already set up
|
|
42
42
|
const existing = loadState();
|
|
@@ -72,7 +72,7 @@ export async function runInit() {
|
|
|
72
72
|
initial: detectedIdx,
|
|
73
73
|
}, { onCancel });
|
|
74
74
|
|
|
75
|
-
// Step 3: Frequency (mandatory choice
|
|
75
|
+
// Step 3: Frequency (mandatory choice; this is the whole point of init)
|
|
76
76
|
const { choice } = await prompts({
|
|
77
77
|
type: 'select',
|
|
78
78
|
name: 'choice',
|
|
@@ -81,7 +81,7 @@ export async function runInit() {
|
|
|
81
81
|
initial: 0,
|
|
82
82
|
}, { onCancel });
|
|
83
83
|
|
|
84
|
-
// Step 3b: Daily cadence
|
|
84
|
+
// Step 3b: Daily cadence; how many runs per day. Claude Code caps the
|
|
85
85
|
// number of scheduled runs per day, so we ask for a count (1-15).
|
|
86
86
|
let runs_per_day = null;
|
|
87
87
|
if (choice === 'daily') {
|
|
@@ -280,13 +280,13 @@ function writeSessionStartHook() {
|
|
|
280
280
|
}
|
|
281
281
|
|
|
282
282
|
// Claude Code runs SessionStart hooks with a stripped PATH that often omits
|
|
283
|
-
// the node/npx bin dir
|
|
284
|
-
// homebrew bin when Claude Code is launched from the macOS Dock/Finder
|
|
283
|
+
// the node/npx bin dir; C:\Program Files\nodejs on Windows, or the nvm/
|
|
284
|
+
// homebrew bin when Claude Code is launched from the macOS Dock/Finder; so a
|
|
285
285
|
// bare `npx` lookup fails and the session shows a "hook error". Resolve the
|
|
286
286
|
// absolute npx path at init time (full PATH available) and bake it in.
|
|
287
287
|
//
|
|
288
|
-
// `2>/dev/null || true` makes the hook non-fatal: this is an optional
|
|
289
|
-
// tool, so a missing npx, offline registry, or
|
|
288
|
+
// `2>/dev/null || true` makes the hook non-fatal: this is an optional
|
|
289
|
+
// contribution tool, so a missing npx, offline registry, or other hiccup must never
|
|
290
290
|
// disrupt the user's session. Hooks run under bash/sh on every platform (Git
|
|
291
291
|
// Bash on Windows), so this syntax is portable.
|
|
292
292
|
function hookCommand() {
|
|
@@ -301,7 +301,7 @@ function resolveNpxPath() {
|
|
|
301
301
|
}
|
|
302
302
|
|
|
303
303
|
// macOS / Linux: prefer `which npx`, then npx alongside the running node
|
|
304
|
-
// binary, then a bare `npx` fallback (relies on PATH
|
|
304
|
+
// binary, then a bare `npx` fallback (relies on PATH; last resort).
|
|
305
305
|
try {
|
|
306
306
|
const r = spawnSync('which', ['npx'], { encoding: 'utf-8' });
|
|
307
307
|
if (r.status === 0) {
|
|
@@ -321,7 +321,7 @@ function resolveNpxPath() {
|
|
|
321
321
|
}
|
|
322
322
|
|
|
323
323
|
function resolveWindowsNpxPath() {
|
|
324
|
-
// First try `where npx.cmd
|
|
324
|
+
// First try `where npx.cmd`; most reliable when PATH is correct.
|
|
325
325
|
try {
|
|
326
326
|
const r = spawnSync('where', ['npx.cmd'], { encoding: 'utf-8' });
|
|
327
327
|
if (r.status === 0) {
|
|
@@ -334,7 +334,7 @@ function resolveWindowsNpxPath() {
|
|
|
334
334
|
const alongside = join(dirname(process.execPath), 'npx.cmd');
|
|
335
335
|
if (existsSync(alongside)) return alongside;
|
|
336
336
|
|
|
337
|
-
// Last-resort guess
|
|
337
|
+
// Last-resort guess; user's hook may need manual edit if this is wrong.
|
|
338
338
|
return 'C:\\Program Files\\nodejs\\npx.cmd';
|
|
339
339
|
}
|
|
340
340
|
|
|
@@ -361,7 +361,7 @@ function printClosingGuidance(platform, flow, freq, runsPerDay = 1) {
|
|
|
361
361
|
const cadence = freq === 'daily' && runsPerDay > 1 ? `daily (${runsPerDay}× per day)` : freq;
|
|
362
362
|
if (platform === 'claude-code') {
|
|
363
363
|
if (flow === 'scheduled') {
|
|
364
|
-
console.log(`Next: open Claude Code and send any message
|
|
364
|
+
console.log(`Next: open Claude Code and send any message; even "hi".`);
|
|
365
365
|
console.log(`On that first message Claude sets up /schedule at ${cadence} cadence and`);
|
|
366
366
|
console.log(`confirms with "Scheduled ✓". Prefer to be explicit? Just run /tfg-schedule.`);
|
|
367
367
|
console.log('');
|
|
@@ -369,13 +369,13 @@ function printClosingGuidance(platform, flow, freq, runsPerDay = 1) {
|
|
|
369
369
|
console.log(`runs setup hooks after your first message, so type something to kick it off.`);
|
|
370
370
|
console.log('');
|
|
371
371
|
console.log('');
|
|
372
|
-
console.log(`Once it's scheduled, runs happen on Anthropic's cloud
|
|
372
|
+
console.log(`Once it's scheduled, runs happen on Anthropic's cloud; your computer can be`);
|
|
373
373
|
console.log(`off and Claude Code doesn't need to be open. It just runs on its own from here.`);
|
|
374
374
|
console.log('');
|
|
375
375
|
console.log(`Verify it worked: run /schedule in Claude Code to see your recurring task,`);
|
|
376
376
|
console.log(`or check for the "Auto-contributing" badge on your dashboard.`);
|
|
377
377
|
} else {
|
|
378
|
-
console.log(`Next: open Claude Code and send any message
|
|
378
|
+
console.log(`Next: open Claude Code and send any message; even "hi"; and Claude will`);
|
|
379
379
|
console.log(`offer to research your first org. Prefer to be explicit? Just run /tfg.`);
|
|
380
380
|
console.log('');
|
|
381
381
|
console.log(`Heads up: nothing happens the instant the window opens. Claude Code only`);
|
package/src/mcp-server.js
CHANGED
|
@@ -24,7 +24,7 @@ Tell the user to run this in their terminal (not here in the chat), then restart
|
|
|
24
24
|
The init command asks them to choose a contribution cadence (daily / weekly / one-off) and wires up everything else automatically. It takes about 30 seconds.`;
|
|
25
25
|
|
|
26
26
|
// Gate: only fires for genuinely cold installs where state.json is missing
|
|
27
|
-
// entirely. Existing users
|
|
27
|
+
// entirely. Existing users; including those on the pre-0.4.0 schema; pass
|
|
28
28
|
// through untouched. Init writes state.json on first successful completion,
|
|
29
29
|
// so after that this never fires again.
|
|
30
30
|
function notInitialized() {
|
|
@@ -61,16 +61,16 @@ The command walks them through everything in under a minute:
|
|
|
61
61
|
3. Pick a cadence: **daily** (recommended; choose how many runs per day), weekly, or one-off
|
|
62
62
|
4. Confirm
|
|
63
63
|
|
|
64
|
-
init writes everything
|
|
64
|
+
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.
|
|
65
65
|
|
|
66
|
-
**What is Tokens for Good?** A way for developers to
|
|
66
|
+
**What is Tokens for Good?** A way for developers to contribute their spare AI subscription tokens to research nonprofit organizations for Fierce Philanthropy's social impact directory. Each org takes about 5 minutes. Contributors get credit on a public leaderboard.`;
|
|
67
67
|
|
|
68
68
|
// --- Resources ---
|
|
69
69
|
|
|
70
70
|
server.resource('about', 'tokens-for-good://about', 'text/plain', async () => ({
|
|
71
71
|
contents: [{
|
|
72
72
|
uri: 'tokens-for-good://about',
|
|
73
|
-
text: `Tokens for Good -
|
|
73
|
+
text: `Tokens for Good - Contribute Your Spare AI Tokens to Research Nonprofits
|
|
74
74
|
|
|
75
75
|
What: An MCP server that lets AI coding tool users (Claude Code, Opencode, Cursor, Windsurf, Devin) contribute their spare subscription tokens to research nonprofit organizations for Fierce Philanthropy's social impact directory.
|
|
76
76
|
|
|
@@ -87,7 +87,7 @@ Research pipeline (per org, all done by your AI):
|
|
|
87
87
|
- Research the org using web search + web fetch, following the v3 EVIDENCE TABLE methodology
|
|
88
88
|
- Fill in an EVIDENCE TABLE (8 rows of verbatim quotes + real URLs); leave blanks honestly when the evidence doesn't exist
|
|
89
89
|
- The server (not you) computes the score deterministically from the merged consolidator output, out of 120
|
|
90
|
-
- Real URLs only
|
|
90
|
+
- Real URLs only; placeholder citations (example.com) are auto-rejected
|
|
91
91
|
|
|
92
92
|
Contributor tiers:
|
|
93
93
|
- New: first 5 orgs, easy orgs only
|
|
@@ -112,10 +112,10 @@ server.tool('claim_org', 'Claim the next available nonprofit org to research.',
|
|
|
112
112
|
try {
|
|
113
113
|
const result = await client.claimOrg(plat || platform);
|
|
114
114
|
return {
|
|
115
|
-
content: [{ type: 'text', text: `Claimed: ${result.org.name} (${result.org.url})\nclaim_id: ${result.claim_id}\nexpires: ${result.expires_at}\nNext: get_methodology step="research", then submit_report
|
|
115
|
+
content: [{ type: 'text', text: `Claimed: ${result.org.name} (${result.org.url})\nclaim_id: ${result.claim_id}\nexpires: ${result.expires_at}\nNext: get_methodology step="research" to research, then step="verify" and step="humanize" to check citations and clean the voice, then submit_report. (The /tfg skill walks all four.)` }],
|
|
116
116
|
};
|
|
117
117
|
} catch (err) {
|
|
118
|
-
// 409 means you already have an active claim
|
|
118
|
+
// 409 means you already have an active claim; usually an auto-assigned
|
|
119
119
|
// 3rd-researcher slot you were handed without asking. Surface the existing
|
|
120
120
|
// org + claim_id so the agent can continue the assigned task instead of
|
|
121
121
|
// bouncing off "you have a claim" with no idea which one.
|
|
@@ -220,10 +220,10 @@ server.tool('get_next_validation', 'Get your assigned v3 validation: both resear
|
|
|
220
220
|
validateMethodology = 'Using ONLY the cached page text provided, remove EVIDENCE TABLE rows whose quote is not on its cited page (verdict "fabricated"), and correct quotes that do not match. You may only SUBTRACT or CORRECT-to-source, never ADD. Submit the corrected reports with submit_validation.';
|
|
221
221
|
}
|
|
222
222
|
const reports = (result.source_reports || []).map((r, i) =>
|
|
223
|
-
`### Source report ${i + 1}
|
|
223
|
+
`### Source report ${i + 1}; claim_id ${r.claim_id} (submitted ${r.submitted_at || 'unknown'})\n\nServer citation verdicts: ${JSON.stringify(r.citation_verdicts || {})}\n\n${r.report_markdown}`
|
|
224
224
|
).join('\n\n---\n\n');
|
|
225
225
|
const pages = (result.cached_pages || []).map((p) =>
|
|
226
|
-
`#### ${p.url} [${p.fetch_status}${p.http_status ? ' ' + p.http_status : ''}]\n${p.text ? p.text : '(no text
|
|
226
|
+
`#### ${p.url} [${p.fetch_status}${p.http_status ? ' ' + p.http_status : ''}]\n${p.text ? p.text : '(no text; not machine-checkable)'}`
|
|
227
227
|
).join('\n\n');
|
|
228
228
|
return {
|
|
229
229
|
content: [{ type: 'text', text: `Validation assigned:\nOrg: ${result.org?.name}\nRound: ${result.round_id}\nYour validation claim ID (submit against this one): ${result.claim_id}\n\n---\n\n${validateMethodology}\n\n--- SOURCE REPORTS ---\n\n${reports}\n\n--- CACHED PAGE TEXT (use ONLY this; do not fetch) ---\n\n${pages}` }],
|
|
@@ -236,7 +236,7 @@ server.tool('get_next_validation', 'Get your assigned v3 validation: both resear
|
|
|
236
236
|
}
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
-
server.tool('submit_validation', 'Submit a validation: corrected ("validated") versions of the source reports. You may only SUBTRACT unsupported/fabricated EVIDENCE TABLE rows or CORRECT a quote to match its cited page
|
|
239
|
+
server.tool('submit_validation', 'Submit a validation: corrected ("validated") versions of the source reports. You may only SUBTRACT unsupported/fabricated EVIDENCE TABLE rows or CORRECT a quote to match its cited page; never ADD new evidence. Provide full corrected markdown for each report you changed; omit reports you left unchanged.', {
|
|
240
240
|
claim_id: z.string().describe('Your validation claim_id from get_next_validation'),
|
|
241
241
|
validated_reports: z.array(z.object({
|
|
242
242
|
claim_id: z.string().describe('A source report\'s claim_id'),
|
|
@@ -290,14 +290,14 @@ server.tool('list_agents', 'List your agents (concurrent workers). Each has its
|
|
|
290
290
|
}
|
|
291
291
|
});
|
|
292
292
|
|
|
293
|
-
server.tool('create_agent', 'Create a new agent (a separate concurrent worker) and get its API key
|
|
293
|
+
server.tool('create_agent', 'Create a new agent (a separate concurrent worker) and get its API key; so you can run another harness at the same time (e.g. a local Qwen alongside Codex). Returns the key ONCE. Subject to your agent limit (default 2).', {
|
|
294
294
|
label: z.string().describe('A name for the new worker, e.g. "qwen-local".'),
|
|
295
295
|
prefer_low_fetch_roles: z.boolean().optional().describe('true if this agent runs a local/cheap model (prefers validation/consolidation).'),
|
|
296
296
|
}, async ({ label, prefer_low_fetch_roles }) => {
|
|
297
297
|
if (!client) return { content: [{ type: 'text', text: 'Error: TFG_API_KEY not set.' }] };
|
|
298
298
|
try {
|
|
299
299
|
const result = await client.createAgent(label, !!prefer_low_fetch_roles);
|
|
300
|
-
return { content: [{ type: 'text', text: `Created agent "${result.agent.label}" (id ${result.agent.id}).\n\nIts API key (shown once
|
|
300
|
+
return { content: [{ type: 'text', text: `Created agent "${result.agent.label}" (id ${result.agent.id}).\n\nIts API key (shown once; configure the other harness with it):\n${result.api_key}` }] };
|
|
301
301
|
} catch (err) {
|
|
302
302
|
return { content: [{ type: 'text', text: `Error: ${err.message}` }] };
|
|
303
303
|
}
|
|
@@ -352,7 +352,7 @@ server.tool('my_impact', 'See your personal contribution stats, tier, and histor
|
|
|
352
352
|
const result = await client.getImpact();
|
|
353
353
|
const c = result.contributor;
|
|
354
354
|
|
|
355
|
-
// Older server builds omit github_handle from the impact response
|
|
355
|
+
// Older server builds omit github_handle from the impact response; fall
|
|
356
356
|
// back to display_name so we never print "@undefined".
|
|
357
357
|
const who = c.github_handle ? `@${c.github_handle}` : (c.display_name || 'you');
|
|
358
358
|
return {
|
|
@@ -367,7 +367,7 @@ server.tool('setup_guide', 'Get setup instructions for Tokens for Good. Use this
|
|
|
367
367
|
return { content: [{ type: 'text', text: NO_KEY_INSTRUCTIONS }] };
|
|
368
368
|
});
|
|
369
369
|
|
|
370
|
-
server.tool('setup_automation', 'Get the scheduled-research prompt + setup instructions for the user\'s platform. Usually called by the /tfg-schedule skill (which extracts the prompt and invokes /schedule). Safe to call directly too
|
|
370
|
+
server.tool('setup_automation', 'Get the scheduled-research prompt + setup instructions for the user\'s platform. Usually called by the /tfg-schedule skill (which extracts the prompt and invokes /schedule). Safe to call directly too; returns human-readable instructions.', {
|
|
371
371
|
frequency: z.enum(['daily', 'weekly']).optional().describe('How often to contribute'),
|
|
372
372
|
runs_per_day: z.number().int().min(1).max(15).optional().describe('For daily cadence: how many research runs per day (1-15). Defaults to 1.'),
|
|
373
373
|
}, async ({ frequency, runs_per_day }) => {
|
|
@@ -376,12 +376,12 @@ server.tool('setup_automation', 'Get the scheduled-research prompt + setup instr
|
|
|
376
376
|
return { content: [{ type: 'text', text: instructions }] };
|
|
377
377
|
});
|
|
378
378
|
|
|
379
|
-
server.tool('mark_setup_complete', 'Called by the /tfg-schedule skill after /schedule confirms, or by the /tfg skill after a successful first submission. Flips local state so the SessionStart hook stops emitting first-session instructions. Idempotent
|
|
379
|
+
server.tool('mark_setup_complete', 'Called by the /tfg-schedule skill after /schedule confirms, or by the /tfg skill after a successful first submission. Flips local state so the SessionStart hook stops emitting first-session instructions. Idempotent; safe to call multiple times.', {}, async () => {
|
|
380
380
|
markSetupComplete();
|
|
381
381
|
|
|
382
382
|
// If the user just wired up a recurring schedule, tell the server too. This
|
|
383
383
|
// is what flips `has_schedule`, which lights the "Auto-contributing" badge on
|
|
384
|
-
// their dashboard
|
|
384
|
+
// their dashboard; the only product-side confirmation that scheduling worked.
|
|
385
385
|
// Best-effort: the badge is non-critical, so a failure here never blocks setup.
|
|
386
386
|
const state = loadState();
|
|
387
387
|
if (client && (state.intended_flow === 'scheduled' || state.auto_schedule)) {
|
|
@@ -399,7 +399,7 @@ server.tool('snooze', 'Snooze Tokens for Good reminders. Call this when the user
|
|
|
399
399
|
days: z.number().int().min(1).max(365).describe('Days to snooze (1 = tomorrow, 7 = next week)'),
|
|
400
400
|
}, async ({ days }) => {
|
|
401
401
|
snoozeDays(days);
|
|
402
|
-
return { content: [{ type: 'text', text: `Got it
|
|
402
|
+
return { content: [{ type: 'text', text: `Got it; Tokens for Good will stay quiet for ${days} day${days === 1 ? '' : 's'}.` }] };
|
|
403
403
|
});
|
|
404
404
|
|
|
405
405
|
// --- Prompts (session start) ---
|
|
@@ -445,7 +445,7 @@ server.prompt('session_start', 'Check if you should research an org', {}, async
|
|
|
445
445
|
return {
|
|
446
446
|
messages: [{
|
|
447
447
|
role: 'user',
|
|
448
|
-
content: { type: 'text', text: `Tokens for Good: Would you like to
|
|
448
|
+
content: { type: 'text', text: `Tokens for Good: Would you like to contribute your spare tokens to research a nonprofit today?\n\n1. Set up automatic daily contributions (recommended): run /tfg-schedule\n2. Just run one now: run /tfg\n3. Ask me tomorrow\n4. Ask me in a week` },
|
|
449
449
|
}],
|
|
450
450
|
};
|
|
451
451
|
} else {
|
package/src/platform.js
CHANGED
|
@@ -32,8 +32,8 @@ export function getSchedulePrompt(apiKey) {
|
|
|
32
32
|
API Base: ${base}
|
|
33
33
|
Your API key: ${apiKey}
|
|
34
34
|
|
|
35
|
-
## Calling the API
|
|
36
|
-
Use the **Bash tool with \`curl\`** for every authenticated ${base}/* call (next-action, claim, submit, consolidate). **WebFetch will NOT work for these
|
|
35
|
+
## Calling the API; read first
|
|
36
|
+
Use the **Bash tool with \`curl\`** for every authenticated ${base}/* call (next-action, claim, submit, validate, consolidate). **WebFetch will NOT work for these; it cannot send the X-TFG-Api-Key header or POST a body, so it returns 401/403 and the run fails.** Always include \`-H "X-TFG-Api-Key: ${apiKey}"\`. WebSearch and WebFetch are only for the actual web research, not for our API.
|
|
37
37
|
|
|
38
38
|
## Instructions
|
|
39
39
|
1. Fetch the batch flow (public): \`curl -s "${base}/research/schedule-instructions"\`
|
|
@@ -89,7 +89,7 @@ Devin runs in the cloud, fully autonomous.`;
|
|
|
89
89
|
case 'qwen-code':
|
|
90
90
|
return `Set up automated contributions on Qwen Code.
|
|
91
91
|
|
|
92
|
-
Qwen Code v0.14+ has experimental built-in cron
|
|
92
|
+
Qwen Code v0.14+ has experimental built-in cron; enable it with QWEN_CODE_ENABLE_CRON=1 (or "experimental.cron": true in ~/.qwen/settings.json) and then use the Cron tool / /loop skill.
|
|
93
93
|
|
|
94
94
|
For a portable option, use a system cron job (add via crontab -e):
|
|
95
95
|
${cron} cd /path/to/workspace && qwen --prompt "Research a nonprofit org for Fierce Philanthropy using the tokens-for-good MCP tools. Claim an org, research it, then submit the report."
|
|
@@ -19,7 +19,7 @@ export function runSessionStartHook() {
|
|
|
19
19
|
// Never-initialized users: hook is a no-op. Setup happens via init, not here.
|
|
20
20
|
if (!state.intended_flow) return;
|
|
21
21
|
|
|
22
|
-
// First session after init
|
|
22
|
+
// First session after init; emit the handoff instruction exactly once.
|
|
23
23
|
if (!state.first_setup_complete) {
|
|
24
24
|
emit(firstSessionMessage(state));
|
|
25
25
|
return;
|