@montytools/cli 0.1.3 → 0.1.5

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 CHANGED
@@ -132,37 +132,72 @@ function openInBrowser(url) {
132
132
 
133
133
 
134
134
  // ── agent skills ─────────────────────────────────────────────────────────────
135
- // The build skill ships inside this package and is stamped wherever agents
136
- // look for it: ~/.claude/skills (user) and <app>/.claude + <app>/.agents
137
- // (projectself-propagating contract, refreshed whenever the CLI runs).
135
+ // The build skill ships inside this package. Primary installer is the
136
+ // cross-agent standard `npx skills add` (same as InsForge) which fans out to
137
+ // every agent's folder Claude Code, Codex (.agents/skills), Cursor, etc.
138
+ // Manual copy is the offline fallback. A version marker gates the work so
139
+ // ordinary commands stay fast.
138
140
  const CLI_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..");
139
141
  const CLI_VERSION = JSON.parse(readFileSync(join(CLI_ROOT, "package.json"), "utf8")).version;
140
142
  const SKILLS_SRC = join(CLI_ROOT, "skills");
143
+ const GLOBAL_SKILLS_MARKER = join(CONFIG_DIR, "skills-version");
144
+ // InsForge's agent list — enumerate rather than '*' so we only touch real agents.
145
+ const SKILL_AGENTS = ["antigravity", "augment", "claude-code", "cline", "codex", "cursor", "gemini-cli", "github-copilot", "kilo", "qoder", "qwen-code", "roo", "trae", "windsurf"]
146
+ .flatMap((a) => ["-a", a]);
141
147
 
142
- function installSkillsInto(dir, { silent = true } = {}) {
143
- if (!existsSync(SKILLS_SRC)) return false;
144
- let changed = false;
148
+ function readMarker(path) {
149
+ try {
150
+ return readFileSync(path, "utf8").trim();
151
+ } catch {
152
+ return null;
153
+ }
154
+ }
155
+
156
+ function skillsCliAdd({ global = false, cwd = undefined } = {}) {
157
+ const args = ["-y", "skills", "add", SKILLS_SRC, "-y", "--copy", ...(global ? ["-g"] : []), ...SKILL_AGENTS];
158
+ const res = spawnSync("npx", args, { cwd, stdio: "ignore", timeout: 120_000 });
159
+ return res.status === 0;
160
+ }
161
+
162
+ // Fallbacks for offline/npx-less environments: cover the two standard folders.
163
+ function installSkillsInto(dir) {
164
+ if (!existsSync(SKILLS_SRC)) return;
145
165
  for (const name of readdirSync(SKILLS_SRC)) {
146
- const dest = join(dir, name);
147
- const versionFile = join(dest, "VERSION");
148
- const installed = existsSync(versionFile) ? readFileSync(versionFile, "utf8").trim() : null;
149
- if (installed === CLI_VERSION) continue;
150
- cpSync(join(SKILLS_SRC, name), dest, { recursive: true });
151
- writeFileSync(versionFile, CLI_VERSION + "\n");
152
- changed = true;
153
- }
154
- if (changed && !silent) console.log(`skills: installed -> ${dir} (v${CLI_VERSION})`);
155
- return changed;
166
+ cpSync(join(SKILLS_SRC, name), join(dir, name), { recursive: true });
167
+ }
156
168
  }
157
169
 
158
- function installSkills({ appDir = null, silent = true } = {}) {
170
+ function manualInstall(appDir) {
171
+ if (appDir) {
172
+ installSkillsInto(join(appDir, ".claude", "skills"));
173
+ installSkillsInto(join(appDir, ".agents", "skills"));
174
+ } else {
175
+ installSkillsInto(join(homedir(), ".claude", "skills"));
176
+ installSkillsInto(join(homedir(), ".agents", "skills"));
177
+ }
178
+ }
179
+
180
+ function installSkills({ appDir = null, silent = true, force = false } = {}) {
159
181
  if (process.env.MONTY_NO_SKILLS) return;
160
182
  try {
161
- let changed = installSkillsInto(join(homedir(), ".claude", "skills"), { silent });
162
- for (const base of appDir ? [join(appDir, ".claude", "skills"), join(appDir, ".agents", "skills")] : []) {
163
- changed = installSkillsInto(base, { silent }) || changed;
183
+ let changed = false;
184
+ if (force || readMarker(GLOBAL_SKILLS_MARKER) !== CLI_VERSION) {
185
+ if (!skillsCliAdd({ global: true })) manualInstall(null);
186
+ mkdirSync(CONFIG_DIR, { recursive: true });
187
+ writeFileSync(GLOBAL_SKILLS_MARKER, CLI_VERSION + "\n");
188
+ changed = true;
164
189
  }
165
- if (changed && silent) console.log(`skills: refreshed (v${CLI_VERSION})`);
190
+ if (appDir) {
191
+ const marker = join(appDir, ".agents", "skills", "monty-build", "VERSION");
192
+ if (force || readMarker(marker) !== CLI_VERSION) {
193
+ if (!skillsCliAdd({ cwd: appDir })) manualInstall(appDir);
194
+ mkdirSync(dirname(marker), { recursive: true });
195
+ writeFileSync(marker, CLI_VERSION + "\n");
196
+ changed = true;
197
+ }
198
+ }
199
+ if (changed && !silent) console.log(`skills: installed for all agents (v${CLI_VERSION})`);
200
+ else if (changed) console.log(`skills: refreshed (v${CLI_VERSION})`);
166
201
  } catch {
167
202
  /* skills are best-effort — never block a command */
168
203
  }
@@ -304,6 +339,27 @@ async function create() {
304
339
  console.log(`config: WARNING — could not reach ${host}/api/config; copy .env.example to .env.local manually`);
305
340
  }
306
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
+
307
363
  installSkills({ appDir: target });
308
364
  console.log(`created: ${target}`);
309
365
  console.log(`next: cd ${target} && pnpm install && monty dev`);
@@ -463,6 +519,10 @@ async function deploy() {
463
519
  // 3) Multipart POST to the host.
464
520
  const dist = join(appDir, "dist");
465
521
  const files = walk(dist);
522
+ const buildFile = join(appDir, ".monty", "build");
523
+ if (existsSync(buildFile)) {
524
+ meta.buildId = readFileSync(buildFile, "utf8").trim();
525
+ }
466
526
  const form = new FormData();
467
527
  form.set("monty", JSON.stringify(meta));
468
528
  let total = 0;
@@ -588,7 +648,7 @@ switch (command) {
588
648
  default:
589
649
  console.log("usage: monty <login|create|current|select|apps|dev|add|components|docs|deploy|skills>");
590
650
  console.log(" login [--host <url>] [--key <mk_...>] sign in (opens your browser to authorize)");
591
- console.log(" create <slug> [--name N] [--icon I] stamp a new app into ~/Monty/<slug>");
651
+ console.log(" create <slug> [--name N] [--icon I] [--build ID] stamp a new app into ~/Monty/<slug>");
592
652
  console.log(" dev [--port 5173] run the app locally (sandboxed data)");
593
653
  console.log(" add <name...> install curated UI components (see `monty components`)");
594
654
  console.log(" components [query] list the curated component catalog");
@@ -19,6 +19,24 @@ try {
19
19
  writeFileSync(join(target, name, "VERSION"), version + "\n");
20
20
  }
21
21
  console.log(`monty: agent skill installed (~/.claude/skills, v${version})`);
22
+ // Codex: managed block in ~/.codex/AGENTS.md when Codex is present.
23
+ const codexDir = join(homedir(), ".codex");
24
+ const skillFile = join(src, "monty-build", "SKILL.md");
25
+ if (existsSync(codexDir) && existsSync(skillFile)) {
26
+ const body = readFileSync(skillFile, "utf8").replace(/^---[\s\S]*?---\n/, "").trim();
27
+ const START = "<!-- monty-build:start";
28
+ const END = "<!-- monty-build:end -->";
29
+ const block = `${START} v${version} -->\n${body}\n${END}`;
30
+ const file = join(codexDir, "AGENTS.md");
31
+ let cur = existsSync(file) ? readFileSync(file, "utf8") : "";
32
+ if (!cur.includes(`${START} v${version} -->`)) {
33
+ const re = new RegExp(`${START}[\\s\\S]*?${END}`);
34
+ cur = re.test(cur) ? cur.replace(re, block) : (cur ? cur.trimEnd() + "\n\n" : "") + block + "\n";
35
+ writeFileSync(file, cur);
36
+ console.log(`monty: agent contract added to ~/.codex/AGENTS.md (v${version})`);
37
+ }
38
+ }
39
+
22
40
  } catch {
23
41
  /* silent — skills are a convenience, not a dependency */
24
42
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@montytools/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "monty": "./bin/monty.mjs"
@@ -17,6 +17,8 @@ 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
+ (if the prompt includes a `build id`, pass it: `--build <id>` — the
21
+ workspace's New app screen tracks your progress live) →
20
22
  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.