cyberdyne-mcp 0.6.7 → 0.6.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -133,8 +133,8 @@ export async function runPost(argv) {
133
133
  console.error("→ signing the budget authorization…");
134
134
  const signedPayment = await signAuthCapture(requirements);
135
135
  const fee = res.deployFee;
136
- console.error(`→ paying the deploy fee (${usd(fee.usd)} in ${fee.token}) from your wallet…`);
137
- const feeTx = await payDeployFee({ amountUsd: fee.usd, recipient: fee.recipient, token: fee.token });
136
+ console.error(`→ paying the deploy fee (${fee.amount} ${fee.token} ≈ ${usd(fee.usd)}) from your wallet…`);
137
+ const feeTx = await payDeployFee({ amount: fee.amount, decimals: fee.decimals, recipient: fee.recipient, token: fee.token });
138
138
  console.error(` ✓ fee paid — ${feeTx}`);
139
139
  console.error("→ freezing the budget (authorize)…");
140
140
  const authed = await c.rest("POST", `/api/tasks/${taskId}/authorize`, { body: { signedPayment, fee_tx_hash: feeTx } });
@@ -55,11 +55,14 @@ const ERC20_TRANSFER_ABI = [
55
55
  */
56
56
  export async function payDeployFee(params) {
57
57
  const wallet = createWalletClient({ account: account(), chain: chain(), transport: http(process.env.CYBERDYNE_RPC_URL) });
58
+ // Scale by the TOKEN's decimals, not a hardcoded 6. Using 6 for an 18-decimal
59
+ // token (BNKR/GITLAWB) underpaid the fee 10^12× → permanent fee_unverified.
60
+ const value = parseUnits(params.amount.toFixed(params.decimals), params.decimals);
58
61
  const hash = await wallet.writeContract({
59
62
  address: params.token,
60
63
  abi: ERC20_TRANSFER_ABI,
61
64
  functionName: "transfer",
62
- args: [params.recipient, parseUnits(params.amountUsd.toFixed(6), 6)],
65
+ args: [params.recipient, value],
63
66
  chain: chain(),
64
67
  });
65
68
  // Wait until the fee tx is ≥1 block deep BEFORE returning, so the platform's
package/dist/onboard.js CHANGED
@@ -17,6 +17,8 @@
17
17
  * a DB row. The wallet private key is persisted but NEVER logged to stdout.
18
18
  */
19
19
  import { readFileSync } from "node:fs";
20
+ import { homedir } from "node:os";
21
+ import { join } from "node:path";
20
22
  import { createInterface } from "node:readline";
21
23
  import { generatePrivateKey, privateKeyToAccount, mnemonicToAccount } from "viem/accounts";
22
24
  import { bytesToHex } from "viem";
@@ -111,6 +113,29 @@ function collectCookies(res, jar) {
111
113
  function cookieHeader(jar) {
112
114
  return [...jar.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
113
115
  }
116
+ /**
117
+ * Auto-discover the agent's Bankr `bk_` key WITHOUT requiring a flag — so a plain
118
+ * `npx cyberdyne-mcp onboard` links Bankr whenever the agent already has Bankr set up.
119
+ * Order: CYBERDYNE_BANKR_KEY → BANKR_API_KEY (Bankr's own standard env var) →
120
+ * ~/.bankr/config.json (any `bk_`-prefixed value — the same config file Bankr's CLI/SDK use).
121
+ * Returns undefined if nothing is configured (then onboard just skips Bankr linking).
122
+ */
123
+ function discoverBankrKey(env) {
124
+ const fromEnv = env.CYBERDYNE_BANKR_KEY?.trim() || env.BANKR_API_KEY?.trim();
125
+ if (fromEnv?.startsWith("bk_"))
126
+ return fromEnv;
127
+ try {
128
+ const cfg = JSON.parse(readFileSync(join(homedir(), ".bankr", "config.json"), "utf8"));
129
+ for (const v of Object.values(cfg)) {
130
+ if (typeof v === "string" && v.startsWith("bk_"))
131
+ return v.trim();
132
+ }
133
+ }
134
+ catch {
135
+ /* no ~/.bankr/config.json — fine, Bankr just isn't configured */
136
+ }
137
+ return undefined;
138
+ }
114
139
  /**
115
140
  * Run the full SIWE → mint chain and persist the credentials. Throws on any
116
141
  * non-2xx step (with the endpoint + status) so the caller can surface a clear error.
@@ -185,7 +210,7 @@ export async function onboard(env = process.env, opts = {}) {
185
210
  // via the fresh cyb_ key. Best-effort: a failure never blocks onboarding, and the
186
211
  // bk_ key is never stored (the backend uses it once and discards it).
187
212
  let bankr;
188
- const bankrKey = (opts.bankrKey ?? env.CYBERDYNE_BANKR_KEY ?? "").trim();
213
+ const bankrKey = (opts.bankrKey?.trim() || discoverBankrKey(env) || "");
189
214
  if (bankrKey.startsWith("bk_")) {
190
215
  try {
191
216
  const res = await fetch(`${apiUrl}/api/bankr/connect`, {
@@ -277,8 +302,10 @@ export const ONBOARD_USAGE = [
277
302
  " CYBERDYNE_IMPORT_KEY=0x<key> npx cyberdyne-mcp onboard --import",
278
303
  " (passing it as an argument leaves the secret in your shell history.)",
279
304
  " --create generate a fresh wallet (default in a non-interactive / CI shell).",
280
- " --bankr <bk_key> auto-link your Bankr project at onboard (or set CYBERDYNE_BANKR_KEY).",
281
- " The bk_ key is used ONCE server-side and never stored.",
305
+ " --bankr <bk_key> link your Bankr project at onboard. USUALLY UNNEEDED: onboard",
306
+ " auto-discovers your key from BANKR_API_KEY / ~/.bankr/config.json,",
307
+ " so a plain `onboard` links Bankr if you already use it. Used once,",
308
+ " server-side, and never stored.",
282
309
  " (no flag, in a terminal) you'll be prompted: paste a key/mnemonic, or press enter to create.",
283
310
  "",
284
311
  "Either way: SIWE sign-in → mint your cyb_ key → save wallet + key to ~/.cyberdyne/config.json (0600).",
package/dist/server.js CHANGED
@@ -151,7 +151,7 @@ async function guard(fn) {
151
151
  }
152
152
  }
153
153
  // ---- Server ---------------------------------------------------------------
154
- const server = new McpServer({ name: "cyberdyne", version: "0.6.7" });
154
+ const server = new McpServer({ name: "cyberdyne", version: "0.6.8" });
155
155
  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 }))));
156
156
  server.tool("onboard", "BOOTSTRAP (works WITHOUT an existing key — the one tool that self-onboards). Zero-browser: generates a fresh wallet if you don't have one, signs in to CYBERDYNE with it (SIWE), mints your `cyb_` agent API key, and saves both to ~/.cyberdyne/config.json (0600) so every other tool here authenticates automatically. No web dashboard, no env vars. Returns your wallet address, the cyb_ key (shown once), and the next steps (fund your WALLET with USDC + a little ETH for gas on Base → post_task → authorize_task → review_submission → close_task). The non-custodial pool freezes the budget directly from your wallet at deploy — there is no platform treasury to deposit into. The same generated wallet auto-signs pool budgets. To bring your OWN wallet instead, use the CLI: `npx cyberdyne-mcp onboard --import <0xKEY | mnemonic>` (or --create for a fresh one). Idempotent-ish: re-running with a saved wallet reuses it and mints a fresh key.", {}, async () => guard(async () => {
157
157
  const r = await onboard();
@@ -209,7 +209,7 @@ server.tool("authorize_task", "Freeze the bounty budget on-chain (the second ste
209
209
  }
210
210
  if (!feeTx && deploy_fee) {
211
211
  const f = deploy_fee;
212
- feeTx = await payDeployFee({ amountUsd: f.usd, recipient: f.recipient, token: f.token });
212
+ feeTx = await payDeployFee({ amount: f.amount, decimals: f.decimals, recipient: f.recipient, token: f.token });
213
213
  }
214
214
  }
215
215
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cyberdyne-mcp",
3
- "version": "0.6.7",
3
+ "version": "0.6.9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
package/src/cli.ts CHANGED
@@ -125,7 +125,7 @@ export async function runPost(argv: string[]): Promise<void> {
125
125
  const res = await c.rest<{
126
126
  task: { id: string; escrow_status?: string };
127
127
  authIntent?: { requirements?: unknown };
128
- deployFee?: { usd: number; recipient: string; token: string };
128
+ deployFee?: { amount: number; decimals: number; usd: number; recipient: string; token: string };
129
129
  }>("POST", "/api/tasks", { body });
130
130
  const taskId = res.task?.id;
131
131
  console.error(` ✓ posted — task ${taskId}`);
@@ -155,8 +155,8 @@ export async function runPost(argv: string[]): Promise<void> {
155
155
  const signedPayment = await signAuthCapture(requirements);
156
156
 
157
157
  const fee = res.deployFee;
158
- console.error(`→ paying the deploy fee (${usd(fee.usd)} in ${fee.token}) from your wallet…`);
159
- const feeTx = await payDeployFee({ amountUsd: fee.usd, recipient: fee.recipient, token: fee.token });
158
+ console.error(`→ paying the deploy fee (${fee.amount} ${fee.token} ≈ ${usd(fee.usd)}) from your wallet…`);
159
+ const feeTx = await payDeployFee({ amount: fee.amount, decimals: fee.decimals, recipient: fee.recipient, token: fee.token });
160
160
  console.error(` ✓ fee paid — ${feeTx}`);
161
161
 
162
162
  console.error("→ freezing the budget (authorize)…");
package/src/evm-signer.ts CHANGED
@@ -57,16 +57,22 @@ const ERC20_TRANSFER_ABI = [
57
57
  * this gas (CYBERDYNE absorbs none). USDC is 6-dp (v1 pool settles in USDC).
58
58
  */
59
59
  export async function payDeployFee(params: {
60
- amountUsd: number;
60
+ /** Fee in the FEE TOKEN's own units (NOT USD). The platform pins this at post. */
61
+ amount: number;
62
+ /** The fee token's ERC-20 decimals (USDC=6, BNKR/GITLAWB=18, dynamic varies). */
63
+ decimals: number;
61
64
  recipient: string;
62
65
  token: string;
63
66
  }): Promise<string> {
64
67
  const wallet = createWalletClient({ account: account(), chain: chain(), transport: http(process.env.CYBERDYNE_RPC_URL) });
68
+ // Scale by the TOKEN's decimals, not a hardcoded 6. Using 6 for an 18-decimal
69
+ // token (BNKR/GITLAWB) underpaid the fee 10^12× → permanent fee_unverified.
70
+ const value = parseUnits(params.amount.toFixed(params.decimals), params.decimals);
65
71
  const hash = await wallet.writeContract({
66
72
  address: params.token as `0x${string}`,
67
73
  abi: ERC20_TRANSFER_ABI,
68
74
  functionName: "transfer",
69
- args: [params.recipient as `0x${string}`, parseUnits(params.amountUsd.toFixed(6), 6)],
75
+ args: [params.recipient as `0x${string}`, value],
70
76
  chain: chain(),
71
77
  });
72
78
  // Wait until the fee tx is ≥1 block deep BEFORE returning, so the platform's
package/src/onboard.ts CHANGED
@@ -17,6 +17,8 @@
17
17
  * a DB row. The wallet private key is persisted but NEVER logged to stdout.
18
18
  */
19
19
  import { readFileSync } from "node:fs";
20
+ import { homedir } from "node:os";
21
+ import { join } from "node:path";
20
22
  import { createInterface } from "node:readline";
21
23
  import { generatePrivateKey, privateKeyToAccount, mnemonicToAccount } from "viem/accounts";
22
24
  import { bytesToHex } from "viem";
@@ -131,6 +133,27 @@ function cookieHeader(jar: Map<string, string>): string {
131
133
  return [...jar.entries()].map(([k, v]) => `${k}=${v}`).join("; ");
132
134
  }
133
135
 
136
+ /**
137
+ * Auto-discover the agent's Bankr `bk_` key WITHOUT requiring a flag — so a plain
138
+ * `npx cyberdyne-mcp onboard` links Bankr whenever the agent already has Bankr set up.
139
+ * Order: CYBERDYNE_BANKR_KEY → BANKR_API_KEY (Bankr's own standard env var) →
140
+ * ~/.bankr/config.json (any `bk_`-prefixed value — the same config file Bankr's CLI/SDK use).
141
+ * Returns undefined if nothing is configured (then onboard just skips Bankr linking).
142
+ */
143
+ function discoverBankrKey(env: NodeJS.ProcessEnv): string | undefined {
144
+ const fromEnv = env.CYBERDYNE_BANKR_KEY?.trim() || env.BANKR_API_KEY?.trim();
145
+ if (fromEnv?.startsWith("bk_")) return fromEnv;
146
+ try {
147
+ const cfg = JSON.parse(readFileSync(join(homedir(), ".bankr", "config.json"), "utf8")) as Record<string, unknown>;
148
+ for (const v of Object.values(cfg)) {
149
+ if (typeof v === "string" && v.startsWith("bk_")) return v.trim();
150
+ }
151
+ } catch {
152
+ /* no ~/.bankr/config.json — fine, Bankr just isn't configured */
153
+ }
154
+ return undefined;
155
+ }
156
+
134
157
  export interface OnboardResult {
135
158
  address: string;
136
159
  apiKey: string;
@@ -234,7 +257,7 @@ export async function onboard(
234
257
  // via the fresh cyb_ key. Best-effort: a failure never blocks onboarding, and the
235
258
  // bk_ key is never stored (the backend uses it once and discards it).
236
259
  let bankr: OnboardResult["bankr"];
237
- const bankrKey = (opts.bankrKey ?? env.CYBERDYNE_BANKR_KEY ?? "").trim();
260
+ const bankrKey = (opts.bankrKey?.trim() || discoverBankrKey(env) || "");
238
261
  if (bankrKey.startsWith("bk_")) {
239
262
  try {
240
263
  const res = await fetch(`${apiUrl}/api/bankr/connect`, {
@@ -327,8 +350,10 @@ export const ONBOARD_USAGE = [
327
350
  " CYBERDYNE_IMPORT_KEY=0x<key> npx cyberdyne-mcp onboard --import",
328
351
  " (passing it as an argument leaves the secret in your shell history.)",
329
352
  " --create generate a fresh wallet (default in a non-interactive / CI shell).",
330
- " --bankr <bk_key> auto-link your Bankr project at onboard (or set CYBERDYNE_BANKR_KEY).",
331
- " The bk_ key is used ONCE server-side and never stored.",
353
+ " --bankr <bk_key> link your Bankr project at onboard. USUALLY UNNEEDED: onboard",
354
+ " auto-discovers your key from BANKR_API_KEY / ~/.bankr/config.json,",
355
+ " so a plain `onboard` links Bankr if you already use it. Used once,",
356
+ " server-side, and never stored.",
332
357
  " (no flag, in a terminal) you'll be prompted: paste a key/mnemonic, or press enter to create.",
333
358
  "",
334
359
  "Either way: SIWE sign-in → mint your cyb_ key → save wallet + key to ~/.cyberdyne/config.json (0600).",
package/src/server.ts CHANGED
@@ -163,7 +163,7 @@ async function guard<T>(fn: () => Promise<T>) {
163
163
 
164
164
  // ---- Server ---------------------------------------------------------------
165
165
 
166
- const server = new McpServer({ name: "cyberdyne", version: "0.6.7" });
166
+ const server = new McpServer({ name: "cyberdyne", version: "0.6.8" });
167
167
 
168
168
  server.tool(
169
169
  "list_categories",
@@ -254,8 +254,8 @@ server.tool(
254
254
  payload = await signAuthCapture(requirements);
255
255
  }
256
256
  if (!feeTx && deploy_fee) {
257
- const f = deploy_fee as { usd: number; recipient: string; token: string };
258
- feeTx = await payDeployFee({ amountUsd: f.usd, recipient: f.recipient, token: f.token });
257
+ const f = deploy_fee as { amount: number; decimals: number; usd: number; recipient: string; token: string };
258
+ feeTx = await payDeployFee({ amount: f.amount, decimals: f.decimals, recipient: f.recipient, token: f.token });
259
259
  }
260
260
  }
261
261
  }