@4mica/cli 0.2.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 +59 -0
- package/dist/index.js +628 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
- package/snapshot.json +100 -0
- package/templates/agent-buyer-node/.env.example +16 -0
- package/templates/agent-buyer-node/README.md +26 -0
- package/templates/agent-buyer-node/_gitignore +6 -0
- package/templates/agent-buyer-node/package.json +19 -0
- package/templates/agent-buyer-node/src/brain.ts +133 -0
- package/templates/agent-buyer-node/src/critic.ts +195 -0
- package/templates/agent-buyer-node/src/pay.ts +54 -0
- package/templates/agent-buyer-node/tsconfig.json +21 -0
- package/templates/agent-seller-express/.env.example +16 -0
- package/templates/agent-seller-express/README.md +26 -0
- package/templates/agent-seller-express/_gitignore +6 -0
- package/templates/agent-seller-express/package.json +22 -0
- package/templates/agent-seller-express/src/brain.ts +121 -0
- package/templates/agent-seller-express/src/comedian.ts +153 -0
- package/templates/agent-seller-express/tsconfig.json +21 -0
- package/templates/buyer-express/.env.example +12 -0
- package/templates/buyer-express/README.md +26 -0
- package/templates/buyer-express/_gitignore +6 -0
- package/templates/buyer-express/package.json +18 -0
- package/templates/buyer-express/src/buyer.ts +82 -0
- package/templates/buyer-express/tsconfig.json +21 -0
- package/templates/buyer-hono/.env.example +12 -0
- package/templates/buyer-hono/README.md +26 -0
- package/templates/buyer-hono/_gitignore +6 -0
- package/templates/buyer-hono/package.json +18 -0
- package/templates/buyer-hono/src/buyer.ts +82 -0
- package/templates/buyer-hono/tsconfig.json +21 -0
- package/templates/buyer-next/.env.example +12 -0
- package/templates/buyer-next/README.md +26 -0
- package/templates/buyer-next/_gitignore +6 -0
- package/templates/buyer-next/package.json +18 -0
- package/templates/buyer-next/src/buyer.ts +82 -0
- package/templates/buyer-next/tsconfig.json +21 -0
- package/templates/seller-express/.env.example +12 -0
- package/templates/seller-express/README.md +26 -0
- package/templates/seller-express/_gitignore +6 -0
- package/templates/seller-express/package.json +22 -0
- package/templates/seller-express/src/server.ts +64 -0
- package/templates/seller-express/tsconfig.json +21 -0
- package/templates/seller-hono/.env.example +12 -0
- package/templates/seller-hono/README.md +26 -0
- package/templates/seller-hono/_gitignore +6 -0
- package/templates/seller-hono/package.json +22 -0
- package/templates/seller-hono/src/server.ts +69 -0
- package/templates/seller-hono/tsconfig.json +21 -0
- package/templates/seller-next/.env.example +12 -0
- package/templates/seller-next/README.md +26 -0
- package/templates/seller-next/_gitignore +6 -0
- package/templates/seller-next/app/api/protected/route.ts +27 -0
- package/templates/seller-next/app/api/session/route.ts +8 -0
- package/templates/seller-next/app/globals.css +556 -0
- package/templates/seller-next/app/icon.svg +5 -0
- package/templates/seller-next/app/layout.tsx +22 -0
- package/templates/seller-next/app/page.tsx +189 -0
- package/templates/seller-next/next.config.mjs +3 -0
- package/templates/seller-next/package.json +25 -0
- package/templates/seller-next/public/logo-light.svg +19 -0
- package/templates/seller-next/public/logo.svg +19 -0
- package/templates/seller-next/scripts/serve.mjs +46 -0
- package/templates/seller-next/tsconfig.json +30 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 4mica Network
|
|
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,59 @@
|
|
|
1
|
+
# @4mica/cli
|
|
2
|
+
|
|
3
|
+
The command-line tool for the [4Mica](https://4mica.io) x402 payment network.
|
|
4
|
+
Scaffold a trading agent or payment app, install it, run it, and manage your
|
|
5
|
+
agents from a local dashboard.
|
|
6
|
+
|
|
7
|
+
## Quick start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npx @4mica/cli
|
|
11
|
+
# or
|
|
12
|
+
npm create @4mica
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
Answer a few prompts and you'll have a running project in demo mode — no
|
|
16
|
+
credentials required.
|
|
17
|
+
|
|
18
|
+
## Commands
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
4mica init [dir] # scaffold a new agent or app (default command)
|
|
22
|
+
4mica dashboard # open the dashboard to manage agents & transactions
|
|
23
|
+
4mica dev # run the current project's dev server
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### `4mica init`
|
|
27
|
+
|
|
28
|
+
Interactive by default. Every prompt has a matching flag for non-interactive /
|
|
29
|
+
CI use:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
4mica init my-app \
|
|
33
|
+
--type seller \ # agent | seller | buyer
|
|
34
|
+
--framework hono \ # express | hono | next (seller/buyer)
|
|
35
|
+
--role seller \ # seller | buyer (agent)
|
|
36
|
+
--pm pnpm \ # pnpm | npm | yarn | bun
|
|
37
|
+
--no-run \ # scaffold without starting the dev server
|
|
38
|
+
--yes # accept defaults, no prompts
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Templates
|
|
42
|
+
|
|
43
|
+
| Type | Frameworks | Based on |
|
|
44
|
+
| --- | --- | --- |
|
|
45
|
+
| Trading agent | seller (Express) · buyer (Node) | seller/buyer agents |
|
|
46
|
+
| Paywalled API (seller) | Express · Hono · Next | x402 paywall |
|
|
47
|
+
| Buyer client | Express · Hono · Next targets | x402 handshake |
|
|
48
|
+
|
|
49
|
+
Every template runs in **demo mode** with a mock verifier. To go live, copy
|
|
50
|
+
`.env.example` → `.env`, fill in your `4MICA_*` credentials, and swap the mock
|
|
51
|
+
verifier for `createClient()` from `@4mica/sdk-node`.
|
|
52
|
+
|
|
53
|
+
## Dashboard
|
|
54
|
+
|
|
55
|
+
`4mica dashboard` launches a local dashboard (Stripe test-mode style) to add and
|
|
56
|
+
remove agents that can trade, view and publish an agent's profile (accuracy,
|
|
57
|
+
uptime, pricing, policy, verification), inspect transactions, and manage the
|
|
58
|
+
trade allow-list. It runs on mock data today; a hosted version with accounts and
|
|
59
|
+
a sandbox→live switch is coming.
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import yargs from 'yargs';
|
|
3
|
+
import { hideBin } from 'yargs/helpers';
|
|
4
|
+
import { readFile, access, readdir, mkdir, writeFile } from 'fs/promises';
|
|
5
|
+
import { join, resolve, dirname, relative, isAbsolute } from 'path';
|
|
6
|
+
import { execa } from 'execa';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { findUp } from 'find-up';
|
|
9
|
+
import ora from 'ora';
|
|
10
|
+
import pc from 'picocolors';
|
|
11
|
+
import { intro, outro, confirm, cancel, select, text, note, isCancel } from '@clack/prompts';
|
|
12
|
+
import terminalLink from 'terminal-link';
|
|
13
|
+
|
|
14
|
+
function bundledTemplatesDir() {
|
|
15
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
return join(here, "..", "templates");
|
|
17
|
+
}
|
|
18
|
+
async function resolveRunContext(cwd = process.cwd()) {
|
|
19
|
+
const workspaceFile = await findUp("pnpm-workspace.yaml", { cwd });
|
|
20
|
+
if (workspaceFile) {
|
|
21
|
+
const root = dirname(workspaceFile);
|
|
22
|
+
try {
|
|
23
|
+
const pkg = JSON.parse(
|
|
24
|
+
await readFile(join(root, "package.json"), "utf8")
|
|
25
|
+
);
|
|
26
|
+
if (pkg.name === "4mica") {
|
|
27
|
+
return {
|
|
28
|
+
inMonorepo: true,
|
|
29
|
+
monorepoRoot: root,
|
|
30
|
+
templatesDir: bundledTemplatesDir()
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
} catch {
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return { inMonorepo: false, templatesDir: bundledTemplatesDir() };
|
|
37
|
+
}
|
|
38
|
+
var LOCKFILES = {
|
|
39
|
+
"pnpm-lock.yaml": "pnpm",
|
|
40
|
+
"package-lock.json": "npm",
|
|
41
|
+
"yarn.lock": "yarn",
|
|
42
|
+
"bun.lockb": "bun",
|
|
43
|
+
"bun.lock": "bun"
|
|
44
|
+
};
|
|
45
|
+
function fromUserAgent() {
|
|
46
|
+
const ua = process.env.npm_config_user_agent;
|
|
47
|
+
if (!ua) return void 0;
|
|
48
|
+
const name = ua.split("/")[0];
|
|
49
|
+
if (name === "pnpm" || name === "npm" || name === "yarn" || name === "bun") {
|
|
50
|
+
return name;
|
|
51
|
+
}
|
|
52
|
+
return void 0;
|
|
53
|
+
}
|
|
54
|
+
async function fromLockfile(cwd) {
|
|
55
|
+
const found = await findUp(Object.keys(LOCKFILES), { cwd });
|
|
56
|
+
if (!found) return void 0;
|
|
57
|
+
const file = found.slice(found.lastIndexOf("/") + 1);
|
|
58
|
+
return LOCKFILES[file];
|
|
59
|
+
}
|
|
60
|
+
async function detectPackageManager(cwd, override) {
|
|
61
|
+
return override ?? fromUserAgent() ?? await fromLockfile(cwd) ?? "pnpm";
|
|
62
|
+
}
|
|
63
|
+
function installArgs(_pm) {
|
|
64
|
+
return ["install"];
|
|
65
|
+
}
|
|
66
|
+
function runArgs(pm, script) {
|
|
67
|
+
return pm === "yarn" ? [script] : ["run", script];
|
|
68
|
+
}
|
|
69
|
+
async function install(pm, cwd) {
|
|
70
|
+
await execa(pm, installArgs(), { cwd, stdio: "pipe" });
|
|
71
|
+
}
|
|
72
|
+
async function runScript(pm, script, cwd) {
|
|
73
|
+
await execa(pm, runArgs(pm, script), { cwd, stdio: "inherit" });
|
|
74
|
+
}
|
|
75
|
+
async function gitInit(cwd) {
|
|
76
|
+
try {
|
|
77
|
+
await execa("git", ["init", "-q"], { cwd });
|
|
78
|
+
await execa("git", ["add", "-A"], { cwd });
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async function openBrowser(url) {
|
|
83
|
+
const cmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
84
|
+
try {
|
|
85
|
+
await execa(cmd, [url], { stdio: "ignore", detached: true });
|
|
86
|
+
} catch {
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
var brand = (s) => pc.cyan(pc.bold(s));
|
|
90
|
+
var dim = (s) => pc.dim(s);
|
|
91
|
+
var ok = (s) => pc.green(s);
|
|
92
|
+
var warn = (s) => pc.yellow(s);
|
|
93
|
+
var err = (s) => pc.red(s);
|
|
94
|
+
function spinner(text2) {
|
|
95
|
+
return ora({ text: text2, color: "cyan" }).start();
|
|
96
|
+
}
|
|
97
|
+
async function step(text2, fn, doneText) {
|
|
98
|
+
const s = spinner(text2);
|
|
99
|
+
try {
|
|
100
|
+
const result = await fn();
|
|
101
|
+
s.succeed(ok(doneText ?? text2));
|
|
102
|
+
return result;
|
|
103
|
+
} catch (error) {
|
|
104
|
+
s.fail(err(`${text2} \u2014 ${error.message}`));
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
// src/commands/dashboard.ts
|
|
110
|
+
async function exists(path) {
|
|
111
|
+
try {
|
|
112
|
+
await access(path);
|
|
113
|
+
return true;
|
|
114
|
+
} catch {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function launchDashboard(opts = {}) {
|
|
119
|
+
const context = await resolveRunContext();
|
|
120
|
+
if (!context.inMonorepo || !context.monorepoRoot) {
|
|
121
|
+
process.stdout.write(
|
|
122
|
+
`
|
|
123
|
+
${warn("Dashboard")} runs from the 4Mica monorepo in this release.
|
|
124
|
+
Clone ${pc.cyan("github.com/4mica-Network/homepage")} and run ${pc.bold("pnpm --filter @4mica/dashboard dev")},
|
|
125
|
+
or use the hosted dashboard (coming soon). ${dim("More: 4mica.io/docs")}
|
|
126
|
+
|
|
127
|
+
`
|
|
128
|
+
);
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const dashboardDir = join(context.monorepoRoot, "apps", "dashboard");
|
|
132
|
+
if (!await exists(dashboardDir)) {
|
|
133
|
+
process.stdout.write(
|
|
134
|
+
`
|
|
135
|
+
${warn("apps/dashboard not found in this checkout.")}
|
|
136
|
+
|
|
137
|
+
`
|
|
138
|
+
);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
const pm = await detectPackageManager(context.monorepoRoot);
|
|
142
|
+
const args = pm === "yarn" ? ["workspace", "@4mica/dashboard", "dev"] : ["--filter", "@4mica/dashboard", "dev"];
|
|
143
|
+
process.stdout.write(`
|
|
144
|
+
${ok("Starting the 4Mica dashboard\u2026")}
|
|
145
|
+
|
|
146
|
+
`);
|
|
147
|
+
if (opts.background) {
|
|
148
|
+
const child2 = execa(pm, args, {
|
|
149
|
+
cwd: context.monorepoRoot,
|
|
150
|
+
stdio: "ignore",
|
|
151
|
+
detached: true
|
|
152
|
+
});
|
|
153
|
+
child2.unref();
|
|
154
|
+
if (opts.open) {
|
|
155
|
+
setTimeout(() => void openBrowser("http://localhost:4173"), 2500);
|
|
156
|
+
}
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
const child = execa(pm, args, {
|
|
160
|
+
cwd: context.monorepoRoot,
|
|
161
|
+
stdio: "inherit"
|
|
162
|
+
});
|
|
163
|
+
if (opts.open) {
|
|
164
|
+
setTimeout(() => void openBrowser("http://localhost:4173"), 2500);
|
|
165
|
+
}
|
|
166
|
+
await child;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// src/commands/dev.ts
|
|
170
|
+
async function devCommand(script = "dev") {
|
|
171
|
+
const cwd = process.cwd();
|
|
172
|
+
const pm = await detectPackageManager(cwd);
|
|
173
|
+
await runScript(pm, script, cwd);
|
|
174
|
+
}
|
|
175
|
+
var cache = null;
|
|
176
|
+
async function loadSnapshot() {
|
|
177
|
+
if (cache) return cache;
|
|
178
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
179
|
+
const path = join(here, "..", "snapshot.json");
|
|
180
|
+
try {
|
|
181
|
+
cache = JSON.parse(await readFile(path, "utf8"));
|
|
182
|
+
} catch {
|
|
183
|
+
cache = { catalog: {}, catalogs: {}, fourMica: {} };
|
|
184
|
+
}
|
|
185
|
+
return cache;
|
|
186
|
+
}
|
|
187
|
+
function resolveCatalog(snap, spec, name) {
|
|
188
|
+
const named = spec.slice("catalog:".length).trim();
|
|
189
|
+
const table = named ? snap.catalogs[named] ?? {} : snap.catalog;
|
|
190
|
+
return table[name] ?? "latest";
|
|
191
|
+
}
|
|
192
|
+
function resolveWorkspace(snap, name) {
|
|
193
|
+
const version = snap.fourMica[name];
|
|
194
|
+
return version ? `^${version}` : "latest";
|
|
195
|
+
}
|
|
196
|
+
function rewriteBlock(block, snap, keepWorkspace) {
|
|
197
|
+
if (!block) return;
|
|
198
|
+
for (const [name, spec] of Object.entries(block)) {
|
|
199
|
+
if (spec.startsWith("workspace:")) {
|
|
200
|
+
block[name] = keepWorkspace ? spec : resolveWorkspace(snap, name);
|
|
201
|
+
} else if (spec.startsWith("catalog:")) {
|
|
202
|
+
block[name] = resolveCatalog(snap, spec, name);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
async function rewritePackageJson(pkg, opts) {
|
|
207
|
+
const snap = await loadSnapshot();
|
|
208
|
+
rewriteBlock(
|
|
209
|
+
pkg.dependencies,
|
|
210
|
+
snap,
|
|
211
|
+
opts.keepWorkspace
|
|
212
|
+
);
|
|
213
|
+
rewriteBlock(
|
|
214
|
+
pkg.devDependencies,
|
|
215
|
+
snap,
|
|
216
|
+
opts.keepWorkspace
|
|
217
|
+
);
|
|
218
|
+
if (!opts.keepWorkspace) {
|
|
219
|
+
pkg.private = void 0;
|
|
220
|
+
}
|
|
221
|
+
return pkg;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// src/core/substitute.ts
|
|
225
|
+
function substitute(body, tokens) {
|
|
226
|
+
const table = tokens;
|
|
227
|
+
return body.replace(/__([A-Z_]+)__/g, (match, key) => {
|
|
228
|
+
const value = table[key];
|
|
229
|
+
return value === void 0 ? match : value;
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
function toPackageName(input) {
|
|
233
|
+
const cleaned = input.trim().toLowerCase().replace(/[^a-z0-9-~._]+/g, "-").replace(/^[-_.]+|[-_.]+$/g, "");
|
|
234
|
+
return cleaned || "my-4mica-app";
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// src/core/copy-engine.ts
|
|
238
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", "dist", ".next", ".turbo", ".git"]);
|
|
239
|
+
var TEXT_EXT = /* @__PURE__ */ new Set([
|
|
240
|
+
".ts",
|
|
241
|
+
".tsx",
|
|
242
|
+
".js",
|
|
243
|
+
".jsx",
|
|
244
|
+
".mjs",
|
|
245
|
+
".cjs",
|
|
246
|
+
".json",
|
|
247
|
+
".md",
|
|
248
|
+
".css",
|
|
249
|
+
".html",
|
|
250
|
+
".svg",
|
|
251
|
+
".txt",
|
|
252
|
+
".yaml",
|
|
253
|
+
".yml",
|
|
254
|
+
""
|
|
255
|
+
]);
|
|
256
|
+
function targetName(name) {
|
|
257
|
+
if (name === "_gitignore") return ".gitignore";
|
|
258
|
+
if (name === "_npmrc") return ".npmrc";
|
|
259
|
+
return name;
|
|
260
|
+
}
|
|
261
|
+
function extOf(name) {
|
|
262
|
+
const dot = name.lastIndexOf(".");
|
|
263
|
+
return dot === -1 ? "" : name.slice(dot);
|
|
264
|
+
}
|
|
265
|
+
async function copyTemplate(opts) {
|
|
266
|
+
await walk(opts.templateDir, opts);
|
|
267
|
+
}
|
|
268
|
+
async function walk(dir, opts) {
|
|
269
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
270
|
+
for (const entry of entries) {
|
|
271
|
+
if (entry.isDirectory()) {
|
|
272
|
+
if (SKIP_DIRS.has(entry.name)) continue;
|
|
273
|
+
await walk(join(dir, entry.name), opts);
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
await copyFile(join(dir, entry.name), opts);
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async function copyFile(srcPath, opts) {
|
|
280
|
+
const rel = relative(opts.templateDir, srcPath);
|
|
281
|
+
const parts = rel.split("/");
|
|
282
|
+
parts[parts.length - 1] = targetName(parts[parts.length - 1]);
|
|
283
|
+
const destPath = join(opts.targetDir, ...parts);
|
|
284
|
+
await mkdir(join(destPath, ".."), { recursive: true });
|
|
285
|
+
const base = parts[parts.length - 1];
|
|
286
|
+
if (base === "package.json") {
|
|
287
|
+
await writePackageJson(srcPath, destPath, opts);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (TEXT_EXT.has(extOf(base))) {
|
|
291
|
+
const body = await readFile(srcPath, "utf8");
|
|
292
|
+
await writeFile(destPath, substitute(body, opts.tokens));
|
|
293
|
+
return;
|
|
294
|
+
}
|
|
295
|
+
const raw = await readFile(srcPath);
|
|
296
|
+
await writeFile(destPath, raw);
|
|
297
|
+
}
|
|
298
|
+
async function writePackageJson(srcPath, destPath, opts) {
|
|
299
|
+
const body = substitute(await readFile(srcPath, "utf8"), opts.tokens);
|
|
300
|
+
const pkg = JSON.parse(body);
|
|
301
|
+
const rewritten = await rewritePackageJson(pkg, {
|
|
302
|
+
keepWorkspace: opts.keepWorkspace
|
|
303
|
+
});
|
|
304
|
+
await writeFile(destPath, `${JSON.stringify(rewritten, null, 2)}
|
|
305
|
+
`);
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/core/template-registry.ts
|
|
309
|
+
function resolveTemplate(opts) {
|
|
310
|
+
if (opts.type === "agent") {
|
|
311
|
+
return opts.agentRole === "seller" ? {
|
|
312
|
+
dir: "agent-seller-express",
|
|
313
|
+
port: 4100,
|
|
314
|
+
tmpfile: "4mica-agent.url",
|
|
315
|
+
runScript: "dev",
|
|
316
|
+
description: "Trading seller agent: sells information, paywalls the premium result with dynamic per-request pricing"
|
|
317
|
+
} : {
|
|
318
|
+
dir: "agent-buyer-node",
|
|
319
|
+
port: 4100,
|
|
320
|
+
tmpfile: "4mica-agent.url",
|
|
321
|
+
runScript: "start",
|
|
322
|
+
description: "Trading buyer agent: pursues a goal under a budget, pays sellers over x402, and adapts its strategy"
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
const ports = {
|
|
326
|
+
express: 3e3,
|
|
327
|
+
hono: 3001,
|
|
328
|
+
next: 3002
|
|
329
|
+
};
|
|
330
|
+
const tmpfile = `4mica-${opts.framework}.url`;
|
|
331
|
+
if (opts.type === "seller") {
|
|
332
|
+
return {
|
|
333
|
+
dir: `seller-${opts.framework}`,
|
|
334
|
+
port: ports[opts.framework],
|
|
335
|
+
tmpfile,
|
|
336
|
+
runScript: "dev",
|
|
337
|
+
description: `Seller (recipient): gate a ${opts.framework} route behind a 4Mica x402 paywall`
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
return {
|
|
341
|
+
dir: `buyer-${opts.framework}`,
|
|
342
|
+
port: ports[opts.framework],
|
|
343
|
+
tmpfile,
|
|
344
|
+
runScript: "start",
|
|
345
|
+
description: `Buyer (payer): perform the x402 handshake against a ${opts.framework} seller`
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
function bail() {
|
|
349
|
+
cancel("Scaffolding cancelled.");
|
|
350
|
+
process.exit(0);
|
|
351
|
+
}
|
|
352
|
+
function unwrap(value) {
|
|
353
|
+
if (isCancel(value)) bail();
|
|
354
|
+
return value;
|
|
355
|
+
}
|
|
356
|
+
async function runInitFlow(flags, defaultPm) {
|
|
357
|
+
if (flags.type === void 0) {
|
|
358
|
+
const fromScratch = unwrap(
|
|
359
|
+
await confirm({
|
|
360
|
+
message: "Generate a new 4Mica project from scratch?",
|
|
361
|
+
initialValue: true
|
|
362
|
+
})
|
|
363
|
+
);
|
|
364
|
+
if (!fromScratch) {
|
|
365
|
+
cancel(
|
|
366
|
+
"No problem \u2014 add 4Mica to an existing app with `npm i @4mica/sdk @4mica/sdk-node`, then wrap a route with a paywall. Docs: https://4mica.io/docs"
|
|
367
|
+
);
|
|
368
|
+
process.exit(0);
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
const type = flags.type ?? unwrap(
|
|
372
|
+
await select({
|
|
373
|
+
message: "What are you building?",
|
|
374
|
+
options: [
|
|
375
|
+
{
|
|
376
|
+
value: "agent",
|
|
377
|
+
label: "Trading agent",
|
|
378
|
+
hint: "buys/sells over x402"
|
|
379
|
+
},
|
|
380
|
+
{ value: "seller", label: "Paywalled API (seller)" },
|
|
381
|
+
{ value: "buyer", label: "Buyer client" }
|
|
382
|
+
]
|
|
383
|
+
})
|
|
384
|
+
);
|
|
385
|
+
let agentRole = flags.agentRole ?? "seller";
|
|
386
|
+
let framework = flags.framework ?? "express";
|
|
387
|
+
if (type === "agent") {
|
|
388
|
+
if (flags.agentRole === void 0) {
|
|
389
|
+
agentRole = unwrap(
|
|
390
|
+
await select({
|
|
391
|
+
message: "Which side does your agent play?",
|
|
392
|
+
options: [
|
|
393
|
+
{
|
|
394
|
+
value: "seller",
|
|
395
|
+
label: "Seller agent",
|
|
396
|
+
hint: "earns by selling info"
|
|
397
|
+
},
|
|
398
|
+
{
|
|
399
|
+
value: "buyer",
|
|
400
|
+
label: "Buyer agent",
|
|
401
|
+
hint: "spends to reach a goal"
|
|
402
|
+
}
|
|
403
|
+
]
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
}
|
|
407
|
+
} else if (flags.framework === void 0) {
|
|
408
|
+
framework = unwrap(
|
|
409
|
+
await select({
|
|
410
|
+
message: "Which framework?",
|
|
411
|
+
options: [
|
|
412
|
+
{ value: "express", label: "Express" },
|
|
413
|
+
{ value: "hono", label: "Hono" },
|
|
414
|
+
{ value: "next", label: "Next.js" }
|
|
415
|
+
]
|
|
416
|
+
})
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const defaultName = type === "agent" ? `my-${agentRole}-agent` : `my-${type}-${framework}`;
|
|
420
|
+
const name = flags.name ?? toPackageName(
|
|
421
|
+
unwrap(
|
|
422
|
+
await text({
|
|
423
|
+
message: "Project name?",
|
|
424
|
+
placeholder: defaultName,
|
|
425
|
+
defaultValue: defaultName
|
|
426
|
+
})
|
|
427
|
+
)
|
|
428
|
+
);
|
|
429
|
+
const packageManager = flags.packageManager ?? unwrap(
|
|
430
|
+
await select({
|
|
431
|
+
message: "Install with which package manager?",
|
|
432
|
+
initialValue: defaultPm,
|
|
433
|
+
options: [
|
|
434
|
+
{ value: "pnpm", label: "pnpm" },
|
|
435
|
+
{ value: "npm", label: "npm" },
|
|
436
|
+
{ value: "yarn", label: "yarn" },
|
|
437
|
+
{ value: "bun", label: "bun" }
|
|
438
|
+
]
|
|
439
|
+
})
|
|
440
|
+
);
|
|
441
|
+
const openDashboard = flags.openDashboard ?? unwrap(
|
|
442
|
+
await confirm({
|
|
443
|
+
message: "Open the 4Mica dashboard to manage agents & transactions?",
|
|
444
|
+
initialValue: true
|
|
445
|
+
})
|
|
446
|
+
);
|
|
447
|
+
return {
|
|
448
|
+
dir: flags.dir ?? name,
|
|
449
|
+
name,
|
|
450
|
+
type,
|
|
451
|
+
framework,
|
|
452
|
+
agentRole,
|
|
453
|
+
packageManager,
|
|
454
|
+
install: flags.install ?? true,
|
|
455
|
+
run: flags.run ?? true,
|
|
456
|
+
openDashboard
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
// src/commands/init.ts
|
|
461
|
+
async function exists2(path) {
|
|
462
|
+
try {
|
|
463
|
+
await access(path);
|
|
464
|
+
return true;
|
|
465
|
+
} catch {
|
|
466
|
+
return false;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
function isInside(child, parent) {
|
|
470
|
+
const rel = relative(parent, child);
|
|
471
|
+
return !rel.startsWith("..") && !isAbsolute(rel);
|
|
472
|
+
}
|
|
473
|
+
async function initCommand(flags) {
|
|
474
|
+
intro(brand("4mica"));
|
|
475
|
+
const context = await resolveRunContext();
|
|
476
|
+
const defaultPm = await detectPackageManager(
|
|
477
|
+
process.cwd(),
|
|
478
|
+
flags.packageManager
|
|
479
|
+
);
|
|
480
|
+
const opts = await runInitFlow(flags, defaultPm);
|
|
481
|
+
const template = resolveTemplate(opts);
|
|
482
|
+
const templateDir = join(context.templatesDir, template.dir);
|
|
483
|
+
const targetDir = resolve(process.cwd(), opts.dir);
|
|
484
|
+
if (!await exists2(templateDir)) {
|
|
485
|
+
throw new Error(`Template not found: ${template.dir}`);
|
|
486
|
+
}
|
|
487
|
+
if (await exists2(targetDir)) {
|
|
488
|
+
throw new Error(
|
|
489
|
+
`Directory already exists: ${opts.dir} \u2014 choose another name.`
|
|
490
|
+
);
|
|
491
|
+
}
|
|
492
|
+
const keepWorkspace = context.inMonorepo && context.monorepoRoot !== void 0 && isInside(targetDir, context.monorepoRoot);
|
|
493
|
+
await step(
|
|
494
|
+
`Scaffolding ${pc.bold(opts.name)}`,
|
|
495
|
+
() => copyTemplate({
|
|
496
|
+
templateDir,
|
|
497
|
+
targetDir,
|
|
498
|
+
tokens: {
|
|
499
|
+
PROJECT_NAME: opts.name,
|
|
500
|
+
DESCRIPTION: template.description,
|
|
501
|
+
PORT: String(template.port),
|
|
502
|
+
TMPFILE: template.tmpfile
|
|
503
|
+
},
|
|
504
|
+
keepWorkspace
|
|
505
|
+
})
|
|
506
|
+
);
|
|
507
|
+
await gitInit(targetDir);
|
|
508
|
+
if (opts.install) {
|
|
509
|
+
await step(
|
|
510
|
+
`Installing dependencies with ${opts.packageManager}`,
|
|
511
|
+
() => install(opts.packageManager, targetDir),
|
|
512
|
+
"Dependencies installed"
|
|
513
|
+
);
|
|
514
|
+
}
|
|
515
|
+
printNextSteps(opts, template.runScript);
|
|
516
|
+
outro(ok("Done \u2014 happy building!"));
|
|
517
|
+
if (opts.openDashboard) {
|
|
518
|
+
await launchDashboard({ open: true, background: true });
|
|
519
|
+
}
|
|
520
|
+
if (opts.install && opts.run) {
|
|
521
|
+
await runScript(opts.packageManager, template.runScript, targetDir);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
function printNextSteps(opts, script) {
|
|
525
|
+
const docs = terminalLink("4mica.io/docs", "https://4mica.io/docs");
|
|
526
|
+
const lines = [`cd ${opts.dir}`];
|
|
527
|
+
if (!opts.install) lines.push(`${opts.packageManager} install`);
|
|
528
|
+
lines.push(`${opts.packageManager} run ${script}`);
|
|
529
|
+
lines.push("");
|
|
530
|
+
lines.push(`${dim("Demo mode needs no config. Going live?")} ${docs}`);
|
|
531
|
+
lines.push(`${dim("Env template:")} ${warn(join(opts.dir, ".env.example"))}`);
|
|
532
|
+
note(lines.join("\n"), "Next steps");
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
// src/cli.ts
|
|
536
|
+
var BUILD_TYPES = ["agent", "seller", "buyer"];
|
|
537
|
+
var FRAMEWORKS = ["express", "hono", "next"];
|
|
538
|
+
var AGENT_ROLES = ["seller", "buyer"];
|
|
539
|
+
var PMS = ["pnpm", "npm", "yarn", "bun"];
|
|
540
|
+
function flagsFromArgv(argv) {
|
|
541
|
+
const flags = {};
|
|
542
|
+
if (argv.dir) flags.dir = String(argv.dir);
|
|
543
|
+
if (argv.name) flags.name = String(argv.name);
|
|
544
|
+
if (argv.type) flags.type = argv.type;
|
|
545
|
+
if (argv.framework) flags.framework = argv.framework;
|
|
546
|
+
if (argv.role) flags.agentRole = argv.role;
|
|
547
|
+
if (argv.pm) flags.packageManager = argv.pm;
|
|
548
|
+
if (argv.install === false) flags.install = false;
|
|
549
|
+
if (argv.run === false) flags.run = false;
|
|
550
|
+
if (argv.dashboard !== void 0)
|
|
551
|
+
flags.openDashboard = Boolean(argv.dashboard);
|
|
552
|
+
if (argv.yes) {
|
|
553
|
+
flags.type ??= "agent";
|
|
554
|
+
if (flags.type === "agent") flags.agentRole ??= "seller";
|
|
555
|
+
else flags.framework ??= "express";
|
|
556
|
+
flags.packageManager ??= "pnpm";
|
|
557
|
+
flags.install ??= true;
|
|
558
|
+
flags.run ??= false;
|
|
559
|
+
flags.openDashboard ??= false;
|
|
560
|
+
flags.name ??= flags.type === "agent" ? `my-${flags.agentRole}-agent` : `my-${flags.type}-${flags.framework}`;
|
|
561
|
+
}
|
|
562
|
+
return flags;
|
|
563
|
+
}
|
|
564
|
+
async function run() {
|
|
565
|
+
const cli = yargs(hideBin(process.argv)).scriptName("4mica").usage("$0 <command> [options]").command(
|
|
566
|
+
["init [dir]", "$0 [dir]"],
|
|
567
|
+
"Scaffold a new 4Mica agent or payment app",
|
|
568
|
+
(y) => y.positional("dir", { type: "string", describe: "Target directory" }).option("type", { choices: BUILD_TYPES, describe: "What to build" }).option("framework", {
|
|
569
|
+
choices: FRAMEWORKS,
|
|
570
|
+
describe: "Framework (seller/buyer)"
|
|
571
|
+
}).option("role", {
|
|
572
|
+
choices: AGENT_ROLES,
|
|
573
|
+
describe: "Trading role (agent)"
|
|
574
|
+
}).option("name", { type: "string", describe: "Project name" }).option("pm", { choices: PMS, describe: "Package manager" }).option("install", {
|
|
575
|
+
type: "boolean",
|
|
576
|
+
default: true,
|
|
577
|
+
describe: "Install dependencies"
|
|
578
|
+
}).option("run", {
|
|
579
|
+
type: "boolean",
|
|
580
|
+
default: true,
|
|
581
|
+
describe: "Start the dev server after scaffolding"
|
|
582
|
+
}).option("dashboard", {
|
|
583
|
+
type: "boolean",
|
|
584
|
+
describe: "Open the dashboard after scaffolding"
|
|
585
|
+
}).option("yes", {
|
|
586
|
+
alias: "y",
|
|
587
|
+
type: "boolean",
|
|
588
|
+
describe: "Accept defaults; no prompts (CI)"
|
|
589
|
+
}),
|
|
590
|
+
async (argv) => {
|
|
591
|
+
await initCommand(flagsFromArgv(argv));
|
|
592
|
+
}
|
|
593
|
+
).command(
|
|
594
|
+
"dashboard",
|
|
595
|
+
"Launch the 4Mica dashboard (manage agents, transactions, whitelist)",
|
|
596
|
+
(y) => y.option("open", {
|
|
597
|
+
type: "boolean",
|
|
598
|
+
default: true,
|
|
599
|
+
describe: "Open a browser"
|
|
600
|
+
}),
|
|
601
|
+
async (argv) => {
|
|
602
|
+
await launchDashboard({ open: Boolean(argv.open) });
|
|
603
|
+
}
|
|
604
|
+
).command(
|
|
605
|
+
"dev",
|
|
606
|
+
"Run the current project's dev server",
|
|
607
|
+
(y) => y,
|
|
608
|
+
async () => {
|
|
609
|
+
await devCommand();
|
|
610
|
+
}
|
|
611
|
+
).demandCommand(0).strict().help().alias("help", "h").version().alias("version", "v").fail((msg, error) => {
|
|
612
|
+
process.stderr.write(err(`
|
|
613
|
+
${msg || error?.message || "Error"}
|
|
614
|
+
`));
|
|
615
|
+
process.exit(1);
|
|
616
|
+
});
|
|
617
|
+
await cli.parse();
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/index.ts
|
|
621
|
+
run().catch((error) => {
|
|
622
|
+
process.stderr.write(err(`
|
|
623
|
+
${error.message}
|
|
624
|
+
`));
|
|
625
|
+
process.exitCode = 1;
|
|
626
|
+
});
|
|
627
|
+
//# sourceMappingURL=index.js.map
|
|
628
|
+
//# sourceMappingURL=index.js.map
|