cyberdyne-mcp 0.6.4 → 0.6.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/dist/cli.js +1 -45
- package/dist/server.js +11 -34
- package/package.json +2 -4
- package/src/cli.ts +1 -43
- package/src/server.ts +11 -67
- package/dist/founder-check.js +0 -91
- package/dist/smoke.js +0 -90
- package/src/founder-check.ts +0 -103
- package/src/smoke.ts +0 -108
package/dist/cli.js
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bankr-style convenience CLI for CYBERDYNE — `
|
|
2
|
+
* Bankr-style convenience CLI for CYBERDYNE — `post` / `tasks`.
|
|
3
3
|
*
|
|
4
4
|
* These are ADDITIONAL command-line entry points (not MCP tools). They run, print
|
|
5
5
|
* a human-readable summary to stderr, and exit — exactly like `onboard`/`login`.
|
|
6
6
|
* They mirror the Bankr CLI UX (`bankr fees`, `bankr launch`): a single command
|
|
7
7
|
* that does the full thing autonomously using the saved `cyb_` key + wallet.
|
|
8
8
|
*
|
|
9
|
-
* treasury (alias balance, fees) — your balance + where to send USDC (bankr fees)
|
|
10
9
|
* post — open a task; on the pool rail, sign + pay +
|
|
11
10
|
* authorize in one shot (bankr launch)
|
|
12
11
|
* tasks — list your own posted tasks with status
|
|
@@ -64,49 +63,6 @@ function describe(e) {
|
|
|
64
63
|
return e.message;
|
|
65
64
|
return e instanceof Error ? e.message : String(e);
|
|
66
65
|
}
|
|
67
|
-
// ── treasury (alias: balance, fees) ─────────────────────────────────────────
|
|
68
|
-
// `bankr fees` equivalent: your balance + the deposit address to fund it.
|
|
69
|
-
export async function runTreasury() {
|
|
70
|
-
if (!hasKey())
|
|
71
|
-
fail(NO_KEY);
|
|
72
|
-
const c = client();
|
|
73
|
-
try {
|
|
74
|
-
const { treasury } = await c.rest("GET", "/api/treasury");
|
|
75
|
-
// The deposit address only resolves on the live rail; treat a 403/503 as
|
|
76
|
-
// "not available yet" rather than failing the whole command.
|
|
77
|
-
let deposit = null;
|
|
78
|
-
try {
|
|
79
|
-
deposit = await c.rest("GET", "/api/treasury/deposit");
|
|
80
|
-
}
|
|
81
|
-
catch {
|
|
82
|
-
deposit = null;
|
|
83
|
-
}
|
|
84
|
-
const lines = ["CYBERDYNE treasury"];
|
|
85
|
-
if (!treasury) {
|
|
86
|
-
lines.push(" balance : — (no treasury yet — fund it to create one)");
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
lines.push(` balance : ${usd(treasury.balance_usd)}`);
|
|
90
|
-
lines.push(` total funded : ${usd(treasury.total_funded ?? treasury.total_funded_usd)}`);
|
|
91
|
-
lines.push(` total spent : ${usd(treasury.total_spent ?? treasury.total_spent_usd)}`);
|
|
92
|
-
}
|
|
93
|
-
if (deposit?.deposit_address) {
|
|
94
|
-
lines.push("");
|
|
95
|
-
lines.push(` deposit USDC to: ${deposit.deposit_address}`);
|
|
96
|
-
lines.push(` chain : Base (chain id ${deposit.chain_id ?? 8453})`);
|
|
97
|
-
lines.push(" → send USDC from your verified wallet, then credit it with the `deposit` MCP tool.");
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
lines.push("");
|
|
101
|
-
lines.push(" deposit address: not available (live deposits not enabled on this rail yet).");
|
|
102
|
-
}
|
|
103
|
-
console.error(lines.join("\n"));
|
|
104
|
-
process.exit(0);
|
|
105
|
-
}
|
|
106
|
-
catch (e) {
|
|
107
|
-
fail(describe(e));
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
66
|
// ── post (bankr launch) ──────────────────────────────────────────────────────
|
|
111
67
|
// Open a task. Per-unit `--reward` × `--quantity` = the budget. On the pool rail
|
|
112
68
|
// (BNKR/GITLAWB or quantity>1) the response carries authIntent + deployFee: sign
|
package/dist/server.js
CHANGED
|
@@ -8,11 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* list_categories — the static task taxonomy (no network)
|
|
10
10
|
* search_humans — POST /api/a2a {search_humans} → capability index
|
|
11
|
-
* get_treasury — GET /api/treasury → the agent's balance
|
|
12
|
-
* fund_treasury — POST /api/treasury/fund → demo top-up (testnet only)
|
|
13
|
-
* get_deposit_address — GET /api/treasury/deposit → where to send real USDC (live)
|
|
14
|
-
* deposit — POST /api/treasury/deposit → credit treasury from a real USDC tx
|
|
15
|
-
* withdraw_treasury — POST /api/treasury/withdraw → pull unspent treasury back to your wallet (live)
|
|
16
11
|
* post_task — POST /api/tasks → open an FCFS pool bounty
|
|
17
12
|
* authorize_task — POST /api/tasks/[id]/authorize → sign budget + pay fee + freeze
|
|
18
13
|
* get_task — GET /api/tasks/[id] → status + submissions/claims
|
|
@@ -32,9 +27,9 @@
|
|
|
32
27
|
* agent-picks-human. EVERY task is an open bounty: the agent freezes a budget once,
|
|
33
28
|
* ANY eligible human submits first-come-first-served, and the agent approves/rejects
|
|
34
29
|
* each submission — approved pays one unit in-token, rejected reopens the slot, and
|
|
35
|
-
* any unfilled budget is refunded on close.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
30
|
+
* any unfilled budget is refunded on close. The agent funds the budget directly
|
|
31
|
+
* from its OWN wallet at deploy (non-custodial) — there is no platform treasury.
|
|
32
|
+
* post_task({ ..., quantity }) → returns { task, authIntent, deployFee }
|
|
38
33
|
* → authorize_task({ task_id, auth_intent, deploy_fee }) (sign budget + pay fee + freeze)
|
|
39
34
|
* → humans submit FCFS → poll get_task
|
|
40
35
|
* → review_submission per pending submission (approve → pay one unit;
|
|
@@ -119,13 +114,8 @@ if (process.argv[2] === "login") {
|
|
|
119
114
|
}
|
|
120
115
|
// Bankr-style convenience CLI subcommands (additional entry points, not MCP tools).
|
|
121
116
|
// Each runs autonomously with the saved key/wallet, prints a summary, and exits.
|
|
122
|
-
// treasury (alias balance, fees) — balance + deposit address (like `bankr fees`)
|
|
123
117
|
// post — open a task; pool rail auto sign+pay+authorize (like `bankr launch`)
|
|
124
118
|
// tasks — list your own posted tasks with status
|
|
125
|
-
if (["treasury", "balance", "fees"].includes(process.argv[2] ?? "")) {
|
|
126
|
-
const { runTreasury } = await import("./cli.js");
|
|
127
|
-
await runTreasury();
|
|
128
|
-
}
|
|
129
119
|
if (process.argv[2] === "post") {
|
|
130
120
|
const { runPost } = await import("./cli.js");
|
|
131
121
|
await runPost(process.argv.slice(3));
|
|
@@ -158,9 +148,9 @@ async function guard(fn) {
|
|
|
158
148
|
}
|
|
159
149
|
}
|
|
160
150
|
// ---- Server ---------------------------------------------------------------
|
|
161
|
-
const server = new McpServer({ name: "cyberdyne", version: "0.6.
|
|
151
|
+
const server = new McpServer({ name: "cyberdyne", version: "0.6.6" });
|
|
162
152
|
server.tool("list_categories", "List the kinds of real-world work CYBERDYNE humans can do. Static (no network). Use this to learn the valid `category` values before posting a task.", {}, async () => json(Object.entries(CATEGORIES).map(([id, blurb]) => ({ id, blurb }))));
|
|
163
|
-
server.tool("onboard", "BOOTSTRAP (works WITHOUT an existing key — the one tool that self-onboards). Zero-browser: generates a fresh wallet if you don't have one, signs in to CYBERDYNE with it (SIWE), mints your `cyb_` agent API key, and saves both to ~/.cyberdyne/config.json (0600) so every other tool here authenticates automatically. No web dashboard, no env vars. Returns your wallet address, the cyb_ key (shown once), and the next steps (fund
|
|
153
|
+
server.tool("onboard", "BOOTSTRAP (works WITHOUT an existing key — the one tool that self-onboards). Zero-browser: generates a fresh wallet if you don't have one, signs in to CYBERDYNE with it (SIWE), mints your `cyb_` agent API key, and saves both to ~/.cyberdyne/config.json (0600) so every other tool here authenticates automatically. No web dashboard, no env vars. Returns your wallet address, the cyb_ key (shown once), and the next steps (fund your WALLET with USDC + a little ETH for gas on Base → post_task → authorize_task → review_submission → close_task). The non-custodial pool freezes the budget directly from your wallet at deploy — there is no platform treasury to deposit into. The same generated wallet auto-signs pool budgets. To bring your OWN wallet instead, use the CLI: `npx cyberdyne-mcp onboard --import <0xKEY | mnemonic>` (or --create for a fresh one). Idempotent-ish: re-running with a saved wallet reuses it and mints a fresh key.", {}, async () => guard(async () => {
|
|
164
154
|
const r = await onboard();
|
|
165
155
|
return {
|
|
166
156
|
address: r.address,
|
|
@@ -183,17 +173,7 @@ server.tool("search_humans", "Find verified humans by capability via the live ca
|
|
|
183
173
|
...(min_reputation != null ? { min_reputation } : {}),
|
|
184
174
|
...(location ? { location } : {}),
|
|
185
175
|
})));
|
|
186
|
-
server.tool("
|
|
187
|
-
server.tool("fund_treasury", "Demo top-up (TESTNET/DEMO ONLY): add USD to the treasury balance. DISABLED when the platform is live (returns 403 funding_disabled) — on the live rail fund with REAL USDC via get_deposit_address + deposit instead.", { amount_usd: z.number().positive().describe("USD to add to the treasury balance.") }, async ({ amount_usd }) => guard(() => client.rest("POST", "/api/treasury/fund", { body: { amount_usd } })));
|
|
188
|
-
server.tool("get_deposit_address", "Get the on-chain address to fund your treasury with REAL USDC (live rail). Returns { deposit_address, chain_id, usdc_address, decimals }. Send USDC from your VERIFIED wallet to deposit_address on Base, then call `deposit` with the tx hash to credit your treasury.", {}, async () => guard(() => client.rest("GET", "/api/treasury/deposit")));
|
|
189
|
-
server.tool("deposit", "Credit your treasury from a REAL on-chain USDC deposit (live rail; the real-money replacement for fund_treasury). First send USDC to the address from get_deposit_address (from your verified wallet), then call this with the transaction hash. The transfer is verified on-chain (to = platform wallet, from = your wallet) and credited exactly once — resubmitting the same tx never double-credits.", {
|
|
190
|
-
tx_hash: z
|
|
191
|
-
.string()
|
|
192
|
-
.regex(/^0x[0-9a-fA-F]{64}$/)
|
|
193
|
-
.describe("The Base tx hash of your USDC transfer to the deposit address."),
|
|
194
|
-
}, async ({ tx_hash }) => guard(() => client.rest("POST", "/api/treasury/deposit", { body: { tx_hash } })));
|
|
195
|
-
server.tool("withdraw_treasury", "Recover UNSPENT treasury to your wallet (live rail): pull USDC out of your treasury back to your own VERIFIED deposit wallet on Base — no browser. Available balance is your treasury balance net of any open escrow holds. Funds can ONLY go to your verified wallet (no destination param), so a leaked key can't redirect them. Returns { ok, tx_hash, amount_usd, to }. 400 insufficient_treasury if the balance can't cover it; 403 withdraws_disabled on the demo rail.", { amount_usd: z.number().positive().describe("USD to withdraw from your treasury to your verified wallet.") }, async ({ amount_usd }) => guard(() => client.rest("POST", "/api/treasury/withdraw", { body: { amount_usd } })));
|
|
196
|
-
server.tool("post_task", "Open an FCFS pool bounty on the marketplace. There is NO direct hire and NO agent-picks-human — every task is an open bounty: you freeze a budget, ANY eligible human submits first-come-first-served, and you approve/reject each submission. Funds are NOT charged at post — the budget is frozen later at authorize_task. `reward_usd` is the total budget; `quantity` is how many identical units (humans) it pays — each unit holds reward_usd/quantity (each unit must be >= $0.01). Returns the created task (with its id). REAL-TOKEN POOL rail (USDC/BNKR on Base): the response also includes `authIntent` (the budget authorization to sign) and `deployFee` { usd, bps, recipient, token } (a SEPARATE non-refundable fee tx) — pass BOTH to authorize_task. TESTNET/non-real token (CYOS): no on-chain freeze; response is just { task } (treasury-checked, 402 insufficient_treasury if low).", {
|
|
176
|
+
server.tool("post_task", "Open an FCFS pool bounty on the marketplace. There is NO direct hire and NO agent-picks-human — every task is an open bounty: you freeze a budget, ANY eligible human submits first-come-first-served, and you approve/reject each submission. Funds are NOT charged at post — the budget is frozen later at authorize_task. `reward_usd` is the total budget; `quantity` is how many identical units (humans) it pays — each unit holds reward_usd/quantity (each unit must be >= $0.01). Returns the created task (with its id) plus `authIntent` (the budget authorization to sign) and `deployFee` { usd, bps, recipient, token } (a SEPARATE non-refundable fee tx) — pass BOTH to authorize_task. The non-custodial POOL escrow (USDC/BNKR/GITLAWB on Base) is the only settlement rail; a non-real token (CYOS) or non-live config has no rail and returns 422 settlement_unavailable.", {
|
|
197
177
|
title: z.string().min(2).max(160).describe("Short task title."),
|
|
198
178
|
category: z.enum(TASK_CATEGORIES),
|
|
199
179
|
description: z.string().max(4000).optional().describe("What you need the human to do."),
|
|
@@ -205,7 +185,7 @@ server.tool("post_task", "Open an FCFS pool bounty on the marketplace. There is
|
|
|
205
185
|
pay_token: z.enum(["USDC", "BNKR", "CYOS"]).optional().describe("Settlement token (default USDC)."),
|
|
206
186
|
deadline_hours: z.number().int().positive().optional(),
|
|
207
187
|
}, async (args) => guard(() => client.rest("POST", "/api/tasks", { body: args })));
|
|
208
|
-
server.tool("authorize_task", "Freeze the bounty budget on-chain (the second step of the FCFS flow). REAL-TOKEN POOL rail: pass BOTH `auth_intent` (the authIntent from post_task) AND `deploy_fee` (the deployFee object from post_task) — with CYBERDYNE_EVM_PRIVATE_KEY set, the MCP signs the whole-budget authorization AND pays the separate 2.5% USDC / 5% other-token deploy fee tx from its wallet, then freezes the budget on the audited escrow; or pass a pre-signed `signed_payment` and a pre-paid `fee_tx_hash`. After this, any eligible human submits FCFS and you review_submission each.
|
|
188
|
+
server.tool("authorize_task", "Freeze the bounty budget on-chain (the second step of the FCFS flow). REAL-TOKEN POOL rail: pass BOTH `auth_intent` (the authIntent from post_task) AND `deploy_fee` (the deployFee object from post_task) — with CYBERDYNE_EVM_PRIVATE_KEY set, the MCP signs the whole-budget authorization AND pays the separate 2.5% USDC / 5% other-token deploy fee tx from its wallet, then freezes the budget on the audited escrow; or pass a pre-signed `signed_payment` and a pre-paid `fee_tx_hash`. After this, any eligible human submits FCFS and you review_submission each. The non-custodial POOL escrow is the only rail; a non-real token / non-live config returns 409 settlement_unavailable. Idempotent once frozen.", {
|
|
209
189
|
task_id: z.string().uuid(),
|
|
210
190
|
signed_payment: z.string().optional().describe("Pre-signed base64 auth-capture payload (external/Bankr signer)."),
|
|
211
191
|
auth_intent: z.unknown().optional().describe("The authIntent from post_task — required for MCP wallet auto-signing."),
|
|
@@ -281,13 +261,10 @@ server.registerPrompt("quickstart", {
|
|
|
281
261
|
text: [
|
|
282
262
|
"You are connected to CYBERDYNE — pay verified humans for tasks AI can't do alone. There is ONE model: every task is an open FCFS pool bounty. There is NO direct hire and NO picking a human — you freeze a budget, ANY eligible human submits first-come-first-served, and you approve/reject each submission (approved = paid one unit in-token, rejected = the slot reopens). The live settlement rail is REAL tokens on Base (non-custodial freeze-at-deploy). The human submit-proof step is human-only, in the app; you drive everything else.",
|
|
283
263
|
"",
|
|
284
|
-
"FUND (
|
|
285
|
-
"1. get_deposit_address -> the platform deposit address on Base.",
|
|
286
|
-
"2. Send USDC to it FROM your own verified wallet, then deposit({ tx_hash }) -> credits your treasury (idempotent).",
|
|
287
|
-
" (fund_treasury is demo/testnet only and is disabled on the live rail.) Check get_treasury anytime.",
|
|
264
|
+
"FUND: hold USDC (or BNKR/GITLAWB) + a little ETH for gas in your OWN wallet on Base. The pool freezes the budget directly from your wallet at deploy and pays the deploy fee from it — there is NO platform treasury to deposit into (fully non-custodial).",
|
|
288
265
|
"",
|
|
289
266
|
"POST + PAY (the single FCFS flow):",
|
|
290
|
-
"3. post_task({ title, category, reward_usd, quantity
|
|
267
|
+
"3. post_task({ title, category, reward_usd, quantity }) -> returns { task, authIntent, deployFee }. reward_usd is the TOTAL budget; quantity is how many humans it pays (each unit must be >= $0.01). authIntent is the whole-budget authorization; deployFee is a SEPARATE non-refundable fee tx (2.5% USDC / 5% other token).",
|
|
291
268
|
"4. authorize_task({ task_id, auth_intent, deploy_fee }) -> with CYBERDYNE_EVM_PRIVATE_KEY set, the MCP signs the budget AND pays the deploy fee, then FREEZES the whole budget on the audited escrow (or pass pre-made signed_payment + fee_tx_hash).",
|
|
292
269
|
"5. Any eligible human submits FCFS. Poll get_task; for EACH pending submission call review_submission({ submission_id, approve, score }) -> approve captures one unit (full reward to the human, in-token); reject reopens the slot for the next submitter.",
|
|
293
270
|
"6. close_task({ task_id }) -> refunds the unfilled budget back to your wallet (the deploy fee is non-refundable).",
|
|
@@ -305,5 +282,5 @@ const transport = new StdioServerTransport();
|
|
|
305
282
|
await server.connect(transport);
|
|
306
283
|
console.error(`CYBERDYNE MCP server running on stdio → ${config.apiUrl}` +
|
|
307
284
|
(config.token ? "" : " (no key — run `npx cyberdyne-mcp onboard` to self-generate a wallet + key, or `login cyb_…`, or set CYBERDYNE_IDENTITY_TOKEN; networked tools error until then)") +
|
|
308
|
-
". Tools (
|
|
309
|
-
" CLI: onboard, login,
|
|
285
|
+
". Tools (9): onboard, list_categories, search_humans, post_task, authorize_task, get_task, review_submission, close_task, reclaim." +
|
|
286
|
+
" CLI: onboard, login, post, tasks.");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cyberdyne-mcp",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.6",
|
|
4
4
|
"publishConfig": {
|
|
5
5
|
"access": "public"
|
|
6
6
|
},
|
|
@@ -45,9 +45,7 @@
|
|
|
45
45
|
"build": "tsc",
|
|
46
46
|
"prepublishOnly": "npm run build",
|
|
47
47
|
"start": "node dist/server.js",
|
|
48
|
-
"dev": "tsx src/server.ts"
|
|
49
|
-
"smoke": "tsx src/smoke.ts",
|
|
50
|
-
"founder-check": "tsx src/founder-check.ts"
|
|
48
|
+
"dev": "tsx src/server.ts"
|
|
51
49
|
},
|
|
52
50
|
"engines": {
|
|
53
51
|
"node": ">=18"
|
package/src/cli.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Bankr-style convenience CLI for CYBERDYNE — `
|
|
2
|
+
* Bankr-style convenience CLI for CYBERDYNE — `post` / `tasks`.
|
|
3
3
|
*
|
|
4
4
|
* These are ADDITIONAL command-line entry points (not MCP tools). They run, print
|
|
5
5
|
* a human-readable summary to stderr, and exit — exactly like `onboard`/`login`.
|
|
6
6
|
* They mirror the Bankr CLI UX (`bankr fees`, `bankr launch`): a single command
|
|
7
7
|
* that does the full thing autonomously using the saved `cyb_` key + wallet.
|
|
8
8
|
*
|
|
9
|
-
* treasury (alias balance, fees) — your balance + where to send USDC (bankr fees)
|
|
10
9
|
* post — open a task; on the pool rail, sign + pay +
|
|
11
10
|
* authorize in one shot (bankr launch)
|
|
12
11
|
* tasks — list your own posted tasks with status
|
|
@@ -68,47 +67,6 @@ function describe(e: unknown): string {
|
|
|
68
67
|
return e instanceof Error ? e.message : String(e);
|
|
69
68
|
}
|
|
70
69
|
|
|
71
|
-
// ── treasury (alias: balance, fees) ─────────────────────────────────────────
|
|
72
|
-
// `bankr fees` equivalent: your balance + the deposit address to fund it.
|
|
73
|
-
export async function runTreasury(): Promise<void> {
|
|
74
|
-
if (!hasKey()) fail(NO_KEY);
|
|
75
|
-
const c = client();
|
|
76
|
-
try {
|
|
77
|
-
const { treasury } = await c.rest<{ treasury: Record<string, unknown> | null }>("GET", "/api/treasury");
|
|
78
|
-
|
|
79
|
-
// The deposit address only resolves on the live rail; treat a 403/503 as
|
|
80
|
-
// "not available yet" rather than failing the whole command.
|
|
81
|
-
let deposit: { deposit_address?: string; chain_id?: number; usdc_address?: string } | null = null;
|
|
82
|
-
try {
|
|
83
|
-
deposit = await c.rest("GET", "/api/treasury/deposit");
|
|
84
|
-
} catch {
|
|
85
|
-
deposit = null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const lines: string[] = ["CYBERDYNE treasury"];
|
|
89
|
-
if (!treasury) {
|
|
90
|
-
lines.push(" balance : — (no treasury yet — fund it to create one)");
|
|
91
|
-
} else {
|
|
92
|
-
lines.push(` balance : ${usd(treasury.balance_usd)}`);
|
|
93
|
-
lines.push(` total funded : ${usd(treasury.total_funded ?? treasury.total_funded_usd)}`);
|
|
94
|
-
lines.push(` total spent : ${usd(treasury.total_spent ?? treasury.total_spent_usd)}`);
|
|
95
|
-
}
|
|
96
|
-
if (deposit?.deposit_address) {
|
|
97
|
-
lines.push("");
|
|
98
|
-
lines.push(` deposit USDC to: ${deposit.deposit_address}`);
|
|
99
|
-
lines.push(` chain : Base (chain id ${deposit.chain_id ?? 8453})`);
|
|
100
|
-
lines.push(" → send USDC from your verified wallet, then credit it with the `deposit` MCP tool.");
|
|
101
|
-
} else {
|
|
102
|
-
lines.push("");
|
|
103
|
-
lines.push(" deposit address: not available (live deposits not enabled on this rail yet).");
|
|
104
|
-
}
|
|
105
|
-
console.error(lines.join("\n"));
|
|
106
|
-
process.exit(0);
|
|
107
|
-
} catch (e) {
|
|
108
|
-
fail(describe(e));
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
70
|
// ── post (bankr launch) ──────────────────────────────────────────────────────
|
|
113
71
|
// Open a task. Per-unit `--reward` × `--quantity` = the budget. On the pool rail
|
|
114
72
|
// (BNKR/GITLAWB or quantity>1) the response carries authIntent + deployFee: sign
|
package/src/server.ts
CHANGED
|
@@ -8,11 +8,6 @@
|
|
|
8
8
|
*
|
|
9
9
|
* list_categories — the static task taxonomy (no network)
|
|
10
10
|
* search_humans — POST /api/a2a {search_humans} → capability index
|
|
11
|
-
* get_treasury — GET /api/treasury → the agent's balance
|
|
12
|
-
* fund_treasury — POST /api/treasury/fund → demo top-up (testnet only)
|
|
13
|
-
* get_deposit_address — GET /api/treasury/deposit → where to send real USDC (live)
|
|
14
|
-
* deposit — POST /api/treasury/deposit → credit treasury from a real USDC tx
|
|
15
|
-
* withdraw_treasury — POST /api/treasury/withdraw → pull unspent treasury back to your wallet (live)
|
|
16
11
|
* post_task — POST /api/tasks → open an FCFS pool bounty
|
|
17
12
|
* authorize_task — POST /api/tasks/[id]/authorize → sign budget + pay fee + freeze
|
|
18
13
|
* get_task — GET /api/tasks/[id] → status + submissions/claims
|
|
@@ -32,9 +27,9 @@
|
|
|
32
27
|
* agent-picks-human. EVERY task is an open bounty: the agent freezes a budget once,
|
|
33
28
|
* ANY eligible human submits first-come-first-served, and the agent approves/rejects
|
|
34
29
|
* each submission — approved pays one unit in-token, rejected reopens the slot, and
|
|
35
|
-
* any unfilled budget is refunded on close.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
30
|
+
* any unfilled budget is refunded on close. The agent funds the budget directly
|
|
31
|
+
* from its OWN wallet at deploy (non-custodial) — there is no platform treasury.
|
|
32
|
+
* post_task({ ..., quantity }) → returns { task, authIntent, deployFee }
|
|
38
33
|
* → authorize_task({ task_id, auth_intent, deploy_fee }) (sign budget + pay fee + freeze)
|
|
39
34
|
* → humans submit FCFS → poll get_task
|
|
40
35
|
* → review_submission per pending submission (approve → pay one unit;
|
|
@@ -128,13 +123,8 @@ if (process.argv[2] === "login") {
|
|
|
128
123
|
|
|
129
124
|
// Bankr-style convenience CLI subcommands (additional entry points, not MCP tools).
|
|
130
125
|
// Each runs autonomously with the saved key/wallet, prints a summary, and exits.
|
|
131
|
-
// treasury (alias balance, fees) — balance + deposit address (like `bankr fees`)
|
|
132
126
|
// post — open a task; pool rail auto sign+pay+authorize (like `bankr launch`)
|
|
133
127
|
// tasks — list your own posted tasks with status
|
|
134
|
-
if (["treasury", "balance", "fees"].includes(process.argv[2] ?? "")) {
|
|
135
|
-
const { runTreasury } = await import("./cli.js");
|
|
136
|
-
await runTreasury();
|
|
137
|
-
}
|
|
138
128
|
if (process.argv[2] === "post") {
|
|
139
129
|
const { runPost } = await import("./cli.js");
|
|
140
130
|
await runPost(process.argv.slice(3));
|
|
@@ -170,7 +160,7 @@ async function guard<T>(fn: () => Promise<T>) {
|
|
|
170
160
|
|
|
171
161
|
// ---- Server ---------------------------------------------------------------
|
|
172
162
|
|
|
173
|
-
const server = new McpServer({ name: "cyberdyne", version: "0.6.
|
|
163
|
+
const server = new McpServer({ name: "cyberdyne", version: "0.6.6" });
|
|
174
164
|
|
|
175
165
|
server.tool(
|
|
176
166
|
"list_categories",
|
|
@@ -181,7 +171,7 @@ server.tool(
|
|
|
181
171
|
|
|
182
172
|
server.tool(
|
|
183
173
|
"onboard",
|
|
184
|
-
"BOOTSTRAP (works WITHOUT an existing key — the one tool that self-onboards). Zero-browser: generates a fresh wallet if you don't have one, signs in to CYBERDYNE with it (SIWE), mints your `cyb_` agent API key, and saves both to ~/.cyberdyne/config.json (0600) so every other tool here authenticates automatically. No web dashboard, no env vars. Returns your wallet address, the cyb_ key (shown once), and the next steps (fund
|
|
174
|
+
"BOOTSTRAP (works WITHOUT an existing key — the one tool that self-onboards). Zero-browser: generates a fresh wallet if you don't have one, signs in to CYBERDYNE with it (SIWE), mints your `cyb_` agent API key, and saves both to ~/.cyberdyne/config.json (0600) so every other tool here authenticates automatically. No web dashboard, no env vars. Returns your wallet address, the cyb_ key (shown once), and the next steps (fund your WALLET with USDC + a little ETH for gas on Base → post_task → authorize_task → review_submission → close_task). The non-custodial pool freezes the budget directly from your wallet at deploy — there is no platform treasury to deposit into. The same generated wallet auto-signs pool budgets. To bring your OWN wallet instead, use the CLI: `npx cyberdyne-mcp onboard --import <0xKEY | mnemonic>` (or --create for a fresh one). Idempotent-ish: re-running with a saved wallet reuses it and mints a fresh key.",
|
|
185
175
|
{},
|
|
186
176
|
async () =>
|
|
187
177
|
guard(async () => {
|
|
@@ -218,52 +208,9 @@ server.tool(
|
|
|
218
208
|
),
|
|
219
209
|
);
|
|
220
210
|
|
|
221
|
-
server.tool(
|
|
222
|
-
"get_treasury",
|
|
223
|
-
"Get the agent's own treasury (the source of task rewards on the manual rail). Returns null if the agent has no treasury yet — call fund_treasury to create one.",
|
|
224
|
-
{},
|
|
225
|
-
async () => guard(() => client.rest("GET", "/api/treasury")),
|
|
226
|
-
);
|
|
227
|
-
|
|
228
|
-
server.tool(
|
|
229
|
-
"fund_treasury",
|
|
230
|
-
"Demo top-up (TESTNET/DEMO ONLY): add USD to the treasury balance. DISABLED when the platform is live (returns 403 funding_disabled) — on the live rail fund with REAL USDC via get_deposit_address + deposit instead.",
|
|
231
|
-
{ amount_usd: z.number().positive().describe("USD to add to the treasury balance.") },
|
|
232
|
-
async ({ amount_usd }) =>
|
|
233
|
-
guard(() => client.rest("POST", "/api/treasury/fund", { body: { amount_usd } })),
|
|
234
|
-
);
|
|
235
|
-
|
|
236
|
-
server.tool(
|
|
237
|
-
"get_deposit_address",
|
|
238
|
-
"Get the on-chain address to fund your treasury with REAL USDC (live rail). Returns { deposit_address, chain_id, usdc_address, decimals }. Send USDC from your VERIFIED wallet to deposit_address on Base, then call `deposit` with the tx hash to credit your treasury.",
|
|
239
|
-
{},
|
|
240
|
-
async () => guard(() => client.rest("GET", "/api/treasury/deposit")),
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
server.tool(
|
|
244
|
-
"deposit",
|
|
245
|
-
"Credit your treasury from a REAL on-chain USDC deposit (live rail; the real-money replacement for fund_treasury). First send USDC to the address from get_deposit_address (from your verified wallet), then call this with the transaction hash. The transfer is verified on-chain (to = platform wallet, from = your wallet) and credited exactly once — resubmitting the same tx never double-credits.",
|
|
246
|
-
{
|
|
247
|
-
tx_hash: z
|
|
248
|
-
.string()
|
|
249
|
-
.regex(/^0x[0-9a-fA-F]{64}$/)
|
|
250
|
-
.describe("The Base tx hash of your USDC transfer to the deposit address."),
|
|
251
|
-
},
|
|
252
|
-
async ({ tx_hash }) =>
|
|
253
|
-
guard(() => client.rest("POST", "/api/treasury/deposit", { body: { tx_hash } })),
|
|
254
|
-
);
|
|
255
|
-
|
|
256
|
-
server.tool(
|
|
257
|
-
"withdraw_treasury",
|
|
258
|
-
"Recover UNSPENT treasury to your wallet (live rail): pull USDC out of your treasury back to your own VERIFIED deposit wallet on Base — no browser. Available balance is your treasury balance net of any open escrow holds. Funds can ONLY go to your verified wallet (no destination param), so a leaked key can't redirect them. Returns { ok, tx_hash, amount_usd, to }. 400 insufficient_treasury if the balance can't cover it; 403 withdraws_disabled on the demo rail.",
|
|
259
|
-
{ amount_usd: z.number().positive().describe("USD to withdraw from your treasury to your verified wallet.") },
|
|
260
|
-
async ({ amount_usd }) =>
|
|
261
|
-
guard(() => client.rest("POST", "/api/treasury/withdraw", { body: { amount_usd } })),
|
|
262
|
-
);
|
|
263
|
-
|
|
264
211
|
server.tool(
|
|
265
212
|
"post_task",
|
|
266
|
-
"Open an FCFS pool bounty on the marketplace. There is NO direct hire and NO agent-picks-human — every task is an open bounty: you freeze a budget, ANY eligible human submits first-come-first-served, and you approve/reject each submission. Funds are NOT charged at post — the budget is frozen later at authorize_task. `reward_usd` is the total budget; `quantity` is how many identical units (humans) it pays — each unit holds reward_usd/quantity (each unit must be >= $0.01). Returns the created task (with its id)
|
|
213
|
+
"Open an FCFS pool bounty on the marketplace. There is NO direct hire and NO agent-picks-human — every task is an open bounty: you freeze a budget, ANY eligible human submits first-come-first-served, and you approve/reject each submission. Funds are NOT charged at post — the budget is frozen later at authorize_task. `reward_usd` is the total budget; `quantity` is how many identical units (humans) it pays — each unit holds reward_usd/quantity (each unit must be >= $0.01). Returns the created task (with its id) plus `authIntent` (the budget authorization to sign) and `deployFee` { usd, bps, recipient, token } (a SEPARATE non-refundable fee tx) — pass BOTH to authorize_task. The non-custodial POOL escrow (USDC/BNKR/GITLAWB on Base) is the only settlement rail; a non-real token (CYOS) or non-live config has no rail and returns 422 settlement_unavailable.",
|
|
267
214
|
{
|
|
268
215
|
title: z.string().min(2).max(160).describe("Short task title."),
|
|
269
216
|
category: z.enum(TASK_CATEGORIES),
|
|
@@ -281,7 +228,7 @@ server.tool(
|
|
|
281
228
|
|
|
282
229
|
server.tool(
|
|
283
230
|
"authorize_task",
|
|
284
|
-
"Freeze the bounty budget on-chain (the second step of the FCFS flow). REAL-TOKEN POOL rail: pass BOTH `auth_intent` (the authIntent from post_task) AND `deploy_fee` (the deployFee object from post_task) — with CYBERDYNE_EVM_PRIVATE_KEY set, the MCP signs the whole-budget authorization AND pays the separate 2.5% USDC / 5% other-token deploy fee tx from its wallet, then freezes the budget on the audited escrow; or pass a pre-signed `signed_payment` and a pre-paid `fee_tx_hash`. After this, any eligible human submits FCFS and you review_submission each.
|
|
231
|
+
"Freeze the bounty budget on-chain (the second step of the FCFS flow). REAL-TOKEN POOL rail: pass BOTH `auth_intent` (the authIntent from post_task) AND `deploy_fee` (the deployFee object from post_task) — with CYBERDYNE_EVM_PRIVATE_KEY set, the MCP signs the whole-budget authorization AND pays the separate 2.5% USDC / 5% other-token deploy fee tx from its wallet, then freezes the budget on the audited escrow; or pass a pre-signed `signed_payment` and a pre-paid `fee_tx_hash`. After this, any eligible human submits FCFS and you review_submission each. The non-custodial POOL escrow is the only rail; a non-real token / non-live config returns 409 settlement_unavailable. Idempotent once frozen.",
|
|
285
232
|
{
|
|
286
233
|
task_id: z.string().uuid(),
|
|
287
234
|
signed_payment: z.string().optional().describe("Pre-signed base64 auth-capture payload (external/Bankr signer)."),
|
|
@@ -403,13 +350,10 @@ server.registerPrompt(
|
|
|
403
350
|
text: [
|
|
404
351
|
"You are connected to CYBERDYNE — pay verified humans for tasks AI can't do alone. There is ONE model: every task is an open FCFS pool bounty. There is NO direct hire and NO picking a human — you freeze a budget, ANY eligible human submits first-come-first-served, and you approve/reject each submission (approved = paid one unit in-token, rejected = the slot reopens). The live settlement rail is REAL tokens on Base (non-custodial freeze-at-deploy). The human submit-proof step is human-only, in the app; you drive everything else.",
|
|
405
352
|
"",
|
|
406
|
-
"FUND (
|
|
407
|
-
"1. get_deposit_address -> the platform deposit address on Base.",
|
|
408
|
-
"2. Send USDC to it FROM your own verified wallet, then deposit({ tx_hash }) -> credits your treasury (idempotent).",
|
|
409
|
-
" (fund_treasury is demo/testnet only and is disabled on the live rail.) Check get_treasury anytime.",
|
|
353
|
+
"FUND: hold USDC (or BNKR/GITLAWB) + a little ETH for gas in your OWN wallet on Base. The pool freezes the budget directly from your wallet at deploy and pays the deploy fee from it — there is NO platform treasury to deposit into (fully non-custodial).",
|
|
410
354
|
"",
|
|
411
355
|
"POST + PAY (the single FCFS flow):",
|
|
412
|
-
"3. post_task({ title, category, reward_usd, quantity
|
|
356
|
+
"3. post_task({ title, category, reward_usd, quantity }) -> returns { task, authIntent, deployFee }. reward_usd is the TOTAL budget; quantity is how many humans it pays (each unit must be >= $0.01). authIntent is the whole-budget authorization; deployFee is a SEPARATE non-refundable fee tx (2.5% USDC / 5% other token).",
|
|
413
357
|
"4. authorize_task({ task_id, auth_intent, deploy_fee }) -> with CYBERDYNE_EVM_PRIVATE_KEY set, the MCP signs the budget AND pays the deploy fee, then FREEZES the whole budget on the audited escrow (or pass pre-made signed_payment + fee_tx_hash).",
|
|
414
358
|
"5. Any eligible human submits FCFS. Poll get_task; for EACH pending submission call review_submission({ submission_id, approve, score }) -> approve captures one unit (full reward to the human, in-token); reject reopens the slot for the next submitter.",
|
|
415
359
|
"6. close_task({ task_id }) -> refunds the unfilled budget back to your wallet (the deploy fee is non-refundable).",
|
|
@@ -431,6 +375,6 @@ await server.connect(transport);
|
|
|
431
375
|
console.error(
|
|
432
376
|
`CYBERDYNE MCP server running on stdio → ${config.apiUrl}` +
|
|
433
377
|
(config.token ? "" : " (no key — run `npx cyberdyne-mcp onboard` to self-generate a wallet + key, or `login cyb_…`, or set CYBERDYNE_IDENTITY_TOKEN; networked tools error until then)") +
|
|
434
|
-
". Tools (
|
|
435
|
-
" CLI: onboard, login,
|
|
378
|
+
". Tools (9): onboard, list_categories, search_humans, post_task, authorize_task, get_task, review_submission, close_task, reclaim." +
|
|
379
|
+
" CLI: onboard, login, post, tasks.",
|
|
436
380
|
);
|
package/dist/founder-check.js
DELETED
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example: a trading agent hires a human for a founder liveness check before it buys.
|
|
3
|
-
*
|
|
4
|
-
* A trading agent can run every on-chain check itself — but it can't tell whether
|
|
5
|
-
* a real person is behind a token. Fake / impersonated / deepfaked teams are the
|
|
6
|
-
* #1 token scam, and defeating a deepfake on a live call is something only a human
|
|
7
|
-
* can do. So before a risky buy, the agent hires a human through CYBERDYNE to
|
|
8
|
-
* video-verify the founder, then settles on a passing verify. This is the pattern
|
|
9
|
-
* behind e.g. Bankr (https://bankr.bot) and any x402 trader.
|
|
10
|
-
*
|
|
11
|
-
* This drives the LIVE platform. Requires CYBERDYNE_IDENTITY_TOKEN (a cyb_ key);
|
|
12
|
-
* optionally CYBERDYNE_API_URL. No key is hardcoded — it no-ops without one.
|
|
13
|
-
*
|
|
14
|
-
* CYBERDYNE_API_URL=http://localhost:3000 CYBERDYNE_IDENTITY_TOKEN=cyb_… \
|
|
15
|
-
* npm run build && npm run founder-check
|
|
16
|
-
*/
|
|
17
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
18
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
19
|
-
const token = process.env.CYBERDYNE_IDENTITY_TOKEN;
|
|
20
|
-
if (!token) {
|
|
21
|
-
console.log("founder-check: no-op. Set CYBERDYNE_IDENTITY_TOKEN (a cyb_ key) and optionally " +
|
|
22
|
-
"CYBERDYNE_API_URL to run this example against the live platform.");
|
|
23
|
-
process.exit(0);
|
|
24
|
-
}
|
|
25
|
-
const transport = new StdioClientTransport({
|
|
26
|
-
command: "node",
|
|
27
|
-
args: ["dist/server.js"],
|
|
28
|
-
env: { ...process.env },
|
|
29
|
-
});
|
|
30
|
-
const client = new Client({ name: "trading-agent", version: "0" });
|
|
31
|
-
await client.connect(transport);
|
|
32
|
-
const call = async (name, args = {}) => {
|
|
33
|
-
const r = await client.callTool({ name, arguments: args });
|
|
34
|
-
const data = JSON.parse(r.content[0].text);
|
|
35
|
-
if (r.isError)
|
|
36
|
-
throw new Error(`${name} → ${data.error}`);
|
|
37
|
-
return data;
|
|
38
|
-
};
|
|
39
|
-
console.log("[agent] connected to CYBERDYNE over MCP\n");
|
|
40
|
-
// 0. Make sure the agent treasury can cover the bounty (demo top-up).
|
|
41
|
-
const t = await call("get_treasury");
|
|
42
|
-
if (Number(t.treasury?.balance_usd ?? 0) < 50) {
|
|
43
|
-
await call("fund_treasury", { amount_usd: 100 });
|
|
44
|
-
}
|
|
45
|
-
// 1. The agent can't verify a real human is behind the token — find one who can.
|
|
46
|
-
const found = await call("search_humans", { skills: ["groundtruth"], min_reputation: 4.8 });
|
|
47
|
-
const human = found.humans[0];
|
|
48
|
-
console.log(`search_humans(groundtruth, min_rep 4.8) -> ${found.humans.length} match`);
|
|
49
|
-
if (human)
|
|
50
|
-
console.log(` hiring ${human.handle} ${human.location} rep ${human.reputation}\n`);
|
|
51
|
-
// 2. Post the founder liveness check. Not charged until authorize.
|
|
52
|
-
const posted = await call("post_task", {
|
|
53
|
-
title: "Founder liveness check on $PEPE2",
|
|
54
|
-
category: "groundtruth",
|
|
55
|
-
description: "Get the claimed founder on a short video call and confirm they're a real, specific " +
|
|
56
|
-
"person — not an impersonator or deepfake — matched to a known reference. Return verified / not + notes.",
|
|
57
|
-
steps: [
|
|
58
|
-
"Live video matches a known reference",
|
|
59
|
-
"Passes liveness / deepfake probes => verified; otherwise not-verified + reasons",
|
|
60
|
-
],
|
|
61
|
-
reward_usd: 50,
|
|
62
|
-
duration_min: 30,
|
|
63
|
-
difficulty: "hard",
|
|
64
|
-
deadline_hours: 2,
|
|
65
|
-
});
|
|
66
|
-
const taskId = posted.task.id;
|
|
67
|
-
console.log(`post_task -> ${taskId} reward $${posted.task.reward_usd}`);
|
|
68
|
-
// (search_humans is discovery only — there is NO direct hire. The task is an open
|
|
69
|
-
// FCFS pool bounty: ANY eligible human submits first-come-first-served.)
|
|
70
|
-
void human;
|
|
71
|
-
// 3. Authorize — freeze the budget (POOL: authIntent + deployFee; manual: just task_id).
|
|
72
|
-
const authd = await call("authorize_task", {
|
|
73
|
-
task_id: taskId,
|
|
74
|
-
...(posted.authIntent ? { auth_intent: posted.authIntent } : {}),
|
|
75
|
-
...(posted.deployFee ? { deploy_fee: posted.deployFee } : {}),
|
|
76
|
-
});
|
|
77
|
-
console.log(`authorize_task -> escrow ${authd.task?.escrow_status}`);
|
|
78
|
-
// 4. Poll until the human's proof is in (they submit in the app — human-only).
|
|
79
|
-
const got = await call("get_task", { task_id: taskId });
|
|
80
|
-
console.log(`get_task -> ${got.task.status} submissions ${got.submissions.length}`);
|
|
81
|
-
// 5. If the proof is in, verify it and capture one unit to the human.
|
|
82
|
-
const pending = (got.submissions ?? []).find((s) => s.status === "pending");
|
|
83
|
-
if (pending) {
|
|
84
|
-
const settled = await call("review_submission", { submission_id: pending.id, approve: true, score: 5 });
|
|
85
|
-
console.log(`review_submission -> captured ${JSON.stringify(settled).slice(0, 80)}`);
|
|
86
|
-
console.log("\n[agent] has a human verification it could not produce itself, and the contributor is paid.");
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
console.log("\n[agent] bounty is live and the budget is frozen; humans submit proof in the app FCFS, then the agent reviews each.");
|
|
90
|
-
}
|
|
91
|
-
await client.close();
|
package/dist/smoke.js
DELETED
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live smoke test: drive the MCP server against the REAL platform API like an
|
|
3
|
-
* agent would. Not shipped.
|
|
4
|
-
*
|
|
5
|
-
* Requires both env vars to run for real:
|
|
6
|
-
* CYBERDYNE_API_URL e.g. http://localhost:3000 or https://app.cyberdyne-os.xyz
|
|
7
|
-
* CYBERDYNE_IDENTITY_TOKEN the agent's cyb_ key
|
|
8
|
-
* If either is missing it no-ops with a clear message (no key is ever hardcoded).
|
|
9
|
-
*
|
|
10
|
-
* The server inherits this process's env over stdio, so the same creds drive it.
|
|
11
|
-
*/
|
|
12
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
13
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
14
|
-
const apiUrl = process.env.CYBERDYNE_API_URL;
|
|
15
|
-
const token = process.env.CYBERDYNE_IDENTITY_TOKEN;
|
|
16
|
-
if (!token) {
|
|
17
|
-
console.log("smoke: no-op. Set CYBERDYNE_IDENTITY_TOKEN (a cyb_ key) and optionally " +
|
|
18
|
-
"CYBERDYNE_API_URL to run the live flow against the platform.\n" +
|
|
19
|
-
" e.g. CYBERDYNE_API_URL=http://localhost:3000 CYBERDYNE_IDENTITY_TOKEN=cyb_… npm run smoke");
|
|
20
|
-
process.exit(0);
|
|
21
|
-
}
|
|
22
|
-
const transport = new StdioClientTransport({
|
|
23
|
-
command: "node",
|
|
24
|
-
args: ["dist/server.js"],
|
|
25
|
-
env: { ...process.env },
|
|
26
|
-
});
|
|
27
|
-
const client = new Client({ name: "smoke", version: "0" });
|
|
28
|
-
await client.connect(transport);
|
|
29
|
-
const call = async (name, args = {}) => {
|
|
30
|
-
const r = await client.callTool({ name, arguments: args });
|
|
31
|
-
const data = JSON.parse(r.content[0].text);
|
|
32
|
-
if (r.isError)
|
|
33
|
-
throw new Error(`${name} → ${data.error}`);
|
|
34
|
-
return data;
|
|
35
|
-
};
|
|
36
|
-
console.log(`smoke → ${apiUrl ?? "https://app.cyberdyne-os.xyz"}`);
|
|
37
|
-
console.log("tools:", (await client.listTools()).tools.map((t) => t.name).join(", "));
|
|
38
|
-
// 1. Categories (static, no network).
|
|
39
|
-
const cats = await call("list_categories");
|
|
40
|
-
console.log(`list_categories → ${cats.length} categories`);
|
|
41
|
-
// 2. Treasury — ensure it can cover a small task; top up if needed.
|
|
42
|
-
let treasury = await call("get_treasury");
|
|
43
|
-
const balance = Number(treasury.treasury?.balance_usd ?? 0);
|
|
44
|
-
console.log(`get_treasury → balance $${balance}`);
|
|
45
|
-
if (balance < 5) {
|
|
46
|
-
treasury = await call("fund_treasury", { amount_usd: 25 });
|
|
47
|
-
console.log(`fund_treasury → balance $${treasury.treasury?.balance_usd}`);
|
|
48
|
-
}
|
|
49
|
-
// 3. Discover a human via the live capability index.
|
|
50
|
-
const found = await call("search_humans", { skills: ["capture"] });
|
|
51
|
-
console.log(`search_humans(capture) → ${found.humans.length} match`);
|
|
52
|
-
const human = found.humans[0];
|
|
53
|
-
// (search_humans is for discovery only — there is NO direct hire/assign. Every task
|
|
54
|
-
// is an open FCFS pool bounty: post a budget, humans submit, you review each.)
|
|
55
|
-
void human;
|
|
56
|
-
// 4. Post an FCFS pool bounty. reward_usd is the budget; not charged until authorize.
|
|
57
|
-
const posted = await call("post_task", {
|
|
58
|
-
title: "Read 10 phrases (smoke)",
|
|
59
|
-
category: "capture",
|
|
60
|
-
description: "Record 10 short phrases clearly in a quiet room.",
|
|
61
|
-
reward_usd: 3.5,
|
|
62
|
-
duration_min: 10,
|
|
63
|
-
difficulty: "easy",
|
|
64
|
-
});
|
|
65
|
-
const taskId = posted.task.id;
|
|
66
|
-
console.log(`post_task → ${taskId} (status ${posted.task.status})`);
|
|
67
|
-
// 5. Authorize — freeze the budget (POOL: pass authIntent + deployFee; manual: just task_id).
|
|
68
|
-
const authd = await call("authorize_task", {
|
|
69
|
-
task_id: taskId,
|
|
70
|
-
...(posted.authIntent ? { auth_intent: posted.authIntent } : {}),
|
|
71
|
-
...(posted.deployFee ? { deploy_fee: posted.deployFee } : {}),
|
|
72
|
-
});
|
|
73
|
-
console.log(`authorize_task → escrow_status ${authd.task?.escrow_status}`);
|
|
74
|
-
// 6. Poll the live task. The human submits proof in the app (human-only), so on a
|
|
75
|
-
// fresh task there is typically no submission yet — that is expected here.
|
|
76
|
-
const got = await call("get_task", { task_id: taskId });
|
|
77
|
-
console.log(`get_task → status ${got.task.status}, submissions ${got.submissions.length}, claims ${got.claims.length}`);
|
|
78
|
-
const pending = (got.submissions ?? []).find((s) => s.status === "pending");
|
|
79
|
-
if (pending) {
|
|
80
|
-
const settled = await call("review_submission", { submission_id: pending.id, approve: true, score: 5 });
|
|
81
|
-
console.log(`review_submission → captured one unit ${JSON.stringify(settled).slice(0, 80)}`);
|
|
82
|
-
}
|
|
83
|
-
else {
|
|
84
|
-
console.log("review_submission → skipped: no pending submission yet (a human submits proof in the app). " +
|
|
85
|
-
"Closing the task to refund the unfilled budget.");
|
|
86
|
-
const closed = await call("close_task", { task_id: taskId });
|
|
87
|
-
console.log(`close_task → status ${closed.task?.status ?? "closed"}`);
|
|
88
|
-
}
|
|
89
|
-
await client.close();
|
|
90
|
-
console.log("OK");
|
package/src/founder-check.ts
DELETED
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Example: a trading agent hires a human for a founder liveness check before it buys.
|
|
3
|
-
*
|
|
4
|
-
* A trading agent can run every on-chain check itself — but it can't tell whether
|
|
5
|
-
* a real person is behind a token. Fake / impersonated / deepfaked teams are the
|
|
6
|
-
* #1 token scam, and defeating a deepfake on a live call is something only a human
|
|
7
|
-
* can do. So before a risky buy, the agent hires a human through CYBERDYNE to
|
|
8
|
-
* video-verify the founder, then settles on a passing verify. This is the pattern
|
|
9
|
-
* behind e.g. Bankr (https://bankr.bot) and any x402 trader.
|
|
10
|
-
*
|
|
11
|
-
* This drives the LIVE platform. Requires CYBERDYNE_IDENTITY_TOKEN (a cyb_ key);
|
|
12
|
-
* optionally CYBERDYNE_API_URL. No key is hardcoded — it no-ops without one.
|
|
13
|
-
*
|
|
14
|
-
* CYBERDYNE_API_URL=http://localhost:3000 CYBERDYNE_IDENTITY_TOKEN=cyb_… \
|
|
15
|
-
* npm run build && npm run founder-check
|
|
16
|
-
*/
|
|
17
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
18
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
19
|
-
|
|
20
|
-
const token = process.env.CYBERDYNE_IDENTITY_TOKEN;
|
|
21
|
-
if (!token) {
|
|
22
|
-
console.log(
|
|
23
|
-
"founder-check: no-op. Set CYBERDYNE_IDENTITY_TOKEN (a cyb_ key) and optionally " +
|
|
24
|
-
"CYBERDYNE_API_URL to run this example against the live platform.",
|
|
25
|
-
);
|
|
26
|
-
process.exit(0);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const transport = new StdioClientTransport({
|
|
30
|
-
command: "node",
|
|
31
|
-
args: ["dist/server.js"],
|
|
32
|
-
env: { ...process.env } as Record<string, string>,
|
|
33
|
-
});
|
|
34
|
-
const client = new Client({ name: "trading-agent", version: "0" });
|
|
35
|
-
await client.connect(transport);
|
|
36
|
-
|
|
37
|
-
const call = async (name: string, args: Record<string, unknown> = {}) => {
|
|
38
|
-
const r: any = await client.callTool({ name, arguments: args });
|
|
39
|
-
const data = JSON.parse(r.content[0].text);
|
|
40
|
-
if (r.isError) throw new Error(`${name} → ${data.error}`);
|
|
41
|
-
return data;
|
|
42
|
-
};
|
|
43
|
-
|
|
44
|
-
console.log("[agent] connected to CYBERDYNE over MCP\n");
|
|
45
|
-
|
|
46
|
-
// 0. Make sure the agent treasury can cover the bounty (demo top-up).
|
|
47
|
-
const t = await call("get_treasury");
|
|
48
|
-
if (Number(t.treasury?.balance_usd ?? 0) < 50) {
|
|
49
|
-
await call("fund_treasury", { amount_usd: 100 });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 1. The agent can't verify a real human is behind the token — find one who can.
|
|
53
|
-
const found = await call("search_humans", { skills: ["groundtruth"], min_reputation: 4.8 });
|
|
54
|
-
const human = found.humans[0];
|
|
55
|
-
console.log(`search_humans(groundtruth, min_rep 4.8) -> ${found.humans.length} match`);
|
|
56
|
-
if (human) console.log(` hiring ${human.handle} ${human.location} rep ${human.reputation}\n`);
|
|
57
|
-
|
|
58
|
-
// 2. Post the founder liveness check. Not charged until authorize.
|
|
59
|
-
const posted = await call("post_task", {
|
|
60
|
-
title: "Founder liveness check on $PEPE2",
|
|
61
|
-
category: "groundtruth",
|
|
62
|
-
description:
|
|
63
|
-
"Get the claimed founder on a short video call and confirm they're a real, specific " +
|
|
64
|
-
"person — not an impersonator or deepfake — matched to a known reference. Return verified / not + notes.",
|
|
65
|
-
steps: [
|
|
66
|
-
"Live video matches a known reference",
|
|
67
|
-
"Passes liveness / deepfake probes => verified; otherwise not-verified + reasons",
|
|
68
|
-
],
|
|
69
|
-
reward_usd: 50,
|
|
70
|
-
duration_min: 30,
|
|
71
|
-
difficulty: "hard",
|
|
72
|
-
deadline_hours: 2,
|
|
73
|
-
});
|
|
74
|
-
const taskId = posted.task.id;
|
|
75
|
-
console.log(`post_task -> ${taskId} reward $${posted.task.reward_usd}`);
|
|
76
|
-
|
|
77
|
-
// (search_humans is discovery only — there is NO direct hire. The task is an open
|
|
78
|
-
// FCFS pool bounty: ANY eligible human submits first-come-first-served.)
|
|
79
|
-
void human;
|
|
80
|
-
|
|
81
|
-
// 3. Authorize — freeze the budget (POOL: authIntent + deployFee; manual: just task_id).
|
|
82
|
-
const authd = await call("authorize_task", {
|
|
83
|
-
task_id: taskId,
|
|
84
|
-
...(posted.authIntent ? { auth_intent: posted.authIntent } : {}),
|
|
85
|
-
...(posted.deployFee ? { deploy_fee: posted.deployFee } : {}),
|
|
86
|
-
});
|
|
87
|
-
console.log(`authorize_task -> escrow ${authd.task?.escrow_status}`);
|
|
88
|
-
|
|
89
|
-
// 4. Poll until the human's proof is in (they submit in the app — human-only).
|
|
90
|
-
const got = await call("get_task", { task_id: taskId });
|
|
91
|
-
console.log(`get_task -> ${got.task.status} submissions ${got.submissions.length}`);
|
|
92
|
-
|
|
93
|
-
// 5. If the proof is in, verify it and capture one unit to the human.
|
|
94
|
-
const pending = (got.submissions ?? []).find((s: any) => s.status === "pending");
|
|
95
|
-
if (pending) {
|
|
96
|
-
const settled = await call("review_submission", { submission_id: pending.id, approve: true, score: 5 });
|
|
97
|
-
console.log(`review_submission -> captured ${JSON.stringify(settled).slice(0, 80)}`);
|
|
98
|
-
console.log("\n[agent] has a human verification it could not produce itself, and the contributor is paid.");
|
|
99
|
-
} else {
|
|
100
|
-
console.log("\n[agent] bounty is live and the budget is frozen; humans submit proof in the app FCFS, then the agent reviews each.");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
await client.close();
|
package/src/smoke.ts
DELETED
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Live smoke test: drive the MCP server against the REAL platform API like an
|
|
3
|
-
* agent would. Not shipped.
|
|
4
|
-
*
|
|
5
|
-
* Requires both env vars to run for real:
|
|
6
|
-
* CYBERDYNE_API_URL e.g. http://localhost:3000 or https://app.cyberdyne-os.xyz
|
|
7
|
-
* CYBERDYNE_IDENTITY_TOKEN the agent's cyb_ key
|
|
8
|
-
* If either is missing it no-ops with a clear message (no key is ever hardcoded).
|
|
9
|
-
*
|
|
10
|
-
* The server inherits this process's env over stdio, so the same creds drive it.
|
|
11
|
-
*/
|
|
12
|
-
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
13
|
-
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
14
|
-
|
|
15
|
-
const apiUrl = process.env.CYBERDYNE_API_URL;
|
|
16
|
-
const token = process.env.CYBERDYNE_IDENTITY_TOKEN;
|
|
17
|
-
|
|
18
|
-
if (!token) {
|
|
19
|
-
console.log(
|
|
20
|
-
"smoke: no-op. Set CYBERDYNE_IDENTITY_TOKEN (a cyb_ key) and optionally " +
|
|
21
|
-
"CYBERDYNE_API_URL to run the live flow against the platform.\n" +
|
|
22
|
-
" e.g. CYBERDYNE_API_URL=http://localhost:3000 CYBERDYNE_IDENTITY_TOKEN=cyb_… npm run smoke",
|
|
23
|
-
);
|
|
24
|
-
process.exit(0);
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
const transport = new StdioClientTransport({
|
|
28
|
-
command: "node",
|
|
29
|
-
args: ["dist/server.js"],
|
|
30
|
-
env: { ...process.env } as Record<string, string>,
|
|
31
|
-
});
|
|
32
|
-
const client = new Client({ name: "smoke", version: "0" });
|
|
33
|
-
await client.connect(transport);
|
|
34
|
-
|
|
35
|
-
const call = async (name: string, args: Record<string, unknown> = {}) => {
|
|
36
|
-
const r: any = await client.callTool({ name, arguments: args });
|
|
37
|
-
const data = JSON.parse(r.content[0].text);
|
|
38
|
-
if (r.isError) throw new Error(`${name} → ${data.error}`);
|
|
39
|
-
return data;
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
console.log(`smoke → ${apiUrl ?? "https://app.cyberdyne-os.xyz"}`);
|
|
43
|
-
console.log("tools:", (await client.listTools()).tools.map((t) => t.name).join(", "));
|
|
44
|
-
|
|
45
|
-
// 1. Categories (static, no network).
|
|
46
|
-
const cats = await call("list_categories");
|
|
47
|
-
console.log(`list_categories → ${cats.length} categories`);
|
|
48
|
-
|
|
49
|
-
// 2. Treasury — ensure it can cover a small task; top up if needed.
|
|
50
|
-
let treasury = await call("get_treasury");
|
|
51
|
-
const balance = Number(treasury.treasury?.balance_usd ?? 0);
|
|
52
|
-
console.log(`get_treasury → balance $${balance}`);
|
|
53
|
-
if (balance < 5) {
|
|
54
|
-
treasury = await call("fund_treasury", { amount_usd: 25 });
|
|
55
|
-
console.log(`fund_treasury → balance $${treasury.treasury?.balance_usd}`);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 3. Discover a human via the live capability index.
|
|
59
|
-
const found = await call("search_humans", { skills: ["capture"] });
|
|
60
|
-
console.log(`search_humans(capture) → ${found.humans.length} match`);
|
|
61
|
-
const human = found.humans[0];
|
|
62
|
-
|
|
63
|
-
// (search_humans is for discovery only — there is NO direct hire/assign. Every task
|
|
64
|
-
// is an open FCFS pool bounty: post a budget, humans submit, you review each.)
|
|
65
|
-
void human;
|
|
66
|
-
|
|
67
|
-
// 4. Post an FCFS pool bounty. reward_usd is the budget; not charged until authorize.
|
|
68
|
-
const posted = await call("post_task", {
|
|
69
|
-
title: "Read 10 phrases (smoke)",
|
|
70
|
-
category: "capture",
|
|
71
|
-
description: "Record 10 short phrases clearly in a quiet room.",
|
|
72
|
-
reward_usd: 3.5,
|
|
73
|
-
duration_min: 10,
|
|
74
|
-
difficulty: "easy",
|
|
75
|
-
});
|
|
76
|
-
const taskId = posted.task.id;
|
|
77
|
-
console.log(`post_task → ${taskId} (status ${posted.task.status})`);
|
|
78
|
-
|
|
79
|
-
// 5. Authorize — freeze the budget (POOL: pass authIntent + deployFee; manual: just task_id).
|
|
80
|
-
const authd = await call("authorize_task", {
|
|
81
|
-
task_id: taskId,
|
|
82
|
-
...(posted.authIntent ? { auth_intent: posted.authIntent } : {}),
|
|
83
|
-
...(posted.deployFee ? { deploy_fee: posted.deployFee } : {}),
|
|
84
|
-
});
|
|
85
|
-
console.log(`authorize_task → escrow_status ${authd.task?.escrow_status}`);
|
|
86
|
-
|
|
87
|
-
// 6. Poll the live task. The human submits proof in the app (human-only), so on a
|
|
88
|
-
// fresh task there is typically no submission yet — that is expected here.
|
|
89
|
-
const got = await call("get_task", { task_id: taskId });
|
|
90
|
-
console.log(
|
|
91
|
-
`get_task → status ${got.task.status}, submissions ${got.submissions.length}, claims ${got.claims.length}`,
|
|
92
|
-
);
|
|
93
|
-
|
|
94
|
-
const pending = (got.submissions ?? []).find((s: any) => s.status === "pending");
|
|
95
|
-
if (pending) {
|
|
96
|
-
const settled = await call("review_submission", { submission_id: pending.id, approve: true, score: 5 });
|
|
97
|
-
console.log(`review_submission → captured one unit ${JSON.stringify(settled).slice(0, 80)}`);
|
|
98
|
-
} else {
|
|
99
|
-
console.log(
|
|
100
|
-
"review_submission → skipped: no pending submission yet (a human submits proof in the app). " +
|
|
101
|
-
"Closing the task to refund the unfilled budget.",
|
|
102
|
-
);
|
|
103
|
-
const closed = await call("close_task", { task_id: taskId });
|
|
104
|
-
console.log(`close_task → status ${closed.task?.status ?? "closed"}`);
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
await client.close();
|
|
108
|
-
console.log("OK");
|