cyberdyne-mcp 0.6.1 → 0.6.2
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 +20 -2
- package/package.json +1 -1
- package/src/cli.ts +253 -0
- package/src/server.ts +21 -2
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
|
@@ -113,6 +113,23 @@ if (process.argv[2] === "login") {
|
|
|
113
113
|
"Now run: claude mcp add cyberdyne -- npx -y cyberdyne-mcp");
|
|
114
114
|
process.exit(0);
|
|
115
115
|
}
|
|
116
|
+
// Bankr-style convenience CLI subcommands (additional entry points, not MCP tools).
|
|
117
|
+
// Each runs autonomously with the saved key/wallet, prints a summary, and exits.
|
|
118
|
+
// treasury (alias balance, fees) — balance + deposit address (like `bankr fees`)
|
|
119
|
+
// post — open a task; pool rail auto sign+pay+authorize (like `bankr launch`)
|
|
120
|
+
// tasks — list your own posted tasks with status
|
|
121
|
+
if (["treasury", "balance", "fees"].includes(process.argv[2] ?? "")) {
|
|
122
|
+
const { runTreasury } = await import("./cli.js");
|
|
123
|
+
await runTreasury();
|
|
124
|
+
}
|
|
125
|
+
if (process.argv[2] === "post") {
|
|
126
|
+
const { runPost } = await import("./cli.js");
|
|
127
|
+
await runPost(process.argv.slice(3));
|
|
128
|
+
}
|
|
129
|
+
if (process.argv[2] === "tasks") {
|
|
130
|
+
const { runTasks } = await import("./cli.js");
|
|
131
|
+
await runTasks();
|
|
132
|
+
}
|
|
116
133
|
const config = readConfig();
|
|
117
134
|
const client = new CyberdyneClient(config);
|
|
118
135
|
// ---- Result helpers -------------------------------------------------------
|
|
@@ -137,7 +154,7 @@ async function guard(fn) {
|
|
|
137
154
|
}
|
|
138
155
|
}
|
|
139
156
|
// ---- Server ---------------------------------------------------------------
|
|
140
|
-
const server = new McpServer({ name: "cyberdyne", version: "0.6.
|
|
157
|
+
const server = new McpServer({ name: "cyberdyne", version: "0.6.2" });
|
|
141
158
|
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
159
|
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
160
|
const r = await onboard();
|
|
@@ -313,4 +330,5 @@ const transport = new StdioServerTransport();
|
|
|
313
330
|
await server.connect(transport);
|
|
314
331
|
console.error(`CYBERDYNE MCP server running on stdio → ${config.apiUrl}` +
|
|
315
332
|
(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."
|
|
333
|
+
". 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." +
|
|
334
|
+
" 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
|
@@ -122,6 +122,24 @@ if (process.argv[2] === "login") {
|
|
|
122
122
|
process.exit(0);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
|
+
// Bankr-style convenience CLI subcommands (additional entry points, not MCP tools).
|
|
126
|
+
// Each runs autonomously with the saved key/wallet, prints a summary, and exits.
|
|
127
|
+
// treasury (alias balance, fees) — balance + deposit address (like `bankr fees`)
|
|
128
|
+
// post — open a task; pool rail auto sign+pay+authorize (like `bankr launch`)
|
|
129
|
+
// tasks — list your own posted tasks with status
|
|
130
|
+
if (["treasury", "balance", "fees"].includes(process.argv[2] ?? "")) {
|
|
131
|
+
const { runTreasury } = await import("./cli.js");
|
|
132
|
+
await runTreasury();
|
|
133
|
+
}
|
|
134
|
+
if (process.argv[2] === "post") {
|
|
135
|
+
const { runPost } = await import("./cli.js");
|
|
136
|
+
await runPost(process.argv.slice(3));
|
|
137
|
+
}
|
|
138
|
+
if (process.argv[2] === "tasks") {
|
|
139
|
+
const { runTasks } = await import("./cli.js");
|
|
140
|
+
await runTasks();
|
|
141
|
+
}
|
|
142
|
+
|
|
125
143
|
const config = readConfig();
|
|
126
144
|
const client = new CyberdyneClient(config);
|
|
127
145
|
|
|
@@ -148,7 +166,7 @@ async function guard<T>(fn: () => Promise<T>) {
|
|
|
148
166
|
|
|
149
167
|
// ---- Server ---------------------------------------------------------------
|
|
150
168
|
|
|
151
|
-
const server = new McpServer({ name: "cyberdyne", version: "0.6.
|
|
169
|
+
const server = new McpServer({ name: "cyberdyne", version: "0.6.2" });
|
|
152
170
|
|
|
153
171
|
server.tool(
|
|
154
172
|
"list_categories",
|
|
@@ -442,5 +460,6 @@ await server.connect(transport);
|
|
|
442
460
|
console.error(
|
|
443
461
|
`CYBERDYNE MCP server running on stdio → ${config.apiUrl}` +
|
|
444
462
|
(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."
|
|
463
|
+
". 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." +
|
|
464
|
+
" CLI: onboard, login, treasury (alias balance/fees), post, tasks.",
|
|
446
465
|
);
|