@mstar-harness/cli 0.4.0 → 0.5.1
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 +295 -110
- package/package.json +1 -1
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
|
|
@@ -4241,9 +4241,9 @@ function buildModelAssignments(selections) {
|
|
|
4241
4241
|
}
|
|
4242
4242
|
|
|
4243
4243
|
// src/adapters/codex.ts
|
|
4244
|
-
import
|
|
4245
|
-
import
|
|
4246
|
-
import
|
|
4244
|
+
import fs3 from "node:fs";
|
|
4245
|
+
import os2 from "node:os";
|
|
4246
|
+
import path4 from "node:path";
|
|
4247
4247
|
|
|
4248
4248
|
// src/utils.ts
|
|
4249
4249
|
import fs from "node:fs";
|
|
@@ -4283,21 +4283,169 @@ function resolveProjectRoot() {
|
|
|
4283
4283
|
return process.cwd();
|
|
4284
4284
|
}
|
|
4285
4285
|
|
|
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";
|
|
4291
|
+
var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
|
|
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
|
+
}
|
|
4302
|
+
}
|
|
4303
|
+
function ensureDir(dirPath, dryRun) {
|
|
4304
|
+
if (dryRun)
|
|
4305
|
+
return;
|
|
4306
|
+
if (!fs2.existsSync(dirPath))
|
|
4307
|
+
fs2.mkdirSync(dirPath, { recursive: true });
|
|
4308
|
+
}
|
|
4309
|
+
function runCommand(command, cwd, dryRun) {
|
|
4310
|
+
if (dryRun)
|
|
4311
|
+
return;
|
|
4312
|
+
execFileSync(command[0], command.slice(1), { cwd, stdio: "pipe", encoding: "utf8" });
|
|
4313
|
+
}
|
|
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
|
+
|
|
4286
4407
|
// src/adapters/codex.ts
|
|
4287
4408
|
var MARKETPLACE_NAME = "personal";
|
|
4288
4409
|
var MARKETPLACE_DISPLAY_NAME = "Personal";
|
|
4289
|
-
var
|
|
4290
|
-
var PLUGIN_NAME = "morning-star-harness";
|
|
4291
|
-
var PLUGIN_URL = "https://github.com/btspoony/mstar-harness.git";
|
|
4292
|
-
var PLUGIN_REF = "main";
|
|
4410
|
+
var GLOBAL_MARKETPLACE_PATH = path4.join(os2.homedir(), ".agents", "plugins", "marketplace.json");
|
|
4293
4411
|
var PLUGIN_CATEGORY = "Productivity";
|
|
4294
|
-
|
|
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}`;
|
|
4295
4444
|
return {
|
|
4296
4445
|
name: PLUGIN_NAME,
|
|
4297
4446
|
source: {
|
|
4298
|
-
source: "
|
|
4299
|
-
|
|
4300
|
-
ref: PLUGIN_REF
|
|
4447
|
+
source: "local",
|
|
4448
|
+
path: sourcePath
|
|
4301
4449
|
},
|
|
4302
4450
|
policy: {
|
|
4303
4451
|
installation: "AVAILABLE",
|
|
@@ -4319,12 +4467,12 @@ function normalizeMarketplace(raw) {
|
|
|
4319
4467
|
}
|
|
4320
4468
|
return next;
|
|
4321
4469
|
}
|
|
4322
|
-
function upsertEntry(raw) {
|
|
4470
|
+
function upsertEntry(raw, scope) {
|
|
4323
4471
|
const next = normalizeMarketplace(raw);
|
|
4324
4472
|
const plugins = next.plugins.filter((entry) => {
|
|
4325
4473
|
return !(entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === PLUGIN_NAME);
|
|
4326
4474
|
});
|
|
4327
|
-
plugins.push(mstarEntry());
|
|
4475
|
+
plugins.push(mstarEntry(scope));
|
|
4328
4476
|
next.plugins = plugins;
|
|
4329
4477
|
return next;
|
|
4330
4478
|
}
|
|
@@ -4334,19 +4482,19 @@ function findEntry(raw) {
|
|
|
4334
4482
|
return entry && typeof entry === "object" && !Array.isArray(entry) && entry.name === PLUGIN_NAME;
|
|
4335
4483
|
});
|
|
4336
4484
|
}
|
|
4337
|
-
function validateEntryShape(entry) {
|
|
4485
|
+
function validateEntryShape(entry, scope, marketplacePath) {
|
|
4338
4486
|
const errors2 = [];
|
|
4339
4487
|
if (!entry) {
|
|
4340
|
-
errors2.push(`Missing ${PLUGIN_NAME} entry in ${
|
|
4488
|
+
errors2.push(`Missing ${PLUGIN_NAME} entry in ${marketplacePath}.`);
|
|
4341
4489
|
return errors2;
|
|
4342
4490
|
}
|
|
4343
4491
|
const source = ensureObject(entry.source);
|
|
4344
|
-
|
|
4345
|
-
|
|
4346
|
-
|
|
4347
|
-
|
|
4348
|
-
|
|
4349
|
-
|
|
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
|
+
}
|
|
4350
4498
|
const policy = ensureObject(entry.policy);
|
|
4351
4499
|
if (policy.installation !== "AVAILABLE")
|
|
4352
4500
|
errors2.push("Codex marketplace entry policy.installation must be AVAILABLE.");
|
|
@@ -4356,120 +4504,157 @@ function validateEntryShape(entry) {
|
|
|
4356
4504
|
errors2.push(`Codex marketplace entry category must be ${PLUGIN_CATEGORY}.`);
|
|
4357
4505
|
return errors2;
|
|
4358
4506
|
}
|
|
4359
|
-
function
|
|
4360
|
-
|
|
4361
|
-
|
|
4507
|
+
function marketplacePath(scope) {
|
|
4508
|
+
if (scope === "global")
|
|
4509
|
+
return globalMarketplacePath();
|
|
4510
|
+
return projectMarketplacePath();
|
|
4511
|
+
}
|
|
4512
|
+
function ensureAgentLinks(scope, dryRun) {
|
|
4513
|
+
const 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));
|
|
4527
|
+
}
|
|
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);
|
|
4362
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));
|
|
4363
4542
|
if (!dryRun)
|
|
4364
|
-
writeJson(
|
|
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}`);
|
|
4365
4547
|
return {
|
|
4366
|
-
location:
|
|
4367
|
-
notes
|
|
4368
|
-
existingEntry ? `Updated ${PLUGIN_NAME} entry in personal Codex marketplace.` : `Added ${PLUGIN_NAME} entry to personal Codex marketplace.`,
|
|
4369
|
-
`Source URL: ${PLUGIN_URL}#${PLUGIN_REF}`,
|
|
4370
|
-
`Install after init: codex plugin add ${PLUGIN_NAME} --marketplace ${String(next.name)}`
|
|
4371
|
-
]
|
|
4548
|
+
location: pathToMarketplace,
|
|
4549
|
+
notes
|
|
4372
4550
|
};
|
|
4373
4551
|
}
|
|
4374
|
-
function runDoctor() {
|
|
4375
|
-
const
|
|
4376
|
-
|
|
4377
|
-
|
|
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}`] };
|
|
4378
4557
|
}
|
|
4379
|
-
const marketplace = readJson(
|
|
4558
|
+
const marketplace = readJson(pathToMarketplace);
|
|
4380
4559
|
if (marketplace.name !== MARKETPLACE_NAME) {
|
|
4381
4560
|
errors2.push(`Codex personal marketplace name must be ${MARKETPLACE_NAME}.`);
|
|
4382
4561
|
}
|
|
4383
|
-
errors2.push(...validateEntryShape(findEntry(marketplace)));
|
|
4384
|
-
|
|
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 };
|
|
4385
4576
|
}
|
|
4386
4577
|
var codexAdapter = {
|
|
4387
4578
|
target: "codex",
|
|
4388
4579
|
mode: "install",
|
|
4389
|
-
runInstallInit: (
|
|
4390
|
-
runInstallDoctor: () => runDoctor()
|
|
4580
|
+
runInstallInit: (scope, dryRun) => runInit(scope, dryRun),
|
|
4581
|
+
runInstallDoctor: (scope) => runDoctor(scope)
|
|
4391
4582
|
};
|
|
4392
4583
|
|
|
4393
4584
|
// src/adapters/cursor.ts
|
|
4394
|
-
import
|
|
4395
|
-
import
|
|
4396
|
-
import
|
|
4397
|
-
|
|
4398
|
-
var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
|
|
4399
|
-
var CURSOR_PLUGIN_NAME = "mstar-harness";
|
|
4585
|
+
import fs4 from "node:fs";
|
|
4586
|
+
import os3 from "node:os";
|
|
4587
|
+
import path5 from "node:path";
|
|
4588
|
+
var CURSOR_PLUGIN_NAME = "morning-star-harness";
|
|
4400
4589
|
var CURSOR_PLUGIN_MARKER = ".cursor-plugin/plugin.json";
|
|
4590
|
+
var CURSOR_PLUGIN_LINK = ".cursor/plugins/morning-star-harness";
|
|
4591
|
+
var CURSOR_AGENT_SMOKE_NAMES = ["fullstack-dev", "qc-specialist"];
|
|
4401
4592
|
function globalInstallPath() {
|
|
4402
|
-
return
|
|
4593
|
+
return path5.join(os3.homedir(), ".cursor", "plugins", "local", CURSOR_PLUGIN_NAME);
|
|
4403
4594
|
}
|
|
4404
4595
|
function projectInstallPath() {
|
|
4405
|
-
return
|
|
4406
|
-
}
|
|
4407
|
-
function ensureDir(dirPath, dryRun) {
|
|
4408
|
-
if (dryRun)
|
|
4409
|
-
return;
|
|
4410
|
-
if (!fs3.existsSync(dirPath))
|
|
4411
|
-
fs3.mkdirSync(dirPath, { recursive: true });
|
|
4596
|
+
return path5.join(resolveProjectRoot(), CURSOR_PLUGIN_LINK);
|
|
4412
4597
|
}
|
|
4413
|
-
function
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4598
|
+
function validatePluginAgents() {
|
|
4599
|
+
const errors2 = [];
|
|
4600
|
+
const agentsDir = path5.join(HARNESS_REPO_PATH, "agents");
|
|
4601
|
+
if (!fs4.existsSync(agentsDir)) {
|
|
4602
|
+
errors2.push(`Missing plugin agents directory: ${agentsDir}`);
|
|
4603
|
+
return errors2;
|
|
4604
|
+
}
|
|
4605
|
+
for (const agentName of CURSOR_AGENT_SMOKE_NAMES) {
|
|
4606
|
+
const agentPath = path5.join(agentsDir, `${agentName}.md`);
|
|
4607
|
+
if (!fs4.existsSync(agentPath)) {
|
|
4608
|
+
errors2.push(`Missing plugin agent file: ${agentPath}`);
|
|
4609
|
+
continue;
|
|
4610
|
+
}
|
|
4611
|
+
const content = fs4.readFileSync(agentPath, "utf8");
|
|
4612
|
+
if (!/^---\nname:\s/m.test(content)) {
|
|
4613
|
+
errors2.push(`Plugin agent ${agentName}.md must use Cursor-first frontmatter (name, description, model before OpenCode fields).`);
|
|
4614
|
+
}
|
|
4615
|
+
}
|
|
4616
|
+
return errors2;
|
|
4417
4617
|
}
|
|
4418
4618
|
function globalInit(dryRun) {
|
|
4419
4619
|
const location = globalInstallPath();
|
|
4420
|
-
const notes =
|
|
4421
|
-
|
|
4422
|
-
notes.push(`Plugin already exists at ${location}`);
|
|
4423
|
-
return { location, notes };
|
|
4424
|
-
}
|
|
4425
|
-
ensureDir(path4.dirname(location), dryRun);
|
|
4426
|
-
runCommand(["git", "clone", REPO_URL, location], path4.dirname(location), dryRun);
|
|
4427
|
-
notes.push(`Cloned ${REPO_URL} to ${location}`);
|
|
4620
|
+
const notes = ensureLocalHarnessRepo(dryRun);
|
|
4621
|
+
notes.push(ensureSymlink(HARNESS_REPO_PATH, location, dryRun));
|
|
4428
4622
|
return { location, notes };
|
|
4429
4623
|
}
|
|
4430
4624
|
function projectInit(dryRun) {
|
|
4431
4625
|
const projectRoot = resolveProjectRoot();
|
|
4432
4626
|
const location = projectInstallPath();
|
|
4433
|
-
const notes =
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
return { location, notes };
|
|
4437
|
-
}
|
|
4438
|
-
runCommand(["git", "rev-parse", "--is-inside-work-tree"], projectRoot, dryRun);
|
|
4439
|
-
ensureDir(path4.join(projectRoot, ".cursor", "plugins"), dryRun);
|
|
4440
|
-
runCommand(["git", "submodule", "add", REPO_URL, ".cursor/plugins/mstar-harness"], projectRoot, dryRun);
|
|
4441
|
-
notes.push("Added mstar-harness as git submodule at .cursor/plugins/mstar-harness");
|
|
4627
|
+
const notes = ensureLocalHarnessRepo(dryRun);
|
|
4628
|
+
notes.push(ensureSymlink(HARNESS_REPO_PATH, location, dryRun));
|
|
4629
|
+
notes.push(...appendGitignore(projectRoot, [CURSOR_PLUGIN_LINK], dryRun));
|
|
4442
4630
|
return { location, notes };
|
|
4443
4631
|
}
|
|
4444
4632
|
function globalDoctor() {
|
|
4445
4633
|
const location = globalInstallPath();
|
|
4446
|
-
const errors2 =
|
|
4447
|
-
|
|
4448
|
-
|
|
4449
|
-
|
|
4450
|
-
}
|
|
4451
|
-
const marker = path4.join(location, CURSOR_PLUGIN_MARKER);
|
|
4452
|
-
if (!fs3.existsSync(marker)) {
|
|
4634
|
+
const errors2 = validateLocalHarnessRepo();
|
|
4635
|
+
errors2.push(...validateSymlink(HARNESS_REPO_PATH, location));
|
|
4636
|
+
const marker = path5.join(location, CURSOR_PLUGIN_MARKER);
|
|
4637
|
+
if (!fs4.existsSync(marker)) {
|
|
4453
4638
|
errors2.push(`Missing Cursor plugin marker file: ${marker}`);
|
|
4454
4639
|
}
|
|
4640
|
+
errors2.push(...validatePluginAgents());
|
|
4455
4641
|
return { location, errors: errors2 };
|
|
4456
4642
|
}
|
|
4457
4643
|
function projectDoctor() {
|
|
4458
4644
|
const projectRoot = resolveProjectRoot();
|
|
4459
4645
|
const location = projectInstallPath();
|
|
4460
|
-
const errors2 =
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
if (!fs3.existsSync(gitmodulesPath)) {
|
|
4466
|
-
errors2.push("Missing .gitmodules (expected cursor plugin submodule entry).");
|
|
4467
|
-
return { location, errors: errors2 };
|
|
4646
|
+
const errors2 = validateLocalHarnessRepo();
|
|
4647
|
+
errors2.push(...validateSymlink(HARNESS_REPO_PATH, location));
|
|
4648
|
+
const marker = path5.join(location, CURSOR_PLUGIN_MARKER);
|
|
4649
|
+
if (!fs4.existsSync(marker)) {
|
|
4650
|
+
errors2.push(`Missing Cursor plugin marker file: ${marker}`);
|
|
4468
4651
|
}
|
|
4469
|
-
const
|
|
4470
|
-
|
|
4471
|
-
|
|
4652
|
+
const gitignorePath = path5.join(projectRoot, ".gitignore");
|
|
4653
|
+
const gitignore = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
|
|
4654
|
+
if (!gitignore.split(/\r?\n/).includes(CURSOR_PLUGIN_LINK)) {
|
|
4655
|
+
errors2.push(`Missing .gitignore entry: ${CURSOR_PLUGIN_LINK}`);
|
|
4472
4656
|
}
|
|
4657
|
+
errors2.push(...validatePluginAgents());
|
|
4473
4658
|
return { location, errors: errors2 };
|
|
4474
4659
|
}
|
|
4475
4660
|
var cursorAdapter = {
|
|
@@ -4488,8 +4673,8 @@ var cursorAdapter = {
|
|
|
4488
4673
|
};
|
|
4489
4674
|
|
|
4490
4675
|
// src/adapters/opencode.ts
|
|
4491
|
-
import
|
|
4492
|
-
import
|
|
4676
|
+
import os4 from "node:os";
|
|
4677
|
+
import path6 from "node:path";
|
|
4493
4678
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4494
4679
|
var OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
|
|
4495
4680
|
var MSTAR_OPENCODE_PLUGIN = "@mstar-harness/opencode@latest";
|
|
@@ -4518,11 +4703,11 @@ function getOpencodeModels() {
|
|
|
4518
4703
|
function resolveOpencodeConfigPath(scope, outputPath) {
|
|
4519
4704
|
if (outputPath && outputPath.trim()) {
|
|
4520
4705
|
const raw = outputPath.trim();
|
|
4521
|
-
return
|
|
4706
|
+
return path6.isAbsolute(raw) ? raw : path6.join(resolveProjectRoot(), raw);
|
|
4522
4707
|
}
|
|
4523
4708
|
if (scope === "global")
|
|
4524
|
-
return
|
|
4525
|
-
return
|
|
4709
|
+
return path6.join(os4.homedir(), ".config", "opencode", "opencode.json");
|
|
4710
|
+
return path6.join(resolveProjectRoot(), "opencode.json");
|
|
4526
4711
|
}
|
|
4527
4712
|
function ensureConfigSchema(config) {
|
|
4528
4713
|
const next = ensureObject(config);
|
|
@@ -4630,17 +4815,17 @@ function getAdapter(target) {
|
|
|
4630
4815
|
var SUPPORTED_TARGETS = ["opencode", "cursor", "codex"];
|
|
4631
4816
|
|
|
4632
4817
|
// src/utils.ts
|
|
4633
|
-
import
|
|
4634
|
-
import
|
|
4818
|
+
import fs5 from "node:fs";
|
|
4819
|
+
import path7 from "node:path";
|
|
4635
4820
|
function parseCsv(raw) {
|
|
4636
4821
|
if (!raw)
|
|
4637
4822
|
return;
|
|
4638
4823
|
return raw.split(",").map((item) => item.trim()).filter(Boolean);
|
|
4639
4824
|
}
|
|
4640
4825
|
function readJson2(filePath) {
|
|
4641
|
-
if (!
|
|
4826
|
+
if (!fs5.existsSync(filePath))
|
|
4642
4827
|
return {};
|
|
4643
|
-
const content =
|
|
4828
|
+
const content = fs5.readFileSync(filePath, "utf8").trim();
|
|
4644
4829
|
if (!content)
|
|
4645
4830
|
return {};
|
|
4646
4831
|
try {
|
|
@@ -4650,18 +4835,18 @@ function readJson2(filePath) {
|
|
|
4650
4835
|
}
|
|
4651
4836
|
}
|
|
4652
4837
|
function writeJson2(filePath, value) {
|
|
4653
|
-
const parent =
|
|
4654
|
-
if (!
|
|
4655
|
-
|
|
4656
|
-
|
|
4838
|
+
const parent = path7.dirname(filePath);
|
|
4839
|
+
if (!fs5.existsSync(parent))
|
|
4840
|
+
fs5.mkdirSync(parent, { recursive: true });
|
|
4841
|
+
fs5.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
|
|
4657
4842
|
`, "utf8");
|
|
4658
4843
|
}
|
|
4659
4844
|
|
|
4660
4845
|
// src/index.ts
|
|
4661
|
-
var packageJsonPath =
|
|
4846
|
+
var packageJsonPath = path8.resolve(path8.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
4662
4847
|
var packageVersion = (() => {
|
|
4663
4848
|
try {
|
|
4664
|
-
const parsed = JSON.parse(
|
|
4849
|
+
const parsed = JSON.parse(fs6.readFileSync(packageJsonPath, "utf8"));
|
|
4665
4850
|
return parsed.version || "0.0.0";
|
|
4666
4851
|
} catch {
|
|
4667
4852
|
return "0.0.0";
|