create-fetch-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/LICENSE +21 -0
- package/README.md +151 -0
- package/bin/cli.js +102 -0
- package/package.json +55 -0
- package/src/agentverse.js +63 -0
- package/src/env.js +158 -0
- package/src/scaffold.js +316 -0
- package/src/seeds.js +12 -0
- package/src/skills.js +115 -0
- package/src/wizard.js +155 -0
- package/src/workers.js +455 -0
- package/templates/gitignore +44 -0
- package/templates/orchestrator-workers/agents/__init__.py +0 -0
- package/templates/orchestrator-workers/agents/models/__init__.py +0 -0
- package/templates/orchestrator-workers/agents/models/models.py +23 -0
- package/templates/orchestrator-workers/agents/orchestrator/__init__.py +0 -0
- package/templates/orchestrator-workers/agents/services/__init__.py +0 -0
- package/templates/orchestrator-workers/agents/services/state_service.py +22 -0
- package/templates/orchestrator-workers/requirements.txt +42 -0
- package/templates/single-agent/requirements.txt +42 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 anishkancherla-fetchai
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
# create-fetch-agent
|
|
2
|
+
|
|
3
|
+
Scaffold a **runnable Fetch.ai [uAgents](https://uagents.fetch.ai) project** with
|
|
4
|
+
one command, then layer in context for your AI coding tool.
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
npx create-fetch-agent my-app
|
|
8
|
+
# or
|
|
9
|
+
npm create fetch-agent@latest my-app
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
The generated project runs immediately — seeds are pre-filled, addresses are
|
|
13
|
+
derived from them, ports are assigned, and a `Makefile` starts every agent. The
|
|
14
|
+
only things left as `TODO` are the workflow functions where *your* logic goes.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## What you get
|
|
19
|
+
|
|
20
|
+
An interactive wizard asks a few questions and then:
|
|
21
|
+
|
|
22
|
+
1. **Generates a runnable uAgents project** (single agent, or an orchestrator +
|
|
23
|
+
workers system).
|
|
24
|
+
2. **Pre-generates unique seeds** for every agent and wires up addresses + ports.
|
|
25
|
+
3. **Installs AI-editor context** (optional) so Cursor / Claude Code / Antigravity
|
|
26
|
+
/ `AGENTS.md` know how to extend the project correctly.
|
|
27
|
+
4. **Bootstraps the Python environment** (optional) with `uv`, `poetry`, or `pip`.
|
|
28
|
+
5. **Prints honest Agentverse guidance** for connecting your agents to ASI:One.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Wizard options
|
|
33
|
+
|
|
34
|
+
| Prompt | Choices |
|
|
35
|
+
| --- | --- |
|
|
36
|
+
| **Project name** | directory created under the current folder (or pass it as an argument) |
|
|
37
|
+
| **What are you building?** | `Single agent` · `Chat agent (ASI:One ready)` · `Orchestrator + workers` · `Payment agent (FET + Stripe)` |
|
|
38
|
+
| **Worker count & names** | only for orchestrator + workers (defaults `alice`, `bob`) |
|
|
39
|
+
| **Python setup** | `uv` (default) · `poetry` · `pip + venv` |
|
|
40
|
+
| **AI-editor context** | any of `Cursor` · `Claude Code` · `Antigravity` · `AGENTS.md` (or none) |
|
|
41
|
+
| **Register on Agentverse** | `Later` · `Yes` (prints inspector steps) |
|
|
42
|
+
| **Install dependencies now?** | yes / no |
|
|
43
|
+
|
|
44
|
+
> **v1 scope:** `Single agent` and `Orchestrator + workers` are fully implemented.
|
|
45
|
+
> `Chat agent` builds on the single-agent base (already chat/ASI:One ready) plus
|
|
46
|
+
> the `chat-protocol` skill. `Payment agent` builds the single-agent base plus the
|
|
47
|
+
> payment skills as context — the full payment code path is documented future
|
|
48
|
+
> work, not half-built code.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## The two project shapes
|
|
53
|
+
|
|
54
|
+
### Single agent (`Single agent` / `Chat agent`)
|
|
55
|
+
|
|
56
|
+
A flat, self-contained, chat-enabled agent that's ASI:One ready out of the box:
|
|
57
|
+
|
|
58
|
+
```
|
|
59
|
+
my-app/
|
|
60
|
+
agent.py # speaks the chat protocol; agent_workflow(query) is your hook
|
|
61
|
+
.env # AGENT_SEED_PHRASE (pre-generated)
|
|
62
|
+
requirements.txt
|
|
63
|
+
Makefile # make run
|
|
64
|
+
README.md
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Orchestrator + workers (`Orchestrator + workers`)
|
|
68
|
+
|
|
69
|
+
A hub-and-spoke system around one shared message contract. The **orchestrator** is
|
|
70
|
+
the sole ASI:One bridge: it owns the chat protocol, routes each message to a worker
|
|
71
|
+
by name, and relays the worker's result back to the user. **Workers** run a
|
|
72
|
+
`<name>_workflow(state)` and send the state back.
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
my-app/
|
|
76
|
+
agents/
|
|
77
|
+
models/
|
|
78
|
+
models.py # SharedAgentState (the message contract)
|
|
79
|
+
config.py # <NAME>_SEED + <NAME>_ADDRESS per agent (no hardcoded addresses)
|
|
80
|
+
services/
|
|
81
|
+
state_service.py # InMemoryStateService (swap for Redis/Postgres)
|
|
82
|
+
orchestrator/
|
|
83
|
+
orchestrator_agent.py # chat bridge + /health + /message REST stubs
|
|
84
|
+
chat_protocol.py # routing branches, one per worker
|
|
85
|
+
<worker>/
|
|
86
|
+
<worker>_agent.py # <worker>_workflow(state) — your extension point
|
|
87
|
+
.env # one <NAME>_SEED_PHRASE per agent (pre-generated)
|
|
88
|
+
Makefile # make orchestrator + make <worker>
|
|
89
|
+
requirements.txt
|
|
90
|
+
README.md
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Ports are deterministic: the orchestrator owns `8003`; workers fill
|
|
94
|
+
`8001, 8002, 8004, 8005, …` (skipping `8003`).
|
|
95
|
+
|
|
96
|
+
---
|
|
97
|
+
|
|
98
|
+
## The three-layer model
|
|
99
|
+
|
|
100
|
+
`create-fetch-agent` deliberately separates three concerns:
|
|
101
|
+
|
|
102
|
+
1. **`create-fetch-agent` (this tool)** — owns project structure, runnable starter
|
|
103
|
+
code, seed generation, dependency install, and Agentverse guidance.
|
|
104
|
+
2. **The [`fetch-help`](https://github.com/harvest7777/fetch-help) template** — the
|
|
105
|
+
canonical orchestrator + workers architecture this tool stamps out and
|
|
106
|
+
parameterizes (names, counts, ports, seeds).
|
|
107
|
+
3. **[`fetch-skills`](https://www.npmjs.com/package/fetch-skills)** — a context
|
|
108
|
+
installer that writes `SKILL.md` instruction files for AI coding tools. It
|
|
109
|
+
writes *no code*; this tool delegates the "AI-editor context" step to it
|
|
110
|
+
instead of reinventing thousands of lines of skill markdown.
|
|
111
|
+
|
|
112
|
+
**Design philosophy — hybrid:** emit a *minimal runnable skeleton* (works on the
|
|
113
|
+
first run with no AI tool) whose extension points are pre-marked, then install
|
|
114
|
+
fetch-skills context so your AI tool can flesh those points out correctly.
|
|
115
|
+
|
|
116
|
+
### Where AI-editor context lands
|
|
117
|
+
|
|
118
|
+
| Tool | Path |
|
|
119
|
+
| --- | --- |
|
|
120
|
+
| Cursor | `.cursor/skills/<skill>/SKILL.md` |
|
|
121
|
+
| Claude Code | `.claude/skills/<skill>/SKILL.md` |
|
|
122
|
+
| Antigravity | `.agent/skills/<skill>/SKILL.md` |
|
|
123
|
+
| AGENTS.md | `AGENTS.md` (skills concatenated) |
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
## Talking to your agents (Agentverse / ASI:One)
|
|
128
|
+
|
|
129
|
+
Every generated agent sets `mailbox=True` and `publish_agent_details=True`, so
|
|
130
|
+
"registration" is the browser inspector + mailbox connect flow. Each agent logs
|
|
131
|
+
its exact inspector URL on startup. The CLI prints the step-by-step flow; for the
|
|
132
|
+
orchestrator system you only chat with the orchestrator — it routes to the workers.
|
|
133
|
+
|
|
134
|
+
Programmatic registration (`AGENTVERSE_API_KEY`) is documented future work, not v1.
|
|
135
|
+
|
|
136
|
+
---
|
|
137
|
+
|
|
138
|
+
## Development
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
npm install
|
|
142
|
+
npm test # fast unit + integration tests
|
|
143
|
+
CFA_PACK=1 npm test # also verify the published file set via `npm pack`
|
|
144
|
+
CFA_SMOKE=1 npm test # also boot a generated orchestrator and curl /health (needs Python)
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## License
|
|
150
|
+
|
|
151
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
|
|
6
|
+
import { runWizard } from "../src/wizard.js";
|
|
7
|
+
import { scaffold } from "../src/scaffold.js";
|
|
8
|
+
import { installSkills } from "../src/skills.js";
|
|
9
|
+
import { bootstrap, manualInstallCommands } from "../src/env.js";
|
|
10
|
+
import { printAgentverseGuidance } from "../src/agentverse.js";
|
|
11
|
+
|
|
12
|
+
function parseArgs(argv) {
|
|
13
|
+
return argv.filter((a) => !a.startsWith("-"));
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function runPrefix(pythonManager) {
|
|
17
|
+
if (pythonManager === "uv") return "uv run ";
|
|
18
|
+
if (pythonManager === "poetry") return "poetry run ";
|
|
19
|
+
return "";
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function printBanner(logger) {
|
|
23
|
+
logger.log("");
|
|
24
|
+
logger.log(chalk.bold.hex("#1A6FE8")(" create-fetch-agent"));
|
|
25
|
+
logger.log(chalk.dim(" Scaffold a runnable Fetch.ai uAgents project."));
|
|
26
|
+
logger.log("");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function printNextSteps(logger, { answers, targetDir, skillPaths }) {
|
|
30
|
+
const rel = path.relative(process.cwd(), targetDir) || ".";
|
|
31
|
+
const prefix = runPrefix(answers.pythonManager);
|
|
32
|
+
|
|
33
|
+
logger.log("");
|
|
34
|
+
logger.log(chalk.bold.green("✔ Project ready: ") + chalk.cyan(rel));
|
|
35
|
+
logger.log("");
|
|
36
|
+
logger.log(chalk.bold("Next steps:"));
|
|
37
|
+
logger.log(` ${chalk.cyan(`cd ${rel}`)}`);
|
|
38
|
+
|
|
39
|
+
if (!answers.installNow) {
|
|
40
|
+
for (const c of manualInstallCommands(answers.pythonManager)) {
|
|
41
|
+
logger.log(` ${chalk.cyan(c)}`);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
logger.log("");
|
|
46
|
+
if (answers.buildType === "orchestrator_workers") {
|
|
47
|
+
logger.log(chalk.dim(" Start each agent in its own terminal (orchestrator first):"));
|
|
48
|
+
logger.log(` ${chalk.cyan(`${prefix}make orchestrator`)}`);
|
|
49
|
+
for (const n of answers.workers) {
|
|
50
|
+
logger.log(` ${chalk.cyan(`${prefix}make ${n}`)}`);
|
|
51
|
+
}
|
|
52
|
+
} else {
|
|
53
|
+
logger.log(` ${chalk.cyan(`${prefix}make run`)}`);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (skillPaths && skillPaths.length) {
|
|
57
|
+
logger.log("");
|
|
58
|
+
logger.log(chalk.bold("AI-editor context ") + chalk.dim("(via fetch-skills)") + chalk.bold(" installed at:"));
|
|
59
|
+
for (const p of skillPaths) logger.log(` ${chalk.cyan(p)}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
logger.log("");
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function main() {
|
|
66
|
+
const logger = console;
|
|
67
|
+
printBanner(logger);
|
|
68
|
+
|
|
69
|
+
const positionals = parseArgs(process.argv.slice(2));
|
|
70
|
+
const answers = await runWizard({ argv: positionals, logger });
|
|
71
|
+
|
|
72
|
+
const { targetDir } = await scaffold(answers);
|
|
73
|
+
logger.log(chalk.green(`\n✔ Generated project files in ${path.basename(targetDir)}/`));
|
|
74
|
+
|
|
75
|
+
let skillPaths = [];
|
|
76
|
+
if (answers.aiTargets && answers.aiTargets.length) {
|
|
77
|
+
logger.log(chalk.bold("\n◆ Installing AI-editor context ") + chalk.dim("(via fetch-skills)"));
|
|
78
|
+
logger.log(chalk.dim("─".repeat(40)));
|
|
79
|
+
const { paths } = await installSkills(answers, { targetRoot: targetDir, logger });
|
|
80
|
+
skillPaths = paths;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (answers.installNow) {
|
|
84
|
+
logger.log(chalk.bold("\n◆ Installing dependencies"));
|
|
85
|
+
logger.log(chalk.dim("─".repeat(40)));
|
|
86
|
+
await bootstrap(answers, { cwd: targetDir, logger });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
printAgentverseGuidance(answers, { logger });
|
|
90
|
+
printNextSteps(logger, { answers, targetDir, skillPaths });
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main().catch((err) => {
|
|
94
|
+
if (err && (err.name === "ExitPromptError" || err.name === "AbortPromptError")) {
|
|
95
|
+
console.log("");
|
|
96
|
+
console.log(chalk.dim("Aborted."));
|
|
97
|
+
process.exit(130);
|
|
98
|
+
}
|
|
99
|
+
console.error("");
|
|
100
|
+
console.error(chalk.red(`create-fetch-agent failed: ${err && err.message ? err.message : err}`));
|
|
101
|
+
process.exit(1);
|
|
102
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-fetch-agent",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Scaffold runnable Fetch.ai uAgent projects with one command, then layer in AI-coding-tool context.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"create-fetch-agent": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin",
|
|
11
|
+
"src",
|
|
12
|
+
"templates"
|
|
13
|
+
],
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "node --test",
|
|
16
|
+
"prepublishOnly": "node --test"
|
|
17
|
+
},
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"fetch.ai",
|
|
26
|
+
"fetchai",
|
|
27
|
+
"uagents",
|
|
28
|
+
"agents",
|
|
29
|
+
"ai-agents",
|
|
30
|
+
"asi",
|
|
31
|
+
"asi-one",
|
|
32
|
+
"scaffold",
|
|
33
|
+
"create",
|
|
34
|
+
"generator",
|
|
35
|
+
"cli"
|
|
36
|
+
],
|
|
37
|
+
"author": "Fetch.ai",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"@inquirer/prompts": "^7.2.0",
|
|
41
|
+
"chalk": "^5.3.0",
|
|
42
|
+
"execa": "^9.5.0",
|
|
43
|
+
"fetch-skills": "2.0.3",
|
|
44
|
+
"fs-extra": "^11.2.0",
|
|
45
|
+
"ora": "^8.1.0"
|
|
46
|
+
},
|
|
47
|
+
"repository": {
|
|
48
|
+
"type": "git",
|
|
49
|
+
"url": "git+https://github.com/anishkancherla-fetchai/create-fetch-agent.git"
|
|
50
|
+
},
|
|
51
|
+
"bugs": {
|
|
52
|
+
"url": "https://github.com/anishkancherla-fetchai/create-fetch-agent/issues"
|
|
53
|
+
},
|
|
54
|
+
"homepage": "https://github.com/anishkancherla-fetchai/create-fetch-agent#readme"
|
|
55
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Print honest Agentverse registration guidance.
|
|
5
|
+
*
|
|
6
|
+
* There is no programmatic registration in v1. The generated agents already set
|
|
7
|
+
* mailbox=True and publish_agent_details=True, so "registration" is the browser
|
|
8
|
+
* inspector + mailbox connect flow. Each agent logs its exact inspector URL on
|
|
9
|
+
* startup; we print the steps and the URL pattern here.
|
|
10
|
+
*/
|
|
11
|
+
export function printAgentverseGuidance(answers, { logger = console } = {}) {
|
|
12
|
+
const isOrchestrator = answers.buildType === "orchestrator_workers";
|
|
13
|
+
const now = answers.registerNow;
|
|
14
|
+
|
|
15
|
+
logger.log("");
|
|
16
|
+
logger.log(chalk.bold.cyan("◆ Register on Agentverse"));
|
|
17
|
+
logger.log(chalk.dim("─".repeat(40)));
|
|
18
|
+
|
|
19
|
+
if (now) {
|
|
20
|
+
logger.log("Let's connect your agent(s) to Agentverse now.");
|
|
21
|
+
} else {
|
|
22
|
+
logger.log("When you're ready to connect your agent(s) to Agentverse:");
|
|
23
|
+
}
|
|
24
|
+
logger.log("");
|
|
25
|
+
|
|
26
|
+
const steps = [
|
|
27
|
+
"Sign in at https://agentverse.ai (and https://asi1.ai to chat via ASI:One).",
|
|
28
|
+
"Start your agent(s) — each logs its Agentverse inspector URL on startup.",
|
|
29
|
+
isOrchestrator
|
|
30
|
+
? "Open EACH agent's inspector URL in the browser (one per agent)."
|
|
31
|
+
: "Open the inspector URL in the browser.",
|
|
32
|
+
'Click "Connect", then choose "Mailbox".',
|
|
33
|
+
isOrchestrator
|
|
34
|
+
? 'On the ORCHESTRATOR inspector, click "Go to Agent Profile" → "Chat with Agent".'
|
|
35
|
+
: 'Click "Go to Agent Profile" → "Chat with Agent".',
|
|
36
|
+
];
|
|
37
|
+
|
|
38
|
+
steps.forEach((s, i) => logger.log(` ${chalk.cyan(`${i + 1}.`)} ${s}`));
|
|
39
|
+
|
|
40
|
+
logger.log("");
|
|
41
|
+
logger.log(chalk.dim(" Inspector URL pattern (the agent logs the exact one):"));
|
|
42
|
+
logger.log(
|
|
43
|
+
chalk.dim(
|
|
44
|
+
" https://agentverse.ai/inspect/?uri=http%3A//127.0.0.1%3A<PORT>&address=<AGENT_ADDRESS>",
|
|
45
|
+
),
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
if (isOrchestrator) {
|
|
49
|
+
logger.log("");
|
|
50
|
+
logger.log(
|
|
51
|
+
chalk.dim(
|
|
52
|
+
" Tip: only the orchestrator needs ASI:One chat — it routes to the workers.",
|
|
53
|
+
),
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
logger.log("");
|
|
58
|
+
logger.log(
|
|
59
|
+
chalk.dim(
|
|
60
|
+
" Programmatic registration (AGENTVERSE_API_KEY) is documented future work, not v1.",
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
}
|
package/src/env.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { execa } from "execa";
|
|
2
|
+
import ora from "ora";
|
|
3
|
+
import chalk from "chalk";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Convert a pinned requirements.txt into a Poetry pyproject.toml.
|
|
7
|
+
*
|
|
8
|
+
* @param {string} projectName
|
|
9
|
+
* @param {string} requirementsText contents of requirements.txt
|
|
10
|
+
* @returns {string} pyproject.toml contents
|
|
11
|
+
*/
|
|
12
|
+
export function renderPyproject(projectName, requirementsText) {
|
|
13
|
+
const pkgName = String(projectName)
|
|
14
|
+
.toLowerCase()
|
|
15
|
+
.replace(/[^a-z0-9]+/g, "-")
|
|
16
|
+
.replace(/^-+|-+$/g, "") || "fetch-agent";
|
|
17
|
+
|
|
18
|
+
const deps = [];
|
|
19
|
+
for (const rawLine of requirementsText.split("\n")) {
|
|
20
|
+
const line = rawLine.trim();
|
|
21
|
+
if (!line || line.startsWith("#")) continue;
|
|
22
|
+
const m = line.match(/^([A-Za-z0-9._-]+)\s*==\s*(.+)$/);
|
|
23
|
+
if (m) {
|
|
24
|
+
deps.push(`${m[1]} = "${m[2]}"`);
|
|
25
|
+
} else {
|
|
26
|
+
const name = line.split(/[<>=!~ ]/)[0];
|
|
27
|
+
if (name) deps.push(`${name} = "*"`);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return `[tool.poetry]
|
|
32
|
+
name = "${pkgName}"
|
|
33
|
+
version = "0.1.0"
|
|
34
|
+
description = "A Fetch.ai uAgents project."
|
|
35
|
+
authors = []
|
|
36
|
+
package-mode = false
|
|
37
|
+
|
|
38
|
+
[tool.poetry.dependencies]
|
|
39
|
+
python = "^3.12"
|
|
40
|
+
${deps.join("\n")}
|
|
41
|
+
|
|
42
|
+
[build-system]
|
|
43
|
+
requires = ["poetry-core"]
|
|
44
|
+
build-backend = "poetry.core.masonry.api"
|
|
45
|
+
`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Manual commands to print when an automated install can't run.
|
|
50
|
+
*/
|
|
51
|
+
export function manualInstallCommands(pythonManager) {
|
|
52
|
+
switch (pythonManager) {
|
|
53
|
+
case "uv":
|
|
54
|
+
return ["uv venv", "uv pip install -r requirements.txt"];
|
|
55
|
+
case "poetry":
|
|
56
|
+
return ["poetry install"];
|
|
57
|
+
default:
|
|
58
|
+
return [
|
|
59
|
+
"python3.12 -m venv .venv",
|
|
60
|
+
"source .venv/bin/activate",
|
|
61
|
+
"pip install -r requirements.txt",
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async function runStep({ label, cmd, args, cwd, logger }) {
|
|
67
|
+
const interactive = logger === console && Boolean(process.stdout.isTTY);
|
|
68
|
+
const spinner = interactive ? ora({ text: label, color: "cyan" }).start() : null;
|
|
69
|
+
if (!spinner) logger.log(` ${label}`);
|
|
70
|
+
try {
|
|
71
|
+
await execa(cmd, args, { cwd, all: true });
|
|
72
|
+
if (spinner) spinner.succeed(label);
|
|
73
|
+
else logger.log(` ✔ ${label}`);
|
|
74
|
+
return true;
|
|
75
|
+
} catch (err) {
|
|
76
|
+
if (spinner) spinner.fail(label);
|
|
77
|
+
else logger.log(` ✖ ${label}`);
|
|
78
|
+
const detail = (err.all || err.shortMessage || err.message || "").trim();
|
|
79
|
+
if (detail) logger.log(chalk.dim(detail.split("\n").slice(-12).join("\n")));
|
|
80
|
+
throw err;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function commandExists(cmd) {
|
|
85
|
+
try {
|
|
86
|
+
await execa(cmd, ["--version"]);
|
|
87
|
+
return true;
|
|
88
|
+
} catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Bootstrap the Python environment + dependencies for a generated project.
|
|
95
|
+
*
|
|
96
|
+
* Branches on the chosen package manager. Never throws on a missing tool —
|
|
97
|
+
* instead prints the exact manual commands so the user can finish by hand.
|
|
98
|
+
*
|
|
99
|
+
* @returns {Promise<{ok: boolean, manual?: string[]}>}
|
|
100
|
+
*/
|
|
101
|
+
export async function bootstrap(answers, { cwd, logger = console } = {}) {
|
|
102
|
+
const pm = answers.pythonManager;
|
|
103
|
+
const manual = manualInstallCommands(pm);
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
if (pm === "uv") {
|
|
107
|
+
if (!(await commandExists("uv"))) {
|
|
108
|
+
return printManual(logger, manual, "`uv` was not found on your PATH.");
|
|
109
|
+
}
|
|
110
|
+
await runStep({ label: "Creating virtual environment (uv venv)", cmd: "uv", args: ["venv"], cwd, logger });
|
|
111
|
+
await runStep({
|
|
112
|
+
label: "Installing dependencies (uv pip install)",
|
|
113
|
+
cmd: "uv",
|
|
114
|
+
args: ["pip", "install", "-r", "requirements.txt"],
|
|
115
|
+
cwd,
|
|
116
|
+
logger,
|
|
117
|
+
});
|
|
118
|
+
return { ok: true };
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
if (pm === "poetry") {
|
|
122
|
+
if (!(await commandExists("poetry"))) {
|
|
123
|
+
return printManual(logger, manual, "`poetry` was not found on your PATH.");
|
|
124
|
+
}
|
|
125
|
+
await runStep({ label: "Installing dependencies (poetry install)", cmd: "poetry", args: ["install"], cwd, logger });
|
|
126
|
+
return { ok: true };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// pip (default fallback)
|
|
130
|
+
const python = (await commandExists("python3.12"))
|
|
131
|
+
? "python3.12"
|
|
132
|
+
: (await commandExists("python3"))
|
|
133
|
+
? "python3"
|
|
134
|
+
: null;
|
|
135
|
+
if (!python) {
|
|
136
|
+
return printManual(logger, manual, "No `python3.12` / `python3` found on your PATH.");
|
|
137
|
+
}
|
|
138
|
+
await runStep({ label: `Creating virtual environment (${python} -m venv .venv)`, cmd: python, args: ["-m", "venv", ".venv"], cwd, logger });
|
|
139
|
+
await runStep({
|
|
140
|
+
label: "Installing dependencies (.venv/bin/pip install)",
|
|
141
|
+
cmd: ".venv/bin/pip",
|
|
142
|
+
args: ["install", "-r", "requirements.txt"],
|
|
143
|
+
cwd,
|
|
144
|
+
logger,
|
|
145
|
+
});
|
|
146
|
+
return { ok: true };
|
|
147
|
+
} catch (err) {
|
|
148
|
+
logger.log(chalk.yellow("\nAutomated install did not complete. Run these manually:"));
|
|
149
|
+
for (const c of manual) logger.log(chalk.cyan(` ${c}`));
|
|
150
|
+
return { ok: false, manual };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
function printManual(logger, manual, reason) {
|
|
155
|
+
logger.log(chalk.yellow(`\n${reason} Run these manually:`));
|
|
156
|
+
for (const c of manual) logger.log(chalk.cyan(` ${c}`));
|
|
157
|
+
return { ok: false, manual };
|
|
158
|
+
}
|