@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.
- package/dist/mstar-harness.js +371 -79
- package/package.json +2 -2
package/dist/mstar-harness.js
CHANGED
|
@@ -2344,8 +2344,8 @@ var require_commander = __commonJS((exports) => {
|
|
|
2344
2344
|
});
|
|
2345
2345
|
|
|
2346
2346
|
// src/index.ts
|
|
2347
|
-
import
|
|
2348
|
-
import
|
|
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/
|
|
4244
|
-
import
|
|
4245
|
-
import
|
|
4246
|
-
import
|
|
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/
|
|
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
|
|
4270
|
-
var
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
|
|
4274
|
-
|
|
4275
|
-
|
|
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 (!
|
|
4281
|
-
|
|
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
|
|
4289
|
-
const
|
|
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
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
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
|
-
|
|
4296
|
-
|
|
4297
|
-
|
|
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
|
-
|
|
4305
|
-
|
|
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
|
-
|
|
4318
|
-
|
|
4319
|
-
|
|
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
|
-
|
|
4332
|
-
|
|
4333
|
-
|
|
4334
|
-
|
|
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
|
|
4340
|
-
|
|
4341
|
-
|
|
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
|
|
4362
|
-
import
|
|
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
|
|
4682
|
+
return path6.isAbsolute(raw) ? raw : path6.join(resolveProjectRoot(), raw);
|
|
4392
4683
|
}
|
|
4393
4684
|
if (scope === "global")
|
|
4394
|
-
return
|
|
4395
|
-
return
|
|
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
|
|
4503
|
-
import
|
|
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
|
|
4510
|
-
if (!
|
|
4801
|
+
function readJson2(filePath) {
|
|
4802
|
+
if (!fs5.existsSync(filePath))
|
|
4511
4803
|
return {};
|
|
4512
|
-
const content =
|
|
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
|
|
4522
|
-
const parent =
|
|
4523
|
-
if (!
|
|
4524
|
-
|
|
4525
|
-
|
|
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 =
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
-
|
|
4657
|
-
const persistedErrors = adapter.validateConfig?.(
|
|
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
|
|
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 =
|
|
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
|
|
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
|
-
|
|
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.
|
|
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",
|