@mstar-harness/cli 0.3.1 → 0.5.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.
Files changed (2) hide show
  1. package/dist/mstar-harness.js +371 -79
  2. package/package.json +2 -2
@@ -2344,8 +2344,8 @@ var require_commander = __commonJS((exports) => {
2344
2344
  });
2345
2345
 
2346
2346
  // src/index.ts
2347
- import fs3 from "fs";
2348
- import path6 from "path";
2347
+ import fs6 from "fs";
2348
+ import path8 from "path";
2349
2349
  import { fileURLToPath } from "url";
2350
2350
 
2351
2351
  // ../../node_modules/@inquirer/core/dist/lib/key.js
@@ -4240,13 +4240,13 @@ function buildModelAssignments(selections) {
4240
4240
  return { ...assignments, ...pickRandom(others, selections.others) };
4241
4241
  }
4242
4242
 
4243
- // src/adapters/cursor.ts
4244
- import fs from "node:fs";
4245
- import os from "node:os";
4246
- import path3 from "node:path";
4247
- import { execFileSync } from "node:child_process";
4243
+ // src/adapters/codex.ts
4244
+ import fs3 from "node:fs";
4245
+ import os2 from "node:os";
4246
+ import path4 from "node:path";
4248
4247
 
4249
4248
  // src/utils.ts
4249
+ import fs from "node:fs";
4250
4250
  import path2 from "node:path";
4251
4251
  function normalizeModelList(raw) {
4252
4252
  return raw.split(`
@@ -4257,6 +4257,25 @@ function ensureObject(value) {
4257
4257
  return value;
4258
4258
  return {};
4259
4259
  }
4260
+ function readJson(filePath) {
4261
+ if (!fs.existsSync(filePath))
4262
+ return {};
4263
+ const content = fs.readFileSync(filePath, "utf8").trim();
4264
+ if (!content)
4265
+ return {};
4266
+ try {
4267
+ return JSON.parse(content);
4268
+ } catch (error) {
4269
+ throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
4270
+ }
4271
+ }
4272
+ function writeJson(filePath, value) {
4273
+ const parent = path2.dirname(filePath);
4274
+ if (!fs.existsSync(parent))
4275
+ fs.mkdirSync(parent, { recursive: true });
4276
+ fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
4277
+ `, "utf8");
4278
+ }
4260
4279
  function resolveProjectRoot() {
4261
4280
  const candidate = process.env.MSTAR_CLI_PROJECT_ROOT || process.env.INIT_CWD || process.env.PWD;
4262
4281
  if (candidate && candidate.trim())
@@ -4264,62 +4283,336 @@ function resolveProjectRoot() {
4264
4283
  return process.cwd();
4265
4284
  }
4266
4285
 
4267
- // src/adapters/cursor.ts
4286
+ // src/adapters/shared-install.ts
4287
+ import fs2 from "node:fs";
4288
+ import os from "node:os";
4289
+ import path3 from "node:path";
4290
+ import { execFileSync } from "node:child_process";
4268
4291
  var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
4269
- var CURSOR_PLUGIN_NAME = "mstar-harness";
4270
- var CURSOR_PLUGIN_MARKER = ".cursor-plugin/plugin.json";
4271
- function globalInstallPath() {
4272
- return path3.join(os.homedir(), ".cursor", "plugins", "local", CURSOR_PLUGIN_NAME);
4273
- }
4274
- function projectInstallPath() {
4275
- return path3.join(resolveProjectRoot(), ".cursor", "plugins", CURSOR_PLUGIN_NAME);
4292
+ var PLUGIN_NAME = "morning-star-harness";
4293
+ var HARNESS_REPO_PATH = path3.join(os.homedir(), ".mstar", "harness");
4294
+ var HARNESS_MARKER = ".codex-plugin/plugin.json";
4295
+ function pathOrSymlinkExists(filePath) {
4296
+ try {
4297
+ fs2.lstatSync(filePath);
4298
+ return true;
4299
+ } catch {
4300
+ return false;
4301
+ }
4276
4302
  }
4277
4303
  function ensureDir(dirPath, dryRun) {
4278
4304
  if (dryRun)
4279
4305
  return;
4280
- if (!fs.existsSync(dirPath))
4281
- fs.mkdirSync(dirPath, { recursive: true });
4306
+ if (!fs2.existsSync(dirPath))
4307
+ fs2.mkdirSync(dirPath, { recursive: true });
4282
4308
  }
4283
4309
  function runCommand(command, cwd, dryRun) {
4284
4310
  if (dryRun)
4285
4311
  return;
4286
4312
  execFileSync(command[0], command.slice(1), { cwd, stdio: "pipe", encoding: "utf8" });
4287
4313
  }
4288
- function globalInit(dryRun) {
4289
- const location = globalInstallPath();
4314
+ function ensureLocalHarnessRepo(dryRun) {
4315
+ const notes = [];
4316
+ if (fs2.existsSync(HARNESS_REPO_PATH)) {
4317
+ const errors2 = validateLocalHarnessRepo();
4318
+ if (errors2.length) {
4319
+ throw new Error(errors2.join(`
4320
+ `));
4321
+ }
4322
+ notes.push(`Using existing local harness repo at ${HARNESS_REPO_PATH}`);
4323
+ return notes;
4324
+ }
4325
+ ensureDir(path3.dirname(HARNESS_REPO_PATH), dryRun);
4326
+ runCommand(["git", "clone", REPO_URL, HARNESS_REPO_PATH], path3.dirname(HARNESS_REPO_PATH), dryRun);
4327
+ notes.push(`Cloned ${REPO_URL} to ${HARNESS_REPO_PATH}`);
4328
+ return notes;
4329
+ }
4330
+ function validateLocalHarnessRepo() {
4331
+ const errors2 = [];
4332
+ if (!fs2.existsSync(HARNESS_REPO_PATH)) {
4333
+ errors2.push(`Missing local harness repo: ${HARNESS_REPO_PATH}`);
4334
+ return errors2;
4335
+ }
4336
+ const marker = path3.join(HARNESS_REPO_PATH, HARNESS_MARKER);
4337
+ if (!fs2.existsSync(marker)) {
4338
+ errors2.push(`Local harness repo is missing marker file: ${marker}`);
4339
+ }
4340
+ return errors2;
4341
+ }
4342
+ function ensureSymlink(target, linkPath, dryRun) {
4343
+ if (pathOrSymlinkExists(linkPath)) {
4344
+ const stat = fs2.lstatSync(linkPath);
4345
+ if (!stat.isSymbolicLink()) {
4346
+ throw new Error(`Path exists and is not a symlink: ${linkPath}`);
4347
+ }
4348
+ if (!fs2.existsSync(target)) {
4349
+ throw new Error(`Symlink target is missing: ${target}`);
4350
+ }
4351
+ const actual = fs2.realpathSync(linkPath);
4352
+ const expected = fs2.realpathSync(target);
4353
+ if (actual !== expected) {
4354
+ throw new Error(`Symlink ${linkPath} points to ${actual}, expected ${expected}`);
4355
+ }
4356
+ return `Symlink already exists: ${linkPath} -> ${target}`;
4357
+ }
4358
+ ensureDir(path3.dirname(linkPath), dryRun);
4359
+ if (!dryRun)
4360
+ fs2.symlinkSync(target, linkPath);
4361
+ return `Linked ${linkPath} -> ${target}`;
4362
+ }
4363
+ function validateSymlink(target, linkPath) {
4364
+ const errors2 = [];
4365
+ if (!pathOrSymlinkExists(linkPath)) {
4366
+ errors2.push(`Missing symlink: ${linkPath}`);
4367
+ return errors2;
4368
+ }
4369
+ const stat = fs2.lstatSync(linkPath);
4370
+ if (!stat.isSymbolicLink()) {
4371
+ errors2.push(`Path exists but is not a symlink: ${linkPath}`);
4372
+ return errors2;
4373
+ }
4374
+ if (!fs2.existsSync(target)) {
4375
+ errors2.push(`Symlink target is missing: ${target}`);
4376
+ return errors2;
4377
+ }
4378
+ const actual = fs2.realpathSync(linkPath);
4379
+ const expected = fs2.realpathSync(target);
4380
+ if (actual !== expected) {
4381
+ errors2.push(`Symlink ${linkPath} points to ${actual}, expected ${expected}`);
4382
+ }
4383
+ return errors2;
4384
+ }
4385
+ function appendGitignore(projectRoot, entries, dryRun) {
4386
+ const gitignorePath = path3.join(projectRoot, ".gitignore");
4387
+ const current = fs2.existsSync(gitignorePath) ? fs2.readFileSync(gitignorePath, "utf8") : "";
4388
+ const lines = new Set(current.split(/\r?\n/).map((line) => line.trim()));
4389
+ const missing = entries.filter((entry) => !lines.has(entry));
4390
+ if (!missing.length)
4391
+ return [];
4392
+ if (!dryRun) {
4393
+ const prefix = current && !current.endsWith(`
4394
+ `) ? `
4395
+ ` : "";
4396
+ fs2.appendFileSync(gitignorePath, `${prefix}${missing.join(`
4397
+ `)}
4398
+ `, "utf8");
4399
+ }
4400
+ return missing.map((entry) => `Added ${entry} to .gitignore`);
4401
+ }
4402
+ function homeRelativeSourcePath(targetPath) {
4403
+ const rel = path3.relative(os.homedir(), targetPath).split(path3.sep).join("/");
4404
+ return rel.startsWith("..") ? targetPath : `./${rel}`;
4405
+ }
4406
+
4407
+ // src/adapters/codex.ts
4408
+ var MARKETPLACE_NAME = "personal";
4409
+ var MARKETPLACE_DISPLAY_NAME = "Personal";
4410
+ var GLOBAL_MARKETPLACE_PATH = path4.join(os2.homedir(), ".agents", "plugins", "marketplace.json");
4411
+ var PLUGIN_CATEGORY = "Productivity";
4412
+ var CODEX_PLUGIN_LINK = ".codex/plugins/mstar-harness";
4413
+ var CODEX_AGENT_NAMES = [
4414
+ "product-manager",
4415
+ "architect",
4416
+ "fullstack-dev",
4417
+ "fullstack-dev-2",
4418
+ "frontend-dev",
4419
+ "qa-engineer",
4420
+ "qc-specialist",
4421
+ "qc-specialist-2",
4422
+ "qc-specialist-3",
4423
+ "ops-engineer",
4424
+ "writing-specialist",
4425
+ "prompt-engineer"
4426
+ ];
4427
+ function globalMarketplacePath() {
4428
+ return GLOBAL_MARKETPLACE_PATH;
4429
+ }
4430
+ function projectMarketplacePath() {
4431
+ return path4.join(resolveProjectRoot(), ".agents", "plugins", "marketplace.json");
4432
+ }
4433
+ function agentSourcePath(agentName) {
4434
+ return path4.join(HARNESS_REPO_PATH, "codex", "agents", `${agentName}.toml`);
4435
+ }
4436
+ function globalAgentLinkPath(agentName) {
4437
+ return path4.join(os2.homedir(), ".codex", "agents", `${agentName}.toml`);
4438
+ }
4439
+ function projectAgentLinkPath(agentName) {
4440
+ return path4.join(resolveProjectRoot(), ".codex", "agents", `${agentName}.toml`);
4441
+ }
4442
+ function mstarEntry(scope) {
4443
+ const sourcePath = scope === "global" ? homeRelativeSourcePath(HARNESS_REPO_PATH) : `./${CODEX_PLUGIN_LINK}`;
4444
+ return {
4445
+ name: PLUGIN_NAME,
4446
+ source: {
4447
+ source: "local",
4448
+ path: sourcePath
4449
+ },
4450
+ policy: {
4451
+ installation: "AVAILABLE",
4452
+ authentication: "ON_INSTALL"
4453
+ },
4454
+ category: PLUGIN_CATEGORY
4455
+ };
4456
+ }
4457
+ function normalizeMarketplace(raw) {
4458
+ const next = ensureObject(raw);
4459
+ next.name = MARKETPLACE_NAME;
4460
+ const iface = ensureObject(next.interface);
4461
+ if (typeof iface.displayName !== "string" || !iface.displayName.trim()) {
4462
+ iface.displayName = MARKETPLACE_DISPLAY_NAME;
4463
+ }
4464
+ next.interface = iface;
4465
+ if (!Array.isArray(next.plugins)) {
4466
+ next.plugins = [];
4467
+ }
4468
+ return next;
4469
+ }
4470
+ function upsertEntry(raw, scope) {
4471
+ const next = normalizeMarketplace(raw);
4472
+ const plugins = next.plugins.filter((entry) => {
4473
+ return !(entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === PLUGIN_NAME);
4474
+ });
4475
+ plugins.push(mstarEntry(scope));
4476
+ next.plugins = plugins;
4477
+ return next;
4478
+ }
4479
+ function findEntry(raw) {
4480
+ const plugins = Array.isArray(raw.plugins) ? raw.plugins : [];
4481
+ return plugins.find((entry) => {
4482
+ return entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === PLUGIN_NAME;
4483
+ });
4484
+ }
4485
+ function validateEntryShape(entry, scope, marketplacePath) {
4486
+ const errors2 = [];
4487
+ if (!entry) {
4488
+ errors2.push(`Missing ${PLUGIN_NAME} entry in ${marketplacePath}.`);
4489
+ return errors2;
4490
+ }
4491
+ const source = ensureObject(entry.source);
4492
+ const expectedSourcePath = mstarEntry(scope).source.path;
4493
+ if (source.source !== "local")
4494
+ errors2.push("Codex marketplace entry source.source must be `local`.");
4495
+ if (source.path !== expectedSourcePath) {
4496
+ errors2.push(`Codex marketplace entry source.path must be ${expectedSourcePath}.`);
4497
+ }
4498
+ const policy = ensureObject(entry.policy);
4499
+ if (policy.installation !== "AVAILABLE")
4500
+ errors2.push("Codex marketplace entry policy.installation must be AVAILABLE.");
4501
+ if (policy.authentication !== "ON_INSTALL")
4502
+ errors2.push("Codex marketplace entry policy.authentication must be ON_INSTALL.");
4503
+ if (entry.category !== PLUGIN_CATEGORY)
4504
+ errors2.push(`Codex marketplace entry category must be ${PLUGIN_CATEGORY}.`);
4505
+ return errors2;
4506
+ }
4507
+ function marketplacePath(scope) {
4508
+ if (scope === "global")
4509
+ return globalMarketplacePath();
4510
+ return projectMarketplacePath();
4511
+ }
4512
+ function ensureAgentLinks(scope, dryRun) {
4290
4513
  const notes = [];
4291
- if (fs.existsSync(location)) {
4292
- notes.push(`Plugin already exists at ${location}`);
4293
- return { location, notes };
4514
+ for (const agentName of CODEX_AGENT_NAMES) {
4515
+ const source = agentSourcePath(agentName);
4516
+ const linkPath = scope === "global" ? globalAgentLinkPath(agentName) : projectAgentLinkPath(agentName);
4517
+ notes.push(ensureSymlink(source, linkPath, dryRun));
4518
+ }
4519
+ return notes;
4520
+ }
4521
+ function validateAgentLinks(scope) {
4522
+ const errors2 = [];
4523
+ for (const agentName of CODEX_AGENT_NAMES) {
4524
+ const source = agentSourcePath(agentName);
4525
+ const linkPath = scope === "global" ? globalAgentLinkPath(agentName) : projectAgentLinkPath(agentName);
4526
+ errors2.push(...validateSymlink(source, linkPath));
4294
4527
  }
4295
- ensureDir(path3.dirname(location), dryRun);
4296
- runCommand(["git", "clone", REPO_URL, location], path3.dirname(location), dryRun);
4297
- notes.push(`Cloned ${REPO_URL} to ${location}`);
4528
+ return errors2;
4529
+ }
4530
+ function runInit(scope, dryRun) {
4531
+ const pathToMarketplace = marketplacePath(scope);
4532
+ const current = readJson(pathToMarketplace);
4533
+ const next = upsertEntry(current, scope);
4534
+ const existingEntry = findEntry(current);
4535
+ const notes = ensureLocalHarnessRepo(dryRun);
4536
+ if (scope === "project") {
4537
+ const projectRoot = resolveProjectRoot();
4538
+ notes.push(ensureSymlink(HARNESS_REPO_PATH, path4.join(projectRoot, CODEX_PLUGIN_LINK), dryRun));
4539
+ notes.push(...appendGitignore(projectRoot, [CODEX_PLUGIN_LINK, ".codex/agents/*.toml"], dryRun));
4540
+ }
4541
+ notes.push(...ensureAgentLinks(scope, dryRun));
4542
+ if (!dryRun)
4543
+ writeJson(pathToMarketplace, next);
4544
+ notes.push(existingEntry ? `Updated ${PLUGIN_NAME} local marketplace entry.` : `Added ${PLUGIN_NAME} local marketplace entry.`);
4545
+ notes.push(`Source path: ${mstarEntry(scope).source.path}`);
4546
+ notes.push(`Install after init: codex plugin add ${PLUGIN_NAME} --marketplace ${MARKETPLACE_NAME}`);
4547
+ return {
4548
+ location: pathToMarketplace,
4549
+ notes
4550
+ };
4551
+ }
4552
+ function runDoctor(scope) {
4553
+ const pathToMarketplace = marketplacePath(scope);
4554
+ const errors2 = validateLocalHarnessRepo();
4555
+ if (!fs3.existsSync(pathToMarketplace)) {
4556
+ return { location: pathToMarketplace, errors: [...errors2, `Missing Codex marketplace: ${pathToMarketplace}`] };
4557
+ }
4558
+ const marketplace = readJson(pathToMarketplace);
4559
+ if (marketplace.name !== MARKETPLACE_NAME) {
4560
+ errors2.push(`Codex personal marketplace name must be ${MARKETPLACE_NAME}.`);
4561
+ }
4562
+ errors2.push(...validateEntryShape(findEntry(marketplace), scope, pathToMarketplace));
4563
+ if (scope === "project") {
4564
+ const projectRoot = resolveProjectRoot();
4565
+ errors2.push(...validateSymlink(HARNESS_REPO_PATH, path4.join(projectRoot, CODEX_PLUGIN_LINK)));
4566
+ const gitignorePath = path4.join(projectRoot, ".gitignore");
4567
+ const gitignore = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf8") : "";
4568
+ const lines = gitignore.split(/\r?\n/);
4569
+ if (!lines.includes(CODEX_PLUGIN_LINK))
4570
+ errors2.push(`Missing .gitignore entry: ${CODEX_PLUGIN_LINK}`);
4571
+ if (!lines.includes(".codex/agents/*.toml"))
4572
+ errors2.push("Missing .gitignore entry: .codex/agents/*.toml");
4573
+ }
4574
+ errors2.push(...validateAgentLinks(scope));
4575
+ return { location: pathToMarketplace, errors: errors2 };
4576
+ }
4577
+ var codexAdapter = {
4578
+ target: "codex",
4579
+ mode: "install",
4580
+ runInstallInit: (scope, dryRun) => runInit(scope, dryRun),
4581
+ runInstallDoctor: (scope) => runDoctor(scope)
4582
+ };
4583
+
4584
+ // src/adapters/cursor.ts
4585
+ import fs4 from "node:fs";
4586
+ import os3 from "node:os";
4587
+ import path5 from "node:path";
4588
+ var CURSOR_PLUGIN_NAME = "mstar-harness";
4589
+ var CURSOR_PLUGIN_MARKER = ".cursor-plugin/plugin.json";
4590
+ function globalInstallPath() {
4591
+ return path5.join(os3.homedir(), ".cursor", "plugins", "local", CURSOR_PLUGIN_NAME);
4592
+ }
4593
+ function projectInstallPath() {
4594
+ return path5.join(resolveProjectRoot(), ".cursor", "plugins", CURSOR_PLUGIN_NAME);
4595
+ }
4596
+ function globalInit(dryRun) {
4597
+ const location = globalInstallPath();
4598
+ const notes = ensureLocalHarnessRepo(dryRun);
4599
+ notes.push(ensureSymlink(HARNESS_REPO_PATH, location, dryRun));
4298
4600
  return { location, notes };
4299
4601
  }
4300
4602
  function projectInit(dryRun) {
4301
4603
  const projectRoot = resolveProjectRoot();
4302
4604
  const location = projectInstallPath();
4303
- const notes = [];
4304
- if (fs.existsSync(location)) {
4305
- notes.push(`Submodule path already exists at ${location}`);
4306
- return { location, notes };
4307
- }
4308
- runCommand(["git", "rev-parse", "--is-inside-work-tree"], projectRoot, dryRun);
4309
- ensureDir(path3.join(projectRoot, ".cursor", "plugins"), dryRun);
4310
- runCommand(["git", "submodule", "add", REPO_URL, ".cursor/plugins/mstar-harness"], projectRoot, dryRun);
4311
- notes.push("Added mstar-harness as git submodule at .cursor/plugins/mstar-harness");
4605
+ const notes = ensureLocalHarnessRepo(dryRun);
4606
+ notes.push(ensureSymlink(HARNESS_REPO_PATH, location, dryRun));
4607
+ notes.push(...appendGitignore(projectRoot, [".cursor/plugins/mstar-harness"], dryRun));
4312
4608
  return { location, notes };
4313
4609
  }
4314
4610
  function globalDoctor() {
4315
4611
  const location = globalInstallPath();
4316
- const errors2 = [];
4317
- if (!fs.existsSync(location)) {
4318
- errors2.push(`Missing plugin directory: ${location}`);
4319
- return { location, errors: errors2 };
4320
- }
4321
- const marker = path3.join(location, CURSOR_PLUGIN_MARKER);
4322
- if (!fs.existsSync(marker)) {
4612
+ const errors2 = validateLocalHarnessRepo();
4613
+ errors2.push(...validateSymlink(HARNESS_REPO_PATH, location));
4614
+ const marker = path5.join(location, CURSOR_PLUGIN_MARKER);
4615
+ if (!fs4.existsSync(marker)) {
4323
4616
  errors2.push(`Missing Cursor plugin marker file: ${marker}`);
4324
4617
  }
4325
4618
  return { location, errors: errors2 };
@@ -4327,18 +4620,16 @@ function globalDoctor() {
4327
4620
  function projectDoctor() {
4328
4621
  const projectRoot = resolveProjectRoot();
4329
4622
  const location = projectInstallPath();
4330
- const errors2 = [];
4331
- if (!fs.existsSync(location)) {
4332
- errors2.push(`Missing submodule directory: ${location}`);
4333
- }
4334
- const gitmodulesPath = path3.join(projectRoot, ".gitmodules");
4335
- if (!fs.existsSync(gitmodulesPath)) {
4336
- errors2.push("Missing .gitmodules (expected cursor plugin submodule entry).");
4337
- return { location, errors: errors2 };
4623
+ const errors2 = validateLocalHarnessRepo();
4624
+ errors2.push(...validateSymlink(HARNESS_REPO_PATH, location));
4625
+ const marker = path5.join(location, CURSOR_PLUGIN_MARKER);
4626
+ if (!fs4.existsSync(marker)) {
4627
+ errors2.push(`Missing Cursor plugin marker file: ${marker}`);
4338
4628
  }
4339
- const gitmodules = fs.readFileSync(gitmodulesPath, "utf8");
4340
- if (!gitmodules.includes("path = .cursor/plugins/mstar-harness")) {
4341
- errors2.push("Missing .cursor/plugins/mstar-harness entry in .gitmodules.");
4629
+ const gitignorePath = path5.join(projectRoot, ".gitignore");
4630
+ const gitignore = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
4631
+ if (!gitignore.split(/\r?\n/).includes(".cursor/plugins/mstar-harness")) {
4632
+ errors2.push("Missing .gitignore entry: .cursor/plugins/mstar-harness");
4342
4633
  }
4343
4634
  return { location, errors: errors2 };
4344
4635
  }
@@ -4358,8 +4649,8 @@ var cursorAdapter = {
4358
4649
  };
4359
4650
 
4360
4651
  // src/adapters/opencode.ts
4361
- import os2 from "node:os";
4362
- import path4 from "node:path";
4652
+ import os4 from "node:os";
4653
+ import path6 from "node:path";
4363
4654
  import { execFileSync as execFileSync2 } from "node:child_process";
4364
4655
  var OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
4365
4656
  var MSTAR_OPENCODE_PLUGIN = "@mstar-harness/opencode@latest";
@@ -4388,11 +4679,11 @@ function getOpencodeModels() {
4388
4679
  function resolveOpencodeConfigPath(scope, outputPath) {
4389
4680
  if (outputPath && outputPath.trim()) {
4390
4681
  const raw = outputPath.trim();
4391
- return path4.isAbsolute(raw) ? raw : path4.join(resolveProjectRoot(), raw);
4682
+ return path6.isAbsolute(raw) ? raw : path6.join(resolveProjectRoot(), raw);
4392
4683
  }
4393
4684
  if (scope === "global")
4394
- return path4.join(os2.homedir(), ".config", "opencode", "opencode.json");
4395
- return path4.join(resolveProjectRoot(), "opencode.json");
4685
+ return path6.join(os4.homedir(), ".config", "opencode", "opencode.json");
4686
+ return path6.join(resolveProjectRoot(), "opencode.json");
4396
4687
  }
4397
4688
  function ensureConfigSchema(config) {
4398
4689
  const next = ensureObject(config);
@@ -4486,7 +4777,8 @@ var opencodeAdapter = {
4486
4777
  // src/adapters/index.ts
4487
4778
  var adapters = {
4488
4779
  opencode: opencodeAdapter,
4489
- cursor: cursorAdapter
4780
+ cursor: cursorAdapter,
4781
+ codex: codexAdapter
4490
4782
  };
4491
4783
  function getAdapter(target) {
4492
4784
  const adapter = adapters[target];
@@ -4496,20 +4788,20 @@ function getAdapter(target) {
4496
4788
  }
4497
4789
 
4498
4790
  // src/types.ts
4499
- var SUPPORTED_TARGETS = ["opencode", "cursor"];
4791
+ var SUPPORTED_TARGETS = ["opencode", "cursor", "codex"];
4500
4792
 
4501
4793
  // src/utils.ts
4502
- import fs2 from "node:fs";
4503
- import path5 from "node:path";
4794
+ import fs5 from "node:fs";
4795
+ import path7 from "node:path";
4504
4796
  function parseCsv(raw) {
4505
4797
  if (!raw)
4506
4798
  return;
4507
4799
  return raw.split(",").map((item) => item.trim()).filter(Boolean);
4508
4800
  }
4509
- function readJson(filePath) {
4510
- if (!fs2.existsSync(filePath))
4801
+ function readJson2(filePath) {
4802
+ if (!fs5.existsSync(filePath))
4511
4803
  return {};
4512
- const content = fs2.readFileSync(filePath, "utf8").trim();
4804
+ const content = fs5.readFileSync(filePath, "utf8").trim();
4513
4805
  if (!content)
4514
4806
  return {};
4515
4807
  try {
@@ -4518,19 +4810,19 @@ function readJson(filePath) {
4518
4810
  throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
4519
4811
  }
4520
4812
  }
4521
- function writeJson(filePath, value) {
4522
- const parent = path5.dirname(filePath);
4523
- if (!fs2.existsSync(parent))
4524
- fs2.mkdirSync(parent, { recursive: true });
4525
- fs2.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
4813
+ function writeJson2(filePath, value) {
4814
+ const parent = path7.dirname(filePath);
4815
+ if (!fs5.existsSync(parent))
4816
+ fs5.mkdirSync(parent, { recursive: true });
4817
+ fs5.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
4526
4818
  `, "utf8");
4527
4819
  }
4528
4820
 
4529
4821
  // src/index.ts
4530
- var packageJsonPath = path6.resolve(path6.dirname(fileURLToPath(import.meta.url)), "../package.json");
4822
+ var packageJsonPath = path8.resolve(path8.dirname(fileURLToPath(import.meta.url)), "../package.json");
4531
4823
  var packageVersion = (() => {
4532
4824
  try {
4533
- const parsed = JSON.parse(fs3.readFileSync(packageJsonPath, "utf8"));
4825
+ const parsed = JSON.parse(fs6.readFileSync(packageJsonPath, "utf8"));
4534
4826
  return parsed.version || "0.0.0";
4535
4827
  } catch {
4536
4828
  return "0.0.0";
@@ -4609,7 +4901,7 @@ async function resolveSelections(options, models) {
4609
4901
  })
4610
4902
  };
4611
4903
  }
4612
- async function runInit(options) {
4904
+ async function runInit2(options) {
4613
4905
  const target = options.target || (options.yes ? "opencode" : await pickTargetInteractive());
4614
4906
  const scope = options.scope || "project";
4615
4907
  const adapter = getAdapter(target);
@@ -4641,7 +4933,7 @@ async function runInit(options) {
4641
4933
  const configPath = adapter.resolveConfigPath?.(scope, options.output);
4642
4934
  if (!configPath)
4643
4935
  throw new Error(`Adapter ${target} does not implement config path resolution.`);
4644
- const current = readJson(configPath);
4936
+ const current = readJson2(configPath);
4645
4937
  const updated = adapter.mutateConfigForInit?.(current, assignments);
4646
4938
  if (!updated)
4647
4939
  throw new Error(`Adapter ${target} does not implement init mutation.`);
@@ -4653,8 +4945,8 @@ async function runInit(options) {
4653
4945
  - `)}`);
4654
4946
  }
4655
4947
  if (!options.dryRun) {
4656
- writeJson(configPath, updated);
4657
- const persistedErrors = adapter.validateConfig?.(readJson(configPath)) || [];
4948
+ writeJson2(configPath, updated);
4949
+ const persistedErrors = adapter.validateConfig?.(readJson2(configPath)) || [];
4658
4950
  if (persistedErrors.length) {
4659
4951
  throw new Error(`Post-write verification failed:
4660
4952
  - ${persistedErrors.join(`
@@ -4671,7 +4963,7 @@ async function runInit(options) {
4671
4963
  console.log(` - ${roleId}: ${modelId}`);
4672
4964
  }
4673
4965
  }
4674
- function runDoctor(options) {
4966
+ function runDoctor2(options) {
4675
4967
  const target = options.target || "opencode";
4676
4968
  const adapter = getAdapter(target);
4677
4969
  const scope = options.scope || "project";
@@ -4696,7 +4988,7 @@ function runDoctor(options) {
4696
4988
  if (!configPath) {
4697
4989
  throw new Error(`Adapter ${target} does not implement config doctor flow.`);
4698
4990
  }
4699
- const config = readJson(configPath);
4991
+ const config = readJson2(configPath);
4700
4992
  const errors2 = adapter.validateConfig?.(config) || [];
4701
4993
  console.log(`Config file: ${configPath}`);
4702
4994
  if (!errors2.length) {
@@ -4716,10 +5008,10 @@ function runDoctor(options) {
4716
5008
  }
4717
5009
  program2.name("mstar-harness").description("Morning Star harness CLI for target-based agent bootstrap").version(packageVersion);
4718
5010
  program2.command("init").description("Interactive/non-interactive setup for target agent bootstrap").option("-y, --yes", "Non-interactive mode").option("--target <target>", "Install target", "opencode").option("--scope <scope>", "Config scope: global|project (default: project)").option("--output <path>", "Config file path override, relative to project root").option("--dry-run", "Preview result without writing config").option("--pm-model <model>", "Model for project-manager").option("--strategic-models <a,b,c>", "Models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Models for qc trio").option("--other-models <a,b,c>", "Models for random assignment to remaining roles").action(async (options) => {
4719
- await runInit(options);
5011
+ await runInit2(options);
4720
5012
  });
4721
5013
  program2.command("doctor").description("Validate Morning Star setup for a target agent config").option("--target <target>", "Target agent for doctor checks", "opencode").option("--scope <scope>", "Config scope: global|project", "project").option("--output <path>", "Config file path override, relative to project root").action((options) => {
4722
- runDoctor(options);
5014
+ runDoctor2(options);
4723
5015
  });
4724
5016
  program2.parseAsync(process.argv).catch((error) => {
4725
5017
  console.error(import_picocolors.default.red(`Setup failed: ${error.message}`));
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mstar-harness/cli",
3
- "version": "0.3.1",
4
- "description": "Morning Star harness installer CLI (OpenCode, Cursor).",
3
+ "version": "0.5.0",
4
+ "description": "Morning Star harness installer CLI (OpenCode, Cursor, Codex).",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",