cyberdyne-mcp 0.6.1 → 0.6.3
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 +28 -0
- package/dist/cli.js +226 -0
- package/dist/server.js +60 -50
- package/package.json +1 -1
- package/src/cli.ts +253 -0
- package/src/server.ts +61 -50
package/README.md
CHANGED
|
@@ -39,6 +39,34 @@ fund (`get_deposit_address` → send USDC on Base → `deposit`) and `post_task`
|
|
|
39
39
|
> agent-side action — onboard, fund, post, assign, authorize, review, close — is
|
|
40
40
|
> headless.
|
|
41
41
|
|
|
42
|
+
## CLI
|
|
43
|
+
|
|
44
|
+
Beyond `onboard`/`login`, the package ships Bankr-style convenience subcommands —
|
|
45
|
+
each runs once, prints a summary, and exits (no MCP needed). They use the wallet +
|
|
46
|
+
`cyb_` key saved by `onboard`.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
npx -y cyberdyne-mcp onboard # generate a wallet + mint your cyb_ key (run this first)
|
|
50
|
+
npx -y cyberdyne-mcp treasury # balance + where to send USDC (alias: balance, fees)
|
|
51
|
+
npx -y cyberdyne-mcp post --title "Like our launch tweet" --token BNKR --reward 100 --quantity 1
|
|
52
|
+
npx -y cyberdyne-mcp tasks # list your posted tasks + status
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
| Command | Usage | What it does |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| `treasury` | `cyberdyne-mcp treasury` (alias `balance`, `fees`) | Like `bankr fees`. Prints balance, total funded, total spent, **and** the deposit address to send USDC to. |
|
|
58
|
+
| `post` | `cyberdyne-mcp post --title <t> --reward <n> [--token USDC\|BNKR\|GITLAWB] [--quantity <n>] [--category <c>] [--action follow\|retweet\|reply\|quote\|original-post] [--url <x.com/…>] [--rail pool\|custodial]` | Like `bankr launch`. Opens a task. On the **pool** rail (default for BNKR/GITLAWB or `--quantity>1`) it autonomously signs the budget, pays the deploy fee from your wallet, and authorizes — printing each stage and the final task id + `escrow_status`. On the custodial rail it just prints the posted task. |
|
|
59
|
+
| `tasks` | `cyberdyne-mcp tasks` | Lists your own posted tasks: id, title, token, quantity, filled/remaining, status. |
|
|
60
|
+
|
|
61
|
+
Flags accept both `--flag value` and `--flag=value`. `--title` and `--reward` (per
|
|
62
|
+
unit, in the pay token) are required for `post`; everything else has a default
|
|
63
|
+
(`--token USDC`, `--quantity 1`, `--category social`). The pool rail needs the saved
|
|
64
|
+
signing wallet — if none is present, `post` tells you to run `onboard` first.
|
|
65
|
+
|
|
66
|
+
> The human **submit-proof** step still happens in the app (human-only). After a
|
|
67
|
+
> pool launch, humans claim + submit FCFS; review each submission (via the
|
|
68
|
+
> `review_submission` MCP tool) to capture a unit.
|
|
69
|
+
|
|
42
70
|
## Configuration (environment)
|
|
43
71
|
|
|
44
72
|
stdio MCP servers take their credentials from the environment. Set:
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bankr-style convenience CLI for CYBERDYNE — `treasury` / `post` / `tasks`.
|
|
3
|
+
*
|
|
4
|
+
* These are ADDITIONAL command-line entry points (not MCP tools). They run, print
|
|
5
|
+
* a human-readable summary to stderr, and exit — exactly like `onboard`/`login`.
|
|
6
|
+
* They mirror the Bankr CLI UX (`bankr fees`, `bankr launch`): a single command
|
|
7
|
+
* that does the full thing autonomously using the saved `cyb_` key + wallet.
|
|
8
|
+
*
|
|
9
|
+
* treasury (alias balance, fees) — your balance + where to send USDC (bankr fees)
|
|
10
|
+
* post — open a task; on the pool rail, sign + pay +
|
|
11
|
+
* authorize in one shot (bankr launch)
|
|
12
|
+
* tasks — list your own posted tasks with status
|
|
13
|
+
*
|
|
14
|
+
* Networking reuses CyberdyneClient (the saved Bearer key); pool signing/fee
|
|
15
|
+
* payment reuses src/evm-signer.ts — no signing logic is reinvented here.
|
|
16
|
+
*/
|
|
17
|
+
import { CyberdyneClient, readConfig, ApiError, MissingTokenError } from "./client.js";
|
|
18
|
+
// ── tiny argv parser ───────────────────────────────────────────────────────
|
|
19
|
+
// Supports `--flag value` and `--flag=value`. Bare `--flag` (no value) ⇒ "true".
|
|
20
|
+
export function parseFlags(argv) {
|
|
21
|
+
const out = {};
|
|
22
|
+
for (let i = 0; i < argv.length; i++) {
|
|
23
|
+
const tok = argv[i];
|
|
24
|
+
if (!tok.startsWith("--"))
|
|
25
|
+
continue;
|
|
26
|
+
const body = tok.slice(2);
|
|
27
|
+
const eq = body.indexOf("=");
|
|
28
|
+
if (eq >= 0) {
|
|
29
|
+
out[body.slice(0, eq)] = body.slice(eq + 1);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const next = argv[i + 1];
|
|
33
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
34
|
+
out[body] = next;
|
|
35
|
+
i++;
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
out[body] = "true";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
43
|
+
function client() {
|
|
44
|
+
return new CyberdyneClient(readConfig());
|
|
45
|
+
}
|
|
46
|
+
function hasKey() {
|
|
47
|
+
return !!readConfig().token;
|
|
48
|
+
}
|
|
49
|
+
const NO_KEY = "No CYBERDYNE key saved. Run: npx -y cyberdyne-mcp onboard";
|
|
50
|
+
/** Format a USD-ish numeric value (handles string/number/null) to 2dp. */
|
|
51
|
+
function usd(v) {
|
|
52
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
53
|
+
return Number.isFinite(n) ? `$${n.toFixed(2)}` : "—";
|
|
54
|
+
}
|
|
55
|
+
function fail(msg) {
|
|
56
|
+
console.error(`✗ ${msg}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
/** Map a thrown client error to a clean one-line message. */
|
|
60
|
+
function describe(e) {
|
|
61
|
+
if (e instanceof MissingTokenError)
|
|
62
|
+
return NO_KEY;
|
|
63
|
+
if (e instanceof ApiError)
|
|
64
|
+
return e.message;
|
|
65
|
+
return e instanceof Error ? e.message : String(e);
|
|
66
|
+
}
|
|
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
|
+
// ── post (bankr launch) ──────────────────────────────────────────────────────
|
|
111
|
+
// Open a task. Per-unit `--reward` × `--quantity` = the budget. On the pool rail
|
|
112
|
+
// (BNKR/GITLAWB or quantity>1) the response carries authIntent + deployFee: sign
|
|
113
|
+
// the budget + pay the fee + authorize, all from the saved wallet, autonomously.
|
|
114
|
+
const PAY_TOKENS = new Set(["USDC", "BNKR", "GITLAWB"]);
|
|
115
|
+
export async function runPost(argv) {
|
|
116
|
+
if (!hasKey())
|
|
117
|
+
fail(NO_KEY);
|
|
118
|
+
const f = parseFlags(argv);
|
|
119
|
+
const title = f.title?.trim();
|
|
120
|
+
if (!title)
|
|
121
|
+
fail("--title is required");
|
|
122
|
+
const rewardPerUnit = Number(f.reward);
|
|
123
|
+
if (!Number.isFinite(rewardPerUnit) || rewardPerUnit <= 0)
|
|
124
|
+
fail("--reward <n> is required (per-unit, in the pay token)");
|
|
125
|
+
const token = (f.token ?? "USDC").toUpperCase();
|
|
126
|
+
if (!PAY_TOKENS.has(token))
|
|
127
|
+
fail(`--token must be one of USDC, BNKR, GITLAWB (got ${token})`);
|
|
128
|
+
const quantity = f.quantity != null ? Math.trunc(Number(f.quantity)) : 1;
|
|
129
|
+
if (!Number.isFinite(quantity) || quantity < 1)
|
|
130
|
+
fail("--quantity must be a positive integer");
|
|
131
|
+
const category = (f.category ?? "social").trim();
|
|
132
|
+
const action = f.action?.trim();
|
|
133
|
+
const url = f.url?.trim();
|
|
134
|
+
// Rail: default pool when token is a real ecosystem token (BNKR/GITLAWB) or it's
|
|
135
|
+
// a multi-unit bounty; else custodial single-hold. `--rail` overrides.
|
|
136
|
+
const railFlag = f.rail?.trim().toLowerCase();
|
|
137
|
+
const rail = railFlag === "pool" || railFlag === "custodial"
|
|
138
|
+
? railFlag
|
|
139
|
+
: token === "BNKR" || token === "GITLAWB" || quantity > 1
|
|
140
|
+
? "pool"
|
|
141
|
+
: "custodial";
|
|
142
|
+
// reward_usd is the TOTAL budget (= per-unit × quantity). For non-USDC tokens this
|
|
143
|
+
// figure is the TOKEN amount (the platform settles in-token on the pool rail).
|
|
144
|
+
const reward_usd = Number((rewardPerUnit * quantity).toFixed(6));
|
|
145
|
+
const body = {
|
|
146
|
+
title,
|
|
147
|
+
category,
|
|
148
|
+
reward_usd,
|
|
149
|
+
quantity,
|
|
150
|
+
pay_token: token,
|
|
151
|
+
rail,
|
|
152
|
+
};
|
|
153
|
+
if (category === "social" && action)
|
|
154
|
+
body.social_action = action;
|
|
155
|
+
if (category === "social" && url)
|
|
156
|
+
body.social_target_url = url;
|
|
157
|
+
const c = client();
|
|
158
|
+
try {
|
|
159
|
+
console.error(`→ posting "${title}" (${rewardPerUnit} ${token} × ${quantity} = ${reward_usd} ${token}, ${rail} rail)…`);
|
|
160
|
+
const res = await c.rest("POST", "/api/tasks", { body });
|
|
161
|
+
const taskId = res.task?.id;
|
|
162
|
+
console.error(` ✓ posted — task ${taskId}`);
|
|
163
|
+
// Custodial rail (no authIntent): nothing else to do; the hold opens later.
|
|
164
|
+
if (!res.authIntent || !res.deployFee) {
|
|
165
|
+
console.error(`\n✓ Task ${taskId} is open (custodial rail). Next: assign a human + authorize, then release on a valid proof.`);
|
|
166
|
+
process.exit(0);
|
|
167
|
+
}
|
|
168
|
+
// POOL rail — the autonomous `bankr launch` path. Sign the budget with the saved
|
|
169
|
+
// wallet, pay the separate deploy fee, then authorize. Reuses evm-signer (the
|
|
170
|
+
// exact logic the authorize_task MCP tool uses).
|
|
171
|
+
const { hasEvmKey, signAuthCapture, payDeployFee } = await import("./evm-signer.js");
|
|
172
|
+
if (!hasEvmKey()) {
|
|
173
|
+
fail("pool rail needs a signing wallet, but none is saved. Run `npx -y cyberdyne-mcp onboard` " +
|
|
174
|
+
`(the task ${taskId} is posted but not yet funded).`);
|
|
175
|
+
}
|
|
176
|
+
const requirements = res.authIntent.requirements ?? res.authIntent;
|
|
177
|
+
console.error("→ signing the budget authorization…");
|
|
178
|
+
const signedPayment = await signAuthCapture(requirements);
|
|
179
|
+
const fee = res.deployFee;
|
|
180
|
+
console.error(`→ paying the deploy fee (${usd(fee.usd)} in ${fee.token}) from your wallet…`);
|
|
181
|
+
const feeTx = await payDeployFee({ amountUsd: fee.usd, recipient: fee.recipient, token: fee.token });
|
|
182
|
+
console.error(` ✓ fee paid — ${feeTx}`);
|
|
183
|
+
console.error("→ freezing the budget (authorize)…");
|
|
184
|
+
const authed = await c.rest("POST", `/api/tasks/${taskId}/authorize`, { body: { signedPayment, fee_tx_hash: feeTx } });
|
|
185
|
+
const escrow = authed.task?.escrow_status ?? "held";
|
|
186
|
+
console.error(`\n✓ Launched. task ${taskId} — escrow_status: ${escrow}. ` +
|
|
187
|
+
"Humans can now claim + submit FCFS; review each submission to capture a unit.");
|
|
188
|
+
process.exit(0);
|
|
189
|
+
}
|
|
190
|
+
catch (e) {
|
|
191
|
+
fail(describe(e));
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
// ── tasks ─────────────────────────────────────────────────────────────────
|
|
195
|
+
// List the agent's own posted tasks (GET /api/tasks?mine=posted — works with the
|
|
196
|
+
// agent key). Short: id, title, token, qty, filled/remaining, status.
|
|
197
|
+
export async function runTasks() {
|
|
198
|
+
if (!hasKey())
|
|
199
|
+
fail(NO_KEY);
|
|
200
|
+
const c = client();
|
|
201
|
+
try {
|
|
202
|
+
const { tasks } = await c.rest("GET", "/api/tasks", {
|
|
203
|
+
query: { mine: "posted", limit: 50 },
|
|
204
|
+
});
|
|
205
|
+
if (!tasks || tasks.length === 0) {
|
|
206
|
+
console.error("No posted tasks yet. Post one: npx -y cyberdyne-mcp post --title \"…\" --reward 1");
|
|
207
|
+
process.exit(0);
|
|
208
|
+
}
|
|
209
|
+
const lines = [`Your posted tasks (${tasks.length}):`];
|
|
210
|
+
for (const t of tasks) {
|
|
211
|
+
const qty = Number(t.quantity ?? 1) || 1;
|
|
212
|
+
const filled = Number(t.filled_count ?? t.captured_count ?? 0) || 0;
|
|
213
|
+
const remaining = Math.max(qty - filled, 0);
|
|
214
|
+
const token = String(t.pay_token ?? "USDC");
|
|
215
|
+
const status = String(t.escrow_status ? `${t.status}/${t.escrow_status}` : t.status ?? "—");
|
|
216
|
+
const id = String(t.id ?? "—");
|
|
217
|
+
const title = String(t.title ?? "").slice(0, 40);
|
|
218
|
+
lines.push(` ${id} ${title.padEnd(40)} ${token.padEnd(7)} qty ${qty} filled ${filled}/${qty} (rem ${remaining}) ${status}`);
|
|
219
|
+
}
|
|
220
|
+
console.error(lines.join("\n"));
|
|
221
|
+
process.exit(0);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
fail(describe(e));
|
|
225
|
+
}
|
|
226
|
+
}
|
package/dist/server.js
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
* get_deposit_address — GET /api/treasury/deposit → where to send real USDC (live)
|
|
14
14
|
* deposit — POST /api/treasury/deposit → credit treasury from a real USDC tx
|
|
15
15
|
* withdraw_treasury — POST /api/treasury/withdraw → pull unspent treasury back to your wallet (live)
|
|
16
|
-
* post_task — POST /api/tasks → open
|
|
17
|
-
*
|
|
18
|
-
* authorize_task — POST /api/tasks/[id]/authorize → open the escrow hold
|
|
16
|
+
* post_task — POST /api/tasks → open an FCFS pool bounty
|
|
17
|
+
* authorize_task — POST /api/tasks/[id]/authorize → sign budget + pay fee + freeze
|
|
19
18
|
* get_task — GET /api/tasks/[id] → status + submissions/claims
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* review_submission — POST /api/submissions/[id]/review → approve (pay one unit) / reject (reopen)
|
|
20
|
+
* close_task — POST /api/tasks/[id]/close → refund the unfilled budget
|
|
21
|
+
* assign_task — POST /api/tasks/[id]/assign → DEPRECATED (testnet-only; no direct hire)
|
|
22
|
+
* release_payment — POST /api/tasks/[id]/release → DEPRECATED (testnet-only; use review_submission)
|
|
23
23
|
*
|
|
24
24
|
* Auth: every networked tool sends the agent's `cyb_…` key. The REST routes take
|
|
25
25
|
* it as `Authorization: Bearer …`; search_humans goes through the a2a JSON-RPC
|
|
@@ -27,26 +27,24 @@
|
|
|
27
27
|
* `identity_token`.
|
|
28
28
|
*
|
|
29
29
|
* The HUMAN submit-proof step happens in the app/UI (human-only — agents cannot
|
|
30
|
-
* submit on a human's behalf). There
|
|
30
|
+
* submit on a human's behalf). There is ONE settlement model for real tokens:
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* →
|
|
32
|
+
* FCFS POOL BOUNTY (non-custodial pool escrow). There is NO direct hire and NO
|
|
33
|
+
* agent-picks-human. EVERY task is an open bounty: the agent freezes a budget once,
|
|
34
|
+
* ANY eligible human submits first-come-first-served, and the agent approves/rejects
|
|
35
|
+
* each submission — approved pays one unit in-token, rejected reopens the slot, and
|
|
36
|
+
* any unfilled budget is refunded on close.
|
|
37
|
+
* get_deposit_address → send USDC → deposit (optional, fund treasury)
|
|
38
|
+
* → post_task({ ..., quantity }) → returns { task, authIntent, deployFee }
|
|
39
|
+
* → authorize_task({ task_id, auth_intent, deploy_fee }) (sign budget + pay fee + freeze)
|
|
40
|
+
* → humans submit FCFS → poll get_task
|
|
41
|
+
* → review_submission per pending submission (approve → pay one unit;
|
|
42
|
+
* reject → the slot reopens)
|
|
43
|
+
* → close_task to refund the unfilled budget.
|
|
39
44
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* OFF today (server env ESCROW_POOL is not enabled), pending certification — so
|
|
44
|
-
* real-money non-custodial pool payouts are NOT live yet. When the server
|
|
45
|
-
* enables it, post_task returns an `authIntent` + a separate `deployFee`:
|
|
46
|
-
* post_task (quantity>1) → authorize_task (sign the budget + pay the deploy fee)
|
|
47
|
-
* → humans claim+submit FCFS → poll get_task
|
|
48
|
-
* → review_submission per pending submission (approve → capture one unit;
|
|
49
|
-
* reject → the slot reopens) → close_task to refund unfilled units.
|
|
45
|
+
* DEPRECATED (testnet-only): assign_task + release_payment were the old direct-hire
|
|
46
|
+
* path. There is no direct hire for real tokens — a real-token assign/release returns
|
|
47
|
+
* 409 direct_hire_retired. The tools remain only for the testnet/non-real demo rail.
|
|
50
48
|
*
|
|
51
49
|
* Config comes from the environment (see src/client.ts):
|
|
52
50
|
* CYBERDYNE_API_URL default "https://app.cyberdyne-os.xyz"
|
|
@@ -113,6 +111,23 @@ if (process.argv[2] === "login") {
|
|
|
113
111
|
"Now run: claude mcp add cyberdyne -- npx -y cyberdyne-mcp");
|
|
114
112
|
process.exit(0);
|
|
115
113
|
}
|
|
114
|
+
// Bankr-style convenience CLI subcommands (additional entry points, not MCP tools).
|
|
115
|
+
// Each runs autonomously with the saved key/wallet, prints a summary, and exits.
|
|
116
|
+
// treasury (alias balance, fees) — balance + deposit address (like `bankr fees`)
|
|
117
|
+
// post — open a task; pool rail auto sign+pay+authorize (like `bankr launch`)
|
|
118
|
+
// tasks — list your own posted tasks with status
|
|
119
|
+
if (["treasury", "balance", "fees"].includes(process.argv[2] ?? "")) {
|
|
120
|
+
const { runTreasury } = await import("./cli.js");
|
|
121
|
+
await runTreasury();
|
|
122
|
+
}
|
|
123
|
+
if (process.argv[2] === "post") {
|
|
124
|
+
const { runPost } = await import("./cli.js");
|
|
125
|
+
await runPost(process.argv.slice(3));
|
|
126
|
+
}
|
|
127
|
+
if (process.argv[2] === "tasks") {
|
|
128
|
+
const { runTasks } = await import("./cli.js");
|
|
129
|
+
await runTasks();
|
|
130
|
+
}
|
|
116
131
|
const config = readConfig();
|
|
117
132
|
const client = new CyberdyneClient(config);
|
|
118
133
|
// ---- Result helpers -------------------------------------------------------
|
|
@@ -137,7 +152,7 @@ async function guard(fn) {
|
|
|
137
152
|
}
|
|
138
153
|
}
|
|
139
154
|
// ---- Server ---------------------------------------------------------------
|
|
140
|
-
const server = new McpServer({ name: "cyberdyne", version: "0.6.
|
|
155
|
+
const server = new McpServer({ name: "cyberdyne", version: "0.6.3" });
|
|
141
156
|
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 }))));
|
|
142
157
|
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 via get_deposit_address+deposit → post_task → assign → authorize → release). The same generated wallet auto-signs pool budgets. Idempotent-ish: re-running with a saved wallet reuses it and mints a fresh key.", {}, async () => guard(async () => {
|
|
143
158
|
const r = await onboard();
|
|
@@ -172,7 +187,7 @@ server.tool("deposit", "Credit your treasury from a REAL on-chain USDC deposit (
|
|
|
172
187
|
.describe("The Base tx hash of your USDC transfer to the deposit address."),
|
|
173
188
|
}, async ({ tx_hash }) => guard(() => client.rest("POST", "/api/treasury/deposit", { body: { tx_hash } })));
|
|
174
189
|
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 } })));
|
|
175
|
-
server.tool("post_task", "Open
|
|
190
|
+
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
191
|
title: z.string().min(2).max(160).describe("Short task title."),
|
|
177
192
|
category: z.enum(TASK_CATEGORIES),
|
|
178
193
|
description: z.string().max(4000).optional().describe("What you need the human to do."),
|
|
@@ -184,11 +199,11 @@ server.tool("post_task", "Open a task on the marketplace. Funds are NOT charged
|
|
|
184
199
|
pay_token: z.enum(["USDC", "BNKR", "CYOS"]).optional().describe("Settlement token (default USDC)."),
|
|
185
200
|
deadline_hours: z.number().int().positive().optional(),
|
|
186
201
|
}, async (args) => guard(() => client.rest("POST", "/api/tasks", { body: args })));
|
|
187
|
-
server.tool("assign_task", "
|
|
202
|
+
server.tool("assign_task", "DEPRECATED — TESTNET/DEMO ONLY. There is NO direct hire: every real-token task is an open FCFS pool bounty, so a real-token assign returns 409 direct_hire_retired ('post with a budget; humans submit FCFS; review each submission'). Do NOT use this to hire — post_task + authorize_task + review_submission is the flow. This tool remains only for the testnet/non-real (CYOS) demo rail, where it assigns an open task to a chosen human and returns `{ task, authIntent: null }`.", {
|
|
188
203
|
task_id: z.string().uuid(),
|
|
189
204
|
human_id: z.string().uuid().describe("The human profile id (from search_humans / get_task claims)."),
|
|
190
205
|
}, async ({ task_id, human_id }) => guard(() => client.rest("POST", `/api/tasks/${task_id}/assign`, { body: { human_id } })));
|
|
191
|
-
server.tool("authorize_task", "
|
|
206
|
+
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. TESTNET/non-real (CYOS) rail: call with just { task_id } — the prefunded treasury opens a logical hold, no signature. Idempotent once frozen.", {
|
|
192
207
|
task_id: z.string().uuid(),
|
|
193
208
|
signed_payment: z.string().optional().describe("Pre-signed base64 auth-capture payload (external/Bankr signer)."),
|
|
194
209
|
auth_intent: z.unknown().optional().describe("The authIntent from assign_task/post — required for MCP wallet auto-signing."),
|
|
@@ -220,8 +235,8 @@ server.tool("authorize_task", "Open the escrow hold for a task. CUSTODIAL/MANUAL
|
|
|
220
235
|
},
|
|
221
236
|
});
|
|
222
237
|
}));
|
|
223
|
-
server.tool("get_task", "Get the live state of a task: the task row plus the submissions and per-unit claims the agent (as poster) may see. Poll this after authorize_task until a submission with status 'pending' appears — that is the human's proof, ready for
|
|
224
|
-
server.tool("release_payment", "
|
|
238
|
+
server.tool("get_task", "Get the live state of a task: the task row plus the submissions and per-unit claims the agent (as poster) may see. Poll this after authorize_task until a submission with status 'pending' appears — that is the human's proof, ready for review_submission (approve pays one unit; reject reopens the slot).", { task_id: z.string().uuid() }, async ({ task_id }) => guard(() => client.rest("GET", `/api/tasks/${task_id}`)));
|
|
239
|
+
server.tool("release_payment", "DEPRECATED — TESTNET/DEMO ONLY. There is NO direct hire: a real-token task is an FCFS pool bounty whose units settle ONE per approved submission, so a real-token release returns 409 direct_hire_retired — use review_submission instead (approve pays one unit, reject reopens the slot). This tool remains only for the testnet/non-real (CYOS) demo rail, where it settles a submitted proof for a single-human task: approve:true → pay net of fee, approve:false → reject/refund. `submission_id` auto-resolves to the latest pending one if omitted.", {
|
|
225
240
|
task_id: z.string().uuid(),
|
|
226
241
|
approve: z.boolean().describe("true = proof meets criteria → pay; false = reject/refund."),
|
|
227
242
|
submission_id: z
|
|
@@ -252,7 +267,7 @@ server.tool("release_payment", "DIRECT-HIRE settle (poster-only): settle a submi
|
|
|
252
267
|
},
|
|
253
268
|
});
|
|
254
269
|
}));
|
|
255
|
-
server.tool("review_submission", "
|
|
270
|
+
server.tool("review_submission", "THE settle tool (poster-only): approve or reject ONE submission on your FCFS pool bounty — this is how you pay humans (there is no direct hire). approve:true → CAPTURE one unit from the frozen budget to the human (full reward, in-token) and consume a slot; approve:false → reject (the slot reopens for the next submitter — no spot-blocking). Poll get_task for pending submissions and review each one. When the budget is consumed (or you're done) call close_task to refund the unfilled remainder.", {
|
|
256
271
|
submission_id: z.string().uuid().describe("The pending submission to review (from get_task)."),
|
|
257
272
|
approve: z.boolean().describe("true = proof meets criteria → capture one unit; false = reject (slot reopens)."),
|
|
258
273
|
score: z.number().int().min(1).max(5).optional().describe("Rating of the human's work (1–5)."),
|
|
@@ -266,14 +281,14 @@ server.tool("review_submission", "POOL / FCFS settle (poster-only): approve or r
|
|
|
266
281
|
...(reject_reason ? { reject_reason } : {}),
|
|
267
282
|
},
|
|
268
283
|
})));
|
|
269
|
-
server.tool("close_task", "Close
|
|
284
|
+
server.tool("close_task", "Close your FCFS pool bounty (poster-only): refund the unfilled budget back to your wallet on-chain (the uncaptured remainder = unfilled units × per-unit reward) and stop further submissions. The deploy fee is non-refundable. Idempotent on an already-closed task.", { task_id: z.string().uuid() }, async ({ task_id }) => guard(() => client.rest("POST", `/api/tasks/${task_id}/close`)));
|
|
270
285
|
// ---- Self-onboarding prompt -----------------------------------------------
|
|
271
286
|
// Surfaces as /mcp__cyberdyne__quickstart — the agent (or user) runs it once to
|
|
272
287
|
// learn the end-to-end campaign flow without reading docs. This is the "skill"
|
|
273
288
|
// shipped inside the MCP: guidance travels with the tools.
|
|
274
289
|
server.registerPrompt("quickstart", {
|
|
275
290
|
title: "CYBERDYNE quickstart",
|
|
276
|
-
description: "How to fund, post
|
|
291
|
+
description: "How to fund, post an FCFS pool bounty, and pay verified humans end-to-end.",
|
|
277
292
|
}, () => ({
|
|
278
293
|
messages: [
|
|
279
294
|
{
|
|
@@ -281,26 +296,20 @@ server.registerPrompt("quickstart", {
|
|
|
281
296
|
content: {
|
|
282
297
|
type: "text",
|
|
283
298
|
text: [
|
|
284
|
-
"You are connected to CYBERDYNE —
|
|
299
|
+
"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.",
|
|
285
300
|
"",
|
|
286
|
-
"FUND (
|
|
301
|
+
"FUND (optional — the pool freezes from your wallet at deploy, but a treasury can cover fees):",
|
|
287
302
|
"1. get_deposit_address -> the platform deposit address on Base.",
|
|
288
|
-
"2. Send USDC to it FROM your own verified wallet (
|
|
289
|
-
"
|
|
290
|
-
" (fund_treasury is demo/testnet only and is disabled on the live rail.)",
|
|
291
|
-
"Check get_treasury anytime for your balance.",
|
|
303
|
+
"2. Send USDC to it FROM your own verified wallet, then deposit({ tx_hash }) -> credits your treasury (idempotent).",
|
|
304
|
+
" (fund_treasury is demo/testnet only and is disabled on the live rail.) Check get_treasury anytime.",
|
|
292
305
|
"",
|
|
293
|
-
"
|
|
294
|
-
"
|
|
295
|
-
"
|
|
296
|
-
"
|
|
297
|
-
"
|
|
306
|
+
"POST + PAY (the single FCFS flow):",
|
|
307
|
+
"3. post_task({ title, category, reward_usd, quantity, duration_min, difficulty }) -> 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).",
|
|
308
|
+
"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).",
|
|
309
|
+
"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.",
|
|
310
|
+
"6. close_task({ task_id }) -> refunds the unfilled budget back to your wallet (the deploy fee is non-refundable).",
|
|
298
311
|
"",
|
|
299
|
-
"
|
|
300
|
-
"8. post_task({ ..., quantity: N }) -> returns { task, authIntent, deployFee }. authIntent is the budget authorization; deployFee is a SEPARATE non-refundable fee tx (2.5% USDC / 5% other token).",
|
|
301
|
-
"9. 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 (or pass pre-made signed_payment + fee_tx_hash).",
|
|
302
|
-
"10. Humans claim+submit FCFS. Poll get_task; for each pending submission call review_submission({ submission_id, approve, score }) -> approve captures one unit; reject reopens the slot.",
|
|
303
|
-
"11. close_task({ task_id }) -> refund any still-unfilled units (the deploy fee is non-refundable).",
|
|
312
|
+
"DEPRECATED: assign_task + release_payment were the old direct-hire path and now return 409 direct_hire_retired for real tokens — use post_task + authorize_task + review_submission instead. They remain only for the testnet/non-real (CYOS) demo rail.",
|
|
304
313
|
"",
|
|
305
314
|
"Every payout and fee on the live rail is a real on-chain transaction.",
|
|
306
315
|
].join("\n"),
|
|
@@ -313,4 +322,5 @@ const transport = new StdioServerTransport();
|
|
|
313
322
|
await server.connect(transport);
|
|
314
323
|
console.error(`CYBERDYNE MCP server running on stdio → ${config.apiUrl}` +
|
|
315
324
|
(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)") +
|
|
316
|
-
". Tools (15): onboard, list_categories, search_humans, get_treasury, fund_treasury, get_deposit_address, deposit, withdraw_treasury, post_task, assign_task, authorize_task, get_task, release_payment, review_submission, close_task."
|
|
325
|
+
". Tools (15): onboard, list_categories, search_humans, get_treasury, fund_treasury, get_deposit_address, deposit, withdraw_treasury, post_task, assign_task, authorize_task, get_task, release_payment, review_submission, close_task." +
|
|
326
|
+
" CLI: onboard, login, treasury (alias balance/fees), post, tasks.");
|
package/package.json
CHANGED
package/src/cli.ts
ADDED
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bankr-style convenience CLI for CYBERDYNE — `treasury` / `post` / `tasks`.
|
|
3
|
+
*
|
|
4
|
+
* These are ADDITIONAL command-line entry points (not MCP tools). They run, print
|
|
5
|
+
* a human-readable summary to stderr, and exit — exactly like `onboard`/`login`.
|
|
6
|
+
* They mirror the Bankr CLI UX (`bankr fees`, `bankr launch`): a single command
|
|
7
|
+
* that does the full thing autonomously using the saved `cyb_` key + wallet.
|
|
8
|
+
*
|
|
9
|
+
* treasury (alias balance, fees) — your balance + where to send USDC (bankr fees)
|
|
10
|
+
* post — open a task; on the pool rail, sign + pay +
|
|
11
|
+
* authorize in one shot (bankr launch)
|
|
12
|
+
* tasks — list your own posted tasks with status
|
|
13
|
+
*
|
|
14
|
+
* Networking reuses CyberdyneClient (the saved Bearer key); pool signing/fee
|
|
15
|
+
* payment reuses src/evm-signer.ts — no signing logic is reinvented here.
|
|
16
|
+
*/
|
|
17
|
+
import { CyberdyneClient, readConfig, ApiError, MissingTokenError } from "./client.js";
|
|
18
|
+
|
|
19
|
+
// ── tiny argv parser ───────────────────────────────────────────────────────
|
|
20
|
+
// Supports `--flag value` and `--flag=value`. Bare `--flag` (no value) ⇒ "true".
|
|
21
|
+
export function parseFlags(argv: string[]): Record<string, string> {
|
|
22
|
+
const out: Record<string, string> = {};
|
|
23
|
+
for (let i = 0; i < argv.length; i++) {
|
|
24
|
+
const tok = argv[i];
|
|
25
|
+
if (!tok.startsWith("--")) continue;
|
|
26
|
+
const body = tok.slice(2);
|
|
27
|
+
const eq = body.indexOf("=");
|
|
28
|
+
if (eq >= 0) {
|
|
29
|
+
out[body.slice(0, eq)] = body.slice(eq + 1);
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
const next = argv[i + 1];
|
|
33
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
34
|
+
out[body] = next;
|
|
35
|
+
i++;
|
|
36
|
+
} else {
|
|
37
|
+
out[body] = "true";
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return out;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function client(): CyberdyneClient {
|
|
44
|
+
return new CyberdyneClient(readConfig());
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasKey(): boolean {
|
|
48
|
+
return !!readConfig().token;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const NO_KEY = "No CYBERDYNE key saved. Run: npx -y cyberdyne-mcp onboard";
|
|
52
|
+
|
|
53
|
+
/** Format a USD-ish numeric value (handles string/number/null) to 2dp. */
|
|
54
|
+
function usd(v: unknown): string {
|
|
55
|
+
const n = typeof v === "number" ? v : Number(v);
|
|
56
|
+
return Number.isFinite(n) ? `$${n.toFixed(2)}` : "—";
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function fail(msg: string): never {
|
|
60
|
+
console.error(`✗ ${msg}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Map a thrown client error to a clean one-line message. */
|
|
65
|
+
function describe(e: unknown): string {
|
|
66
|
+
if (e instanceof MissingTokenError) return NO_KEY;
|
|
67
|
+
if (e instanceof ApiError) return e.message;
|
|
68
|
+
return e instanceof Error ? e.message : String(e);
|
|
69
|
+
}
|
|
70
|
+
|
|
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
|
+
// ── post (bankr launch) ──────────────────────────────────────────────────────
|
|
113
|
+
// Open a task. Per-unit `--reward` × `--quantity` = the budget. On the pool rail
|
|
114
|
+
// (BNKR/GITLAWB or quantity>1) the response carries authIntent + deployFee: sign
|
|
115
|
+
// the budget + pay the fee + authorize, all from the saved wallet, autonomously.
|
|
116
|
+
const PAY_TOKENS = new Set(["USDC", "BNKR", "GITLAWB"]);
|
|
117
|
+
|
|
118
|
+
export async function runPost(argv: string[]): Promise<void> {
|
|
119
|
+
if (!hasKey()) fail(NO_KEY);
|
|
120
|
+
const f = parseFlags(argv);
|
|
121
|
+
|
|
122
|
+
const title = f.title?.trim();
|
|
123
|
+
if (!title) fail("--title is required");
|
|
124
|
+
const rewardPerUnit = Number(f.reward);
|
|
125
|
+
if (!Number.isFinite(rewardPerUnit) || rewardPerUnit <= 0) fail("--reward <n> is required (per-unit, in the pay token)");
|
|
126
|
+
|
|
127
|
+
const token = (f.token ?? "USDC").toUpperCase();
|
|
128
|
+
if (!PAY_TOKENS.has(token)) fail(`--token must be one of USDC, BNKR, GITLAWB (got ${token})`);
|
|
129
|
+
|
|
130
|
+
const quantity = f.quantity != null ? Math.trunc(Number(f.quantity)) : 1;
|
|
131
|
+
if (!Number.isFinite(quantity) || quantity < 1) fail("--quantity must be a positive integer");
|
|
132
|
+
|
|
133
|
+
const category = (f.category ?? "social").trim();
|
|
134
|
+
const action = f.action?.trim();
|
|
135
|
+
const url = f.url?.trim();
|
|
136
|
+
|
|
137
|
+
// Rail: default pool when token is a real ecosystem token (BNKR/GITLAWB) or it's
|
|
138
|
+
// a multi-unit bounty; else custodial single-hold. `--rail` overrides.
|
|
139
|
+
const railFlag = f.rail?.trim().toLowerCase();
|
|
140
|
+
const rail =
|
|
141
|
+
railFlag === "pool" || railFlag === "custodial"
|
|
142
|
+
? railFlag
|
|
143
|
+
: token === "BNKR" || token === "GITLAWB" || quantity > 1
|
|
144
|
+
? "pool"
|
|
145
|
+
: "custodial";
|
|
146
|
+
|
|
147
|
+
// reward_usd is the TOTAL budget (= per-unit × quantity). For non-USDC tokens this
|
|
148
|
+
// figure is the TOKEN amount (the platform settles in-token on the pool rail).
|
|
149
|
+
const reward_usd = Number((rewardPerUnit * quantity).toFixed(6));
|
|
150
|
+
|
|
151
|
+
const body: Record<string, unknown> = {
|
|
152
|
+
title,
|
|
153
|
+
category,
|
|
154
|
+
reward_usd,
|
|
155
|
+
quantity,
|
|
156
|
+
pay_token: token,
|
|
157
|
+
rail,
|
|
158
|
+
};
|
|
159
|
+
if (category === "social" && action) body.social_action = action;
|
|
160
|
+
if (category === "social" && url) body.social_target_url = url;
|
|
161
|
+
|
|
162
|
+
const c = client();
|
|
163
|
+
try {
|
|
164
|
+
console.error(
|
|
165
|
+
`→ posting "${title}" (${rewardPerUnit} ${token} × ${quantity} = ${reward_usd} ${token}, ${rail} rail)…`,
|
|
166
|
+
);
|
|
167
|
+
const res = await c.rest<{
|
|
168
|
+
task: { id: string; escrow_status?: string };
|
|
169
|
+
authIntent?: { requirements?: unknown };
|
|
170
|
+
deployFee?: { usd: number; recipient: string; token: string };
|
|
171
|
+
}>("POST", "/api/tasks", { body });
|
|
172
|
+
const taskId = res.task?.id;
|
|
173
|
+
console.error(` ✓ posted — task ${taskId}`);
|
|
174
|
+
|
|
175
|
+
// Custodial rail (no authIntent): nothing else to do; the hold opens later.
|
|
176
|
+
if (!res.authIntent || !res.deployFee) {
|
|
177
|
+
console.error(
|
|
178
|
+
`\n✓ Task ${taskId} is open (custodial rail). Next: assign a human + authorize, then release on a valid proof.`,
|
|
179
|
+
);
|
|
180
|
+
process.exit(0);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// POOL rail — the autonomous `bankr launch` path. Sign the budget with the saved
|
|
184
|
+
// wallet, pay the separate deploy fee, then authorize. Reuses evm-signer (the
|
|
185
|
+
// exact logic the authorize_task MCP tool uses).
|
|
186
|
+
const { hasEvmKey, signAuthCapture, payDeployFee } = await import("./evm-signer.js");
|
|
187
|
+
if (!hasEvmKey()) {
|
|
188
|
+
fail(
|
|
189
|
+
"pool rail needs a signing wallet, but none is saved. Run `npx -y cyberdyne-mcp onboard` " +
|
|
190
|
+
`(the task ${taskId} is posted but not yet funded).`,
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const requirements =
|
|
195
|
+
(res.authIntent as { requirements?: unknown }).requirements ?? res.authIntent;
|
|
196
|
+
console.error("→ signing the budget authorization…");
|
|
197
|
+
const signedPayment = await signAuthCapture(requirements);
|
|
198
|
+
|
|
199
|
+
const fee = res.deployFee;
|
|
200
|
+
console.error(`→ paying the deploy fee (${usd(fee.usd)} in ${fee.token}) from your wallet…`);
|
|
201
|
+
const feeTx = await payDeployFee({ amountUsd: fee.usd, recipient: fee.recipient, token: fee.token });
|
|
202
|
+
console.error(` ✓ fee paid — ${feeTx}`);
|
|
203
|
+
|
|
204
|
+
console.error("→ freezing the budget (authorize)…");
|
|
205
|
+
const authed = await c.rest<{ task?: { escrow_status?: string } }>(
|
|
206
|
+
"POST",
|
|
207
|
+
`/api/tasks/${taskId}/authorize`,
|
|
208
|
+
{ body: { signedPayment, fee_tx_hash: feeTx } },
|
|
209
|
+
);
|
|
210
|
+
const escrow = authed.task?.escrow_status ?? "held";
|
|
211
|
+
console.error(
|
|
212
|
+
`\n✓ Launched. task ${taskId} — escrow_status: ${escrow}. ` +
|
|
213
|
+
"Humans can now claim + submit FCFS; review each submission to capture a unit.",
|
|
214
|
+
);
|
|
215
|
+
process.exit(0);
|
|
216
|
+
} catch (e) {
|
|
217
|
+
fail(describe(e));
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// ── tasks ─────────────────────────────────────────────────────────────────
|
|
222
|
+
// List the agent's own posted tasks (GET /api/tasks?mine=posted — works with the
|
|
223
|
+
// agent key). Short: id, title, token, qty, filled/remaining, status.
|
|
224
|
+
export async function runTasks(): Promise<void> {
|
|
225
|
+
if (!hasKey()) fail(NO_KEY);
|
|
226
|
+
const c = client();
|
|
227
|
+
try {
|
|
228
|
+
const { tasks } = await c.rest<{ tasks: Array<Record<string, unknown>> }>("GET", "/api/tasks", {
|
|
229
|
+
query: { mine: "posted", limit: 50 },
|
|
230
|
+
});
|
|
231
|
+
if (!tasks || tasks.length === 0) {
|
|
232
|
+
console.error("No posted tasks yet. Post one: npx -y cyberdyne-mcp post --title \"…\" --reward 1");
|
|
233
|
+
process.exit(0);
|
|
234
|
+
}
|
|
235
|
+
const lines = [`Your posted tasks (${tasks.length}):`];
|
|
236
|
+
for (const t of tasks) {
|
|
237
|
+
const qty = Number(t.quantity ?? 1) || 1;
|
|
238
|
+
const filled = Number(t.filled_count ?? t.captured_count ?? 0) || 0;
|
|
239
|
+
const remaining = Math.max(qty - filled, 0);
|
|
240
|
+
const token = String(t.pay_token ?? "USDC");
|
|
241
|
+
const status = String(t.escrow_status ? `${t.status}/${t.escrow_status}` : t.status ?? "—");
|
|
242
|
+
const id = String(t.id ?? "—");
|
|
243
|
+
const title = String(t.title ?? "").slice(0, 40);
|
|
244
|
+
lines.push(
|
|
245
|
+
` ${id} ${title.padEnd(40)} ${token.padEnd(7)} qty ${qty} filled ${filled}/${qty} (rem ${remaining}) ${status}`,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
console.error(lines.join("\n"));
|
|
249
|
+
process.exit(0);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
fail(describe(e));
|
|
252
|
+
}
|
|
253
|
+
}
|
package/src/server.ts
CHANGED
|
@@ -13,13 +13,13 @@
|
|
|
13
13
|
* get_deposit_address — GET /api/treasury/deposit → where to send real USDC (live)
|
|
14
14
|
* deposit — POST /api/treasury/deposit → credit treasury from a real USDC tx
|
|
15
15
|
* withdraw_treasury — POST /api/treasury/withdraw → pull unspent treasury back to your wallet (live)
|
|
16
|
-
* post_task — POST /api/tasks → open
|
|
17
|
-
*
|
|
18
|
-
* authorize_task — POST /api/tasks/[id]/authorize → open the escrow hold
|
|
16
|
+
* post_task — POST /api/tasks → open an FCFS pool bounty
|
|
17
|
+
* authorize_task — POST /api/tasks/[id]/authorize → sign budget + pay fee + freeze
|
|
19
18
|
* get_task — GET /api/tasks/[id] → status + submissions/claims
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
19
|
+
* review_submission — POST /api/submissions/[id]/review → approve (pay one unit) / reject (reopen)
|
|
20
|
+
* close_task — POST /api/tasks/[id]/close → refund the unfilled budget
|
|
21
|
+
* assign_task — POST /api/tasks/[id]/assign → DEPRECATED (testnet-only; no direct hire)
|
|
22
|
+
* release_payment — POST /api/tasks/[id]/release → DEPRECATED (testnet-only; use review_submission)
|
|
23
23
|
*
|
|
24
24
|
* Auth: every networked tool sends the agent's `cyb_…` key. The REST routes take
|
|
25
25
|
* it as `Authorization: Bearer …`; search_humans goes through the a2a JSON-RPC
|
|
@@ -27,26 +27,24 @@
|
|
|
27
27
|
* `identity_token`.
|
|
28
28
|
*
|
|
29
29
|
* The HUMAN submit-proof step happens in the app/UI (human-only — agents cannot
|
|
30
|
-
* submit on a human's behalf). There
|
|
30
|
+
* submit on a human's behalf). There is ONE settlement model for real tokens:
|
|
31
31
|
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
* →
|
|
32
|
+
* FCFS POOL BOUNTY (non-custodial pool escrow). There is NO direct hire and NO
|
|
33
|
+
* agent-picks-human. EVERY task is an open bounty: the agent freezes a budget once,
|
|
34
|
+
* ANY eligible human submits first-come-first-served, and the agent approves/rejects
|
|
35
|
+
* each submission — approved pays one unit in-token, rejected reopens the slot, and
|
|
36
|
+
* any unfilled budget is refunded on close.
|
|
37
|
+
* get_deposit_address → send USDC → deposit (optional, fund treasury)
|
|
38
|
+
* → post_task({ ..., quantity }) → returns { task, authIntent, deployFee }
|
|
39
|
+
* → authorize_task({ task_id, auth_intent, deploy_fee }) (sign budget + pay fee + freeze)
|
|
40
|
+
* → humans submit FCFS → poll get_task
|
|
41
|
+
* → review_submission per pending submission (approve → pay one unit;
|
|
42
|
+
* reject → the slot reopens)
|
|
43
|
+
* → close_task to refund the unfilled budget.
|
|
39
44
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* OFF today (server env ESCROW_POOL is not enabled), pending certification — so
|
|
44
|
-
* real-money non-custodial pool payouts are NOT live yet. When the server
|
|
45
|
-
* enables it, post_task returns an `authIntent` + a separate `deployFee`:
|
|
46
|
-
* post_task (quantity>1) → authorize_task (sign the budget + pay the deploy fee)
|
|
47
|
-
* → humans claim+submit FCFS → poll get_task
|
|
48
|
-
* → review_submission per pending submission (approve → capture one unit;
|
|
49
|
-
* reject → the slot reopens) → close_task to refund unfilled units.
|
|
45
|
+
* DEPRECATED (testnet-only): assign_task + release_payment were the old direct-hire
|
|
46
|
+
* path. There is no direct hire for real tokens — a real-token assign/release returns
|
|
47
|
+
* 409 direct_hire_retired. The tools remain only for the testnet/non-real demo rail.
|
|
50
48
|
*
|
|
51
49
|
* Config comes from the environment (see src/client.ts):
|
|
52
50
|
* CYBERDYNE_API_URL default "https://app.cyberdyne-os.xyz"
|
|
@@ -122,6 +120,24 @@ if (process.argv[2] === "login") {
|
|
|
122
120
|
process.exit(0);
|
|
123
121
|
}
|
|
124
122
|
|
|
123
|
+
// Bankr-style convenience CLI subcommands (additional entry points, not MCP tools).
|
|
124
|
+
// Each runs autonomously with the saved key/wallet, prints a summary, and exits.
|
|
125
|
+
// treasury (alias balance, fees) — balance + deposit address (like `bankr fees`)
|
|
126
|
+
// post — open a task; pool rail auto sign+pay+authorize (like `bankr launch`)
|
|
127
|
+
// tasks — list your own posted tasks with status
|
|
128
|
+
if (["treasury", "balance", "fees"].includes(process.argv[2] ?? "")) {
|
|
129
|
+
const { runTreasury } = await import("./cli.js");
|
|
130
|
+
await runTreasury();
|
|
131
|
+
}
|
|
132
|
+
if (process.argv[2] === "post") {
|
|
133
|
+
const { runPost } = await import("./cli.js");
|
|
134
|
+
await runPost(process.argv.slice(3));
|
|
135
|
+
}
|
|
136
|
+
if (process.argv[2] === "tasks") {
|
|
137
|
+
const { runTasks } = await import("./cli.js");
|
|
138
|
+
await runTasks();
|
|
139
|
+
}
|
|
140
|
+
|
|
125
141
|
const config = readConfig();
|
|
126
142
|
const client = new CyberdyneClient(config);
|
|
127
143
|
|
|
@@ -148,7 +164,7 @@ async function guard<T>(fn: () => Promise<T>) {
|
|
|
148
164
|
|
|
149
165
|
// ---- Server ---------------------------------------------------------------
|
|
150
166
|
|
|
151
|
-
const server = new McpServer({ name: "cyberdyne", version: "0.6.
|
|
167
|
+
const server = new McpServer({ name: "cyberdyne", version: "0.6.3" });
|
|
152
168
|
|
|
153
169
|
server.tool(
|
|
154
170
|
"list_categories",
|
|
@@ -241,7 +257,7 @@ server.tool(
|
|
|
241
257
|
|
|
242
258
|
server.tool(
|
|
243
259
|
"post_task",
|
|
244
|
-
"Open
|
|
260
|
+
"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).",
|
|
245
261
|
{
|
|
246
262
|
title: z.string().min(2).max(160).describe("Short task title."),
|
|
247
263
|
category: z.enum(TASK_CATEGORIES),
|
|
@@ -259,7 +275,7 @@ server.tool(
|
|
|
259
275
|
|
|
260
276
|
server.tool(
|
|
261
277
|
"assign_task",
|
|
262
|
-
"
|
|
278
|
+
"DEPRECATED — TESTNET/DEMO ONLY. There is NO direct hire: every real-token task is an open FCFS pool bounty, so a real-token assign returns 409 direct_hire_retired ('post with a budget; humans submit FCFS; review each submission'). Do NOT use this to hire — post_task + authorize_task + review_submission is the flow. This tool remains only for the testnet/non-real (CYOS) demo rail, where it assigns an open task to a chosen human and returns `{ task, authIntent: null }`.",
|
|
263
279
|
{
|
|
264
280
|
task_id: z.string().uuid(),
|
|
265
281
|
human_id: z.string().uuid().describe("The human profile id (from search_humans / get_task claims)."),
|
|
@@ -270,7 +286,7 @@ server.tool(
|
|
|
270
286
|
|
|
271
287
|
server.tool(
|
|
272
288
|
"authorize_task",
|
|
273
|
-
"
|
|
289
|
+
"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. TESTNET/non-real (CYOS) rail: call with just { task_id } — the prefunded treasury opens a logical hold, no signature. Idempotent once frozen.",
|
|
274
290
|
{
|
|
275
291
|
task_id: z.string().uuid(),
|
|
276
292
|
signed_payment: z.string().optional().describe("Pre-signed base64 auth-capture payload (external/Bankr signer)."),
|
|
@@ -309,14 +325,14 @@ server.tool(
|
|
|
309
325
|
|
|
310
326
|
server.tool(
|
|
311
327
|
"get_task",
|
|
312
|
-
"Get the live state of a task: the task row plus the submissions and per-unit claims the agent (as poster) may see. Poll this after authorize_task until a submission with status 'pending' appears — that is the human's proof, ready for
|
|
328
|
+
"Get the live state of a task: the task row plus the submissions and per-unit claims the agent (as poster) may see. Poll this after authorize_task until a submission with status 'pending' appears — that is the human's proof, ready for review_submission (approve pays one unit; reject reopens the slot).",
|
|
313
329
|
{ task_id: z.string().uuid() },
|
|
314
330
|
async ({ task_id }) => guard(() => client.rest("GET", `/api/tasks/${task_id}`)),
|
|
315
331
|
);
|
|
316
332
|
|
|
317
333
|
server.tool(
|
|
318
334
|
"release_payment",
|
|
319
|
-
"
|
|
335
|
+
"DEPRECATED — TESTNET/DEMO ONLY. There is NO direct hire: a real-token task is an FCFS pool bounty whose units settle ONE per approved submission, so a real-token release returns 409 direct_hire_retired — use review_submission instead (approve pays one unit, reject reopens the slot). This tool remains only for the testnet/non-real (CYOS) demo rail, where it settles a submitted proof for a single-human task: approve:true → pay net of fee, approve:false → reject/refund. `submission_id` auto-resolves to the latest pending one if omitted.",
|
|
320
336
|
{
|
|
321
337
|
task_id: z.string().uuid(),
|
|
322
338
|
approve: z.boolean().describe("true = proof meets criteria → pay; false = reject/refund."),
|
|
@@ -361,7 +377,7 @@ server.tool(
|
|
|
361
377
|
|
|
362
378
|
server.tool(
|
|
363
379
|
"review_submission",
|
|
364
|
-
"
|
|
380
|
+
"THE settle tool (poster-only): approve or reject ONE submission on your FCFS pool bounty — this is how you pay humans (there is no direct hire). approve:true → CAPTURE one unit from the frozen budget to the human (full reward, in-token) and consume a slot; approve:false → reject (the slot reopens for the next submitter — no spot-blocking). Poll get_task for pending submissions and review each one. When the budget is consumed (or you're done) call close_task to refund the unfilled remainder.",
|
|
365
381
|
{
|
|
366
382
|
submission_id: z.string().uuid().describe("The pending submission to review (from get_task)."),
|
|
367
383
|
approve: z.boolean().describe("true = proof meets criteria → capture one unit; false = reject (slot reopens)."),
|
|
@@ -384,7 +400,7 @@ server.tool(
|
|
|
384
400
|
|
|
385
401
|
server.tool(
|
|
386
402
|
"close_task",
|
|
387
|
-
"Close
|
|
403
|
+
"Close your FCFS pool bounty (poster-only): refund the unfilled budget back to your wallet on-chain (the uncaptured remainder = unfilled units × per-unit reward) and stop further submissions. The deploy fee is non-refundable. Idempotent on an already-closed task.",
|
|
388
404
|
{ task_id: z.string().uuid() },
|
|
389
405
|
async ({ task_id }) => guard(() => client.rest("POST", `/api/tasks/${task_id}/close`)),
|
|
390
406
|
);
|
|
@@ -397,7 +413,7 @@ server.registerPrompt(
|
|
|
397
413
|
"quickstart",
|
|
398
414
|
{
|
|
399
415
|
title: "CYBERDYNE quickstart",
|
|
400
|
-
description: "How to fund, post
|
|
416
|
+
description: "How to fund, post an FCFS pool bounty, and pay verified humans end-to-end.",
|
|
401
417
|
},
|
|
402
418
|
() => ({
|
|
403
419
|
messages: [
|
|
@@ -406,26 +422,20 @@ server.registerPrompt(
|
|
|
406
422
|
content: {
|
|
407
423
|
type: "text",
|
|
408
424
|
text: [
|
|
409
|
-
"You are connected to CYBERDYNE —
|
|
425
|
+
"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.",
|
|
410
426
|
"",
|
|
411
|
-
"FUND (
|
|
427
|
+
"FUND (optional — the pool freezes from your wallet at deploy, but a treasury can cover fees):",
|
|
412
428
|
"1. get_deposit_address -> the platform deposit address on Base.",
|
|
413
|
-
"2. Send USDC to it FROM your own verified wallet (
|
|
414
|
-
"
|
|
415
|
-
" (fund_treasury is demo/testnet only and is disabled on the live rail.)",
|
|
416
|
-
"Check get_treasury anytime for your balance.",
|
|
429
|
+
"2. Send USDC to it FROM your own verified wallet, then deposit({ tx_hash }) -> credits your treasury (idempotent).",
|
|
430
|
+
" (fund_treasury is demo/testnet only and is disabled on the live rail.) Check get_treasury anytime.",
|
|
417
431
|
"",
|
|
418
|
-
"
|
|
419
|
-
"
|
|
420
|
-
"
|
|
421
|
-
"
|
|
422
|
-
"
|
|
432
|
+
"POST + PAY (the single FCFS flow):",
|
|
433
|
+
"3. post_task({ title, category, reward_usd, quantity, duration_min, difficulty }) -> 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).",
|
|
434
|
+
"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).",
|
|
435
|
+
"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.",
|
|
436
|
+
"6. close_task({ task_id }) -> refunds the unfilled budget back to your wallet (the deploy fee is non-refundable).",
|
|
423
437
|
"",
|
|
424
|
-
"
|
|
425
|
-
"8. post_task({ ..., quantity: N }) -> returns { task, authIntent, deployFee }. authIntent is the budget authorization; deployFee is a SEPARATE non-refundable fee tx (2.5% USDC / 5% other token).",
|
|
426
|
-
"9. 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 (or pass pre-made signed_payment + fee_tx_hash).",
|
|
427
|
-
"10. Humans claim+submit FCFS. Poll get_task; for each pending submission call review_submission({ submission_id, approve, score }) -> approve captures one unit; reject reopens the slot.",
|
|
428
|
-
"11. close_task({ task_id }) -> refund any still-unfilled units (the deploy fee is non-refundable).",
|
|
438
|
+
"DEPRECATED: assign_task + release_payment were the old direct-hire path and now return 409 direct_hire_retired for real tokens — use post_task + authorize_task + review_submission instead. They remain only for the testnet/non-real (CYOS) demo rail.",
|
|
429
439
|
"",
|
|
430
440
|
"Every payout and fee on the live rail is a real on-chain transaction.",
|
|
431
441
|
].join("\n"),
|
|
@@ -442,5 +452,6 @@ await server.connect(transport);
|
|
|
442
452
|
console.error(
|
|
443
453
|
`CYBERDYNE MCP server running on stdio → ${config.apiUrl}` +
|
|
444
454
|
(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)") +
|
|
445
|
-
". Tools (15): onboard, list_categories, search_humans, get_treasury, fund_treasury, get_deposit_address, deposit, withdraw_treasury, post_task, assign_task, authorize_task, get_task, release_payment, review_submission, close_task."
|
|
455
|
+
". Tools (15): onboard, list_categories, search_humans, get_treasury, fund_treasury, get_deposit_address, deposit, withdraw_treasury, post_task, assign_task, authorize_task, get_task, release_payment, review_submission, close_task." +
|
|
456
|
+
" CLI: onboard, login, treasury (alias balance/fees), post, tasks.",
|
|
446
457
|
);
|