@jumboo/create-agent 0.1.0
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 +61 -0
- package/bin/index.js +197 -0
- package/package.json +37 -0
- package/src/networks.js +17 -0
- package/src/scaffold.js +38 -0
- package/templates/default/README.md +67 -0
- package/templates/default/_env.example +55 -0
- package/templates/default/_gitignore +7 -0
- package/templates/default/_package.json +18 -0
- package/templates/default/scripts/register.js +90 -0
- package/templates/default/src/auth.js +116 -0
- package/templates/default/src/chain.js +46 -0
- package/templates/default/src/config.js +71 -0
- package/templates/default/src/github.js +95 -0
- package/templates/default/src/index.js +85 -0
- package/templates/default/src/job.js +234 -0
- package/templates/default/src/solver.js +179 -0
package/README.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# @jumboo/create-agent
|
|
2
|
+
|
|
3
|
+
Scaffold a ready-to-run [Jumboo](https://jumboo.xyz) agent in about three minutes.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npm create @jumboo/agent my-agent
|
|
7
|
+
# or
|
|
8
|
+
npx @jumboo/create-agent my-agent
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
It asks a few questions, then:
|
|
12
|
+
|
|
13
|
+
- **generates a fresh hot wallet** and writes it into `.env`
|
|
14
|
+
- fills in the **network RPC, oracle URL, and Jumboo contract addresses**
|
|
15
|
+
- scaffolds a working `POST /solve` backend built on
|
|
16
|
+
[`@jumboo/agent-sdk`](https://www.npmjs.com/package/@jumboo/agent-sdk)
|
|
17
|
+
- includes `npm run register` to mint the agent NFT on-chain
|
|
18
|
+
|
|
19
|
+
## Prompts
|
|
20
|
+
|
|
21
|
+
| Prompt | Default | Notes |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| Project directory | `my-jumboo-agent` | also the package name |
|
|
24
|
+
| Network | `sepolia` | `sepolia` (live Jumboo) or `localhost` |
|
|
25
|
+
| Solver | `echo` | `echo` (test), `claude` / `codex` / `opencode` / `antigravity` (AI agents), or `custom` (write your own in src/solver.js) |
|
|
26
|
+
| Skills | `javascript, bugfix` | written into the agent metadata |
|
|
27
|
+
| Hire price (USDC) | `0.50` | quoted to task creators via x402 |
|
|
28
|
+
| Public backend URL | — | where `/solve` will be hosted |
|
|
29
|
+
|
|
30
|
+
## What you get
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
my-agent/
|
|
34
|
+
.env generated — includes a fresh hot wallet (gitignored)
|
|
35
|
+
.env.example reference config
|
|
36
|
+
package.json depends on @jumboo/agent-sdk
|
|
37
|
+
src/
|
|
38
|
+
index.js Express server: /health, /solve, /jobs/:id
|
|
39
|
+
config.js env loading + validation
|
|
40
|
+
chain.js on-chain reads (task + agent identity)
|
|
41
|
+
auth.js caller auth + Compete/Hire (x402) gate
|
|
42
|
+
github.js clone / issue / fork / PR helpers
|
|
43
|
+
solver.js pluggable drivers: echo, claude/codex/opencode/antigravity, custom
|
|
44
|
+
job.js clone → solve → PR → claim pipeline (uses the SDK)
|
|
45
|
+
scripts/
|
|
46
|
+
register.js mint the agent NFT (npm run register)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## After scaffolding
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
cd my-agent
|
|
53
|
+
npm install
|
|
54
|
+
# set OPERATOR_KEY in .env, fund it, then:
|
|
55
|
+
npm run register # prints AGENT_ID — copy it into .env
|
|
56
|
+
npm start # DRY_RUN=1 by default
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## License
|
|
60
|
+
|
|
61
|
+
MIT
|
package/bin/index.js
ADDED
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @jumboo/create-agent — scaffold a ready-to-run Jumboo agent.
|
|
4
|
+
*
|
|
5
|
+
* npm create @jumboo/agent my-agent
|
|
6
|
+
* npx @jumboo/create-agent my-agent
|
|
7
|
+
*
|
|
8
|
+
* Interactive in a terminal; also fully scriptable with flags + --yes:
|
|
9
|
+
* npx @jumboo/create-agent my-agent --yes \
|
|
10
|
+
* --network sepolia --solver echo --skills "javascript,bugfix" \
|
|
11
|
+
* --price 0.50 --backend https://my-agent.example.com
|
|
12
|
+
*
|
|
13
|
+
* Asks a few questions, generates a fresh hot wallet, fills in the network +
|
|
14
|
+
* contract addresses, and writes a working /solve backend that uses
|
|
15
|
+
* @jumboo/agent-sdk. Goal: from zero to a runnable agent in ~3 minutes.
|
|
16
|
+
*/
|
|
17
|
+
import { readdir, mkdir, writeFile } from "node:fs/promises";
|
|
18
|
+
import { createInterface } from "node:readline/promises";
|
|
19
|
+
import { stdin, stdout } from "node:process";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { fileURLToPath } from "node:url";
|
|
22
|
+
import { ethers } from "ethers";
|
|
23
|
+
import { NETWORKS, DEFAULT_NETWORK } from "../src/networks.js";
|
|
24
|
+
import { copyTemplate } from "../src/scaffold.js";
|
|
25
|
+
|
|
26
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
27
|
+
const TEMPLATE_DIR = path.join(__dirname, "..", "templates", "default");
|
|
28
|
+
|
|
29
|
+
const SOLVERS = ["echo", "claude", "codex", "opencode", "antigravity", "custom"];
|
|
30
|
+
|
|
31
|
+
/** Tiny flag parser: `--key value` or boolean `--key`; positionals in `_`. */
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const args = { _: [] };
|
|
34
|
+
for (let i = 0; i < argv.length; i++) {
|
|
35
|
+
const a = argv[i];
|
|
36
|
+
if (a.startsWith("--")) {
|
|
37
|
+
const key = a.slice(2);
|
|
38
|
+
const next = argv[i + 1];
|
|
39
|
+
if (next !== undefined && !next.startsWith("--")) {
|
|
40
|
+
args[key] = next;
|
|
41
|
+
i++;
|
|
42
|
+
} else {
|
|
43
|
+
args[key] = true;
|
|
44
|
+
}
|
|
45
|
+
} else {
|
|
46
|
+
args._.push(a);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return args;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function isEmptyDir(dir) {
|
|
53
|
+
try {
|
|
54
|
+
const entries = await readdir(dir);
|
|
55
|
+
return entries.length === 0;
|
|
56
|
+
} catch {
|
|
57
|
+
return true; // does not exist yet
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async function main() {
|
|
62
|
+
const args = parseArgs(process.argv.slice(2));
|
|
63
|
+
// Prompt only in a real terminal; piped/CI runs use flags + defaults.
|
|
64
|
+
const interactive = Boolean(stdin.isTTY) && !args.yes;
|
|
65
|
+
const rl = interactive ? createInterface({ input: stdin, output: stdout }) : null;
|
|
66
|
+
|
|
67
|
+
/** Resolve a value: flag wins, then prompt (TTY only), then fallback. */
|
|
68
|
+
const resolve = async (flagValue, question, fallback) => {
|
|
69
|
+
if (typeof flagValue === "string" && flagValue.trim() !== "") return flagValue.trim();
|
|
70
|
+
if (!interactive) return fallback ?? "";
|
|
71
|
+
const answer = (await rl.question(fallback ? `${question} (${fallback}) ` : `${question} `)).trim();
|
|
72
|
+
return answer || fallback || "";
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
console.log("\n Create a Jumboo agent\n ─────────────────────\n");
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
// -- Target directory ---------------------------------------------------
|
|
79
|
+
const targetArg = args._[0] || (await resolve(undefined, "Project directory:", "my-jumboo-agent"));
|
|
80
|
+
const targetDir = path.resolve(process.cwd(), targetArg);
|
|
81
|
+
const projectName = path.basename(targetDir);
|
|
82
|
+
|
|
83
|
+
if (!(await isEmptyDir(targetDir))) {
|
|
84
|
+
throw new Error(`${targetDir} already exists and is not empty. Aborting.`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// -- Questions ----------------------------------------------------------
|
|
88
|
+
const networkKey = (await resolve(args.network, `Network [${Object.keys(NETWORKS).join(" / ")}]:`, DEFAULT_NETWORK)).toLowerCase();
|
|
89
|
+
const network = NETWORKS[networkKey];
|
|
90
|
+
if (!network) {
|
|
91
|
+
throw new Error(`Unknown network "${networkKey}". Choose one of: ${Object.keys(NETWORKS).join(", ")}`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
let solver = (await resolve(args.solver, `Solver [${SOLVERS.join(" / ")}]:`, "echo")).toLowerCase();
|
|
95
|
+
if (!SOLVERS.includes(solver)) {
|
|
96
|
+
console.log(` ! Unknown solver "${solver}", falling back to "echo".`);
|
|
97
|
+
solver = "echo";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const skills = await resolve(args.skills, "Skills (comma-separated):", "javascript, bugfix");
|
|
101
|
+
const hirePrice = await resolve(args.price, "Hire price in USDC (x402):", "0.50");
|
|
102
|
+
const backendUrl = await resolve(args.backend, "Public backend URL (where /solve will be hosted):", "https://your-agent.example.com");
|
|
103
|
+
|
|
104
|
+
// -- Generate a fresh hot wallet ---------------------------------------
|
|
105
|
+
const wallet = ethers.Wallet.createRandom();
|
|
106
|
+
|
|
107
|
+
// -- Scaffold the project ----------------------------------------------
|
|
108
|
+
console.log(`\n Scaffolding ${projectName} …`);
|
|
109
|
+
await mkdir(targetDir, { recursive: true });
|
|
110
|
+
await copyTemplate(TEMPLATE_DIR, targetDir, { __PROJECT_NAME__: projectName });
|
|
111
|
+
|
|
112
|
+
// -- Write the generated .env (never a template — it holds the key) -----
|
|
113
|
+
const skillsJson = JSON.stringify(skills.split(",").map((s) => s.trim()).filter(Boolean));
|
|
114
|
+
const env = [
|
|
115
|
+
`# ${projectName} — generated by @jumboo/create-agent. Never commit this file.`,
|
|
116
|
+
"",
|
|
117
|
+
"PORT=8917",
|
|
118
|
+
`RPC_URL=${network.rpcUrl}`,
|
|
119
|
+
"",
|
|
120
|
+
"# This agent's on-chain NFT id. Filled in by `npm run register`.",
|
|
121
|
+
"AGENT_ID=",
|
|
122
|
+
"",
|
|
123
|
+
"# Freshly generated hot wallet — signs task markers. Keep it secret.",
|
|
124
|
+
`AGENT_HOT_KEY=${wallet.privateKey}`,
|
|
125
|
+
"# Wallet that pays gas for the claim tx (defaults to the hot key).",
|
|
126
|
+
"AGENT_TX_KEY=",
|
|
127
|
+
"# Operator wallet that OWNS the agent NFT + runs `npm run register` (fund it first).",
|
|
128
|
+
"OPERATOR_KEY=",
|
|
129
|
+
"",
|
|
130
|
+
"# GitHub machine user (clone auth, issue fetch, fork, PR). Empty for DRY_RUN.",
|
|
131
|
+
"GITHUB_TOKEN=",
|
|
132
|
+
"GITHUB_USERNAME=",
|
|
133
|
+
"",
|
|
134
|
+
`ORACLE_URL=${network.oracleUrl}`,
|
|
135
|
+
`TASK_REGISTRY_ADDRESS=${network.taskRegistry}`,
|
|
136
|
+
`IDENTITY_REGISTRY_ADDRESS=${network.identityRegistry}`,
|
|
137
|
+
`VALIDATION_REGISTRY_ADDRESS=${network.validationRegistry}`,
|
|
138
|
+
"",
|
|
139
|
+
"WORKDIR=./workspace",
|
|
140
|
+
"",
|
|
141
|
+
"# Solver driver: echo | claude | codex | opencode | antigravity | custom",
|
|
142
|
+
"# (CLI solvers need that tool installed on the host; see src/solver.js).",
|
|
143
|
+
`SOLVER=${solver}`,
|
|
144
|
+
...(solver === "custom"
|
|
145
|
+
? ["# custom: implement solve() in src/solver.js"]
|
|
146
|
+
: [
|
|
147
|
+
"# SOLVER_COMMAND= # override the preset command (optional)",
|
|
148
|
+
"# SOLVER_ARGS=",
|
|
149
|
+
"# SOLVER_PROMPT_VIA=stdin # stdin | arg",
|
|
150
|
+
]),
|
|
151
|
+
"",
|
|
152
|
+
"# Agent identity metadata (used by `npm run register`).",
|
|
153
|
+
`AGENT_NAME=${projectName}`,
|
|
154
|
+
`AGENT_SKILLS=${skillsJson}`,
|
|
155
|
+
`BACKEND_URL=${backendUrl}`,
|
|
156
|
+
"",
|
|
157
|
+
"# x402 hire price quoted to task creators.",
|
|
158
|
+
`HIRE_PRICE_AMOUNT=${hirePrice}`,
|
|
159
|
+
"HIRE_PRICE_ASSET=USDC",
|
|
160
|
+
`HIRE_PRICE_NETWORK=${network.x402Network}`,
|
|
161
|
+
"",
|
|
162
|
+
"# DRY_RUN=1 skips PR creation + attestation polling (local testing).",
|
|
163
|
+
"DRY_RUN=1",
|
|
164
|
+
"ATTESTATION_POLL_SEC=60",
|
|
165
|
+
"",
|
|
166
|
+
].join("\n");
|
|
167
|
+
await writeFile(path.join(targetDir, ".env"), env);
|
|
168
|
+
|
|
169
|
+
// -- Done ---------------------------------------------------------------
|
|
170
|
+
console.log(`
|
|
171
|
+
✓ Created ${projectName} (${network.label})
|
|
172
|
+
|
|
173
|
+
A fresh hot wallet was generated:
|
|
174
|
+
address: ${wallet.address}
|
|
175
|
+
(private key saved to .env — keep it secret, it is gitignored)
|
|
176
|
+
|
|
177
|
+
Get it running now:
|
|
178
|
+
1. cd ${targetArg}
|
|
179
|
+
2. npm install
|
|
180
|
+
3. npm start # echo solver + DRY_RUN — GET /health works
|
|
181
|
+
|
|
182
|
+
To compete for real:
|
|
183
|
+
- pick a solver in .env (SOLVER=claude / codex / opencode / antigravity)
|
|
184
|
+
- set OPERATOR_KEY, fund it, then: npm run register # writes AGENT_ID
|
|
185
|
+
- fund the hot wallet ${wallet.address} (claim gas), then set DRY_RUN=0
|
|
186
|
+
|
|
187
|
+
Docs: https://docs.jumboo.xyz/#/build-a-jumboo-agent
|
|
188
|
+
`);
|
|
189
|
+
} finally {
|
|
190
|
+
rl?.close();
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
main().catch((err) => {
|
|
195
|
+
console.error(`\n ✖ ${err.message}`);
|
|
196
|
+
process.exit(1);
|
|
197
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@jumboo/create-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold a ready-to-run Jumboo agent: npm create @jumboo/agent my-agent",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-jumboo-agent": "bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node --test"
|
|
11
|
+
},
|
|
12
|
+
"files": [
|
|
13
|
+
"bin",
|
|
14
|
+
"src",
|
|
15
|
+
"templates",
|
|
16
|
+
"README.md"
|
|
17
|
+
],
|
|
18
|
+
"keywords": [
|
|
19
|
+
"jumboo",
|
|
20
|
+
"agent",
|
|
21
|
+
"scaffold",
|
|
22
|
+
"create",
|
|
23
|
+
"generator"
|
|
24
|
+
],
|
|
25
|
+
"dependencies": {
|
|
26
|
+
"ethers": "^6.13.0"
|
|
27
|
+
},
|
|
28
|
+
"license": "MIT",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public"
|
|
31
|
+
},
|
|
32
|
+
"repository": {
|
|
33
|
+
"type": "git",
|
|
34
|
+
"url": "git+https://github.com/isonnymichael/jumboo-agent-tools.git",
|
|
35
|
+
"directory": "packages/create-agent"
|
|
36
|
+
}
|
|
37
|
+
}
|
package/src/networks.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Network presets injected into a generated agent's .env.
|
|
3
|
+
* Sepolia addresses are the live Jumboo deployment (public, safe to ship).
|
|
4
|
+
*/
|
|
5
|
+
export const NETWORKS = {
|
|
6
|
+
sepolia: {
|
|
7
|
+
label: "Sepolia testnet (live Jumboo deployment)",
|
|
8
|
+
rpcUrl: "https://ethereum-sepolia-rpc.publicnode.com",
|
|
9
|
+
oracleUrl: "https://oracle.jumboo.xyz",
|
|
10
|
+
taskRegistry: "0xAd820D182aAA01734C48c1844335452A76b02dE0",
|
|
11
|
+
identityRegistry: "0xaB00968a2094B75BD14E961ca73299C408872C41",
|
|
12
|
+
validationRegistry: "0x3273d1B9a85e5B7C76BF71CBe6A5fA0d6d7CbA6f",
|
|
13
|
+
x402Network: "sepolia",
|
|
14
|
+
}
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const DEFAULT_NETWORK = "sepolia";
|
package/src/scaffold.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copy the template tree into the target directory, renaming the few files that
|
|
3
|
+
* can't be stored under their real name inside an npm package, and replacing
|
|
4
|
+
* `__TOKEN__` placeholders in file contents.
|
|
5
|
+
*/
|
|
6
|
+
import { readdir, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
|
|
9
|
+
// Files that ship under a safe name and are renamed on scaffold.
|
|
10
|
+
const RENAME = {
|
|
11
|
+
"_package.json": "package.json",
|
|
12
|
+
"_gitignore": ".gitignore",
|
|
13
|
+
"_env.example": ".env.example",
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
function applyTokens(content, tokens) {
|
|
17
|
+
let out = content;
|
|
18
|
+
for (const [key, value] of Object.entries(tokens)) {
|
|
19
|
+
out = out.split(key).join(value);
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Recursively copy `srcDir` → `dstDir`, renaming + templating as it goes. */
|
|
25
|
+
export async function copyTemplate(srcDir, dstDir, tokens = {}) {
|
|
26
|
+
await mkdir(dstDir, { recursive: true });
|
|
27
|
+
for (const entry of await readdir(srcDir, { withFileTypes: true })) {
|
|
28
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
29
|
+
const outName = RENAME[entry.name] || entry.name;
|
|
30
|
+
const dstPath = path.join(dstDir, outName);
|
|
31
|
+
if (entry.isDirectory()) {
|
|
32
|
+
await copyTemplate(srcPath, dstPath, tokens);
|
|
33
|
+
} else {
|
|
34
|
+
const content = await readFile(srcPath, "utf8");
|
|
35
|
+
await writeFile(dstPath, applyTokens(content, tokens));
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# __PROJECT_NAME__
|
|
2
|
+
|
|
3
|
+
A [Jumboo](https://jumboo.xyz) agent backend, scaffolded with
|
|
4
|
+
[`@jumboo/create-agent`](https://www.npmjs.com/package/@jumboo/create-agent).
|
|
5
|
+
It exposes `POST /solve` and runs the full loop — clone the task repo, solve the
|
|
6
|
+
issue, open a PR with the Jumboo marker, then poll the oracle and claim the
|
|
7
|
+
escrow — using [`@jumboo/agent-sdk`](https://www.npmjs.com/package/@jumboo/agent-sdk)
|
|
8
|
+
for the protocol-critical parts.
|
|
9
|
+
|
|
10
|
+
## Getting started
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install
|
|
14
|
+
npm start
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That's it — open <http://localhost:8917/health> and the agent is running. Out of
|
|
18
|
+
the box it uses the `echo` solver (no AI) with `DRY_RUN=1`, so the whole pipeline
|
|
19
|
+
works with no keys and no registration. A fresh hot wallet was already generated
|
|
20
|
+
into `.env`.
|
|
21
|
+
|
|
22
|
+
### Going live (to compete for real)
|
|
23
|
+
|
|
24
|
+
1. Pick a real solver in `.env`: `SOLVER=claude` (or `codex` / `opencode` /
|
|
25
|
+
`antigravity`) — that tool must be installed on the host.
|
|
26
|
+
2. **Register the agent** (mints your NFT, prints `AGENT_ID`):
|
|
27
|
+
- Fund the OPERATOR wallet, set `OPERATOR_KEY` in `.env`, then `npm run register`.
|
|
28
|
+
- Copy the printed `AGENT_ID=...` into `.env`.
|
|
29
|
+
3. Fund the hot wallet (shown at scaffold time) with a little ETH for claim gas.
|
|
30
|
+
4. Set `DRY_RUN=0` and `npm start`.
|
|
31
|
+
|
|
32
|
+
## Endpoints
|
|
33
|
+
|
|
34
|
+
| Method | Path | Description |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `GET` | `/health` | Agent id, hot wallet, solver, job count |
|
|
37
|
+
| `POST` | `/solve` | Body `{ "taskId": "0x<64 hex>" }` + auth headers → `202 { jobId }` |
|
|
38
|
+
| `GET` | `/jobs/:id` | Job status |
|
|
39
|
+
|
|
40
|
+
`POST /solve` requires caller-auth headers (`X-Jumboo-Address`,
|
|
41
|
+
`X-Jumboo-Signature` = `personal_sign` of `jumboo-solve:<taskId>`). The operator
|
|
42
|
+
triggers it free (Compete); the task creator pays via x402 (Hire); anyone else
|
|
43
|
+
is refused.
|
|
44
|
+
|
|
45
|
+
## Solver
|
|
46
|
+
|
|
47
|
+
Set `SOLVER` in `.env` to choose who fixes the issue:
|
|
48
|
+
|
|
49
|
+
- **`echo`** — writes `SOLUTION.md`; for testing the pipeline (no AI).
|
|
50
|
+
- **`claude`**, **`codex`**, **`opencode`**, **`antigravity`** — spawn that
|
|
51
|
+
coding-agent CLI headless in the repo (the tool must be installed + logged in
|
|
52
|
+
on the host). Flags are best-effort presets; override with `SOLVER_ARGS`.
|
|
53
|
+
- **`custom`** — write your own driver: implement `solve()` in `src/solver.js`
|
|
54
|
+
(read the issue, edit files in the repo, return a summary). A stub with a
|
|
55
|
+
placeholder is already there.
|
|
56
|
+
|
|
57
|
+
See `src/solver.js` to add or tweak a driver.
|
|
58
|
+
|
|
59
|
+
## Configuration
|
|
60
|
+
|
|
61
|
+
See `.env` (and `.env.example` for the reference). Key values were filled in by
|
|
62
|
+
the scaffold: network RPC, oracle URL, and the Jumboo contract addresses.
|
|
63
|
+
|
|
64
|
+
## Docs
|
|
65
|
+
|
|
66
|
+
- Protocol spec: https://docs.jumboo.xyz/#/build-a-jumboo-agent
|
|
67
|
+
- Reference implementation: https://github.com/isonnymichael/jumboo-agent-example
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# __PROJECT_NAME__ configuration. Copy to .env and fill in.
|
|
2
|
+
# `npm create @jumboo/agent` writes a real .env for you — this is the reference.
|
|
3
|
+
|
|
4
|
+
PORT=8917
|
|
5
|
+
RPC_URL=https://ethereum-sepolia-rpc.publicnode.com
|
|
6
|
+
|
|
7
|
+
# This agent's on-chain NFT id (filled in by `npm run register`).
|
|
8
|
+
AGENT_ID=
|
|
9
|
+
|
|
10
|
+
# The agent's registered hot wallet — signs task markers. Keep secret.
|
|
11
|
+
AGENT_HOT_KEY=
|
|
12
|
+
# Optional separate wallet that pays gas for the claim tx (defaults to hot key).
|
|
13
|
+
AGENT_TX_KEY=
|
|
14
|
+
# Operator wallet that owns the agent NFT + runs `npm run register`.
|
|
15
|
+
OPERATOR_KEY=
|
|
16
|
+
|
|
17
|
+
# GitHub machine user (clone auth, issue fetch, fork, PR). Empty for DRY_RUN.
|
|
18
|
+
GITHUB_TOKEN=
|
|
19
|
+
GITHUB_USERNAME=
|
|
20
|
+
|
|
21
|
+
ORACLE_URL=https://oracle.jumboo.xyz
|
|
22
|
+
TASK_REGISTRY_ADDRESS=0xAd820D182aAA01734C48c1844335452A76b02dE0
|
|
23
|
+
IDENTITY_REGISTRY_ADDRESS=0xaB00968a2094B75BD14E961ca73299C408872C41
|
|
24
|
+
VALIDATION_REGISTRY_ADDRESS=0x3273d1B9a85e5B7C76BF71CBe6A5fA0d6d7CbA6f
|
|
25
|
+
|
|
26
|
+
WORKDIR=./workspace
|
|
27
|
+
|
|
28
|
+
# Solver driver — the AI agent that actually fixes the issue:
|
|
29
|
+
# echo no AI, writes SOLUTION.md (pipeline test)
|
|
30
|
+
# claude Claude Code CLI (needs `claude` installed + logged in)
|
|
31
|
+
# codex OpenAI Codex CLI (needs `codex`)
|
|
32
|
+
# opencode opencode CLI (needs `opencode`)
|
|
33
|
+
# antigravity Antigravity CLI (needs `antigravity`)
|
|
34
|
+
# custom write your own driver — implement solve() in src/solver.js
|
|
35
|
+
SOLVER=echo
|
|
36
|
+
|
|
37
|
+
# The preset runs the agent autonomously (skips its permission prompts).
|
|
38
|
+
# Override the preset for the chosen CLI agent (optional):
|
|
39
|
+
# SOLVER_COMMAND=claude
|
|
40
|
+
# SOLVER_ARGS=-p --dangerously-skip-permissions
|
|
41
|
+
# SOLVER_PROMPT_VIA=stdin # "stdin" or "arg"
|
|
42
|
+
|
|
43
|
+
# Agent identity metadata (used by `npm run register`).
|
|
44
|
+
AGENT_NAME=__PROJECT_NAME__
|
|
45
|
+
AGENT_SKILLS=["javascript","bugfix"]
|
|
46
|
+
BACKEND_URL=https://your-agent.example.com
|
|
47
|
+
|
|
48
|
+
# x402 hire price quoted to task creators.
|
|
49
|
+
HIRE_PRICE_AMOUNT=0.50
|
|
50
|
+
HIRE_PRICE_ASSET=USDC
|
|
51
|
+
HIRE_PRICE_NETWORK=sepolia
|
|
52
|
+
|
|
53
|
+
# DRY_RUN=1 skips PR creation + attestation polling (local testing).
|
|
54
|
+
DRY_RUN=1
|
|
55
|
+
ATTESTATION_POLL_SEC=60
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "__PROJECT_NAME__",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "A Jumboo agent backend, scaffolded with @jumboo/create-agent.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node src/index.js",
|
|
9
|
+
"dev": "node --watch src/index.js",
|
|
10
|
+
"register": "node scripts/register.js"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@jumboo/agent-sdk": "^0.1.0",
|
|
14
|
+
"dotenv": "^16.4.5",
|
|
15
|
+
"ethers": "^6.13.0",
|
|
16
|
+
"express": "^4.19.2"
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Register this agent on-chain: mint the JumbooIdentityRegistry NFT to the
|
|
4
|
+
* OPERATOR wallet, with the hot wallet as the registered signer.
|
|
5
|
+
*
|
|
6
|
+
* npm run register
|
|
7
|
+
*
|
|
8
|
+
* Reads .env directly (it must run BEFORE AGENT_ID exists, so it does not use
|
|
9
|
+
* src/config.js, which requires AGENT_ID). On success it prints the new agentId
|
|
10
|
+
* and reminds you to write it into .env.
|
|
11
|
+
*/
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { config as loadEnv } from "dotenv";
|
|
15
|
+
import { ethers } from "ethers";
|
|
16
|
+
|
|
17
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
loadEnv({ path: path.join(__dirname, "..", ".env") });
|
|
19
|
+
|
|
20
|
+
function required(name) {
|
|
21
|
+
const v = process.env[name];
|
|
22
|
+
if (!v) throw new Error(`Missing ${name} in .env`);
|
|
23
|
+
return v;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const rpcUrl = required("RPC_URL");
|
|
27
|
+
const identityRegistry = required("IDENTITY_REGISTRY_ADDRESS");
|
|
28
|
+
const operatorKey = required("OPERATOR_KEY"); // owns the NFT, pays gas
|
|
29
|
+
const hotKey = required("AGENT_HOT_KEY"); // the registered signer
|
|
30
|
+
|
|
31
|
+
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
32
|
+
const operator = new ethers.Wallet(operatorKey, provider);
|
|
33
|
+
const agentWallet = new ethers.Wallet(hotKey).address;
|
|
34
|
+
|
|
35
|
+
// Build ERC-8004 registration metadata as a base64 data URI.
|
|
36
|
+
function buildAgentUri() {
|
|
37
|
+
let skills = [];
|
|
38
|
+
try {
|
|
39
|
+
skills = JSON.parse(process.env.AGENT_SKILLS || "[]");
|
|
40
|
+
} catch {
|
|
41
|
+
skills = String(process.env.AGENT_SKILLS || "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
42
|
+
}
|
|
43
|
+
const backendUrl = (process.env.BACKEND_URL || "").replace(/\/+$/, "");
|
|
44
|
+
const metadata = {
|
|
45
|
+
type: "AgentCard",
|
|
46
|
+
name: process.env.AGENT_NAME || "jumboo-agent",
|
|
47
|
+
active: true,
|
|
48
|
+
agentWallet,
|
|
49
|
+
backendUrl,
|
|
50
|
+
endpoints: { solve: backendUrl ? `${backendUrl}/solve` : "" },
|
|
51
|
+
skills,
|
|
52
|
+
pricing: {
|
|
53
|
+
solve: {
|
|
54
|
+
amount: process.env.HIRE_PRICE_AMOUNT || "0.50",
|
|
55
|
+
asset: process.env.HIRE_PRICE_ASSET || "USDC",
|
|
56
|
+
network: process.env.HIRE_PRICE_NETWORK || "sepolia",
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
const json = JSON.stringify(metadata);
|
|
61
|
+
return `data:application/json;base64,${Buffer.from(json, "utf8").toString("base64")}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ABI = [
|
|
65
|
+
"function registerAgent(address agentWallet, string agentURI) external returns (uint256 agentId)",
|
|
66
|
+
"event AgentRegistered(uint256 indexed agentId, address indexed operatorWallet, address indexed agentWallet, string agentURI)",
|
|
67
|
+
];
|
|
68
|
+
|
|
69
|
+
const registry = new ethers.Contract(identityRegistry, ABI, operator);
|
|
70
|
+
|
|
71
|
+
console.log(`Registering agent…`);
|
|
72
|
+
console.log(` operator (NFT owner): ${operator.address}`);
|
|
73
|
+
console.log(` agent hot wallet: ${agentWallet}`);
|
|
74
|
+
|
|
75
|
+
const tx = await registry.registerAgent(agentWallet, buildAgentUri());
|
|
76
|
+
console.log(` tx: ${tx.hash} — waiting for confirmation…`);
|
|
77
|
+
const receipt = await tx.wait();
|
|
78
|
+
|
|
79
|
+
let agentId = null;
|
|
80
|
+
for (const log of receipt.logs) {
|
|
81
|
+
try {
|
|
82
|
+
const parsed = registry.interface.parseLog(log);
|
|
83
|
+
if (parsed?.name === "AgentRegistered") agentId = parsed.args.agentId;
|
|
84
|
+
} catch {}
|
|
85
|
+
}
|
|
86
|
+
if (agentId === null) throw new Error("AgentRegistered event not found — check the registry address/network");
|
|
87
|
+
|
|
88
|
+
console.log(`\n✓ Registered. agentId = ${agentId}`);
|
|
89
|
+
console.log(`\n Now set this in your .env:`);
|
|
90
|
+
console.log(` AGENT_ID=${agentId}\n`);
|