opencode-rules-md 0.8.5 → 0.9.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/cli.mjs +252 -261
- package/dist/src/cli/config.d.ts +11 -0
- package/dist/src/cli/config.d.ts.map +1 -1
- package/dist/src/cli/config.js +15 -0
- package/dist/src/cli/config.js.map +1 -1
- package/dist/src/cli/install.d.ts +35 -20
- package/dist/src/cli/install.d.ts.map +1 -1
- package/dist/src/cli/install.js +50 -131
- package/dist/src/cli/install.js.map +1 -1
- package/dist/src/cli/main.d.ts +5 -0
- package/dist/src/cli/main.d.ts.map +1 -1
- package/dist/src/cli/main.js +29 -26
- package/dist/src/cli/main.js.map +1 -1
- package/dist/src/cli/spawn.d.ts +29 -0
- package/dist/src/cli/spawn.d.ts.map +1 -0
- package/dist/src/cli/spawn.js +48 -0
- package/dist/src/cli/spawn.js.map +1 -0
- package/dist/src/cli/status.d.ts +15 -8
- package/dist/src/cli/status.d.ts.map +1 -1
- package/dist/src/cli/status.js +63 -31
- package/dist/src/cli/status.js.map +1 -1
- package/dist/src/cli/uninstall.d.ts +5 -2
- package/dist/src/cli/uninstall.d.ts.map +1 -1
- package/dist/src/cli/uninstall.js +78 -51
- package/dist/src/cli/uninstall.js.map +1 -1
- package/dist/src/cli/update.d.ts +50 -16
- package/dist/src/cli/update.d.ts.map +1 -1
- package/dist/src/cli/update.js +156 -124
- package/dist/src/cli/update.js.map +1 -1
- package/dist/src/rule-discovery.d.ts.map +1 -1
- package/dist/src/rule-discovery.js +18 -10
- package/dist/src/rule-discovery.js.map +1 -1
- package/dist/src/rule-filter.d.ts +9 -0
- package/dist/src/rule-filter.d.ts.map +1 -1
- package/dist/src/rule-filter.js +48 -11
- package/dist/src/rule-filter.js.map +1 -1
- package/dist/src/rule-metadata.d.ts +2 -0
- package/dist/src/rule-metadata.d.ts.map +1 -1
- package/dist/src/rule-metadata.js +4 -0
- package/dist/src/rule-metadata.js.map +1 -1
- package/dist/src/runtime-context.d.ts +6 -0
- package/dist/src/runtime-context.d.ts.map +1 -1
- package/dist/src/runtime-context.js +16 -0
- package/dist/src/runtime-context.js.map +1 -1
- package/dist/src/session-store.d.ts.map +1 -1
- package/dist/src/session-store.js +1 -0
- package/dist/src/session-store.js.map +1 -1
- package/dist/tui/index.js +13 -9
- package/dist/tui/index.js.map +4 -4
- package/package.json +1 -1
- package/src/cli/config.ts +19 -0
- package/src/cli/install.ts +66 -174
- package/src/cli/main.ts +34 -25
- package/src/cli/spawn.ts +76 -0
- package/src/cli/status.ts +83 -37
- package/src/cli/uninstall.ts +88 -57
- package/src/cli/update.ts +182 -148
- package/src/rule-discovery.ts +22 -10
- package/src/rule-filter.ts +73 -12
- package/src/rule-metadata.ts +8 -0
- package/src/runtime-context.ts +16 -0
- package/src/session-store.ts +1 -0
package/dist/cli.mjs
CHANGED
|
@@ -5,8 +5,27 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
|
5
5
|
// src/cli/main.ts
|
|
6
6
|
import { parseArgs } from "node:util";
|
|
7
7
|
|
|
8
|
-
// src/cli/
|
|
9
|
-
import {
|
|
8
|
+
// src/cli/spawn.ts
|
|
9
|
+
import { createRequire as createRequire2 } from "node:module";
|
|
10
|
+
var require2 = createRequire2(import.meta.url);
|
|
11
|
+
async function spawnOpencodePlugin(args, opts = {}) {
|
|
12
|
+
const env = opts.env ?? process.env;
|
|
13
|
+
const stdio = opts.stdio ?? "inherit";
|
|
14
|
+
const spawnFn = opts.spawn ?? defaultSpawn;
|
|
15
|
+
return spawnFn("opencode", ["plugin", ...args], { env, stdio });
|
|
16
|
+
}
|
|
17
|
+
function defaultSpawn(command, args, options) {
|
|
18
|
+
const cp = require2("node:child_process");
|
|
19
|
+
const result = cp.spawnSync(command, args, {
|
|
20
|
+
env: options.env,
|
|
21
|
+
stdio: options.stdio
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
status: result.status,
|
|
25
|
+
stdout: result.stdout?.toString() ?? "",
|
|
26
|
+
stderr: result.stderr?.toString() ?? ""
|
|
27
|
+
};
|
|
28
|
+
}
|
|
10
29
|
|
|
11
30
|
// src/cli/config.ts
|
|
12
31
|
import { join, dirname } from "path";
|
|
@@ -147,6 +166,13 @@ function normalizePlugin(raw) {
|
|
|
147
166
|
}
|
|
148
167
|
return [];
|
|
149
168
|
}
|
|
169
|
+
function readInstalledPlugins(config) {
|
|
170
|
+
const modern = config.data["plugin"];
|
|
171
|
+
if (modern !== undefined) {
|
|
172
|
+
return normalizePlugin(modern);
|
|
173
|
+
}
|
|
174
|
+
return normalizePlugin(config.data["plugins"]);
|
|
175
|
+
}
|
|
150
176
|
function escapeRegex(s) {
|
|
151
177
|
return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
152
178
|
}
|
|
@@ -246,6 +272,37 @@ function writeAtomically(fs, path, content) {
|
|
|
246
272
|
}
|
|
247
273
|
}
|
|
248
274
|
|
|
275
|
+
// src/cli/install.ts
|
|
276
|
+
var DEFAULT_SPECIFIER = PLUGIN_NAME;
|
|
277
|
+
function buildSpecifier(version) {
|
|
278
|
+
const trimmed = version?.trim() ?? "";
|
|
279
|
+
if (!trimmed || trimmed === "latest") {
|
|
280
|
+
return DEFAULT_SPECIFIER;
|
|
281
|
+
}
|
|
282
|
+
return `${PLUGIN_NAME}@${trimmed}`;
|
|
283
|
+
}
|
|
284
|
+
var runInstall = async (opts = {}, _fs, env) => {
|
|
285
|
+
const specifier = buildSpecifier(opts.version);
|
|
286
|
+
const spawnFn = opts.spawn ?? spawnOpencodePlugin;
|
|
287
|
+
const targetEnv = env ?? process.env;
|
|
288
|
+
if (opts.dryRun) {
|
|
289
|
+
console.log(`omd: would run: opencode plugin ${specifier} --global`);
|
|
290
|
+
return { status: "skipped", specifier };
|
|
291
|
+
}
|
|
292
|
+
const result = await spawnFn([specifier, "--global"], { env: targetEnv, stdio: "inherit" });
|
|
293
|
+
if ((result.status ?? 0) !== 0) {
|
|
294
|
+
throw new Error(`opencode plugin ${specifier} --global exited with status ${String(result.status)}`);
|
|
295
|
+
}
|
|
296
|
+
return { status: "wrote", specifier };
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
// src/cli/uninstall.ts
|
|
300
|
+
import { join as join3 } from "path";
|
|
301
|
+
|
|
302
|
+
// src/cli/update.ts
|
|
303
|
+
import { homedir as homedir2 } from "os";
|
|
304
|
+
import { join as join2 } from "path";
|
|
305
|
+
|
|
249
306
|
// src/cli/registry.ts
|
|
250
307
|
var REGISTRY_URL = "https://registry.npmjs.org/opencode-rules-md/latest";
|
|
251
308
|
var fetchLatestVersion = async () => {
|
|
@@ -266,87 +323,45 @@ var isStale = (installed, latest) => {
|
|
|
266
323
|
};
|
|
267
324
|
|
|
268
325
|
// src/cli/update.ts
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
var
|
|
272
|
-
function
|
|
273
|
-
return
|
|
326
|
+
var CONFIG_BASENAMES = ["opencode", "tui"];
|
|
327
|
+
var PACKAGES_DIR_BASENAME = [".cache", "opencode", "packages"];
|
|
328
|
+
var CACHE_DIR_BASENAME = "opencode-rules-md";
|
|
329
|
+
function resolveHome(env = process.env) {
|
|
330
|
+
return env.HOME ?? homedir2();
|
|
274
331
|
}
|
|
275
|
-
function
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
332
|
+
function resolvePackagesDir(env = process.env) {
|
|
333
|
+
return join2(resolveHome(env), ...PACKAGES_DIR_BASENAME);
|
|
334
|
+
}
|
|
335
|
+
function resolveCachePaths(env = process.env, fs) {
|
|
336
|
+
const packagesDir = resolvePackagesDir(env);
|
|
337
|
+
if (fs && fs.existsSync(packagesDir)) {
|
|
338
|
+
try {
|
|
339
|
+
const entries = fs.readdirSync(packagesDir);
|
|
340
|
+
return entries.filter((name) => name === CACHE_DIR_BASENAME || name.startsWith(`${CACHE_DIR_BASENAME}@`)).map((name) => join2(packagesDir, name));
|
|
341
|
+
} catch {
|
|
342
|
+
return [];
|
|
280
343
|
}
|
|
281
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath));
|
|
282
|
-
return typeof pkg.version === "string" ? pkg.version : null;
|
|
283
|
-
} catch {
|
|
284
|
-
return null;
|
|
285
344
|
}
|
|
345
|
+
return [
|
|
346
|
+
join2(packagesDir, CACHE_DIR_BASENAME),
|
|
347
|
+
join2(packagesDir, `${CACHE_DIR_BASENAME}@latest`)
|
|
348
|
+
];
|
|
286
349
|
}
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
const cachePath = resolveCachePath(env);
|
|
291
|
-
if (latest === null) {
|
|
292
|
-
log("omd: could not determine latest version from npm registry");
|
|
293
|
-
return {
|
|
294
|
-
status: "unreachable",
|
|
295
|
-
cachePath,
|
|
296
|
-
instruction: "npx opencode-rules-md@latest install"
|
|
297
|
-
};
|
|
298
|
-
}
|
|
299
|
-
let installedVersion = null;
|
|
300
|
-
for (const basename of CONFIG_BASENAMES) {
|
|
301
|
-
const loaded = loadGlobalConfig(fs, env, basename);
|
|
302
|
-
const plugins = normalizePlugin(loaded.data["plugins"]);
|
|
350
|
+
function findInstalledSpecifier(loaded) {
|
|
351
|
+
for (const cfg of loaded) {
|
|
352
|
+
const plugins = readInstalledPlugins(cfg);
|
|
303
353
|
const match = plugins.find((p) => matchesPlugin(p));
|
|
304
|
-
if (match)
|
|
305
|
-
|
|
306
|
-
installedVersion = atIndex >= 0 ? match.slice(atIndex + 1) : null;
|
|
307
|
-
break;
|
|
308
|
-
}
|
|
354
|
+
if (match)
|
|
355
|
+
return match;
|
|
309
356
|
}
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
const cacheVersion = readCacheVersion(fs, cachePath);
|
|
320
|
-
const shouldPurge = cacheVersion === null || cacheVersion !== latest;
|
|
321
|
-
if (opts.dryRun) {
|
|
322
|
-
log(`omd: update check (dry-run)`);
|
|
323
|
-
log(` latest version: ${latest}`);
|
|
324
|
-
log(` installed version: ${installedVersion ?? "not installed"}`);
|
|
325
|
-
log(` would purge: ${shouldPurge ? cachePath : "(nothing to purge)"}`);
|
|
326
|
-
log(` would instruct: ${instruction}`);
|
|
327
|
-
return {
|
|
328
|
-
status: "stale",
|
|
329
|
-
cachePath,
|
|
330
|
-
instruction
|
|
331
|
-
};
|
|
332
|
-
}
|
|
333
|
-
if (shouldPurge) {
|
|
334
|
-
try {
|
|
335
|
-
if (fs.existsSync(cachePath)) {
|
|
336
|
-
purgeDirectory(fs, cachePath);
|
|
337
|
-
}
|
|
338
|
-
} catch {}
|
|
339
|
-
}
|
|
340
|
-
log(`omd: opencode-rules-md is stale (latest: ${latest})`);
|
|
341
|
-
log(`omd: cache purged at ${cachePath}`);
|
|
342
|
-
log(`omd: to reinstall, run:`);
|
|
343
|
-
log(` ${instruction}`);
|
|
344
|
-
return {
|
|
345
|
-
status: "stale",
|
|
346
|
-
cachePath,
|
|
347
|
-
instruction
|
|
348
|
-
};
|
|
349
|
-
};
|
|
357
|
+
return null;
|
|
358
|
+
}
|
|
359
|
+
function extractVersion(specifier) {
|
|
360
|
+
const at = specifier.lastIndexOf("@");
|
|
361
|
+
if (at < 0 || at === specifier.length - 1)
|
|
362
|
+
return null;
|
|
363
|
+
return specifier.slice(at + 1);
|
|
364
|
+
}
|
|
350
365
|
function purgeDirectory(fs, dirPath) {
|
|
351
366
|
let entries = [];
|
|
352
367
|
try {
|
|
@@ -357,18 +372,18 @@ function purgeDirectory(fs, dirPath) {
|
|
|
357
372
|
for (const entry of entries) {
|
|
358
373
|
const entryPath = join2(dirPath, entry);
|
|
359
374
|
try {
|
|
360
|
-
if (fs.existsSync(entryPath))
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
} catch {
|
|
370
|
-
fs.unlinkSync(entryPath);
|
|
375
|
+
if (!fs.existsSync(entryPath))
|
|
376
|
+
continue;
|
|
377
|
+
try {
|
|
378
|
+
const subEntries = fs.readdirSync(entryPath);
|
|
379
|
+
if (subEntries.length === 0) {
|
|
380
|
+
fs.rmdirSync(entryPath);
|
|
381
|
+
} else {
|
|
382
|
+
purgeDirectory(fs, entryPath);
|
|
383
|
+
fs.rmdirSync(entryPath);
|
|
371
384
|
}
|
|
385
|
+
} catch {
|
|
386
|
+
fs.unlinkSync(entryPath);
|
|
372
387
|
}
|
|
373
388
|
} catch {}
|
|
374
389
|
}
|
|
@@ -376,158 +391,119 @@ function purgeDirectory(fs, dirPath) {
|
|
|
376
391
|
fs.rmdirSync(dirPath);
|
|
377
392
|
} catch {}
|
|
378
393
|
}
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
const
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
return
|
|
387
|
-
}
|
|
388
|
-
if (opts.latestVersion !== undefined) {
|
|
389
|
-
return opts.latestVersion;
|
|
390
|
-
}
|
|
391
|
-
const latest = await fetchLatestVersion();
|
|
392
|
-
return latest ?? "latest";
|
|
393
|
-
}
|
|
394
|
-
function readCacheVersion2(fs, cachePath) {
|
|
395
|
-
const pkgPath = join3(cachePath, "package.json");
|
|
396
|
-
try {
|
|
397
|
-
if (!fs.existsSync(pkgPath)) {
|
|
398
|
-
return null;
|
|
399
|
-
}
|
|
400
|
-
const pkg = JSON.parse(fs.readFileSync(pkgPath));
|
|
401
|
-
return typeof pkg.version === "string" ? pkg.version : null;
|
|
402
|
-
} catch {
|
|
403
|
-
return null;
|
|
404
|
-
}
|
|
405
|
-
}
|
|
406
|
-
function purgeCacheIfStale(fs, env, resolvedVersion) {
|
|
407
|
-
if (resolvedVersion === "latest") {
|
|
408
|
-
return false;
|
|
394
|
+
var runUpdate = async (fs, env, log, _error, opts = {}) => {
|
|
395
|
+
const latest = opts.latestVersion !== undefined ? opts.latestVersion : await fetchLatestVersion();
|
|
396
|
+
const cachePaths = resolveCachePaths(env, fs);
|
|
397
|
+
const instruction = "opencode plugin opencode-rules-md --global --force";
|
|
398
|
+
const spawnFn = opts.spawn ?? spawnOpencodePlugin;
|
|
399
|
+
if (latest === null) {
|
|
400
|
+
log("omd: could not determine latest version from npm registry");
|
|
401
|
+
return { status: "unreachable", cachePaths, instruction };
|
|
409
402
|
}
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
403
|
+
const configs = CONFIG_BASENAMES.map((basename) => loadGlobalConfig(fs, env, basename));
|
|
404
|
+
const installedSpecifier = findInstalledSpecifier(configs);
|
|
405
|
+
const installedVersion = installedSpecifier ? extractVersion(installedSpecifier) : null;
|
|
406
|
+
if (installedVersion !== null && !isStale(installedVersion, latest)) {
|
|
407
|
+
log(`omd: opencode-rules-md@${installedVersion} is already the latest`);
|
|
408
|
+
return { status: "current", cachePaths, instruction: "" };
|
|
413
409
|
}
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
410
|
+
if (opts.dryRun) {
|
|
411
|
+
log("omd: update check (dry-run)");
|
|
412
|
+
log(` latest version: ${latest}`);
|
|
413
|
+
log(` installed version: ${installedVersion ?? "not installed"}`);
|
|
414
|
+
log(` would purge: ${cachePaths.join(", ") || "(none found)"}`);
|
|
415
|
+
log(` would instruct: ${instruction}`);
|
|
416
|
+
return { status: "stale", cachePaths, instruction };
|
|
418
417
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
let anyProcessed = false;
|
|
426
|
-
for (const basename of CONFIG_BASENAMES2) {
|
|
427
|
-
const loaded = loadGlobalConfig(fs, env, basename);
|
|
428
|
-
const plugins = normalizePlugin(loaded.data["plugins"]);
|
|
429
|
-
const existingEntry = plugins.find((p) => matchesPlugin(p));
|
|
430
|
-
if (existingEntry === freshEntry) {
|
|
431
|
-
results.push({ path: loaded.path, status: "skipped", backup: null });
|
|
432
|
-
continue;
|
|
433
|
-
}
|
|
434
|
-
anyProcessed = true;
|
|
435
|
-
const withoutStale = plugins.filter((p) => !matchesPlugin(p));
|
|
436
|
-
const newPlugins = [...withoutStale, freshEntry];
|
|
437
|
-
const newData = { ...loaded.data, plugins: newPlugins };
|
|
438
|
-
const newContent = JSON.stringify(newData, null, 2) + `
|
|
439
|
-
`;
|
|
440
|
-
if (opts.dryRun) {
|
|
441
|
-
results.push({ path: loaded.path, status: "wrote", backup: null });
|
|
442
|
-
continue;
|
|
443
|
-
}
|
|
444
|
-
let backup;
|
|
445
|
-
if (loaded.exists) {
|
|
446
|
-
backup = backupIfWritable(fs, loaded.path);
|
|
447
|
-
if (backup !== undefined) {
|
|
448
|
-
const dir = dirname2(loaded.path);
|
|
449
|
-
const segs = loaded.path.replace(/\\/g, "/").split("/");
|
|
450
|
-
const base = segs[segs.length - 1] ?? loaded.path;
|
|
451
|
-
const dot = base.lastIndexOf(".");
|
|
452
|
-
const name = dot >= 0 ? base.slice(0, dot) : base;
|
|
453
|
-
rotateBackups(fs, dir, name, 3);
|
|
418
|
+
log(installedVersion === null ? `omd: opencode-rules-md is not installed; registering latest (${latest})` : `omd: opencode-rules-md is stale (installed ${installedVersion}, latest ${latest})`);
|
|
419
|
+
for (const cachePath of cachePaths) {
|
|
420
|
+
try {
|
|
421
|
+
if (fs.existsSync(cachePath)) {
|
|
422
|
+
purgeDirectory(fs, cachePath);
|
|
423
|
+
log(`omd: purged cache ${cachePath}`);
|
|
454
424
|
}
|
|
455
|
-
}
|
|
456
|
-
writeAtomically(fs, loaded.path, newContent);
|
|
457
|
-
results.push({
|
|
458
|
-
path: loaded.path,
|
|
459
|
-
status: "wrote",
|
|
460
|
-
backup: backup ?? null
|
|
461
|
-
});
|
|
425
|
+
} catch {}
|
|
462
426
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
427
|
+
const result = await spawnFn(["opencode-rules-md", "--global", "--force"], {
|
|
428
|
+
env: process.env,
|
|
429
|
+
stdio: "inherit"
|
|
430
|
+
});
|
|
431
|
+
if ((result.status ?? 0) !== 0) {
|
|
432
|
+
throw new Error(`opencode plugin opencode-rules-md --global --force exited with status ${String(result.status)}`);
|
|
466
433
|
}
|
|
467
|
-
return {
|
|
468
|
-
status: anyProcessed ? "wrote" : "skipped",
|
|
469
|
-
results,
|
|
470
|
-
purged
|
|
471
|
-
};
|
|
434
|
+
return { status: "stale", cachePaths, instruction: "" };
|
|
472
435
|
};
|
|
473
436
|
|
|
474
437
|
// src/cli/uninstall.ts
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
438
|
+
var CONFIG_BASENAMES2 = ["opencode", "tui"];
|
|
439
|
+
function stripFromData(data) {
|
|
440
|
+
let removed = 0;
|
|
441
|
+
const next = { ...data };
|
|
442
|
+
const currentRaw = next["plugin"] ?? next["plugins"];
|
|
443
|
+
if (currentRaw !== undefined) {
|
|
444
|
+
if (typeof currentRaw === "string") {
|
|
445
|
+
if (currentRaw.startsWith("opencode-rules-md")) {
|
|
446
|
+
delete next["plugin"];
|
|
447
|
+
delete next["plugins"];
|
|
448
|
+
removed += 1;
|
|
449
|
+
}
|
|
450
|
+
} else if (Array.isArray(currentRaw)) {
|
|
451
|
+
const filtered = currentRaw.filter((p) => !(typeof p === "string" && p.startsWith("opencode-rules-md")));
|
|
452
|
+
if (filtered.length !== currentRaw.length) {
|
|
453
|
+
removed += currentRaw.length - filtered.length;
|
|
454
|
+
if (filtered.length === 0) {
|
|
455
|
+
delete next["plugin"];
|
|
456
|
+
delete next["plugins"];
|
|
457
|
+
} else {
|
|
458
|
+
next["plugin"] = filtered;
|
|
459
|
+
delete next["plugins"];
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return { next, removed };
|
|
465
|
+
}
|
|
478
466
|
var runUninstall = (opts = {}, fs, env) => {
|
|
479
467
|
const results = [];
|
|
480
468
|
let anyProcessed = false;
|
|
481
469
|
let purged = false;
|
|
482
470
|
if (opts.purge) {
|
|
483
|
-
const
|
|
484
|
-
|
|
471
|
+
const cachePaths = resolveCachePaths(env, fs);
|
|
472
|
+
for (const cachePath of cachePaths) {
|
|
485
473
|
try {
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
} else {
|
|
490
|
-
for (const entry of entries) {
|
|
491
|
-
const entryPath = join4(cachePath, entry);
|
|
492
|
-
if (fs.existsSync(entryPath)) {
|
|
493
|
-
try {
|
|
494
|
-
fs.unlinkSync(entryPath);
|
|
495
|
-
} catch {}
|
|
496
|
-
}
|
|
497
|
-
}
|
|
498
|
-
fs.rmdirSync(cachePath);
|
|
474
|
+
if (fs.existsSync(cachePath)) {
|
|
475
|
+
purgeDirectory(fs, cachePath);
|
|
476
|
+
purged = true;
|
|
499
477
|
}
|
|
500
|
-
purged = true;
|
|
501
478
|
} catch {}
|
|
502
479
|
}
|
|
503
480
|
}
|
|
504
|
-
for (const basename of
|
|
481
|
+
for (const basename of CONFIG_BASENAMES2) {
|
|
505
482
|
const loaded = loadGlobalConfig(fs, env, basename);
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
483
|
+
if (!loaded.exists) {
|
|
484
|
+
results.push({ path: loaded.path, status: "skipped", backup: null });
|
|
485
|
+
continue;
|
|
486
|
+
}
|
|
487
|
+
const { next, removed } = stripFromData(loaded.data);
|
|
488
|
+
if (removed === 0) {
|
|
509
489
|
results.push({ path: loaded.path, status: "skipped", backup: null });
|
|
510
490
|
continue;
|
|
511
491
|
}
|
|
512
492
|
anyProcessed = true;
|
|
513
|
-
const
|
|
514
|
-
const newContent = JSON.stringify(newData, null, 2) + `
|
|
493
|
+
const newContent = JSON.stringify(next, null, 2) + `
|
|
515
494
|
`;
|
|
516
495
|
if (opts.dryRun) {
|
|
517
496
|
results.push({ path: loaded.path, status: "wrote", backup: null });
|
|
518
497
|
continue;
|
|
519
498
|
}
|
|
520
|
-
|
|
521
|
-
if (
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
const name = dot >= 0 ? base.slice(0, dot) : base;
|
|
529
|
-
rotateBackups(fs, dir, name, 3);
|
|
530
|
-
}
|
|
499
|
+
const backup = backupIfWritable(fs, loaded.path);
|
|
500
|
+
if (backup !== undefined) {
|
|
501
|
+
const segs = loaded.path.replace(/\\/g, "/").split("/");
|
|
502
|
+
const base = segs[segs.length - 1] ?? loaded.path;
|
|
503
|
+
const dot = base.lastIndexOf(".");
|
|
504
|
+
const name = dot >= 0 ? base.slice(0, dot) : base;
|
|
505
|
+
const dir = join3(...segs.slice(0, -1));
|
|
506
|
+
rotateBackups(fs, dir, name, 3);
|
|
531
507
|
}
|
|
532
508
|
writeAtomically(fs, loaded.path, newContent);
|
|
533
509
|
results.push({
|
|
@@ -544,29 +520,28 @@ var runUninstall = (opts = {}, fs, env) => {
|
|
|
544
520
|
};
|
|
545
521
|
|
|
546
522
|
// src/cli/status.ts
|
|
547
|
-
import { join as
|
|
548
|
-
import { homedir as
|
|
549
|
-
import { extname } from "path";
|
|
523
|
+
import { join as join4, dirname as dirname2, extname } from "path";
|
|
524
|
+
import { homedir as homedir3 } from "os";
|
|
550
525
|
var RULE_DIR_NAME = "opencode-rules-md";
|
|
551
526
|
var MIN_NODE_VERSION = 20;
|
|
552
|
-
var
|
|
553
|
-
var runStatus = async (fs, env, log) => {
|
|
527
|
+
var CONFIG_BASENAMES3 = ["opencode", "tui"];
|
|
528
|
+
var runStatus = async (fs, env, log, opts = {}) => {
|
|
554
529
|
const configs = [];
|
|
555
|
-
const latest = await fetchLatestVersion();
|
|
556
|
-
for (const basename of
|
|
530
|
+
const latest = opts.latestVersion !== undefined ? opts.latestVersion : await fetchLatestVersion();
|
|
531
|
+
for (const basename of CONFIG_BASENAMES3) {
|
|
557
532
|
const loaded = loadGlobalConfig(fs, env, basename);
|
|
558
533
|
const format = extname(loaded.path);
|
|
559
|
-
const plugins =
|
|
560
|
-
const match = plugins.find((p) => matchesPlugin(p));
|
|
534
|
+
const plugins = readInstalledPlugins(loaded);
|
|
535
|
+
const match = plugins.find((p) => matchesPlugin(p)) ?? null;
|
|
561
536
|
const entry = {
|
|
562
537
|
basename,
|
|
563
538
|
path: loaded.path,
|
|
564
539
|
format,
|
|
565
|
-
installed: match
|
|
566
|
-
notInstalled: !loaded.exists
|
|
540
|
+
installed: match,
|
|
541
|
+
notInstalled: !loaded.exists || match === null,
|
|
567
542
|
otherPlugins: plugins.filter((p) => !matchesPlugin(p)),
|
|
568
543
|
latest,
|
|
569
|
-
isLatest: match !==
|
|
544
|
+
isLatest: match !== null && latest !== null ? match === `opencode-rules-md@${latest}` : null
|
|
570
545
|
};
|
|
571
546
|
configs.push(entry);
|
|
572
547
|
if (!loaded.exists) {
|
|
@@ -578,7 +553,7 @@ var runStatus = async (fs, env, log) => {
|
|
|
578
553
|
const isLatestLabel = entry.isLatest === true ? " (latest)" : entry.isLatest === false ? ` (behind: latest is ${latest ?? "unknown"})` : "";
|
|
579
554
|
log(` opencode-rules-md: ${match}${isLatestLabel}`);
|
|
580
555
|
} else {
|
|
581
|
-
log(
|
|
556
|
+
log(" opencode-rules-md: not installed");
|
|
582
557
|
}
|
|
583
558
|
if (entry.otherPlugins.length > 0) {
|
|
584
559
|
log(` other plugins: ${entry.otherPlugins.join(", ")}`);
|
|
@@ -619,7 +594,7 @@ var runDoctor = async (fs, env, log, error, opts = {}) => {
|
|
|
619
594
|
} else {
|
|
620
595
|
log(" Bun — found on PATH");
|
|
621
596
|
}
|
|
622
|
-
for (const basename of
|
|
597
|
+
for (const basename of CONFIG_BASENAMES3) {
|
|
623
598
|
log(`Checking ${basename} config...`);
|
|
624
599
|
const loaded = loadGlobalConfig(fs, env, basename);
|
|
625
600
|
if (!loaded.exists) {
|
|
@@ -628,33 +603,46 @@ var runDoctor = async (fs, env, log, error, opts = {}) => {
|
|
|
628
603
|
continue;
|
|
629
604
|
}
|
|
630
605
|
log(` ${basename}${extname(loaded.path)}: ${loaded.path}`);
|
|
631
|
-
const plugins =
|
|
632
|
-
const match = plugins.find((p) => matchesPlugin(p));
|
|
606
|
+
const plugins = readInstalledPlugins(loaded);
|
|
607
|
+
const match = plugins.find((p) => matchesPlugin(p)) ?? null;
|
|
633
608
|
if (match) {
|
|
634
|
-
info.push(`${basename}: opencode-rules-md
|
|
609
|
+
info.push(`${basename}: opencode-rules-md installed (${match})`);
|
|
635
610
|
log(` opencode-rules-md: ${match}`);
|
|
636
611
|
} else {
|
|
637
612
|
warnings.push(`${basename}: plugin not installed`);
|
|
638
|
-
log(
|
|
613
|
+
log(" opencode-rules-md: not installed");
|
|
639
614
|
}
|
|
640
|
-
if (loaded.data["plugins"] !== undefined &&
|
|
641
|
-
|
|
615
|
+
if (loaded.data["plugins"] !== undefined && loaded.data["plugin"] === undefined) {
|
|
616
|
+
warnings.push(`${basename}: legacy "plugins" field present (should be "plugin") — run "omd uninstall && omd install" to migrate`);
|
|
617
|
+
log(` [WARN] ${basename}: legacy "plugins" field present (should be "plugin")`);
|
|
618
|
+
}
|
|
619
|
+
const pluginField = loaded.data["plugin"] ?? loaded.data["plugins"];
|
|
620
|
+
if (pluginField !== undefined && !Array.isArray(pluginField) && typeof pluginField !== "object" && typeof pluginField !== "string") {
|
|
621
|
+
issues.push(`${basename}: plugin field has invalid type — expected array, object, or string`);
|
|
642
622
|
error(`[ISSUE] ${basename}: invalid plugin shape`);
|
|
643
623
|
} else {
|
|
644
|
-
log(
|
|
624
|
+
log(" plugin field: valid");
|
|
645
625
|
}
|
|
646
626
|
}
|
|
647
627
|
log("Checking rule directory...");
|
|
648
|
-
const ruleBase =
|
|
628
|
+
const ruleBase = join4(homedir3(), ".local", "share", RULE_DIR_NAME);
|
|
649
629
|
if (!ruleDirExists) {
|
|
650
630
|
warnings.push(`Rule directory not found at ${ruleBase}`);
|
|
651
631
|
log(` ${ruleBase}: not found (plugin rules not installed — this is optional)`);
|
|
652
632
|
} else {
|
|
653
633
|
log(` ${ruleBase}: exists`);
|
|
654
634
|
}
|
|
655
|
-
|
|
635
|
+
log("Checking OpenCode package cache...");
|
|
636
|
+
const cachePaths = resolveCachePaths(env, fs);
|
|
637
|
+
if (cachePaths.length === 0) {
|
|
638
|
+
info.push("No opencode-rules-md cache found under ~/.cache/opencode/packages/");
|
|
639
|
+
log(" no cache entries match opencode-rules-md*");
|
|
640
|
+
} else {
|
|
641
|
+
log(` cache entries: ${cachePaths.join(", ")}`);
|
|
642
|
+
}
|
|
643
|
+
const configDir = env.OPENCODE_CONFIG_DIR ?? join4(homedir3(), ".config", "opencode");
|
|
656
644
|
log("Checking config directory write access...");
|
|
657
|
-
const parentDir =
|
|
645
|
+
const parentDir = dirname2(configDir);
|
|
658
646
|
if (!fs.existsSync(parentDir)) {
|
|
659
647
|
issues.push(`Config parent directory does not exist: ${parentDir}`);
|
|
660
648
|
error(`[ISSUE] Config dir parent missing: ${parentDir}`);
|
|
@@ -719,17 +707,17 @@ Usage:
|
|
|
719
707
|
omd [command] [options]
|
|
720
708
|
|
|
721
709
|
Commands:
|
|
722
|
-
install
|
|
723
|
-
uninstall
|
|
724
|
-
status
|
|
725
|
-
doctor
|
|
726
|
-
update
|
|
710
|
+
install Register opencode-rules-md via OpenCode's plugin command
|
|
711
|
+
uninstall Remove opencode-rules-md from both configs and cache
|
|
712
|
+
status Show installed plugin state for each config
|
|
713
|
+
doctor Run health checks for the plugin environment
|
|
714
|
+
update Check for new versions and refresh the install via OpenCode
|
|
727
715
|
|
|
728
716
|
Options:
|
|
729
717
|
--dry-run Show what would be changed without writing
|
|
730
718
|
--version Pin to a specific version (install only)
|
|
731
719
|
--latest Use the latest version (install only)
|
|
732
|
-
--purge Also remove ~/.cache/opencode/
|
|
720
|
+
--purge Also remove ~/.cache/opencode/packages/opencode-rules-md* (uninstall only)
|
|
733
721
|
--yes Accept all prompts automatically
|
|
734
722
|
|
|
735
723
|
Examples:
|
|
@@ -803,12 +791,12 @@ var runMain = async (opts, argv) => {
|
|
|
803
791
|
case "install": {
|
|
804
792
|
const remaining = argv.slice(argv.indexOf("install") + 1 || argv.indexOf(resolvedCommand) + 1);
|
|
805
793
|
const { values } = parseArgs({
|
|
806
|
-
|
|
794
|
+
args: remaining,
|
|
807
795
|
allowPositionals: true,
|
|
808
796
|
strict: false,
|
|
809
797
|
options: {
|
|
810
798
|
"dry-run": { type: "boolean", default: false },
|
|
811
|
-
version: { type: "string", default:
|
|
799
|
+
version: { type: "string", default: "" },
|
|
812
800
|
latest: { type: "boolean", default: false },
|
|
813
801
|
yes: { type: "boolean", default: false }
|
|
814
802
|
}
|
|
@@ -817,29 +805,21 @@ var runMain = async (opts, argv) => {
|
|
|
817
805
|
version: values["latest"] ? "latest" : String(values["version"] ?? ""),
|
|
818
806
|
dryRun: Boolean(values["dry-run"]),
|
|
819
807
|
yes: Boolean(values["yes"]),
|
|
820
|
-
latestVersion: opts.latestVersion
|
|
808
|
+
latestVersion: opts.latestVersion,
|
|
809
|
+
...opts.spawn ? { spawn: opts.spawn } : {}
|
|
821
810
|
};
|
|
822
811
|
const result = await runInstall(installOpts, fs, env);
|
|
823
812
|
if (result.status === "skipped") {
|
|
824
|
-
stdout(
|
|
813
|
+
stdout(`omd: install skipped (dry-run) — would run: opencode plugin ${result.specifier} --global`);
|
|
825
814
|
return 0;
|
|
826
815
|
}
|
|
827
|
-
|
|
828
|
-
if (r.status === "wrote") {
|
|
829
|
-
stdout(`omd: registered in ${r.path}`);
|
|
830
|
-
} else if (r.status === "skipped") {
|
|
831
|
-
stdout(`omd: ${r.path} — already up to date`);
|
|
832
|
-
}
|
|
833
|
-
}
|
|
834
|
-
if (result.purged) {
|
|
835
|
-
stdout("omd: cache purged");
|
|
836
|
-
}
|
|
816
|
+
stdout(`omd: installed via opencode plugin ${result.specifier} --global`);
|
|
837
817
|
return 0;
|
|
838
818
|
}
|
|
839
819
|
case "uninstall": {
|
|
840
820
|
const remaining = argv.slice(argv.indexOf(resolvedCommand) + 1);
|
|
841
821
|
const { values } = parseArgs({
|
|
842
|
-
|
|
822
|
+
args: remaining,
|
|
843
823
|
allowPositionals: true,
|
|
844
824
|
strict: false,
|
|
845
825
|
options: {
|
|
@@ -871,7 +851,7 @@ var runMain = async (opts, argv) => {
|
|
|
871
851
|
return 0;
|
|
872
852
|
}
|
|
873
853
|
case "status": {
|
|
874
|
-
await runStatus(fs, env, stdout);
|
|
854
|
+
await runStatus(fs, env, stdout, opts.latestVersion !== undefined ? { latestVersion: opts.latestVersion } : {});
|
|
875
855
|
return 0;
|
|
876
856
|
}
|
|
877
857
|
case "doctor": {
|
|
@@ -881,7 +861,7 @@ var runMain = async (opts, argv) => {
|
|
|
881
861
|
case "update": {
|
|
882
862
|
const remaining = argv.slice(argv.indexOf(resolvedCommand) + 1);
|
|
883
863
|
const { values } = parseArgs({
|
|
884
|
-
|
|
864
|
+
args: remaining,
|
|
885
865
|
allowPositionals: true,
|
|
886
866
|
strict: false,
|
|
887
867
|
options: {
|
|
@@ -889,9 +869,20 @@ var runMain = async (opts, argv) => {
|
|
|
889
869
|
}
|
|
890
870
|
});
|
|
891
871
|
const dryRun = Boolean(values["dry-run"]);
|
|
892
|
-
const updateResult = await runUpdate(fs, env, stdout, stderr, {
|
|
893
|
-
|
|
894
|
-
|
|
872
|
+
const updateResult = await runUpdate(fs, env, stdout, stderr, {
|
|
873
|
+
dryRun,
|
|
874
|
+
latestVersion: opts.latestVersion,
|
|
875
|
+
...opts.spawn ? { spawn: opts.spawn } : {}
|
|
876
|
+
});
|
|
877
|
+
switch (updateResult.status) {
|
|
878
|
+
case "current":
|
|
879
|
+
stdout("omd: already at latest version");
|
|
880
|
+
break;
|
|
881
|
+
case "unreachable":
|
|
882
|
+
stderr("omd: could not reach npm registry — try again later");
|
|
883
|
+
return 1;
|
|
884
|
+
case "stale":
|
|
885
|
+
break;
|
|
895
886
|
}
|
|
896
887
|
return 0;
|
|
897
888
|
}
|