@mstar-harness/cli 1.8.8 → 1.8.9
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 +586 -153
- package/package.json +1 -1
package/dist/mstar-harness.js
CHANGED
|
@@ -2343,6 +2343,10 @@ var require_commander = __commonJS((exports) => {
|
|
|
2343
2343
|
exports.InvalidOptionArgumentError = InvalidArgumentError;
|
|
2344
2344
|
});
|
|
2345
2345
|
|
|
2346
|
+
// src/index.ts
|
|
2347
|
+
import fs9 from "fs";
|
|
2348
|
+
import path11 from "path";
|
|
2349
|
+
|
|
2346
2350
|
// ../../node_modules/@inquirer/core/dist/lib/key.js
|
|
2347
2351
|
var isUpKey = (key, keybindings = []) => key.name === "up" || keybindings.includes("vim") && key.name === "k" || keybindings.includes("emacs") && key.ctrl && key.name === "p";
|
|
2348
2352
|
var isDownKey = (key, keybindings = []) => key.name === "down" || keybindings.includes("vim") && key.name === "j" || keybindings.includes("emacs") && key.ctrl && key.name === "n";
|
|
@@ -3969,6 +3973,442 @@ var {
|
|
|
3969
3973
|
Help
|
|
3970
3974
|
} = import__.default;
|
|
3971
3975
|
|
|
3976
|
+
// src/agent-plugins.ts
|
|
3977
|
+
import fs2 from "node:fs";
|
|
3978
|
+
import path3 from "node:path";
|
|
3979
|
+
|
|
3980
|
+
// src/utils.ts
|
|
3981
|
+
import fs from "node:fs";
|
|
3982
|
+
import path2 from "node:path";
|
|
3983
|
+
import { fileURLToPath } from "node:url";
|
|
3984
|
+
function ensureObject(value) {
|
|
3985
|
+
if (value && typeof value === "object" && !Array.isArray(value))
|
|
3986
|
+
return value;
|
|
3987
|
+
return {};
|
|
3988
|
+
}
|
|
3989
|
+
function readJson(filePath) {
|
|
3990
|
+
if (!fs.existsSync(filePath))
|
|
3991
|
+
return {};
|
|
3992
|
+
const content = fs.readFileSync(filePath, "utf8").trim();
|
|
3993
|
+
if (!content)
|
|
3994
|
+
return {};
|
|
3995
|
+
try {
|
|
3996
|
+
return JSON.parse(content);
|
|
3997
|
+
} catch (error) {
|
|
3998
|
+
throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
|
|
3999
|
+
}
|
|
4000
|
+
}
|
|
4001
|
+
function writeJson(filePath, value) {
|
|
4002
|
+
const parent = path2.dirname(filePath);
|
|
4003
|
+
if (!fs.existsSync(parent))
|
|
4004
|
+
fs.mkdirSync(parent, { recursive: true });
|
|
4005
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
|
|
4006
|
+
`, "utf8");
|
|
4007
|
+
}
|
|
4008
|
+
function resolveProjectRoot() {
|
|
4009
|
+
const candidate = process.env.MSTAR_CLI_PROJECT_ROOT || process.env.INIT_CWD || process.env.PWD;
|
|
4010
|
+
if (candidate && candidate.trim())
|
|
4011
|
+
return path2.resolve(candidate);
|
|
4012
|
+
return process.cwd();
|
|
4013
|
+
}
|
|
4014
|
+
function readHarnessVersion() {
|
|
4015
|
+
const packageJsonPath = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
4016
|
+
try {
|
|
4017
|
+
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
4018
|
+
return parsed.version || "0.0.0";
|
|
4019
|
+
} catch {
|
|
4020
|
+
return "0.0.0";
|
|
4021
|
+
}
|
|
4022
|
+
}
|
|
4023
|
+
|
|
4024
|
+
// src/agent-plugins.ts
|
|
4025
|
+
var PLUGIN_SCHEMA_URL = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
|
|
4026
|
+
var MCP_SCHEMA_URL = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
|
|
4027
|
+
var PLUGIN_TOP_LEVEL_FIELDS = {
|
|
4028
|
+
$schema: true,
|
|
4029
|
+
name: true,
|
|
4030
|
+
version: true,
|
|
4031
|
+
description: true,
|
|
4032
|
+
author: true,
|
|
4033
|
+
homepage: true,
|
|
4034
|
+
repository: true,
|
|
4035
|
+
license: true,
|
|
4036
|
+
keywords: true,
|
|
4037
|
+
extensions: true
|
|
4038
|
+
};
|
|
4039
|
+
var PLUGIN_NAME_PATTERN = /^(?!.*(?:--|\.\.))[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
|
|
4040
|
+
function stripMcpPathPrefix(raw) {
|
|
4041
|
+
if (raw.startsWith("./"))
|
|
4042
|
+
return raw.slice(2);
|
|
4043
|
+
if (raw.startsWith("${PLUGIN_ROOT}/"))
|
|
4044
|
+
return raw.slice("${PLUGIN_ROOT}/".length);
|
|
4045
|
+
if (raw.startsWith("${PLUGIN_DATA}/"))
|
|
4046
|
+
return raw.slice("${PLUGIN_DATA}/".length);
|
|
4047
|
+
if (raw === "${PLUGIN_ROOT}" || raw === "${PLUGIN_DATA}")
|
|
4048
|
+
return "";
|
|
4049
|
+
return null;
|
|
4050
|
+
}
|
|
4051
|
+
function escapesPluginRoot(remainder) {
|
|
4052
|
+
const normalized = path3.posix.normalize(remainder);
|
|
4053
|
+
return normalized.startsWith("..") || path3.posix.isAbsolute(normalized);
|
|
4054
|
+
}
|
|
4055
|
+
var SKILL_NAME_PATTERN = /^(?!.*--)[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
|
|
4056
|
+
var HTTP_HEADER_NAME_PATTERN = /^[!#$%&'*+\-.^_`|~0-9A-Za-z]+$/;
|
|
4057
|
+
var MCP_SERVER_TYPES = {
|
|
4058
|
+
stdio: true,
|
|
4059
|
+
"streamable-http": true,
|
|
4060
|
+
sse: true
|
|
4061
|
+
};
|
|
4062
|
+
var STDIO_FIELDS = { type: true, command: true, args: true, env: true, cwd: true };
|
|
4063
|
+
var REMOTE_FIELDS = { type: true, url: true, headers: true };
|
|
4064
|
+
var AUTHOR_FIELDS = { name: true, email: true, url: true };
|
|
4065
|
+
function describeType(value) {
|
|
4066
|
+
if (value === null)
|
|
4067
|
+
return "null";
|
|
4068
|
+
if (Array.isArray(value))
|
|
4069
|
+
return "array";
|
|
4070
|
+
return typeof value;
|
|
4071
|
+
}
|
|
4072
|
+
function isPlainObject2(value) {
|
|
4073
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4074
|
+
}
|
|
4075
|
+
function parseScalar(raw) {
|
|
4076
|
+
const trimmed = raw.trim();
|
|
4077
|
+
if (trimmed.length >= 2 && (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
4078
|
+
return trimmed.slice(1, -1);
|
|
4079
|
+
}
|
|
4080
|
+
return trimmed;
|
|
4081
|
+
}
|
|
4082
|
+
function parseFrontmatter(filePath) {
|
|
4083
|
+
const content = fs2.readFileSync(filePath, "utf8");
|
|
4084
|
+
const match = content.match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n/);
|
|
4085
|
+
if (!match)
|
|
4086
|
+
return null;
|
|
4087
|
+
const result = {};
|
|
4088
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
4089
|
+
const field = line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/);
|
|
4090
|
+
if (!field)
|
|
4091
|
+
continue;
|
|
4092
|
+
result[field[1]] = parseScalar(field[2]);
|
|
4093
|
+
}
|
|
4094
|
+
return result;
|
|
4095
|
+
}
|
|
4096
|
+
function isValidMcpUrl(raw) {
|
|
4097
|
+
let parsed;
|
|
4098
|
+
try {
|
|
4099
|
+
parsed = new URL(raw);
|
|
4100
|
+
} catch {
|
|
4101
|
+
return false;
|
|
4102
|
+
}
|
|
4103
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
4104
|
+
return false;
|
|
4105
|
+
if (!parsed.hostname)
|
|
4106
|
+
return false;
|
|
4107
|
+
if (parsed.username || parsed.password || parsed.hash)
|
|
4108
|
+
return false;
|
|
4109
|
+
const host = parsed.hostname;
|
|
4110
|
+
const isLoopback = host === "localhost" || host === "::1" || host === "[::1]" || /^127\.\d+\.\d+\.\d+$/.test(host);
|
|
4111
|
+
if (!isLoopback && parsed.protocol !== "https:")
|
|
4112
|
+
return false;
|
|
4113
|
+
return true;
|
|
4114
|
+
}
|
|
4115
|
+
function validateManifest(manifest, errors2, warnings) {
|
|
4116
|
+
if (!isPlainObject2(manifest)) {
|
|
4117
|
+
errors2.push("plugin.json: manifest must be a JSON object");
|
|
4118
|
+
return;
|
|
4119
|
+
}
|
|
4120
|
+
const doc = manifest;
|
|
4121
|
+
for (const key of Object.keys(doc)) {
|
|
4122
|
+
if (!Object.hasOwn(PLUGIN_TOP_LEVEL_FIELDS, key)) {
|
|
4123
|
+
warnings.push(`plugin.json: unknown top-level field "${key}" (ignored; client-specific data belongs under "extensions")`);
|
|
4124
|
+
}
|
|
4125
|
+
}
|
|
4126
|
+
const schema = doc["$schema"];
|
|
4127
|
+
if (typeof schema !== "string") {
|
|
4128
|
+
errors2.push(`plugin.json: "$schema" is required and must be the string ${PLUGIN_SCHEMA_URL}`);
|
|
4129
|
+
} else if (schema !== PLUGIN_SCHEMA_URL) {
|
|
4130
|
+
errors2.push(`plugin.json: unsupported "$schema" ${JSON.stringify(schema)} (expected ${PLUGIN_SCHEMA_URL})`);
|
|
4131
|
+
}
|
|
4132
|
+
const name = doc.name;
|
|
4133
|
+
if (typeof name !== "string" || name.length === 0) {
|
|
4134
|
+
errors2.push('plugin.json: "name" is required and must be a non-empty string');
|
|
4135
|
+
} else {
|
|
4136
|
+
if (name.length > 64) {
|
|
4137
|
+
errors2.push(`plugin.json: "name" must be 1-64 characters (got ${name.length})`);
|
|
4138
|
+
}
|
|
4139
|
+
if (!PLUGIN_NAME_PATTERN.test(name)) {
|
|
4140
|
+
errors2.push(`plugin.json: "name" ${JSON.stringify(name)} violates Agent Plugins name rules ` + `(lowercase alphanumerics, hyphens, periods; no "--" or ".."; must start and end alphanumeric)`);
|
|
4141
|
+
}
|
|
4142
|
+
}
|
|
4143
|
+
for (const field of ["version", "description", "homepage", "repository", "license"]) {
|
|
4144
|
+
const value = doc[field];
|
|
4145
|
+
if (value === undefined)
|
|
4146
|
+
continue;
|
|
4147
|
+
if (typeof value !== "string") {
|
|
4148
|
+
errors2.push(`plugin.json: "${field}" must be a string (got ${describeType(value)})`);
|
|
4149
|
+
}
|
|
4150
|
+
}
|
|
4151
|
+
if (doc.author !== undefined) {
|
|
4152
|
+
if (!isPlainObject2(doc.author)) {
|
|
4153
|
+
errors2.push('plugin.json: "author" must be an object with optional string fields name/email/url');
|
|
4154
|
+
} else {
|
|
4155
|
+
const author = doc.author;
|
|
4156
|
+
for (const key of Object.keys(author)) {
|
|
4157
|
+
if (!Object.hasOwn(AUTHOR_FIELDS, key)) {
|
|
4158
|
+
errors2.push(`plugin.json: "author" has unknown field "${key}" (only name, email, url are allowed)`);
|
|
4159
|
+
}
|
|
4160
|
+
}
|
|
4161
|
+
for (const key of ["name", "email", "url"]) {
|
|
4162
|
+
const value = author[key];
|
|
4163
|
+
if (value !== undefined && typeof value !== "string") {
|
|
4164
|
+
errors2.push(`plugin.json: "author.${key}" must be a string (got ${describeType(value)})`);
|
|
4165
|
+
}
|
|
4166
|
+
}
|
|
4167
|
+
}
|
|
4168
|
+
}
|
|
4169
|
+
if (doc.keywords !== undefined) {
|
|
4170
|
+
if (!Array.isArray(doc.keywords) || doc.keywords.some((entry) => typeof entry !== "string")) {
|
|
4171
|
+
errors2.push('plugin.json: "keywords" must be an array of strings');
|
|
4172
|
+
}
|
|
4173
|
+
}
|
|
4174
|
+
if (doc.extensions !== undefined) {
|
|
4175
|
+
if (!isPlainObject2(doc.extensions)) {
|
|
4176
|
+
warnings.push('plugin.json: "extensions" is not an object — ignored');
|
|
4177
|
+
} else {
|
|
4178
|
+
for (const [namespace, value] of Object.entries(doc.extensions)) {
|
|
4179
|
+
if (!isPlainObject2(value)) {
|
|
4180
|
+
warnings.push(`plugin.json: "extensions.${namespace}" is not an object — ignored`);
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
}
|
|
4184
|
+
}
|
|
4185
|
+
}
|
|
4186
|
+
function validateMcpServer(name, entry, errors2) {
|
|
4187
|
+
const prefix = `mcp.json: mcpServers.${name}`;
|
|
4188
|
+
if (!isPlainObject2(entry)) {
|
|
4189
|
+
errors2.push(`${prefix} must be an object`);
|
|
4190
|
+
return;
|
|
4191
|
+
}
|
|
4192
|
+
const server = entry;
|
|
4193
|
+
const type = server.type;
|
|
4194
|
+
if (typeof type !== "string" || !Object.hasOwn(MCP_SERVER_TYPES, type)) {
|
|
4195
|
+
errors2.push(`${prefix}: "type" must be one of "stdio" | "streamable-http" | "sse" (got ${JSON.stringify(type)})`);
|
|
4196
|
+
return;
|
|
4197
|
+
}
|
|
4198
|
+
if (type === "stdio") {
|
|
4199
|
+
for (const key of Object.keys(server)) {
|
|
4200
|
+
if (!Object.hasOwn(STDIO_FIELDS, key)) {
|
|
4201
|
+
errors2.push(`${prefix}: unknown field "${key}" for stdio server (allowed: type, command, args, env, cwd)`);
|
|
4202
|
+
}
|
|
4203
|
+
}
|
|
4204
|
+
const command = server.command;
|
|
4205
|
+
if (typeof command !== "string" || command.length === 0) {
|
|
4206
|
+
errors2.push(`${prefix}: "command" is required and must be a non-empty string`);
|
|
4207
|
+
} else {
|
|
4208
|
+
if (/\s/.test(command)) {
|
|
4209
|
+
errors2.push(`${prefix}: "command" must be a single executable token, not a shell command string`);
|
|
4210
|
+
} else if (command.includes("/") && !command.startsWith("./")) {
|
|
4211
|
+
errors2.push(`${prefix}: "command" must be a bare executable name or a plugin-relative path beginning with "./"`);
|
|
4212
|
+
} else if (command.startsWith("./") && escapesPluginRoot(command.slice(2))) {
|
|
4213
|
+
errors2.push(`${prefix}: "command" must remain within the plugin root (got "${command}")`);
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
if (server.args !== undefined) {
|
|
4217
|
+
if (!Array.isArray(server.args) || server.args.some((arg) => typeof arg !== "string")) {
|
|
4218
|
+
errors2.push(`${prefix}: "args" must be an array of strings`);
|
|
4219
|
+
}
|
|
4220
|
+
}
|
|
4221
|
+
if (server.env !== undefined) {
|
|
4222
|
+
if (!isPlainObject2(server.env)) {
|
|
4223
|
+
errors2.push(`${prefix}: "env" must be an object of strings`);
|
|
4224
|
+
} else {
|
|
4225
|
+
for (const [key, value] of Object.entries(server.env)) {
|
|
4226
|
+
if (key === "PLUGIN_ROOT" || key === "PLUGIN_DATA") {
|
|
4227
|
+
errors2.push(`${prefix}: "env" must not set reserved variable "${key}" (clients supply it themselves)`);
|
|
4228
|
+
}
|
|
4229
|
+
if (typeof value !== "string") {
|
|
4230
|
+
errors2.push(`${prefix}: "env.${key}" must be a string`);
|
|
4231
|
+
}
|
|
4232
|
+
}
|
|
4233
|
+
}
|
|
4234
|
+
}
|
|
4235
|
+
if (server.cwd !== undefined) {
|
|
4236
|
+
if (typeof server.cwd !== "string") {
|
|
4237
|
+
errors2.push(`${prefix}: "cwd" must be a string`);
|
|
4238
|
+
} else {
|
|
4239
|
+
const remainder = stripMcpPathPrefix(server.cwd);
|
|
4240
|
+
if (remainder === null) {
|
|
4241
|
+
errors2.push(`${prefix}: "cwd" must be "./…", "${"${PLUGIN_ROOT}"}…", or "${"${PLUGIN_DATA}"}…"`);
|
|
4242
|
+
} else if (escapesPluginRoot(remainder)) {
|
|
4243
|
+
errors2.push(`${prefix}: "cwd" must remain within the plugin root (got "${server.cwd}")`);
|
|
4244
|
+
}
|
|
4245
|
+
}
|
|
4246
|
+
}
|
|
4247
|
+
return;
|
|
4248
|
+
}
|
|
4249
|
+
for (const key of Object.keys(server)) {
|
|
4250
|
+
if (!Object.hasOwn(REMOTE_FIELDS, key)) {
|
|
4251
|
+
errors2.push(`${prefix}: unknown field "${key}" for ${type} server (allowed: type, url, headers)`);
|
|
4252
|
+
}
|
|
4253
|
+
}
|
|
4254
|
+
const url = server.url;
|
|
4255
|
+
if (typeof url !== "string" || url.length === 0) {
|
|
4256
|
+
errors2.push(`${prefix}: "url" is required and must be a non-empty string`);
|
|
4257
|
+
} else if (!isValidMcpUrl(url)) {
|
|
4258
|
+
errors2.push(`${prefix}: "url" must be an absolute http(s) URL without user info or fragment; non-loopback endpoints must use https`);
|
|
4259
|
+
}
|
|
4260
|
+
if (server.headers !== undefined) {
|
|
4261
|
+
if (!isPlainObject2(server.headers)) {
|
|
4262
|
+
errors2.push(`${prefix}: "headers" must be an object of strings`);
|
|
4263
|
+
} else {
|
|
4264
|
+
const seen = new Set;
|
|
4265
|
+
for (const [key, value] of Object.entries(server.headers)) {
|
|
4266
|
+
if (typeof value !== "string") {
|
|
4267
|
+
errors2.push(`${prefix}: "headers.${key}" must be a string`);
|
|
4268
|
+
continue;
|
|
4269
|
+
}
|
|
4270
|
+
if (value.includes("\r") || value.includes(`
|
|
4271
|
+
`)) {
|
|
4272
|
+
errors2.push(`${prefix}: "headers.${key}" value must be a single HTTP header value`);
|
|
4273
|
+
}
|
|
4274
|
+
if (!HTTP_HEADER_NAME_PATTERN.test(key)) {
|
|
4275
|
+
errors2.push(`${prefix}: "headers.${key}" is not a valid HTTP header name`);
|
|
4276
|
+
} else {
|
|
4277
|
+
const lower = key.toLowerCase();
|
|
4278
|
+
if (seen.has(lower)) {
|
|
4279
|
+
errors2.push(`${prefix}: header "${key}" is duplicated (case-insensitive)`);
|
|
4280
|
+
}
|
|
4281
|
+
seen.add(lower);
|
|
4282
|
+
}
|
|
4283
|
+
}
|
|
4284
|
+
}
|
|
4285
|
+
}
|
|
4286
|
+
}
|
|
4287
|
+
function validateMcp(root, manifestSchema, errors2) {
|
|
4288
|
+
const mcpPath = path3.join(root, "mcp.json");
|
|
4289
|
+
if (!fs2.existsSync(mcpPath))
|
|
4290
|
+
return;
|
|
4291
|
+
let parsed;
|
|
4292
|
+
try {
|
|
4293
|
+
parsed = readJson(mcpPath);
|
|
4294
|
+
} catch (error) {
|
|
4295
|
+
errors2.push(`mcp.json: ${error.message}`);
|
|
4296
|
+
return;
|
|
4297
|
+
}
|
|
4298
|
+
if (!isPlainObject2(parsed)) {
|
|
4299
|
+
errors2.push("mcp.json: configuration must be a JSON object");
|
|
4300
|
+
return;
|
|
4301
|
+
}
|
|
4302
|
+
const doc = parsed;
|
|
4303
|
+
for (const key of Object.keys(doc)) {
|
|
4304
|
+
if (key !== "$schema" && key !== "mcpServers") {
|
|
4305
|
+
errors2.push(`mcp.json: unknown top-level field "${key}" (only "$schema" and "mcpServers" allowed)`);
|
|
4306
|
+
}
|
|
4307
|
+
}
|
|
4308
|
+
const schema = doc["$schema"];
|
|
4309
|
+
if (typeof schema !== "string") {
|
|
4310
|
+
errors2.push(`mcp.json: "$schema" is required and must be the string ${MCP_SCHEMA_URL}`);
|
|
4311
|
+
} else if (schema !== MCP_SCHEMA_URL) {
|
|
4312
|
+
errors2.push(`mcp.json: unsupported "$schema" ${JSON.stringify(schema)} (expected ${MCP_SCHEMA_URL})`);
|
|
4313
|
+
} else {
|
|
4314
|
+
const manifestVersion = typeof manifestSchema === "string" ? manifestSchema.match(/^https:\/\/agent-plugins\.org\/schemas\/([^/]+)\/plugin\.schema\.json$/)?.[1] : undefined;
|
|
4315
|
+
const mcpVersion = schema.match(/^https:\/\/agent-plugins\.org\/schemas\/([^/]+)\/mcp\.schema\.json$/)?.[1];
|
|
4316
|
+
if (manifestVersion && mcpVersion && manifestVersion !== mcpVersion) {
|
|
4317
|
+
errors2.push(`mcp.json: "$schema" targets Agent Plugins ${mcpVersion} but plugin.json targets ${manifestVersion} (versions must match)`);
|
|
4318
|
+
}
|
|
4319
|
+
}
|
|
4320
|
+
const servers = doc.mcpServers;
|
|
4321
|
+
if (!isPlainObject2(servers)) {
|
|
4322
|
+
errors2.push('mcp.json: "mcpServers" is required and must be an object');
|
|
4323
|
+
return;
|
|
4324
|
+
}
|
|
4325
|
+
for (const [serverName, entry] of Object.entries(servers)) {
|
|
4326
|
+
validateMcpServer(serverName, entry, errors2);
|
|
4327
|
+
}
|
|
4328
|
+
}
|
|
4329
|
+
function validateSkills(root, errors2, warnings) {
|
|
4330
|
+
const skillsPath = path3.join(root, "skills");
|
|
4331
|
+
try {
|
|
4332
|
+
if (!fs2.existsSync(skillsPath))
|
|
4333
|
+
return;
|
|
4334
|
+
if (!fs2.statSync(skillsPath).isDirectory()) {
|
|
4335
|
+
errors2.push("skills: skills/ is not a directory (component type invalid)");
|
|
4336
|
+
return;
|
|
4337
|
+
}
|
|
4338
|
+
const entries = fs2.readdirSync(skillsPath, { withFileTypes: true });
|
|
4339
|
+
const realRoot = fs2.realpathSync(root);
|
|
4340
|
+
for (const entry of entries) {
|
|
4341
|
+
if (!entry.isDirectory() && !entry.isSymbolicLink())
|
|
4342
|
+
continue;
|
|
4343
|
+
const skillDir = entry.name;
|
|
4344
|
+
let realSkillPath;
|
|
4345
|
+
try {
|
|
4346
|
+
realSkillPath = fs2.realpathSync(path3.join(skillsPath, skillDir));
|
|
4347
|
+
} catch (error) {
|
|
4348
|
+
warnings.push(`skills: ${skillDir}/ cannot be resolved (${error.message}; skill skipped)`);
|
|
4349
|
+
continue;
|
|
4350
|
+
}
|
|
4351
|
+
const relative = path3.relative(realRoot, realSkillPath);
|
|
4352
|
+
if (relative === ".." || relative.startsWith(`..${path3.sep}`) || path3.isAbsolute(relative)) {
|
|
4353
|
+
warnings.push(`skills: ${skillDir}/ resolves outside the plugin root (${realSkillPath}; skill skipped)`);
|
|
4354
|
+
continue;
|
|
4355
|
+
}
|
|
4356
|
+
const skillMdPath = path3.join(skillsPath, skillDir, "SKILL.md");
|
|
4357
|
+
if (!fs2.existsSync(skillMdPath) || !fs2.statSync(skillMdPath).isFile()) {
|
|
4358
|
+
warnings.push(`skills: ${skillDir}/ has no SKILL.md (directory is not a skill; ignored)`);
|
|
4359
|
+
continue;
|
|
4360
|
+
}
|
|
4361
|
+
const frontmatter = parseFrontmatter(skillMdPath);
|
|
4362
|
+
if (!frontmatter) {
|
|
4363
|
+
warnings.push(`skills: ${skillDir}/SKILL.md is missing YAML frontmatter (name and description are required; skill skipped)`);
|
|
4364
|
+
continue;
|
|
4365
|
+
}
|
|
4366
|
+
const skillName = frontmatter.name;
|
|
4367
|
+
const problems = [];
|
|
4368
|
+
if (skillName !== skillDir) {
|
|
4369
|
+
problems.push(`frontmatter "name" ${JSON.stringify(skillName)} must equal the directory name "${skillDir}"`);
|
|
4370
|
+
} else if (!SKILL_NAME_PATTERN.test(skillName)) {
|
|
4371
|
+
problems.push(`frontmatter "name" violates Agent Skills name rules ` + `(lowercase alphanumerics and hyphens, no "--", no leading or trailing hyphen)`);
|
|
4372
|
+
}
|
|
4373
|
+
if (typeof skillName === "string" && skillName.length > 64) {
|
|
4374
|
+
problems.push(`frontmatter "name" must be at most 64 characters (got ${skillName.length})`);
|
|
4375
|
+
}
|
|
4376
|
+
const description = frontmatter.description;
|
|
4377
|
+
if (typeof description !== "string" || description.trim().length === 0) {
|
|
4378
|
+
problems.push(`frontmatter "description" is required and must be non-empty`);
|
|
4379
|
+
} else if (description.length > 1024) {
|
|
4380
|
+
problems.push(`frontmatter "description" must be at most 1024 characters (got ${description.length})`);
|
|
4381
|
+
}
|
|
4382
|
+
for (const problem of problems) {
|
|
4383
|
+
warnings.push(`skills: ${skillDir}/SKILL.md ${problem} (skill skipped)`);
|
|
4384
|
+
}
|
|
4385
|
+
}
|
|
4386
|
+
} catch (error) {
|
|
4387
|
+
errors2.push(`skills: ${error.message}`);
|
|
4388
|
+
}
|
|
4389
|
+
}
|
|
4390
|
+
function validateAgentPlugin(root) {
|
|
4391
|
+
const errors2 = [];
|
|
4392
|
+
const warnings = [];
|
|
4393
|
+
const manifestPath = path3.join(root, "plugin.json");
|
|
4394
|
+
if (!fs2.existsSync(manifestPath)) {
|
|
4395
|
+
errors2.push(`plugin.json: manifest not found at ${manifestPath} (plugin root must contain plugin.json)`);
|
|
4396
|
+
return { ok: false, errors: errors2, warnings };
|
|
4397
|
+
}
|
|
4398
|
+
let manifest;
|
|
4399
|
+
try {
|
|
4400
|
+
manifest = readJson(manifestPath);
|
|
4401
|
+
} catch (error) {
|
|
4402
|
+
errors2.push(`plugin.json: ${error.message}`);
|
|
4403
|
+
return { ok: false, errors: errors2, warnings };
|
|
4404
|
+
}
|
|
4405
|
+
validateManifest(manifest, errors2, warnings);
|
|
4406
|
+
const manifestSchema = isPlainObject2(manifest) ? manifest["$schema"] : undefined;
|
|
4407
|
+
validateMcp(root, manifestSchema, errors2);
|
|
4408
|
+
validateSkills(root, errors2, warnings);
|
|
4409
|
+
return { ok: errors2.length === 0, errors: errors2, warnings };
|
|
4410
|
+
}
|
|
4411
|
+
|
|
3972
4412
|
// src/constants.ts
|
|
3973
4413
|
var ALL_ROLES = [
|
|
3974
4414
|
"project-manager",
|
|
@@ -4019,62 +4459,18 @@ function buildModelAssignments(selections) {
|
|
|
4019
4459
|
}
|
|
4020
4460
|
|
|
4021
4461
|
// src/adapters/codex.ts
|
|
4022
|
-
import
|
|
4462
|
+
import fs4 from "node:fs";
|
|
4023
4463
|
import os2 from "node:os";
|
|
4024
|
-
import
|
|
4025
|
-
|
|
4026
|
-
// src/utils.ts
|
|
4027
|
-
import fs from "node:fs";
|
|
4028
|
-
import path2 from "node:path";
|
|
4029
|
-
import { fileURLToPath } from "node:url";
|
|
4030
|
-
function ensureObject(value) {
|
|
4031
|
-
if (value && typeof value === "object" && !Array.isArray(value))
|
|
4032
|
-
return value;
|
|
4033
|
-
return {};
|
|
4034
|
-
}
|
|
4035
|
-
function readJson(filePath) {
|
|
4036
|
-
if (!fs.existsSync(filePath))
|
|
4037
|
-
return {};
|
|
4038
|
-
const content = fs.readFileSync(filePath, "utf8").trim();
|
|
4039
|
-
if (!content)
|
|
4040
|
-
return {};
|
|
4041
|
-
try {
|
|
4042
|
-
return JSON.parse(content);
|
|
4043
|
-
} catch (error) {
|
|
4044
|
-
throw new Error(`Invalid JSON in ${filePath}: ${error.message}`);
|
|
4045
|
-
}
|
|
4046
|
-
}
|
|
4047
|
-
function writeJson(filePath, value) {
|
|
4048
|
-
const parent = path2.dirname(filePath);
|
|
4049
|
-
if (!fs.existsSync(parent))
|
|
4050
|
-
fs.mkdirSync(parent, { recursive: true });
|
|
4051
|
-
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
|
|
4052
|
-
`, "utf8");
|
|
4053
|
-
}
|
|
4054
|
-
function resolveProjectRoot() {
|
|
4055
|
-
const candidate = process.env.MSTAR_CLI_PROJECT_ROOT || process.env.INIT_CWD || process.env.PWD;
|
|
4056
|
-
if (candidate && candidate.trim())
|
|
4057
|
-
return path2.resolve(candidate);
|
|
4058
|
-
return process.cwd();
|
|
4059
|
-
}
|
|
4060
|
-
function readHarnessVersion() {
|
|
4061
|
-
const packageJsonPath = path2.resolve(path2.dirname(fileURLToPath(import.meta.url)), "../package.json");
|
|
4062
|
-
try {
|
|
4063
|
-
const parsed = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
4064
|
-
return parsed.version || "0.0.0";
|
|
4065
|
-
} catch {
|
|
4066
|
-
return "0.0.0";
|
|
4067
|
-
}
|
|
4068
|
-
}
|
|
4464
|
+
import path5 from "node:path";
|
|
4069
4465
|
|
|
4070
4466
|
// src/adapters/shared-install.ts
|
|
4071
|
-
import
|
|
4467
|
+
import fs3 from "node:fs";
|
|
4072
4468
|
import os from "node:os";
|
|
4073
|
-
import
|
|
4469
|
+
import path4 from "node:path";
|
|
4074
4470
|
import { execFileSync } from "node:child_process";
|
|
4075
4471
|
var REPO_URL = "https://github.com/btspoony/mstar-harness.git";
|
|
4076
4472
|
var PLUGIN_NAME = "morning-star-harness";
|
|
4077
|
-
var HARNESS_REPO_PATH =
|
|
4473
|
+
var HARNESS_REPO_PATH = path4.join(os.homedir(), ".mstar", "harness");
|
|
4078
4474
|
var HARNESS_MARKERS = [
|
|
4079
4475
|
".codex-plugin/plugin.json",
|
|
4080
4476
|
".zcode-plugin/plugin.json",
|
|
@@ -4082,15 +4478,15 @@ var HARNESS_MARKERS = [
|
|
|
4082
4478
|
];
|
|
4083
4479
|
function harnessMarkerPath() {
|
|
4084
4480
|
for (const marker of HARNESS_MARKERS) {
|
|
4085
|
-
const candidate =
|
|
4086
|
-
if (
|
|
4481
|
+
const candidate = path4.join(HARNESS_REPO_PATH, marker);
|
|
4482
|
+
if (fs3.existsSync(candidate))
|
|
4087
4483
|
return candidate;
|
|
4088
4484
|
}
|
|
4089
|
-
return
|
|
4485
|
+
return path4.join(HARNESS_REPO_PATH, HARNESS_MARKERS[0]);
|
|
4090
4486
|
}
|
|
4091
4487
|
function pathOrSymlinkExists(filePath) {
|
|
4092
4488
|
try {
|
|
4093
|
-
|
|
4489
|
+
fs3.lstatSync(filePath);
|
|
4094
4490
|
return true;
|
|
4095
4491
|
} catch {
|
|
4096
4492
|
return false;
|
|
@@ -4099,8 +4495,8 @@ function pathOrSymlinkExists(filePath) {
|
|
|
4099
4495
|
function ensureDir(dirPath, dryRun) {
|
|
4100
4496
|
if (dryRun)
|
|
4101
4497
|
return;
|
|
4102
|
-
if (!
|
|
4103
|
-
|
|
4498
|
+
if (!fs3.existsSync(dirPath))
|
|
4499
|
+
fs3.mkdirSync(dirPath, { recursive: true });
|
|
4104
4500
|
}
|
|
4105
4501
|
function runCommand(command, cwd, dryRun) {
|
|
4106
4502
|
if (dryRun)
|
|
@@ -4109,7 +4505,7 @@ function runCommand(command, cwd, dryRun) {
|
|
|
4109
4505
|
}
|
|
4110
4506
|
function ensureLocalHarnessRepo(dryRun) {
|
|
4111
4507
|
const notes = [];
|
|
4112
|
-
if (
|
|
4508
|
+
if (fs3.existsSync(HARNESS_REPO_PATH)) {
|
|
4113
4509
|
const errors2 = validateLocalHarnessRepo();
|
|
4114
4510
|
if (errors2.length) {
|
|
4115
4511
|
throw new Error(errors2.join(`
|
|
@@ -4118,34 +4514,34 @@ function ensureLocalHarnessRepo(dryRun) {
|
|
|
4118
4514
|
notes.push(`Using existing local harness repo at ${HARNESS_REPO_PATH}`);
|
|
4119
4515
|
return notes;
|
|
4120
4516
|
}
|
|
4121
|
-
ensureDir(
|
|
4122
|
-
runCommand(["git", "clone", REPO_URL, HARNESS_REPO_PATH],
|
|
4517
|
+
ensureDir(path4.dirname(HARNESS_REPO_PATH), dryRun);
|
|
4518
|
+
runCommand(["git", "clone", REPO_URL, HARNESS_REPO_PATH], path4.dirname(HARNESS_REPO_PATH), dryRun);
|
|
4123
4519
|
notes.push(`Cloned ${REPO_URL} to ${HARNESS_REPO_PATH}`);
|
|
4124
4520
|
return notes;
|
|
4125
4521
|
}
|
|
4126
4522
|
function validateLocalHarnessRepo() {
|
|
4127
4523
|
const errors2 = [];
|
|
4128
|
-
if (!
|
|
4524
|
+
if (!fs3.existsSync(HARNESS_REPO_PATH)) {
|
|
4129
4525
|
errors2.push(`Missing local harness repo: ${HARNESS_REPO_PATH}`);
|
|
4130
4526
|
return errors2;
|
|
4131
4527
|
}
|
|
4132
4528
|
const marker = harnessMarkerPath();
|
|
4133
|
-
if (!
|
|
4529
|
+
if (!fs3.existsSync(marker)) {
|
|
4134
4530
|
errors2.push(`Local harness repo is missing a plugin marker (expected one of: ${HARNESS_MARKERS.join(", ")}).`);
|
|
4135
4531
|
}
|
|
4136
4532
|
return errors2;
|
|
4137
4533
|
}
|
|
4138
4534
|
function ensureGitCheckout(repoUrl, checkoutPath, dryRun) {
|
|
4139
4535
|
const notes = [];
|
|
4140
|
-
const parentDir =
|
|
4536
|
+
const parentDir = path4.dirname(checkoutPath);
|
|
4141
4537
|
if (pathOrSymlinkExists(checkoutPath)) {
|
|
4142
|
-
const stat =
|
|
4538
|
+
const stat = fs3.lstatSync(checkoutPath);
|
|
4143
4539
|
if (stat.isSymbolicLink()) {
|
|
4144
4540
|
notes.push(dryRun ? `Would remove symlink at ${checkoutPath} and clone ${repoUrl}` : `Removed symlink at ${checkoutPath} (Cursor requires a real directory)`);
|
|
4145
4541
|
if (dryRun)
|
|
4146
4542
|
return notes;
|
|
4147
|
-
|
|
4148
|
-
} else if (
|
|
4543
|
+
fs3.unlinkSync(checkoutPath);
|
|
4544
|
+
} else if (fs3.existsSync(path4.join(checkoutPath, ".git"))) {
|
|
4149
4545
|
if (!dryRun) {
|
|
4150
4546
|
runCommand(["git", "-C", checkoutPath, "pull", "--ff-only"], checkoutPath, dryRun);
|
|
4151
4547
|
}
|
|
@@ -4168,39 +4564,39 @@ function validateGitCheckout(checkoutPath, markerRelativePath) {
|
|
|
4168
4564
|
errors2.push(`Missing checkout directory: ${checkoutPath}`);
|
|
4169
4565
|
return errors2;
|
|
4170
4566
|
}
|
|
4171
|
-
const stat =
|
|
4567
|
+
const stat = fs3.lstatSync(checkoutPath);
|
|
4172
4568
|
if (stat.isSymbolicLink()) {
|
|
4173
4569
|
errors2.push(`Path must be a real directory, not a symlink: ${checkoutPath}. Run: mstar-harness init --target cursor`);
|
|
4174
4570
|
return errors2;
|
|
4175
4571
|
}
|
|
4176
|
-
if (!
|
|
4572
|
+
if (!fs3.existsSync(path4.join(checkoutPath, ".git"))) {
|
|
4177
4573
|
errors2.push(`Path is not a git checkout: ${checkoutPath}`);
|
|
4178
4574
|
}
|
|
4179
|
-
const marker =
|
|
4180
|
-
if (!
|
|
4575
|
+
const marker = path4.join(checkoutPath, markerRelativePath);
|
|
4576
|
+
if (!fs3.existsSync(marker)) {
|
|
4181
4577
|
errors2.push(`Missing marker file: ${marker}`);
|
|
4182
4578
|
}
|
|
4183
4579
|
return errors2;
|
|
4184
4580
|
}
|
|
4185
4581
|
function ensureSymlink(target, linkPath, dryRun) {
|
|
4186
4582
|
if (pathOrSymlinkExists(linkPath)) {
|
|
4187
|
-
const stat =
|
|
4583
|
+
const stat = fs3.lstatSync(linkPath);
|
|
4188
4584
|
if (!stat.isSymbolicLink()) {
|
|
4189
4585
|
throw new Error(`Path exists and is not a symlink: ${linkPath}`);
|
|
4190
4586
|
}
|
|
4191
|
-
if (!
|
|
4587
|
+
if (!fs3.existsSync(target)) {
|
|
4192
4588
|
throw new Error(`Symlink target is missing: ${target}`);
|
|
4193
4589
|
}
|
|
4194
|
-
const actual =
|
|
4195
|
-
const expected =
|
|
4590
|
+
const actual = fs3.realpathSync(linkPath);
|
|
4591
|
+
const expected = fs3.realpathSync(target);
|
|
4196
4592
|
if (actual !== expected) {
|
|
4197
4593
|
throw new Error(`Symlink ${linkPath} points to ${actual}, expected ${expected}`);
|
|
4198
4594
|
}
|
|
4199
4595
|
return `Symlink already exists: ${linkPath} -> ${target}`;
|
|
4200
4596
|
}
|
|
4201
|
-
ensureDir(
|
|
4597
|
+
ensureDir(path4.dirname(linkPath), dryRun);
|
|
4202
4598
|
if (!dryRun)
|
|
4203
|
-
|
|
4599
|
+
fs3.symlinkSync(target, linkPath);
|
|
4204
4600
|
return `Linked ${linkPath} -> ${target}`;
|
|
4205
4601
|
}
|
|
4206
4602
|
function validateSymlink(target, linkPath) {
|
|
@@ -4209,17 +4605,17 @@ function validateSymlink(target, linkPath) {
|
|
|
4209
4605
|
errors2.push(`Missing symlink: ${linkPath}`);
|
|
4210
4606
|
return errors2;
|
|
4211
4607
|
}
|
|
4212
|
-
const stat =
|
|
4608
|
+
const stat = fs3.lstatSync(linkPath);
|
|
4213
4609
|
if (!stat.isSymbolicLink()) {
|
|
4214
4610
|
errors2.push(`Path exists but is not a symlink: ${linkPath}`);
|
|
4215
4611
|
return errors2;
|
|
4216
4612
|
}
|
|
4217
|
-
if (!
|
|
4613
|
+
if (!fs3.existsSync(target)) {
|
|
4218
4614
|
errors2.push(`Symlink target is missing: ${target}`);
|
|
4219
4615
|
return errors2;
|
|
4220
4616
|
}
|
|
4221
|
-
const actual =
|
|
4222
|
-
const expected =
|
|
4617
|
+
const actual = fs3.realpathSync(linkPath);
|
|
4618
|
+
const expected = fs3.realpathSync(target);
|
|
4223
4619
|
if (actual !== expected) {
|
|
4224
4620
|
errors2.push(`Symlink ${linkPath} points to ${actual}, expected ${expected}`);
|
|
4225
4621
|
}
|
|
@@ -4244,8 +4640,8 @@ function missingHarnessProcessGitignoreEntries(gitignoreContent) {
|
|
|
4244
4640
|
return HARNESS_PROCESS_GITIGNORE.filter((entry) => !lines.includes(entry));
|
|
4245
4641
|
}
|
|
4246
4642
|
function appendGitignore(projectRoot, entries, dryRun) {
|
|
4247
|
-
const gitignorePath =
|
|
4248
|
-
const current =
|
|
4643
|
+
const gitignorePath = path4.join(projectRoot, ".gitignore");
|
|
4644
|
+
const current = fs3.existsSync(gitignorePath) ? fs3.readFileSync(gitignorePath, "utf8") : "";
|
|
4249
4645
|
const lines = new Set(current.split(/\r?\n/).map((line) => line.trim()));
|
|
4250
4646
|
const missing = entries.filter((entry) => !lines.has(entry));
|
|
4251
4647
|
if (!missing.length)
|
|
@@ -4254,7 +4650,7 @@ function appendGitignore(projectRoot, entries, dryRun) {
|
|
|
4254
4650
|
const prefix = current && !current.endsWith(`
|
|
4255
4651
|
`) ? `
|
|
4256
4652
|
` : "";
|
|
4257
|
-
|
|
4653
|
+
fs3.appendFileSync(gitignorePath, `${prefix}${missing.join(`
|
|
4258
4654
|
`)}
|
|
4259
4655
|
`, "utf8");
|
|
4260
4656
|
}
|
|
@@ -4264,14 +4660,14 @@ function appendHarnessProjectGitignore(projectRoot, dryRun) {
|
|
|
4264
4660
|
return appendGitignore(projectRoot, HARNESS_PROCESS_GITIGNORE, dryRun);
|
|
4265
4661
|
}
|
|
4266
4662
|
function homeRelativeSourcePath(targetPath) {
|
|
4267
|
-
const rel =
|
|
4663
|
+
const rel = path4.relative(os.homedir(), targetPath).split(path4.sep).join("/");
|
|
4268
4664
|
return rel.startsWith("..") ? targetPath : `./${rel}`;
|
|
4269
4665
|
}
|
|
4270
4666
|
|
|
4271
4667
|
// src/adapters/codex.ts
|
|
4272
4668
|
var MARKETPLACE_NAME = "personal";
|
|
4273
4669
|
var MARKETPLACE_DISPLAY_NAME = "Personal";
|
|
4274
|
-
var GLOBAL_MARKETPLACE_PATH =
|
|
4670
|
+
var GLOBAL_MARKETPLACE_PATH = path5.join(os2.homedir(), ".agents", "plugins", "marketplace.json");
|
|
4275
4671
|
var PLUGIN_CATEGORY = "Productivity";
|
|
4276
4672
|
var CODEX_PLUGIN_LINK = ".codex/plugins/mstar-harness";
|
|
4277
4673
|
var CODEX_AGENT_NAMES = [
|
|
@@ -4299,16 +4695,16 @@ function globalMarketplacePath() {
|
|
|
4299
4695
|
return GLOBAL_MARKETPLACE_PATH;
|
|
4300
4696
|
}
|
|
4301
4697
|
function projectMarketplacePath() {
|
|
4302
|
-
return
|
|
4698
|
+
return path5.join(resolveProjectRoot(), ".agents", "plugins", "marketplace.json");
|
|
4303
4699
|
}
|
|
4304
4700
|
function agentSourcePath(agentName) {
|
|
4305
|
-
return
|
|
4701
|
+
return path5.join(HARNESS_REPO_PATH, "codex", "agents", `${agentName}.toml`);
|
|
4306
4702
|
}
|
|
4307
4703
|
function globalAgentLinkPath(agentName) {
|
|
4308
|
-
return
|
|
4704
|
+
return path5.join(os2.homedir(), ".codex", "agents", `${agentName}.toml`);
|
|
4309
4705
|
}
|
|
4310
4706
|
function projectAgentLinkPath(agentName) {
|
|
4311
|
-
return
|
|
4707
|
+
return path5.join(resolveProjectRoot(), ".codex", "agents", `${agentName}.toml`);
|
|
4312
4708
|
}
|
|
4313
4709
|
function mstarEntry(scope) {
|
|
4314
4710
|
const sourcePath = scope === "global" ? homeRelativeSourcePath(HARNESS_REPO_PATH) : `./${CODEX_PLUGIN_LINK}`;
|
|
@@ -4399,10 +4795,10 @@ function validateAgentLinks(scope) {
|
|
|
4399
4795
|
return errors2;
|
|
4400
4796
|
}
|
|
4401
4797
|
function iterationCommandSourcePath(skillName) {
|
|
4402
|
-
return
|
|
4798
|
+
return path5.join(HARNESS_REPO_PATH, "commands", `${skillName}.md`);
|
|
4403
4799
|
}
|
|
4404
4800
|
function projectIterationSkillLinkPath(skillName) {
|
|
4405
|
-
return
|
|
4801
|
+
return path5.join(resolveProjectRoot(), ".agents", "skills", skillName, "SKILL.md");
|
|
4406
4802
|
}
|
|
4407
4803
|
function iterationSkillGitignoreEntry(skillName) {
|
|
4408
4804
|
return `.agents/skills/${skillName}`;
|
|
@@ -4423,8 +4819,8 @@ function ensureIterationSkillLinks(dryRun) {
|
|
|
4423
4819
|
function validateIterationSkillLinks() {
|
|
4424
4820
|
const errors2 = [];
|
|
4425
4821
|
const projectRoot = resolveProjectRoot();
|
|
4426
|
-
const gitignorePath =
|
|
4427
|
-
const gitignore =
|
|
4822
|
+
const gitignorePath = path5.join(projectRoot, ".gitignore");
|
|
4823
|
+
const gitignore = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
|
|
4428
4824
|
const lines = gitignore.split(/\r?\n/);
|
|
4429
4825
|
for (const skillName of CODEX_PROJECT_COMMAND_NAMES) {
|
|
4430
4826
|
const source = iterationCommandSourcePath(skillName);
|
|
@@ -4444,7 +4840,7 @@ function runInit(scope, dryRun) {
|
|
|
4444
4840
|
const notes = ensureLocalHarnessRepo(dryRun);
|
|
4445
4841
|
if (scope === "project") {
|
|
4446
4842
|
const projectRoot = resolveProjectRoot();
|
|
4447
|
-
notes.push(ensureSymlink(HARNESS_REPO_PATH,
|
|
4843
|
+
notes.push(ensureSymlink(HARNESS_REPO_PATH, path5.join(projectRoot, CODEX_PLUGIN_LINK), dryRun));
|
|
4448
4844
|
notes.push(...appendGitignore(projectRoot, [CODEX_PLUGIN_LINK, ".codex/agents/*.toml"], dryRun));
|
|
4449
4845
|
notes.push(...appendHarnessProjectGitignore(projectRoot, dryRun));
|
|
4450
4846
|
notes.push(...ensureIterationSkillLinks(dryRun));
|
|
@@ -4465,7 +4861,7 @@ function runInit(scope, dryRun) {
|
|
|
4465
4861
|
function runDoctor(scope) {
|
|
4466
4862
|
const pathToMarketplace = marketplacePath(scope);
|
|
4467
4863
|
const errors2 = validateLocalHarnessRepo();
|
|
4468
|
-
if (!
|
|
4864
|
+
if (!fs4.existsSync(pathToMarketplace)) {
|
|
4469
4865
|
return { location: pathToMarketplace, errors: [...errors2, `Missing Codex marketplace: ${pathToMarketplace}`] };
|
|
4470
4866
|
}
|
|
4471
4867
|
const marketplace = readJson(pathToMarketplace);
|
|
@@ -4475,9 +4871,9 @@ function runDoctor(scope) {
|
|
|
4475
4871
|
errors2.push(...validateEntryShape(findEntry(marketplace), scope, pathToMarketplace));
|
|
4476
4872
|
if (scope === "project") {
|
|
4477
4873
|
const projectRoot = resolveProjectRoot();
|
|
4478
|
-
errors2.push(...validateSymlink(HARNESS_REPO_PATH,
|
|
4479
|
-
const gitignorePath =
|
|
4480
|
-
const gitignore =
|
|
4874
|
+
errors2.push(...validateSymlink(HARNESS_REPO_PATH, path5.join(projectRoot, CODEX_PLUGIN_LINK)));
|
|
4875
|
+
const gitignorePath = path5.join(projectRoot, ".gitignore");
|
|
4876
|
+
const gitignore = fs4.existsSync(gitignorePath) ? fs4.readFileSync(gitignorePath, "utf8") : "";
|
|
4481
4877
|
const lines = gitignore.split(/\r?\n/);
|
|
4482
4878
|
if (!lines.includes(CODEX_PLUGIN_LINK))
|
|
4483
4879
|
errors2.push(`Missing .gitignore entry: ${CODEX_PLUGIN_LINK}`);
|
|
@@ -4499,33 +4895,33 @@ var codexAdapter = {
|
|
|
4499
4895
|
};
|
|
4500
4896
|
|
|
4501
4897
|
// src/adapters/cursor.ts
|
|
4502
|
-
import
|
|
4898
|
+
import fs5 from "node:fs";
|
|
4503
4899
|
import os3 from "node:os";
|
|
4504
|
-
import
|
|
4900
|
+
import path6 from "node:path";
|
|
4505
4901
|
var CURSOR_PLUGIN_NAME = "morning-star-harness";
|
|
4506
4902
|
var CURSOR_PLUGIN_MARKER = ".cursor-plugin/plugin.json";
|
|
4507
4903
|
var CURSOR_PLUGIN_LINK = ".cursor/plugins/morning-star-harness";
|
|
4508
4904
|
var CURSOR_AGENT_SMOKE_NAMES = ["fullstack-dev", "qc-specialist"];
|
|
4509
4905
|
function globalInstallPath() {
|
|
4510
|
-
return
|
|
4906
|
+
return path6.join(os3.homedir(), ".cursor", "plugins", "local", CURSOR_PLUGIN_NAME);
|
|
4511
4907
|
}
|
|
4512
4908
|
function projectInstallPath() {
|
|
4513
|
-
return
|
|
4909
|
+
return path6.join(resolveProjectRoot(), CURSOR_PLUGIN_LINK);
|
|
4514
4910
|
}
|
|
4515
4911
|
function validatePluginAgents(pluginRoot) {
|
|
4516
4912
|
const errors2 = [];
|
|
4517
|
-
const agentsDir =
|
|
4518
|
-
if (!
|
|
4913
|
+
const agentsDir = path6.join(pluginRoot, "agents");
|
|
4914
|
+
if (!fs5.existsSync(agentsDir)) {
|
|
4519
4915
|
errors2.push(`Missing plugin agents directory: ${agentsDir}`);
|
|
4520
4916
|
return errors2;
|
|
4521
4917
|
}
|
|
4522
4918
|
for (const agentName of CURSOR_AGENT_SMOKE_NAMES) {
|
|
4523
|
-
const agentPath =
|
|
4524
|
-
if (!
|
|
4919
|
+
const agentPath = path6.join(agentsDir, `${agentName}.md`);
|
|
4920
|
+
if (!fs5.existsSync(agentPath)) {
|
|
4525
4921
|
errors2.push(`Missing plugin agent file: ${agentPath}`);
|
|
4526
4922
|
continue;
|
|
4527
4923
|
}
|
|
4528
|
-
const content =
|
|
4924
|
+
const content = fs5.readFileSync(agentPath, "utf8");
|
|
4529
4925
|
if (!/^---\nname:\s/m.test(content)) {
|
|
4530
4926
|
errors2.push(`Plugin agent ${agentName}.md must use Cursor-first frontmatter (name, description, model before OpenCode fields).`);
|
|
4531
4927
|
}
|
|
@@ -4562,8 +4958,8 @@ function projectDoctor() {
|
|
|
4562
4958
|
const location = projectInstallPath();
|
|
4563
4959
|
const errors2 = validateLocalHarnessRepo();
|
|
4564
4960
|
errors2.push(...validateGitCheckout(location, CURSOR_PLUGIN_MARKER));
|
|
4565
|
-
const gitignorePath =
|
|
4566
|
-
const gitignore =
|
|
4961
|
+
const gitignorePath = path6.join(projectRoot, ".gitignore");
|
|
4962
|
+
const gitignore = fs5.existsSync(gitignorePath) ? fs5.readFileSync(gitignorePath, "utf8") : "";
|
|
4567
4963
|
if (!gitignore.split(/\r?\n/).includes(CURSOR_PLUGIN_LINK)) {
|
|
4568
4964
|
errors2.push(`Missing .gitignore entry: ${CURSOR_PLUGIN_LINK}`);
|
|
4569
4965
|
}
|
|
@@ -4589,8 +4985,8 @@ var cursorAdapter = {
|
|
|
4589
4985
|
};
|
|
4590
4986
|
|
|
4591
4987
|
// src/adapters/omp.ts
|
|
4592
|
-
import
|
|
4593
|
-
import
|
|
4988
|
+
import fs6 from "node:fs";
|
|
4989
|
+
import path7 from "node:path";
|
|
4594
4990
|
import { execFileSync as execFileSync2 } from "node:child_process";
|
|
4595
4991
|
var OMP_PLUGIN_MARKER = ".omp-plugin/plugin.json";
|
|
4596
4992
|
var CLAUDE_PLUGIN_MARKER = ".claude-plugin/plugin.json";
|
|
@@ -4652,7 +5048,7 @@ function findInstalledPlugin(plugins) {
|
|
|
4652
5048
|
return true;
|
|
4653
5049
|
if (name.includes("morning-star") || manifestName.includes("morning-star"))
|
|
4654
5050
|
return true;
|
|
4655
|
-
if (pathValue.includes("mstar-harness") || pathValue.includes(`${
|
|
5051
|
+
if (pathValue.includes("mstar-harness") || pathValue.includes(`${path7.sep}morning-star`))
|
|
4656
5052
|
return true;
|
|
4657
5053
|
return false;
|
|
4658
5054
|
});
|
|
@@ -4660,30 +5056,30 @@ function findInstalledPlugin(plugins) {
|
|
|
4660
5056
|
function validatePluginTree(pluginRoot) {
|
|
4661
5057
|
const errors2 = [];
|
|
4662
5058
|
for (const marker of [OMP_PLUGIN_MARKER, CLAUDE_PLUGIN_MARKER]) {
|
|
4663
|
-
const markerPath =
|
|
4664
|
-
if (!
|
|
5059
|
+
const markerPath = path7.join(pluginRoot, marker);
|
|
5060
|
+
if (!fs6.existsSync(markerPath)) {
|
|
4665
5061
|
errors2.push(`Missing omp plugin marker: ${markerPath}`);
|
|
4666
5062
|
}
|
|
4667
5063
|
}
|
|
4668
5064
|
for (const skill of SKILL_SMOKE) {
|
|
4669
|
-
const skillPath =
|
|
4670
|
-
if (!
|
|
5065
|
+
const skillPath = path7.join(pluginRoot, "skills", skill, "SKILL.md");
|
|
5066
|
+
if (!fs6.existsSync(skillPath))
|
|
4671
5067
|
errors2.push(`Missing skill: ${skillPath}`);
|
|
4672
5068
|
}
|
|
4673
5069
|
for (const command of COMMAND_SMOKE) {
|
|
4674
|
-
const commandPath =
|
|
4675
|
-
if (!
|
|
5070
|
+
const commandPath = path7.join(pluginRoot, "commands", `${command}.md`);
|
|
5071
|
+
if (!fs6.existsSync(commandPath))
|
|
4676
5072
|
errors2.push(`Missing command: ${commandPath}`);
|
|
4677
5073
|
}
|
|
4678
|
-
const hostRef =
|
|
4679
|
-
if (!
|
|
5074
|
+
const hostRef = path7.join(pluginRoot, "skills", "mstar-host", "references", "omp.md");
|
|
5075
|
+
if (!fs6.existsSync(hostRef))
|
|
4680
5076
|
errors2.push(`Missing omp host reference: ${hostRef}`);
|
|
4681
5077
|
return errors2;
|
|
4682
5078
|
}
|
|
4683
5079
|
function runInit2(scope, dryRun) {
|
|
4684
5080
|
const notes = ensureLocalHarnessRepo(dryRun);
|
|
4685
5081
|
const projectRoot = resolveProjectRoot();
|
|
4686
|
-
if (
|
|
5082
|
+
if (fs6.existsSync(path7.join(HARNESS_REPO_PATH, ".git"))) {
|
|
4687
5083
|
if (dryRun) {
|
|
4688
5084
|
notes.push(`Would update local harness repo: git -C ${HARNESS_REPO_PATH} pull --ff-only`);
|
|
4689
5085
|
} else {
|
|
@@ -4757,8 +5153,8 @@ function runDoctor2(scope) {
|
|
|
4757
5153
|
}
|
|
4758
5154
|
if (scope === "project") {
|
|
4759
5155
|
const projectRoot = resolveProjectRoot();
|
|
4760
|
-
const gitignorePath =
|
|
4761
|
-
const gitignore =
|
|
5156
|
+
const gitignorePath = path7.join(projectRoot, ".gitignore");
|
|
5157
|
+
const gitignore = fs6.existsSync(gitignorePath) ? fs6.readFileSync(gitignorePath, "utf8") : "";
|
|
4762
5158
|
for (const entry of missingHarnessProcessGitignoreEntries(gitignore)) {
|
|
4763
5159
|
errors2.push(`Missing .gitignore entry: ${entry}`);
|
|
4764
5160
|
}
|
|
@@ -4774,7 +5170,7 @@ var ompAdapter = {
|
|
|
4774
5170
|
|
|
4775
5171
|
// src/adapters/opencode.ts
|
|
4776
5172
|
import os4 from "node:os";
|
|
4777
|
-
import
|
|
5173
|
+
import path8 from "node:path";
|
|
4778
5174
|
var OPENCODE_CONFIG_SCHEMA = "https://opencode.ai/config.json";
|
|
4779
5175
|
var MSTAR_OPENCODE_PLUGIN = "@mstar-harness/opencode@latest";
|
|
4780
5176
|
function isLegacyMorningStarGitPlugin(plugin) {
|
|
@@ -4795,11 +5191,11 @@ function isAnyMstarHarnessOpencodeSlot(plugin) {
|
|
|
4795
5191
|
function resolveOpencodeConfigPath(scope, outputPath) {
|
|
4796
5192
|
if (outputPath && outputPath.trim()) {
|
|
4797
5193
|
const raw = outputPath.trim();
|
|
4798
|
-
return
|
|
5194
|
+
return path8.isAbsolute(raw) ? raw : path8.join(resolveProjectRoot(), raw);
|
|
4799
5195
|
}
|
|
4800
5196
|
if (scope === "global")
|
|
4801
|
-
return
|
|
4802
|
-
return
|
|
5197
|
+
return path8.join(os4.homedir(), ".config", "opencode", "opencode.json");
|
|
5198
|
+
return path8.join(resolveProjectRoot(), "opencode.json");
|
|
4803
5199
|
}
|
|
4804
5200
|
function ensureConfigSchema(config) {
|
|
4805
5201
|
const next = ensureObject(config);
|
|
@@ -4892,9 +5288,9 @@ var opencodeAdapter = {
|
|
|
4892
5288
|
};
|
|
4893
5289
|
|
|
4894
5290
|
// src/adapters/zcode.ts
|
|
4895
|
-
import
|
|
5291
|
+
import fs7 from "node:fs";
|
|
4896
5292
|
import os5 from "node:os";
|
|
4897
|
-
import
|
|
5293
|
+
import path9 from "node:path";
|
|
4898
5294
|
var MARKETPLACE_ID = "mstar-local";
|
|
4899
5295
|
var MARKETPLACE_NAME2 = "mstar-local";
|
|
4900
5296
|
var MARKETPLACE_DESCRIPTION = "Morning Star harness marketplace (GitHub source).";
|
|
@@ -4905,10 +5301,10 @@ var GITHUB_REF = "main";
|
|
|
4905
5301
|
var ZCODE_PLUGIN_MARKER = ".zcode-plugin/plugin.json";
|
|
4906
5302
|
var ZCODE_PLUGIN_CHECKOUT_PROJECT = ".zcode/plugin-checkout";
|
|
4907
5303
|
var ZCODE_AGENT_SMOKE_NAMES = ["fullstack-dev", "qc-specialist"];
|
|
4908
|
-
var ZCODE_PLUGINS_ROOT =
|
|
4909
|
-
var KNOWN_MARKETPLACES_PATH =
|
|
4910
|
-
var MARKETPLACE_DIR =
|
|
4911
|
-
var MARKETPLACE_JSON_PATH =
|
|
5304
|
+
var ZCODE_PLUGINS_ROOT = path9.join(os5.homedir(), ".zcode", "cli", "plugins");
|
|
5305
|
+
var KNOWN_MARKETPLACES_PATH = path9.join(ZCODE_PLUGINS_ROOT, "known_marketplaces.json");
|
|
5306
|
+
var MARKETPLACE_DIR = path9.join(ZCODE_PLUGINS_ROOT, "marketplaces", MARKETPLACE_ID);
|
|
5307
|
+
var MARKETPLACE_JSON_PATH = path9.join(MARKETPLACE_DIR, "marketplace.json");
|
|
4912
5308
|
var GITHUB_SOURCE = { source: "github", repo: GITHUB_REPO, ref: GITHUB_REF };
|
|
4913
5309
|
function nowIso() {
|
|
4914
5310
|
return new Date().toISOString();
|
|
@@ -4964,7 +5360,7 @@ function findMarketplacePlugin(raw) {
|
|
|
4964
5360
|
}
|
|
4965
5361
|
function validateMarketplaceJson() {
|
|
4966
5362
|
const errors2 = [];
|
|
4967
|
-
if (!
|
|
5363
|
+
if (!fs7.existsSync(MARKETPLACE_JSON_PATH)) {
|
|
4968
5364
|
errors2.push(`Missing ZCode marketplace: ${MARKETPLACE_JSON_PATH}`);
|
|
4969
5365
|
return errors2;
|
|
4970
5366
|
}
|
|
@@ -4993,7 +5389,7 @@ function validateMarketplaceJson() {
|
|
|
4993
5389
|
}
|
|
4994
5390
|
function validateKnownMarketplaces() {
|
|
4995
5391
|
const errors2 = [];
|
|
4996
|
-
if (!
|
|
5392
|
+
if (!fs7.existsSync(KNOWN_MARKETPLACES_PATH)) {
|
|
4997
5393
|
errors2.push(`Missing ZCode known_marketplaces.json: ${KNOWN_MARKETPLACES_PATH}`);
|
|
4998
5394
|
return errors2;
|
|
4999
5395
|
}
|
|
@@ -5016,14 +5412,14 @@ function validateKnownMarketplaces() {
|
|
|
5016
5412
|
}
|
|
5017
5413
|
function validatePluginAgents2(pluginRoot) {
|
|
5018
5414
|
const errors2 = [];
|
|
5019
|
-
const agentsDir =
|
|
5020
|
-
if (!
|
|
5415
|
+
const agentsDir = path9.join(pluginRoot, "agents");
|
|
5416
|
+
if (!fs7.existsSync(agentsDir)) {
|
|
5021
5417
|
errors2.push(`Missing plugin agents directory: ${agentsDir}`);
|
|
5022
5418
|
return errors2;
|
|
5023
5419
|
}
|
|
5024
5420
|
for (const agentName of ZCODE_AGENT_SMOKE_NAMES) {
|
|
5025
|
-
const agentPath =
|
|
5026
|
-
if (!
|
|
5421
|
+
const agentPath = path9.join(agentsDir, `${agentName}.md`);
|
|
5422
|
+
if (!fs7.existsSync(agentPath)) {
|
|
5027
5423
|
errors2.push(`Missing plugin agent file: ${agentPath}`);
|
|
5028
5424
|
}
|
|
5029
5425
|
}
|
|
@@ -5040,15 +5436,15 @@ function runInit3(scope, dryRun) {
|
|
|
5040
5436
|
const notes = ensureLocalHarnessRepo(dryRun);
|
|
5041
5437
|
const projectRoot = resolveProjectRoot();
|
|
5042
5438
|
if (scope === "project") {
|
|
5043
|
-
const checkoutPath =
|
|
5439
|
+
const checkoutPath = path9.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
|
|
5044
5440
|
notes.push(...ensureGitCheckout(REPO_URL, checkoutPath, dryRun));
|
|
5045
5441
|
notes.push(...appendGitignore(projectRoot, [ZCODE_PLUGIN_CHECKOUT_PROJECT], dryRun));
|
|
5046
5442
|
notes.push(...appendHarnessProjectGitignore(projectRoot, dryRun));
|
|
5047
5443
|
notes.push(`Materialized local ZCode plugin checkout at ${ZCODE_PLUGIN_CHECKOUT_PROJECT} for smoke checks (the registered marketplace still points at the github repo).`);
|
|
5048
5444
|
}
|
|
5049
5445
|
if (!dryRun) {
|
|
5050
|
-
if (!
|
|
5051
|
-
|
|
5446
|
+
if (!fs7.existsSync(MARKETPLACE_DIR))
|
|
5447
|
+
fs7.mkdirSync(MARKETPLACE_DIR, { recursive: true });
|
|
5052
5448
|
writeJson(MARKETPLACE_JSON_PATH, buildMarketplaceJson());
|
|
5053
5449
|
}
|
|
5054
5450
|
notes.push(`Wrote ZCode marketplace: ${MARKETPLACE_JSON_PATH}`);
|
|
@@ -5068,10 +5464,10 @@ function runDoctor3(scope) {
|
|
|
5068
5464
|
errors2.push(...validateLocalHarnessRepo());
|
|
5069
5465
|
if (scope === "project") {
|
|
5070
5466
|
const projectRoot = resolveProjectRoot();
|
|
5071
|
-
const checkoutPath =
|
|
5467
|
+
const checkoutPath = path9.join(projectRoot, ZCODE_PLUGIN_CHECKOUT_PROJECT);
|
|
5072
5468
|
errors2.push(...validateGitCheckout(checkoutPath, ZCODE_PLUGIN_MARKER));
|
|
5073
|
-
const gitignorePath =
|
|
5074
|
-
const gitignore =
|
|
5469
|
+
const gitignorePath = path9.join(projectRoot, ".gitignore");
|
|
5470
|
+
const gitignore = fs7.existsSync(gitignorePath) ? fs7.readFileSync(gitignorePath, "utf8") : "";
|
|
5075
5471
|
if (!gitignore.split(/\r?\n/).includes(ZCODE_PLUGIN_CHECKOUT_PROJECT)) {
|
|
5076
5472
|
errors2.push(`Missing .gitignore entry: ${ZCODE_PLUGIN_CHECKOUT_PROJECT}`);
|
|
5077
5473
|
}
|
|
@@ -5112,8 +5508,8 @@ function getAdapter(target) {
|
|
|
5112
5508
|
var SUPPORTED_TARGETS = ["opencode", "cursor", "codex", "zcode", "omp"];
|
|
5113
5509
|
|
|
5114
5510
|
// src/utils.ts
|
|
5115
|
-
import
|
|
5116
|
-
import
|
|
5511
|
+
import fs8 from "node:fs";
|
|
5512
|
+
import path10 from "node:path";
|
|
5117
5513
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
5118
5514
|
function parseCsv(raw) {
|
|
5119
5515
|
if (!raw)
|
|
@@ -5121,9 +5517,9 @@ function parseCsv(raw) {
|
|
|
5121
5517
|
return raw.split(",").map((item) => item.trim()).filter(Boolean);
|
|
5122
5518
|
}
|
|
5123
5519
|
function readJson2(filePath) {
|
|
5124
|
-
if (!
|
|
5520
|
+
if (!fs8.existsSync(filePath))
|
|
5125
5521
|
return {};
|
|
5126
|
-
const content =
|
|
5522
|
+
const content = fs8.readFileSync(filePath, "utf8").trim();
|
|
5127
5523
|
if (!content)
|
|
5128
5524
|
return {};
|
|
5129
5525
|
try {
|
|
@@ -5133,16 +5529,22 @@ function readJson2(filePath) {
|
|
|
5133
5529
|
}
|
|
5134
5530
|
}
|
|
5135
5531
|
function writeJson2(filePath, value) {
|
|
5136
|
-
const parent =
|
|
5137
|
-
if (!
|
|
5138
|
-
|
|
5139
|
-
|
|
5532
|
+
const parent = path10.dirname(filePath);
|
|
5533
|
+
if (!fs8.existsSync(parent))
|
|
5534
|
+
fs8.mkdirSync(parent, { recursive: true });
|
|
5535
|
+
fs8.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}
|
|
5140
5536
|
`, "utf8");
|
|
5141
5537
|
}
|
|
5538
|
+
function resolveProjectRoot2() {
|
|
5539
|
+
const candidate = process.env.MSTAR_CLI_PROJECT_ROOT || process.env.INIT_CWD || process.env.PWD;
|
|
5540
|
+
if (candidate && candidate.trim())
|
|
5541
|
+
return path10.resolve(candidate);
|
|
5542
|
+
return process.cwd();
|
|
5543
|
+
}
|
|
5142
5544
|
function readHarnessVersion2() {
|
|
5143
|
-
const packageJsonPath =
|
|
5545
|
+
const packageJsonPath = path10.resolve(path10.dirname(fileURLToPath2(import.meta.url)), "../package.json");
|
|
5144
5546
|
try {
|
|
5145
|
-
const parsed = JSON.parse(
|
|
5547
|
+
const parsed = JSON.parse(fs8.readFileSync(packageJsonPath, "utf8"));
|
|
5146
5548
|
return parsed.version || "0.0.0";
|
|
5147
5549
|
} catch {
|
|
5148
5550
|
return "0.0.0";
|
|
@@ -5289,6 +5691,33 @@ function runDoctor4(options) {
|
|
|
5289
5691
|
console.log(` - ${issue}`);
|
|
5290
5692
|
process.exitCode = 1;
|
|
5291
5693
|
}
|
|
5694
|
+
function resolvePluginRoot(options) {
|
|
5695
|
+
if (options.root)
|
|
5696
|
+
return path11.resolve(options.root);
|
|
5697
|
+
let candidate = resolveProjectRoot2();
|
|
5698
|
+
while (!fs9.existsSync(path11.join(candidate, "plugin.json"))) {
|
|
5699
|
+
const parent = path11.dirname(candidate);
|
|
5700
|
+
if (parent === candidate)
|
|
5701
|
+
break;
|
|
5702
|
+
candidate = parent;
|
|
5703
|
+
}
|
|
5704
|
+
return candidate;
|
|
5705
|
+
}
|
|
5706
|
+
function runPluginValidate(options) {
|
|
5707
|
+
const root = resolvePluginRoot(options);
|
|
5708
|
+
const result = validateAgentPlugin(root);
|
|
5709
|
+
for (const warning of result.warnings) {
|
|
5710
|
+
console.warn(import_picocolors.default.yellow(warning));
|
|
5711
|
+
}
|
|
5712
|
+
if (result.ok) {
|
|
5713
|
+
console.log(import_picocolors.default.green(`OK ${root}: Agent Plugins v1.0.0 conformant`));
|
|
5714
|
+
return;
|
|
5715
|
+
}
|
|
5716
|
+
for (const error of result.errors) {
|
|
5717
|
+
console.error(import_picocolors.default.red(error));
|
|
5718
|
+
}
|
|
5719
|
+
process.exitCode = 1;
|
|
5720
|
+
}
|
|
5292
5721
|
program2.name("mstar-harness").description("Morning Star harness CLI for target-based agent bootstrap").version(packageVersion);
|
|
5293
5722
|
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>", "Optional: model for project-manager (advanced override)").option("--strategic-models <a,b,c>", "Optional: models for architect/product-manager/prompt-engineer").option("--dev-models <a,b,c>", "Optional: models for fullstack-dev/fullstack-dev-2/frontend-dev").option("--qc-models <a,b,c>", "Optional: models for qc trio").option("--other-models <a,b,c>", "Optional: models for remaining roles").action(async (options) => {
|
|
5294
5723
|
await runInit4(options);
|
|
@@ -5296,6 +5725,10 @@ program2.command("init").description("Interactive/non-interactive setup for targ
|
|
|
5296
5725
|
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) => {
|
|
5297
5726
|
runDoctor4(options);
|
|
5298
5727
|
});
|
|
5728
|
+
var pluginCommand = program2.command("plugin").description("Agent Plugins v1.0.0 portable package commands");
|
|
5729
|
+
pluginCommand.command("validate").description("Validate a plugin package against Agent Plugins v1.0.0").option("--root <path>", "Plugin root directory to validate (default: project root)").action((options) => {
|
|
5730
|
+
runPluginValidate(options);
|
|
5731
|
+
});
|
|
5299
5732
|
program2.parseAsync(process.argv).catch((error) => {
|
|
5300
5733
|
console.error(import_picocolors.default.red(`Setup failed: ${error.message}`));
|
|
5301
5734
|
process.exit(1);
|