@montytools/cli 0.1.4 → 0.1.6
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/bin/monty.mjs +91 -8
- package/package.json +1 -1
- package/skills/monty-build/SKILL.md +14 -7
- package/template/AGENTS.md +9 -5
package/bin/monty.mjs
CHANGED
|
@@ -339,9 +339,73 @@ async function create() {
|
|
|
339
339
|
console.log(`config: WARNING — could not reach ${host}/api/config; copy .env.example to .env.local manually`);
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
+
// Build tracking: the /new screen minted an id; recording it here lets
|
|
343
|
+
// `monty deploy` resolve it and flips the UI into "agent is working" mode.
|
|
344
|
+
const buildId = flag("build");
|
|
345
|
+
if (buildId && /^[a-z0-9]{10,64}$/i.test(buildId)) {
|
|
346
|
+
mkdirSync(join(target, ".monty"), { recursive: true });
|
|
347
|
+
writeFileSync(join(target, ".monty", "build"), buildId + "\n");
|
|
348
|
+
const cfg = loadConfig();
|
|
349
|
+
if (cfg?.key) {
|
|
350
|
+
try {
|
|
351
|
+
await fetch(`${cfg.host ?? DEFAULT_HOST}/api/build`, {
|
|
352
|
+
method: "POST",
|
|
353
|
+
headers: { authorization: `Bearer ${cfg.key}`, "content-type": "application/json" },
|
|
354
|
+
body: JSON.stringify({ buildId, slug }),
|
|
355
|
+
});
|
|
356
|
+
console.log("build: workspace notified — the New app screen is following along");
|
|
357
|
+
} catch {
|
|
358
|
+
/* progress signal only — never block create */
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
342
363
|
installSkills({ appDir: target });
|
|
343
364
|
console.log(`created: ${target}`);
|
|
344
|
-
console.log(`next: cd ${target} &&
|
|
365
|
+
console.log(`next: cd ${target} && monty install && monty dev`);
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
|
|
369
|
+
// ── monty install / build / typecheck ───────────────────────────────────────
|
|
370
|
+
// The full app lifecycle goes through the CLI — agents never invoke pnpm,
|
|
371
|
+
// vite, or tsc directly. Same underlying tools, agent-shaped output, and the
|
|
372
|
+
// build-before-typecheck ordering handled for you.
|
|
373
|
+
function installDeps() {
|
|
374
|
+
const appDir = requireAppDir("install");
|
|
375
|
+
const pm = spawnSync("pnpm", ["--version"], { stdio: "ignore" }).status === 0 ? "pnpm" : "npm";
|
|
376
|
+
run(appDir, "install", [pm, "install"],
|
|
377
|
+
"Dependency install failed. Read the package manager error above; usually network or a bad package.json edit.");
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function buildApp() {
|
|
381
|
+
const appDir = requireAppDir("build");
|
|
382
|
+
run(appDir, "build", ["npx", "vite", "build"],
|
|
383
|
+
"The production build failed. Read the vite error above; it names the file to fix.");
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function typecheckApp() {
|
|
387
|
+
const appDir = requireAppDir("typecheck");
|
|
388
|
+
// routeTree.gen.ts is generated by the build — without it tsc fails on a
|
|
389
|
+
// fresh checkout, so build first when it's missing.
|
|
390
|
+
if (!existsSync(join(appDir, "src", "routeTree.gen.ts"))) {
|
|
391
|
+
run(appDir, "build", ["npx", "vite", "build"],
|
|
392
|
+
"The production build failed. Read the vite error above; it names the file to fix.");
|
|
393
|
+
}
|
|
394
|
+
run(appDir, "typecheck", ["npx", "tsc", "--noEmit"],
|
|
395
|
+
"TypeScript errors above. Fix them in the listed files.");
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
|
|
399
|
+
async function freePort(start) {
|
|
400
|
+
for (let p = start; p < start + 50; p++) {
|
|
401
|
+
const ok = await new Promise((resolve) => {
|
|
402
|
+
const probe = createServer();
|
|
403
|
+
probe.once("error", () => resolve(false));
|
|
404
|
+
probe.listen(p, "127.0.0.1", () => probe.close(() => resolve(true)));
|
|
405
|
+
});
|
|
406
|
+
if (ok) return p;
|
|
407
|
+
}
|
|
408
|
+
fail("NO_FREE_PORT", `No free port between ${start} and ${start + 49}. Pass --port <n>.`);
|
|
345
409
|
}
|
|
346
410
|
|
|
347
411
|
// ── monty dev ──────────────────────────────────────────────────────────────
|
|
@@ -352,7 +416,10 @@ async function dev() {
|
|
|
352
416
|
}
|
|
353
417
|
const meta = await compileConfig(appDir);
|
|
354
418
|
const host = loadConfig()?.host ?? DEFAULT_HOST;
|
|
355
|
-
|
|
419
|
+
// Auto-pick a free port (agents run several apps side by side); an
|
|
420
|
+
// explicit --port is honored strictly.
|
|
421
|
+
const requested = flag("port");
|
|
422
|
+
const port = requested ? Number(requested) : await freePort(5173);
|
|
356
423
|
|
|
357
424
|
console.log(`dev: starting vite on :${port} (app "${meta.slug}")`);
|
|
358
425
|
const child = spawn("npx", ["vite", "dev", "--port", String(port), "--strictPort"], {
|
|
@@ -379,9 +446,9 @@ async function dev() {
|
|
|
379
446
|
// implementation. Only catalog registries and core shadcn resolve.
|
|
380
447
|
|
|
381
448
|
function requireAppDir(cmd) {
|
|
382
|
-
const appDir = process.cwd();
|
|
383
|
-
if (!
|
|
384
|
-
fail("NOT_A_MONTY_APP", `
|
|
449
|
+
const appDir = findAppRoot(process.cwd());
|
|
450
|
+
if (!appDir) {
|
|
451
|
+
fail("NOT_A_MONTY_APP", `Not inside a Monty app. \`monty apps\` lists local apps; cd "$(monty select <slug>)" jumps to one — then run \`monty ${cmd}\`.`);
|
|
385
452
|
}
|
|
386
453
|
return appDir;
|
|
387
454
|
}
|
|
@@ -498,6 +565,10 @@ async function deploy() {
|
|
|
498
565
|
// 3) Multipart POST to the host.
|
|
499
566
|
const dist = join(appDir, "dist");
|
|
500
567
|
const files = walk(dist);
|
|
568
|
+
const buildFile = join(appDir, ".monty", "build");
|
|
569
|
+
if (existsSync(buildFile)) {
|
|
570
|
+
meta.buildId = readFileSync(buildFile, "utf8").trim();
|
|
571
|
+
}
|
|
501
572
|
const form = new FormData();
|
|
502
573
|
form.set("monty", JSON.stringify(meta));
|
|
503
574
|
let total = 0;
|
|
@@ -617,20 +688,32 @@ switch (command) {
|
|
|
617
688
|
installSkills({ appDir: findAppRoot(process.cwd()), silent: false });
|
|
618
689
|
console.log("skills: up to date");
|
|
619
690
|
break;
|
|
691
|
+
case "install":
|
|
692
|
+
installDeps();
|
|
693
|
+
break;
|
|
694
|
+
case "build":
|
|
695
|
+
buildApp();
|
|
696
|
+
break;
|
|
697
|
+
case "typecheck":
|
|
698
|
+
typecheckApp();
|
|
699
|
+
break;
|
|
620
700
|
case "deploy":
|
|
621
701
|
await deploy();
|
|
622
702
|
break;
|
|
623
703
|
default:
|
|
624
|
-
console.log("usage: monty <login|create|current|select|apps|dev|add|components|docs|deploy|skills>");
|
|
704
|
+
console.log("usage: monty <login|create|current|select|apps|install|dev|build|typecheck|add|components|docs|deploy|skills>");
|
|
625
705
|
console.log(" login [--host <url>] [--key <mk_...>] sign in (opens your browser to authorize)");
|
|
626
|
-
console.log(" create <slug> [--name N] [--icon I]
|
|
627
|
-
console.log(" dev [--port
|
|
706
|
+
console.log(" create <slug> [--name N] [--icon I] [--build ID] stamp a new app into ~/Monty/<slug>");
|
|
707
|
+
console.log(" dev [--port N] run locally, auto-picks a free port (sandboxed data)");
|
|
628
708
|
console.log(" add <name...> install curated UI components (see `monty components`)");
|
|
629
709
|
console.log(" components [query] list the curated component catalog");
|
|
630
710
|
console.log(" docs <name> view a component's source before installing");
|
|
631
711
|
console.log(" current which app folder am I in?");
|
|
632
712
|
console.log(" select <slug> print an app's folder — cd \"$(monty select x)\"");
|
|
633
713
|
console.log(" apps list local apps in ~/Monty");
|
|
714
|
+
console.log(" install install app dependencies");
|
|
715
|
+
console.log(" build production build (vite, via monty)");
|
|
716
|
+
console.log(" typecheck typecheck (builds first if needed)");
|
|
634
717
|
console.log(" deploy build + upload this app");
|
|
635
718
|
console.log(" skills install/refresh the agent build skill");
|
|
636
719
|
process.exit(command ? 1 : 0);
|
package/package.json
CHANGED
|
@@ -17,21 +17,27 @@ the territory.
|
|
|
17
17
|
tells you where you are; `cd "$(monty select <slug>)"` jumps to an app;
|
|
18
18
|
`monty apps` lists local ones. Never mkdir app folders by hand.
|
|
19
19
|
2. **The loop:** `monty create <slug> --name "Name" --icon <lucide-icon>` →
|
|
20
|
-
|
|
20
|
+
(if the prompt includes a `build id`, pass it: `--build <id>` — the
|
|
21
|
+
workspace's New app screen tracks your progress live) →
|
|
22
|
+
`monty install` → edit `monty.config.ts` (zod tables) + `src/routes/` → verify with
|
|
21
23
|
`monty dev` (localhost:5173, already authenticated, sandboxed data) →
|
|
22
24
|
`monty deploy`. You are done when deploy prints the URL.
|
|
23
|
-
3. **
|
|
25
|
+
3. **Everything through the CLI.** `monty install`, `monty build`,
|
|
26
|
+
`monty typecheck`, `monty dev`, `monty deploy` — never run vite, tsc,
|
|
27
|
+
pnpm, or npm scripts directly. `monty dev` auto-picks a free port and
|
|
28
|
+
prints it; `monty typecheck` builds first when needed.
|
|
29
|
+
4. **One import surface:** `@montytools/sdk` (`defineApp`, zod) and
|
|
24
30
|
`@montytools/sdk/react` (hooks: `useList`, `useInsert`, …). Never import
|
|
25
31
|
Clerk or Convex directly; never fetch external APIs from app code — the
|
|
26
32
|
platform CSP blocks them.
|
|
27
|
-
|
|
33
|
+
5. **Schema is zod in `monty.config.ts`.** Field names `_*`, `updatedAt`,
|
|
28
34
|
`createdBy` are reserved. Push happens automatically on dev/deploy.
|
|
29
|
-
|
|
35
|
+
6. **UI is stock shadcn** (preset already wired). Add curated components with
|
|
30
36
|
`monty add <name>`; browse with `monty components` / `monty docs <name>`.
|
|
31
|
-
|
|
37
|
+
7. **Errors are instructions.** Every failure prints
|
|
32
38
|
`[MontyError CODE] Fix: …` — do exactly what the Fix says; don't guess.
|
|
33
39
|
Typecheck failures block deploy by design.
|
|
34
|
-
|
|
40
|
+
8. **Verify before deploy.** `monty dev` writes to a `#dev` sandbox — live
|
|
35
41
|
team records are never touched, so exercise the app for real.
|
|
36
42
|
|
|
37
43
|
## CLI reference
|
|
@@ -41,7 +47,8 @@ the territory.
|
|
|
41
47
|
| `monty login` | browser sign-in (loopback authorize), once per machine |
|
|
42
48
|
| `monty create <slug>` | stamp a new app into `~/Monty/<slug>` |
|
|
43
49
|
| `monty current` / `select` / `apps` | where am I / jump to app / list local |
|
|
44
|
-
| `monty
|
|
50
|
+
| `monty install` / `build` / `typecheck` | full lifecycle via the CLI — no raw pnpm/vite/tsc |
|
|
51
|
+
| `monty dev` | run locally (auto-picks a free port), sandboxed data, auto-auth |
|
|
45
52
|
| `monty add <name…>` | install curated shadcn components |
|
|
46
53
|
| `monty deploy` | build + typecheck + upload; app appears in the workspace |
|
|
47
54
|
| `monty skills` | (re)install this skill for your agent |
|
package/template/AGENTS.md
CHANGED
|
@@ -102,14 +102,18 @@ Every platform error is one line shaped like:
|
|
|
102
102
|
|
|
103
103
|
## Dev loop
|
|
104
104
|
|
|
105
|
+
Everything goes through the `monty` CLI — never run vite, tsc, pnpm, or npm
|
|
106
|
+
scripts directly:
|
|
107
|
+
|
|
105
108
|
```
|
|
106
|
-
|
|
109
|
+
monty install # dependencies
|
|
110
|
+
monty dev # Vite + HMR, auto-picks a free port and prints it
|
|
107
111
|
```
|
|
108
112
|
|
|
109
|
-
Headless? Verify with `
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
+
Headless? Verify with `monty build` then `monty typecheck` (typecheck builds
|
|
114
|
+
first when needed — the build generates `src/routeTree.gen.ts`). `monty
|
|
115
|
+
deploy` runs both itself (it never uploads code that doesn't compile), so
|
|
116
|
+
deploy is self-verifying.
|
|
113
117
|
|
|
114
118
|
**Driving your app in a browser (agents):** while `monty dev` runs, opening
|
|
115
119
|
`http://localhost:5173` is ALREADY AUTHENTICATED — no sign-in screen (the dev
|