@eventmodelers/cli 0.0.29 → 0.0.30
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/cli.js +86 -6
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
readFileSync,
|
|
15
15
|
appendFileSync,
|
|
16
16
|
} from 'fs';
|
|
17
|
-
import { execSync, spawn } from 'child_process';
|
|
17
|
+
import { execSync, execFileSync, spawn } from 'child_process';
|
|
18
18
|
import { createInterface, emitKeypressEvents, moveCursor, clearScreenDown } from 'readline';
|
|
19
19
|
import { homedir } from 'os';
|
|
20
20
|
import { randomUUID } from 'crypto';
|
|
@@ -546,6 +546,55 @@ function copyDirContents(srcDir, destDir, { skip = [] } = {}) {
|
|
|
546
546
|
if (count) console.log(` ✓ Installed ${count} item${count === 1 ? '' : 's'} into ${relative(process.cwd(), destDir) || '.'}`);
|
|
547
547
|
}
|
|
548
548
|
|
|
549
|
+
// Community/custom build kits (`init --stack <name> --git <url>`) are cloned fresh on
|
|
550
|
+
// every run rather than pulled/updated in place — there's no local state worth
|
|
551
|
+
// preserving between installs, and re-cloning from scratch means a broken or partial
|
|
552
|
+
// previous clone can never linger and cause a confusing stale-file bug. Cached under
|
|
553
|
+
// ~/.eventmodelers (not inside the target project) so it's reusable across projects and
|
|
554
|
+
// definitely not something `uninstall` or the project's own .gitignore need to know about.
|
|
555
|
+
function cloneGitStack(url, branch) {
|
|
556
|
+
const dest = join(homedir(), '.eventmodelers', 'git-stacks', `${url}${branch ? `#${branch}` : ''}`.replace(/[^a-zA-Z0-9._-]/g, '_'));
|
|
557
|
+
rmSync(dest, { recursive: true, force: true });
|
|
558
|
+
mkdirSync(dirname(dest), { recursive: true });
|
|
559
|
+
console.log(`📥 Cloning ${url}${branch ? ` (branch: ${branch})` : ''}...`);
|
|
560
|
+
const cloneArgs = ['clone', '--depth', '1', ...(branch ? ['--branch', branch] : []), url, dest];
|
|
561
|
+
try {
|
|
562
|
+
execFileSync('git', cloneArgs, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
563
|
+
} catch {
|
|
564
|
+
console.error(`❌ Failed to clone "${url}"${branch ? ` (branch: ${branch})` : ''} — check the URL, the branch name, your git access, and that git is installed, then try again.`);
|
|
565
|
+
process.exit(1);
|
|
566
|
+
}
|
|
567
|
+
return dest;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// A community stack repo must mirror the internal stacks/<key>/templates layout exactly
|
|
571
|
+
// (templates/.claude, templates/root, templates/<kitSubdir>) so installStack() can treat
|
|
572
|
+
// it identically to a built-in stack — see STACKS above for the shape. An optional
|
|
573
|
+
// stack.json at the repo root can declare label/kitSubdir/useShared/needsBoardId;
|
|
574
|
+
// kitDirName is always forced to .build-kit (the same runtime contract every built-in
|
|
575
|
+
// stack already uses), so run/status/uninstall recognize a git-installed stack with no
|
|
576
|
+
// changes of their own.
|
|
577
|
+
function resolveGitStackConfig(clonedDir, name) {
|
|
578
|
+
const templatesSource = join(clonedDir, 'templates');
|
|
579
|
+
if (!existsSync(templatesSource)) {
|
|
580
|
+
console.error(`❌ ${relative(process.cwd(), clonedDir) || clonedDir} has no templates/ directory — a build kit repo needs templates/.claude, templates/root, and templates/<kitSubdir>, the same layout this CLI's own stacks/<name>/templates use.`);
|
|
581
|
+
process.exit(1);
|
|
582
|
+
}
|
|
583
|
+
const manifest = readJsonSafe(join(clonedDir, 'stack.json'));
|
|
584
|
+
const kitSubdir = manifest.kitSubdir || 'build-kit';
|
|
585
|
+
if (!existsSync(join(templatesSource, kitSubdir))) {
|
|
586
|
+
console.error(`❌ ${relative(process.cwd(), templatesSource) || templatesSource} is missing its kit subdirectory "${kitSubdir}/" (from stack.json, or the "build-kit" default) — nothing to install.`);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
return {
|
|
590
|
+
label: manifest.label || name,
|
|
591
|
+
kitSubdir,
|
|
592
|
+
kitDirName: '.build-kit',
|
|
593
|
+
useShared: manifest.useShared !== false,
|
|
594
|
+
needsBoardId: manifest.needsBoardId !== false,
|
|
595
|
+
};
|
|
596
|
+
}
|
|
597
|
+
|
|
549
598
|
async function resolveStack(cliStack) {
|
|
550
599
|
if (cliStack) {
|
|
551
600
|
if (!STACKS[cliStack]) {
|
|
@@ -566,7 +615,9 @@ async function installStack(stackKey, stackCfg, options = {}) {
|
|
|
566
615
|
console.log(`Using: ${stackKey} (${stackCfg.label})\n`);
|
|
567
616
|
|
|
568
617
|
const targetDir = process.cwd();
|
|
569
|
-
|
|
618
|
+
// `init --git <url>` passes a resolved clone dir's templates/ here instead — every
|
|
619
|
+
// other input (STACKS, MODELING_KIT, BRIDGE_KIT) keeps using the built-in path.
|
|
620
|
+
const templatesSource = options.templatesSource || join(__dirname, 'stacks', stackKey, 'templates');
|
|
570
621
|
const sharedBuildKit = join(__dirname, 'shared', 'build-kit');
|
|
571
622
|
// Skills with no stack-specific content (connect, learn-eventmodelers-api,
|
|
572
623
|
// update-slice-status, ...) live once here instead of being copy-pasted into
|
|
@@ -1162,8 +1213,10 @@ function credentialOverridesFromOpts(opts) {
|
|
|
1162
1213
|
credentialFlags(program
|
|
1163
1214
|
.command('init')
|
|
1164
1215
|
.alias('install')
|
|
1165
|
-
.description('Scaffold a stack + install the agent kit into the current directory (or --modeling for skills + agent loop only, no backend scaffold; or --bridge to translate board slices into another spec framework)')
|
|
1166
|
-
.option('--stack <name>', `Stack to install (${Object.keys(STACKS).join(', ')})`)
|
|
1216
|
+
.description('Scaffold a stack + install the agent kit into the current directory (or --modeling for skills + agent loop only, no backend scaffold; or --bridge to translate board slices into another spec framework; or --git to install a community/custom build kit from a git repo)')
|
|
1217
|
+
.option('--stack <name>', `Stack to install (${Object.keys(STACKS).join(', ')}), or a name of your choosing when combined with --git`)
|
|
1218
|
+
.option('--git <url>', 'Install a build kit not built into this CLI by cloning this git repo (used with --stack <name> to name it) — the repo must mirror the templates/.claude, templates/root, templates/<kitSubdir> layout of this CLI\'s own stacks/<name>/templates, optionally with a stack.json declaring label/kitSubdir/useShared/needsBoardId')
|
|
1219
|
+
.option('--branch <name>', 'Branch to clone — only meaningful with --git (defaults to the repo\'s default branch)')
|
|
1167
1220
|
.option('--modeling', 'Install skills + the agent loop only — no backend scaffold. Mutually exclusive with --stack/--bridge.')
|
|
1168
1221
|
.option('--bridge', 'Install a bridge kit — translates board slices into another spec framework instead of building code. Mutually exclusive with --stack/--modeling. Requires --target.')
|
|
1169
1222
|
.option('--target <name>', `Bridge target framework (${Object.keys(BRIDGE_TARGETS).join(', ')}) — only meaningful with --bridge`)
|
|
@@ -1174,8 +1227,8 @@ credentialFlags(program
|
|
|
1174
1227
|
const globalOpts = command.optsWithGlobals();
|
|
1175
1228
|
|
|
1176
1229
|
if (opts.modeling || opts.bridge) {
|
|
1177
|
-
if (opts.stack || (opts.modeling && opts.bridge)) {
|
|
1178
|
-
console.error('❌ --stack, --modeling, and --bridge are mutually exclusive — pick one.');
|
|
1230
|
+
if (opts.stack || opts.git || (opts.modeling && opts.bridge)) {
|
|
1231
|
+
console.error('❌ --stack/--git, --modeling, and --bridge are mutually exclusive — pick one.');
|
|
1179
1232
|
process.exit(1);
|
|
1180
1233
|
}
|
|
1181
1234
|
}
|
|
@@ -1222,6 +1275,33 @@ credentialFlags(program
|
|
|
1222
1275
|
return;
|
|
1223
1276
|
}
|
|
1224
1277
|
|
|
1278
|
+
if (opts.branch && !opts.git) {
|
|
1279
|
+
console.error('❌ --branch only applies to --git — nothing to clone without it.');
|
|
1280
|
+
process.exit(1);
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (opts.git) {
|
|
1284
|
+
if (!opts.stack) {
|
|
1285
|
+
console.error('❌ --git requires --stack <name> to name the installed stack.');
|
|
1286
|
+
process.exit(1);
|
|
1287
|
+
}
|
|
1288
|
+
if (STACKS[opts.stack]) {
|
|
1289
|
+
console.error(`❌ "${opts.stack}" is already a built-in stack (${Object.keys(STACKS).join(', ')}) — --git is only for installing a stack that isn't built in.`);
|
|
1290
|
+
process.exit(1);
|
|
1291
|
+
}
|
|
1292
|
+
const clonedDir = cloneGitStack(opts.git, opts.branch);
|
|
1293
|
+
const stackCfg = resolveGitStackConfig(clonedDir, opts.stack);
|
|
1294
|
+
await installStack(opts.stack, stackCfg, {
|
|
1295
|
+
configPath: globalOpts.config,
|
|
1296
|
+
print: globalOpts.print,
|
|
1297
|
+
global: opts.global,
|
|
1298
|
+
force: opts.force,
|
|
1299
|
+
credentialOverrides: credentialOverridesFromOpts(opts),
|
|
1300
|
+
templatesSource: join(clonedDir, 'templates'),
|
|
1301
|
+
});
|
|
1302
|
+
return;
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1225
1305
|
const stackKey = await resolveStack(opts.stack);
|
|
1226
1306
|
await installStack(stackKey, STACKS[stackKey], {
|
|
1227
1307
|
configPath: globalOpts.config,
|
package/package.json
CHANGED