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 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.1" });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberdyne-mcp",
3
- "version": "0.6.1",
3
+ "version": "0.6.2",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
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.1" });
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
  );