@massa-ai/opencode-plugin 1.60.0 → 1.60.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/config-cli.js +290 -101
- package/package.json +3 -3
package/dist/config-cli.js
CHANGED
|
@@ -2343,9 +2343,9 @@ function acquireLock(stateFilePath, options = {}) {
|
|
|
2343
2343
|
}
|
|
2344
2344
|
}
|
|
2345
2345
|
// ../../packages/shared/dist/profile-switch/engine.js
|
|
2346
|
-
import
|
|
2347
|
-
import
|
|
2348
|
-
import
|
|
2346
|
+
import fs7 from "fs";
|
|
2347
|
+
import path11 from "path";
|
|
2348
|
+
import os7 from "os";
|
|
2349
2349
|
import crypto3 from "crypto";
|
|
2350
2350
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
2351
2351
|
|
|
@@ -2425,12 +2425,13 @@ function selectRecord(records) {
|
|
|
2425
2425
|
}
|
|
2426
2426
|
return best ?? pool[pool.length - 1];
|
|
2427
2427
|
}
|
|
2428
|
-
function
|
|
2428
|
+
function resolveClaudeMarketplaceInstall(opts = {}) {
|
|
2429
2429
|
const targetHome = opts.targetHome ?? os5.homedir();
|
|
2430
2430
|
const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
|
|
2431
2431
|
const directoryResult = resolveDirectorySourceRoot(targetHome, pluginKey);
|
|
2432
|
-
if (directoryResult !== undefined)
|
|
2433
|
-
return directoryResult;
|
|
2432
|
+
if (directoryResult !== undefined) {
|
|
2433
|
+
return directoryResult === null ? null : { root: directoryResult, route: "directory-source" };
|
|
2434
|
+
}
|
|
2434
2435
|
const registryPath = path9.join(targetHome, ".claude", "plugins", "installed_plugins.json");
|
|
2435
2436
|
let records;
|
|
2436
2437
|
try {
|
|
@@ -2452,7 +2453,186 @@ function resolveClaudeMarketplaceRoot(opts = {}) {
|
|
|
2452
2453
|
} catch {
|
|
2453
2454
|
return null;
|
|
2454
2455
|
}
|
|
2455
|
-
return installPath;
|
|
2456
|
+
return { root: installPath, route: "registry-cache" };
|
|
2457
|
+
}
|
|
2458
|
+
function resolveClaudeMarketplaceRoot(opts = {}) {
|
|
2459
|
+
return resolveClaudeMarketplaceInstall(opts)?.root ?? null;
|
|
2460
|
+
}
|
|
2461
|
+
function readInstalledPluginVersion(opts = {}) {
|
|
2462
|
+
const targetHome = opts.targetHome ?? os5.homedir();
|
|
2463
|
+
const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
|
|
2464
|
+
const registryPath = path9.join(targetHome, ".claude", "plugins", "installed_plugins.json");
|
|
2465
|
+
let records;
|
|
2466
|
+
try {
|
|
2467
|
+
const parsed = JSON.parse(fs5.readFileSync(registryPath, "utf8"));
|
|
2468
|
+
records = parsed?.plugins?.[pluginKey];
|
|
2469
|
+
} catch {
|
|
2470
|
+
return null;
|
|
2471
|
+
}
|
|
2472
|
+
if (!Array.isArray(records) || records.length === 0)
|
|
2473
|
+
return null;
|
|
2474
|
+
return selectRecord(records)?.version ?? null;
|
|
2475
|
+
}
|
|
2476
|
+
|
|
2477
|
+
// ../../packages/shared/dist/profile-switch/doctor.js
|
|
2478
|
+
import fs6 from "fs";
|
|
2479
|
+
import os6 from "os";
|
|
2480
|
+
import path10 from "path";
|
|
2481
|
+
|
|
2482
|
+
// ../../packages/shared/dist/profile-switch/frontmatter.js
|
|
2483
|
+
function parseFrontmatter(raw) {
|
|
2484
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n([\s\S]*)$/.exec(raw);
|
|
2485
|
+
if (!match) {
|
|
2486
|
+
throw new Error("charter missing YAML frontmatter (--- ... ---) block");
|
|
2487
|
+
}
|
|
2488
|
+
const yamlText = match[1] ?? "";
|
|
2489
|
+
const body = (match[2] ?? "").replace(/^\r?\n/, "");
|
|
2490
|
+
const frontmatter = parseSimpleYaml(yamlText);
|
|
2491
|
+
return { frontmatter, body };
|
|
2492
|
+
}
|
|
2493
|
+
function parseSimpleYaml(text) {
|
|
2494
|
+
const result = {};
|
|
2495
|
+
const lines = text.split(/\r?\n/);
|
|
2496
|
+
let i = 0;
|
|
2497
|
+
while (i < lines.length) {
|
|
2498
|
+
const line = lines[i] ?? "";
|
|
2499
|
+
if (line.trim() === "" || line.trim().startsWith("#")) {
|
|
2500
|
+
i++;
|
|
2501
|
+
continue;
|
|
2502
|
+
}
|
|
2503
|
+
const m = /^([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/.exec(line);
|
|
2504
|
+
if (!m) {
|
|
2505
|
+
i++;
|
|
2506
|
+
continue;
|
|
2507
|
+
}
|
|
2508
|
+
const key = m[1];
|
|
2509
|
+
const rest = (m[2] ?? "").trim();
|
|
2510
|
+
if (rest !== "") {
|
|
2511
|
+
result[key] = unquoteScalar(rest);
|
|
2512
|
+
i++;
|
|
2513
|
+
continue;
|
|
2514
|
+
}
|
|
2515
|
+
const nested = {};
|
|
2516
|
+
i++;
|
|
2517
|
+
while (i < lines.length) {
|
|
2518
|
+
const nestedLine = lines[i] ?? "";
|
|
2519
|
+
if (/^\s{2,}\S/.test(nestedLine) === false)
|
|
2520
|
+
break;
|
|
2521
|
+
const nm = /^\s{2,}([A-Za-z_][A-Za-z0-9_]*):\s*(.*)$/.exec(nestedLine);
|
|
2522
|
+
if (!nm)
|
|
2523
|
+
break;
|
|
2524
|
+
nested[nm[1]] = unquoteScalar((nm[2] ?? "").trim());
|
|
2525
|
+
i++;
|
|
2526
|
+
}
|
|
2527
|
+
result[key] = nested;
|
|
2528
|
+
}
|
|
2529
|
+
return result;
|
|
2530
|
+
}
|
|
2531
|
+
function unquoteScalar(s) {
|
|
2532
|
+
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
2533
|
+
return s.slice(1, -1);
|
|
2534
|
+
}
|
|
2535
|
+
return s;
|
|
2536
|
+
}
|
|
2537
|
+
|
|
2538
|
+
// ../../packages/shared/dist/profile-switch/doctor.js
|
|
2539
|
+
var ENV_OVERRIDE_VARS = ["CLAUDE_CODE_SUBAGENT_MODEL"];
|
|
2540
|
+
function readTextFile(filePath) {
|
|
2541
|
+
try {
|
|
2542
|
+
return fs6.readFileSync(filePath, "utf8");
|
|
2543
|
+
} catch {
|
|
2544
|
+
return null;
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
function readJsonFile(filePath) {
|
|
2548
|
+
const raw = readTextFile(filePath);
|
|
2549
|
+
if (raw === null)
|
|
2550
|
+
return null;
|
|
2551
|
+
try {
|
|
2552
|
+
return JSON.parse(raw);
|
|
2553
|
+
} catch {
|
|
2554
|
+
return null;
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
function readPluginVersion(pluginRoot) {
|
|
2558
|
+
const manifest = readJsonFile(path10.join(pluginRoot, ".claude-plugin", "plugin.json"));
|
|
2559
|
+
return typeof manifest?.version === "string" ? manifest.version : null;
|
|
2560
|
+
}
|
|
2561
|
+
function detectEnvOverride(env) {
|
|
2562
|
+
for (const name of ENV_OVERRIDE_VARS) {
|
|
2563
|
+
const value = env[name];
|
|
2564
|
+
if (typeof value === "string" && value.trim()) {
|
|
2565
|
+
return { name, value: value.trim() };
|
|
2566
|
+
}
|
|
2567
|
+
}
|
|
2568
|
+
return null;
|
|
2569
|
+
}
|
|
2570
|
+
function readRoles(liveRoot, activeProfile) {
|
|
2571
|
+
const agentsDir = path10.join(liveRoot, "agents");
|
|
2572
|
+
let entries;
|
|
2573
|
+
try {
|
|
2574
|
+
entries = fs6.readdirSync(agentsDir, { withFileTypes: true });
|
|
2575
|
+
} catch {
|
|
2576
|
+
return [];
|
|
2577
|
+
}
|
|
2578
|
+
const roles = [];
|
|
2579
|
+
for (const entry of entries) {
|
|
2580
|
+
if (!entry.isFile() || !entry.name.startsWith("massa-ai-") || !entry.name.endsWith(".md")) {
|
|
2581
|
+
continue;
|
|
2582
|
+
}
|
|
2583
|
+
const activeRaw = readTextFile(path10.join(agentsDir, entry.name));
|
|
2584
|
+
let model = null;
|
|
2585
|
+
let effort = null;
|
|
2586
|
+
if (activeRaw !== null) {
|
|
2587
|
+
try {
|
|
2588
|
+
const { frontmatter } = parseFrontmatter(activeRaw);
|
|
2589
|
+
model = typeof frontmatter.model === "string" ? frontmatter.model : null;
|
|
2590
|
+
effort = typeof frontmatter.effort === "string" ? frontmatter.effort : null;
|
|
2591
|
+
} catch {}
|
|
2592
|
+
}
|
|
2593
|
+
let staleVariant = false;
|
|
2594
|
+
if (activeProfile && activeRaw !== null) {
|
|
2595
|
+
const variantRaw = readTextFile(path10.join(liveRoot, "agent-profiles", activeProfile, entry.name));
|
|
2596
|
+
if (variantRaw !== null) {
|
|
2597
|
+
staleVariant = variantRaw !== activeRaw;
|
|
2598
|
+
}
|
|
2599
|
+
}
|
|
2600
|
+
roles.push({ name: entry.name, model, effort, staleVariant });
|
|
2601
|
+
}
|
|
2602
|
+
return roles.sort((a, b) => a.name.localeCompare(b.name));
|
|
2603
|
+
}
|
|
2604
|
+
function runtimeDriftReport(opts = {}) {
|
|
2605
|
+
const targetHome = opts.targetHome ?? os6.homedir();
|
|
2606
|
+
const stateFilePath = opts.stateFilePath ?? path10.join(targetHome, ".config", "massa-ai", "install-state.json");
|
|
2607
|
+
let state = opts.state ?? null;
|
|
2608
|
+
if (state === null) {
|
|
2609
|
+
try {
|
|
2610
|
+
state = readInstallState(stateFilePath);
|
|
2611
|
+
} catch {
|
|
2612
|
+
state = null;
|
|
2613
|
+
}
|
|
2614
|
+
}
|
|
2615
|
+
const platform = state?.platforms?.claude;
|
|
2616
|
+
const stateVersion = typeof platform?.plugin?.version === "string" ? platform.plugin.version : null;
|
|
2617
|
+
const activeProfile = platform?.modelProfile?.profile ?? null;
|
|
2618
|
+
const install = resolveClaudeMarketplaceInstall({ targetHome, pluginKey: opts.pluginKey });
|
|
2619
|
+
const liveRoot = install?.root ?? null;
|
|
2620
|
+
const sourceVersion = liveRoot === null ? null : readPluginVersion(liveRoot);
|
|
2621
|
+
const pinnedVersion = readInstalledPluginVersion({ targetHome, pluginKey: opts.pluginKey });
|
|
2622
|
+
const roles = liveRoot === null ? [] : readRoles(liveRoot, activeProfile);
|
|
2623
|
+
return {
|
|
2624
|
+
host: "claude",
|
|
2625
|
+
route: install?.route ?? "unresolved",
|
|
2626
|
+
liveRoot,
|
|
2627
|
+
sourceVersion,
|
|
2628
|
+
stateVersion,
|
|
2629
|
+
pinnedVersion,
|
|
2630
|
+
activeProfile,
|
|
2631
|
+
roles,
|
|
2632
|
+
envOverride: detectEnvOverride(opts.env ?? process.env),
|
|
2633
|
+
versionDrift: sourceVersion !== null && stateVersion !== null && sourceVersion !== stateVersion,
|
|
2634
|
+
profileMaterialized: roles.some((role) => role.staleVariant)
|
|
2635
|
+
};
|
|
2456
2636
|
}
|
|
2457
2637
|
|
|
2458
2638
|
// ../../packages/shared/dist/profile-switch/engine.js
|
|
@@ -2470,10 +2650,10 @@ function namedError3(name, message) {
|
|
|
2470
2650
|
var UnknownProfileError = (profile, known) => namedError3("UnknownProfileError", `unknown profile "${profile}" \u2014 installed: ${known.length > 0 ? known.join(", ") : "none"}`);
|
|
2471
2651
|
var NoHostsDetectedError = () => namedError3("NoHostsDetectedError", "no installed hosts found");
|
|
2472
2652
|
function defaultStatePath(targetHome) {
|
|
2473
|
-
return
|
|
2653
|
+
return path11.join(targetHome, ".config", "massa-ai", "install-state.json");
|
|
2474
2654
|
}
|
|
2475
2655
|
function resolveCommon(opts) {
|
|
2476
|
-
const targetHome = opts.targetHome ??
|
|
2656
|
+
const targetHome = opts.targetHome ?? os7.homedir();
|
|
2477
2657
|
const stateFilePath = opts.stateFilePath ?? defaultStatePath(targetHome);
|
|
2478
2658
|
return { targetHome, stateFilePath };
|
|
2479
2659
|
}
|
|
@@ -2481,7 +2661,7 @@ function marketplaceRoots(targetHome, state) {
|
|
|
2481
2661
|
return state.platforms.claude?.installRoute === "marketplace" ? { claude: resolveClaudeMarketplaceRoot({ targetHome }) ?? undefined } : {};
|
|
2482
2662
|
}
|
|
2483
2663
|
function claudeMarketplaceUnresolvedReason(targetHome) {
|
|
2484
|
-
const registryPath =
|
|
2664
|
+
const registryPath = path11.join(targetHome, ".claude", "plugins", "installed_plugins.json");
|
|
2485
2665
|
return `claude installRoute is "marketplace" but no install root could be resolved from ${registryPath} ` + "\u2014 re-run the Claude plugin installer, or verify the plugin registry file";
|
|
2486
2666
|
}
|
|
2487
2667
|
function listProfiles(opts = {}) {
|
|
@@ -2489,6 +2669,12 @@ function listProfiles(opts = {}) {
|
|
|
2489
2669
|
const state = readInstallState(stateFilePath);
|
|
2490
2670
|
const roots = marketplaceRoots(targetHome, state);
|
|
2491
2671
|
const universe = opts.hosts ?? HOSTS;
|
|
2672
|
+
const claudeDrift = universe.includes("claude") ? runtimeDriftReport({ targetHome, stateFilePath, state, env: opts.env }) : null;
|
|
2673
|
+
const claudeDriftFields = (host) => host === "claude" && claudeDrift !== null ? {
|
|
2674
|
+
liveRoot: claudeDrift.liveRoot,
|
|
2675
|
+
sourceVersion: claudeDrift.sourceVersion,
|
|
2676
|
+
envOverride: claudeDrift.envOverride ? `${claudeDrift.envOverride.name}=${claudeDrift.envOverride.value}` : null
|
|
2677
|
+
} : { liveRoot: null, sourceVersion: null, envOverride: null };
|
|
2492
2678
|
const hosts = universe.map((host) => {
|
|
2493
2679
|
if (host === "claude" && state.platforms.claude?.installRoute === "marketplace" && roots.claude === undefined) {
|
|
2494
2680
|
const platform2 = state.platforms.claude;
|
|
@@ -2499,7 +2685,8 @@ function listProfiles(opts = {}) {
|
|
|
2499
2685
|
skipReason: null,
|
|
2500
2686
|
activeProfile: platform2.modelProfile?.profile ?? opts.hostDefaults?.[host] ?? "balanced",
|
|
2501
2687
|
bundleVersion: platform2.plugin?.version ?? null,
|
|
2502
|
-
availableProfiles: []
|
|
2688
|
+
availableProfiles: [],
|
|
2689
|
+
...claudeDriftFields(host)
|
|
2503
2690
|
};
|
|
2504
2691
|
}
|
|
2505
2692
|
const layout = resolveHostLayout(host, { targetHome, projectRoot: opts.projectRoot, marketplaceRoot: roots });
|
|
@@ -2511,10 +2698,11 @@ function listProfiles(opts = {}) {
|
|
|
2511
2698
|
skipReason: layout.reason,
|
|
2512
2699
|
activeProfile: null,
|
|
2513
2700
|
bundleVersion: null,
|
|
2514
|
-
availableProfiles: []
|
|
2701
|
+
availableProfiles: [],
|
|
2702
|
+
...claudeDriftFields(host)
|
|
2515
2703
|
};
|
|
2516
2704
|
}
|
|
2517
|
-
const installed =
|
|
2705
|
+
const installed = fs7.existsSync(layout.activeDir);
|
|
2518
2706
|
const availableProfiles = listVariantProfiles(layout);
|
|
2519
2707
|
const platform = state.platforms[host];
|
|
2520
2708
|
return {
|
|
@@ -2524,15 +2712,16 @@ function listProfiles(opts = {}) {
|
|
|
2524
2712
|
skipReason: null,
|
|
2525
2713
|
activeProfile: platform?.modelProfile?.profile ?? opts.hostDefaults?.[host] ?? "balanced",
|
|
2526
2714
|
bundleVersion: platform?.plugin?.version ?? null,
|
|
2527
|
-
availableProfiles
|
|
2715
|
+
availableProfiles,
|
|
2716
|
+
...claudeDriftFields(host)
|
|
2528
2717
|
};
|
|
2529
2718
|
});
|
|
2530
2719
|
return { hosts };
|
|
2531
2720
|
}
|
|
2532
2721
|
function listVariantProfiles(layout) {
|
|
2533
|
-
if (!
|
|
2722
|
+
if (!fs7.existsSync(layout.variantsRoot))
|
|
2534
2723
|
return [];
|
|
2535
|
-
return
|
|
2724
|
+
return fs7.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
2536
2725
|
}
|
|
2537
2726
|
function matchesGlob(filename, glob) {
|
|
2538
2727
|
const starIdx = glob.indexOf("*");
|
|
@@ -2543,7 +2732,7 @@ function matchesGlob(filename, glob) {
|
|
|
2543
2732
|
return filename.length >= prefix.length + suffix.length && filename.startsWith(prefix) && filename.endsWith(suffix);
|
|
2544
2733
|
}
|
|
2545
2734
|
function matchingFileNames(dir, glob) {
|
|
2546
|
-
return
|
|
2735
|
+
return fs7.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && matchesGlob(e.name, glob)).map((e) => e.name);
|
|
2547
2736
|
}
|
|
2548
2737
|
function detectGitAvailability(dir) {
|
|
2549
2738
|
try {
|
|
@@ -2571,7 +2760,7 @@ function gitTrackedFileNames(dir, filenames) {
|
|
|
2571
2760
|
var GUARD_PASS = { blocked: false, unchecked: false };
|
|
2572
2761
|
var GUARD_UNCHECKED = { blocked: false, unchecked: true };
|
|
2573
2762
|
function checkTrackedPathGuard(activeDir, filenames) {
|
|
2574
|
-
if (filenames.length === 0 || !
|
|
2763
|
+
if (filenames.length === 0 || !fs7.existsSync(activeDir))
|
|
2575
2764
|
return GUARD_PASS;
|
|
2576
2765
|
const availability = detectGitAvailability(activeDir);
|
|
2577
2766
|
if (availability === "no-git")
|
|
@@ -2582,53 +2771,53 @@ function checkTrackedPathGuard(activeDir, filenames) {
|
|
|
2582
2771
|
if (tracked.size === 0)
|
|
2583
2772
|
return GUARD_PASS;
|
|
2584
2773
|
const offending = filenames.find((name) => tracked.has(name));
|
|
2585
|
-
return { blocked: true, path:
|
|
2774
|
+
return { blocked: true, path: path11.join(activeDir, offending), unchecked: false };
|
|
2586
2775
|
}
|
|
2587
2776
|
function assertStateWritable(stateFilePath) {
|
|
2588
|
-
const dir =
|
|
2777
|
+
const dir = path11.dirname(stateFilePath);
|
|
2589
2778
|
try {
|
|
2590
|
-
|
|
2779
|
+
fs7.mkdirSync(dir, { recursive: true });
|
|
2591
2780
|
} catch (err) {
|
|
2592
2781
|
throw UnwritableInstallStateError(stateFilePath, err.message);
|
|
2593
2782
|
}
|
|
2594
|
-
const checkPath =
|
|
2783
|
+
const checkPath = fs7.existsSync(stateFilePath) ? stateFilePath : dir;
|
|
2595
2784
|
try {
|
|
2596
|
-
|
|
2785
|
+
fs7.accessSync(checkPath, fs7.constants.W_OK);
|
|
2597
2786
|
} catch (err) {
|
|
2598
2787
|
throw UnwritableInstallStateError(stateFilePath, err.message);
|
|
2599
2788
|
}
|
|
2600
2789
|
}
|
|
2601
2790
|
function copyFileRouteVariant(layout, variantDir) {
|
|
2602
|
-
|
|
2791
|
+
fs7.mkdirSync(layout.activeDir, { recursive: true });
|
|
2603
2792
|
let changed = 0;
|
|
2604
|
-
for (const entry of
|
|
2793
|
+
for (const entry of fs7.readdirSync(variantDir, { withFileTypes: true })) {
|
|
2605
2794
|
if (!entry.isFile() || !matchesGlob(entry.name, layout.activeGlob))
|
|
2606
2795
|
continue;
|
|
2607
|
-
|
|
2796
|
+
fs7.copyFileSync(path11.join(variantDir, entry.name), path11.join(layout.activeDir, entry.name));
|
|
2608
2797
|
changed++;
|
|
2609
2798
|
}
|
|
2610
2799
|
return changed;
|
|
2611
2800
|
}
|
|
2612
2801
|
function repointOpencodeVariant(layout, variantDir) {
|
|
2613
|
-
|
|
2802
|
+
fs7.mkdirSync(layout.activeDir, { recursive: true });
|
|
2614
2803
|
let changed = 0;
|
|
2615
|
-
for (const entry of
|
|
2804
|
+
for (const entry of fs7.readdirSync(variantDir, { withFileTypes: true })) {
|
|
2616
2805
|
if (!entry.isFile() || !matchesGlob(entry.name, layout.activeGlob))
|
|
2617
2806
|
continue;
|
|
2618
|
-
const dest =
|
|
2619
|
-
const target =
|
|
2807
|
+
const dest = path11.join(layout.activeDir, entry.name);
|
|
2808
|
+
const target = path11.resolve(path11.join(variantDir, entry.name));
|
|
2620
2809
|
let destExists = true;
|
|
2621
2810
|
let destIsSymlink = false;
|
|
2622
2811
|
try {
|
|
2623
|
-
destIsSymlink =
|
|
2812
|
+
destIsSymlink = fs7.lstatSync(dest).isSymbolicLink();
|
|
2624
2813
|
} catch {
|
|
2625
2814
|
destExists = false;
|
|
2626
2815
|
}
|
|
2627
2816
|
if (destExists && !destIsSymlink)
|
|
2628
2817
|
continue;
|
|
2629
2818
|
const tmp = `${dest}.massa-ai-switch.${crypto3.randomUUID()}`;
|
|
2630
|
-
|
|
2631
|
-
|
|
2819
|
+
fs7.symlinkSync(target, tmp);
|
|
2820
|
+
fs7.renameSync(tmp, dest);
|
|
2632
2821
|
changed++;
|
|
2633
2822
|
}
|
|
2634
2823
|
return changed;
|
|
@@ -2668,13 +2857,13 @@ function switchProfile(opts) {
|
|
|
2668
2857
|
if (fileHosts.length === 0) {
|
|
2669
2858
|
return { profile: opts.profile, dryRun, hosts: orderRows(universe, skipRows), restartRequired: false };
|
|
2670
2859
|
}
|
|
2671
|
-
const installedFileHosts = fileHosts.filter((h) =>
|
|
2860
|
+
const installedFileHosts = fileHosts.filter((h) => fs7.existsSync(h.layout.activeDir));
|
|
2672
2861
|
if (installedFileHosts.length === 0)
|
|
2673
2862
|
throw NoHostsDetectedError();
|
|
2674
2863
|
const withAvailability = fileHosts.map((h) => {
|
|
2675
|
-
const variantsRootExists =
|
|
2864
|
+
const variantsRootExists = fs7.existsSync(h.layout.variantsRoot);
|
|
2676
2865
|
const variantDir = h.layout.variantDir(opts.profile);
|
|
2677
|
-
const available = variantsRootExists &&
|
|
2866
|
+
const available = variantsRootExists && fs7.existsSync(variantDir) && fs7.statSync(variantDir).isDirectory();
|
|
2678
2867
|
return { ...h, variantsRootExists, variantDir, available };
|
|
2679
2868
|
});
|
|
2680
2869
|
if (!withAvailability.some((h) => h.available)) {
|
|
@@ -2710,7 +2899,7 @@ function switchProfile(opts) {
|
|
|
2710
2899
|
continue;
|
|
2711
2900
|
}
|
|
2712
2901
|
if (dryRun) {
|
|
2713
|
-
rows.push({ host: h.host, status: "
|
|
2902
|
+
rows.push({ host: h.host, status: "would-switch" });
|
|
2714
2903
|
continue;
|
|
2715
2904
|
}
|
|
2716
2905
|
const candidateNames = matchingFileNames(h.variantDir, h.layout.activeGlob);
|
|
@@ -2751,15 +2940,15 @@ function orderRows(universe, rows) {
|
|
|
2751
2940
|
}
|
|
2752
2941
|
// ../../packages/shared/dist/profile-switch/report.js
|
|
2753
2942
|
function reportSucceeded(report) {
|
|
2754
|
-
return report.hosts.every((h) => h.status === "switched" || h.status === "skipped");
|
|
2943
|
+
return report.hosts.every((h) => h.status === "switched" || h.status === "would-switch" || h.status === "skipped");
|
|
2755
2944
|
}
|
|
2756
2945
|
// ../../packages/shared/dist/profile-switch/variant-sync.js
|
|
2757
|
-
import
|
|
2758
|
-
import
|
|
2759
|
-
import
|
|
2946
|
+
import fs8 from "fs";
|
|
2947
|
+
import path12 from "path";
|
|
2948
|
+
import os8 from "os";
|
|
2760
2949
|
import crypto4 from "crypto";
|
|
2761
2950
|
function defaultStatePath2(targetHome) {
|
|
2762
|
-
return
|
|
2951
|
+
return path12.join(targetHome, ".config", "massa-ai", "install-state.json");
|
|
2763
2952
|
}
|
|
2764
2953
|
function marketplaceRoots2(targetHome, state) {
|
|
2765
2954
|
return state.platforms.claude?.installRoute === "marketplace" ? { claude: resolveClaudeMarketplaceRoot({ targetHome }) ?? undefined } : {};
|
|
@@ -2767,13 +2956,13 @@ function marketplaceRoots2(targetHome, state) {
|
|
|
2767
2956
|
var tempFileCounter2 = 0;
|
|
2768
2957
|
function writeFileIntoDirAtomically(destDir, destName, content) {
|
|
2769
2958
|
const unique = `${process.pid}.${++tempFileCounter2}.${crypto4.randomBytes(6).toString("hex")}`;
|
|
2770
|
-
const tempFile =
|
|
2959
|
+
const tempFile = path12.join(destDir, `.${destName}.${unique}.tmp`);
|
|
2771
2960
|
try {
|
|
2772
|
-
|
|
2773
|
-
|
|
2961
|
+
fs8.writeFileSync(tempFile, content);
|
|
2962
|
+
fs8.renameSync(tempFile, path12.join(destDir, destName));
|
|
2774
2963
|
} catch (error) {
|
|
2775
2964
|
try {
|
|
2776
|
-
|
|
2965
|
+
fs8.unlinkSync(tempFile);
|
|
2777
2966
|
} catch {}
|
|
2778
2967
|
throw error;
|
|
2779
2968
|
}
|
|
@@ -2781,20 +2970,20 @@ function writeFileIntoDirAtomically(destDir, destName, content) {
|
|
|
2781
2970
|
function isSafeDirName(name) {
|
|
2782
2971
|
if (name === "." || name === "..")
|
|
2783
2972
|
return false;
|
|
2784
|
-
if (name.includes("/") || name.includes("\\") || name.includes(
|
|
2973
|
+
if (name.includes("/") || name.includes("\\") || name.includes(path12.sep))
|
|
2785
2974
|
return false;
|
|
2786
|
-
return
|
|
2975
|
+
return path12.basename(name) === name;
|
|
2787
2976
|
}
|
|
2788
2977
|
function syncHost(host, sourceRoot, targetHome, marketplaceRoot) {
|
|
2789
2978
|
const layout = resolveHostLayout(host, { targetHome, marketplaceRoot });
|
|
2790
2979
|
if (layout.route === "skip") {
|
|
2791
2980
|
return { host, status: "skipped", profiles: [], retained: [], files: 0, reason: layout.reason };
|
|
2792
2981
|
}
|
|
2793
|
-
const srcDir =
|
|
2794
|
-
if (!
|
|
2982
|
+
const srcDir = path12.join(sourceRoot, "apps", `${host}-plugin`, "agent-profiles");
|
|
2983
|
+
if (!fs8.existsSync(srcDir) || !fs8.statSync(srcDir).isDirectory()) {
|
|
2795
2984
|
return { host, status: "skipped", profiles: [], retained: [], files: 0, reason: "no generated variants for this host" };
|
|
2796
2985
|
}
|
|
2797
|
-
if (!
|
|
2986
|
+
if (!fs8.existsSync(layout.variantsRoot)) {
|
|
2798
2987
|
return {
|
|
2799
2988
|
host,
|
|
2800
2989
|
status: "skipped",
|
|
@@ -2806,24 +2995,24 @@ function syncHost(host, sourceRoot, targetHome, marketplaceRoot) {
|
|
|
2806
2995
|
}
|
|
2807
2996
|
const profiles = [];
|
|
2808
2997
|
let files = 0;
|
|
2809
|
-
for (const entry of
|
|
2998
|
+
for (const entry of fs8.readdirSync(srcDir, { withFileTypes: true })) {
|
|
2810
2999
|
if (!entry.isDirectory())
|
|
2811
3000
|
continue;
|
|
2812
3001
|
if (!isSafeDirName(entry.name))
|
|
2813
3002
|
continue;
|
|
2814
|
-
const srcProfileDir =
|
|
2815
|
-
const destProfileDir =
|
|
2816
|
-
|
|
2817
|
-
for (const fileEntry of
|
|
3003
|
+
const srcProfileDir = path12.join(srcDir, entry.name);
|
|
3004
|
+
const destProfileDir = path12.join(layout.variantsRoot, entry.name);
|
|
3005
|
+
fs8.mkdirSync(destProfileDir, { recursive: true });
|
|
3006
|
+
for (const fileEntry of fs8.readdirSync(srcProfileDir, { withFileTypes: true })) {
|
|
2818
3007
|
if (!fileEntry.isFile())
|
|
2819
3008
|
continue;
|
|
2820
|
-
const content =
|
|
3009
|
+
const content = fs8.readFileSync(path12.join(srcProfileDir, fileEntry.name));
|
|
2821
3010
|
writeFileIntoDirAtomically(destProfileDir, fileEntry.name, content);
|
|
2822
3011
|
files++;
|
|
2823
3012
|
}
|
|
2824
3013
|
profiles.push(entry.name);
|
|
2825
3014
|
}
|
|
2826
|
-
const retained =
|
|
3015
|
+
const retained = fs8.readdirSync(layout.variantsRoot, { withFileTypes: true }).filter((e) => e.isDirectory() && !profiles.includes(e.name)).map((e) => e.name).sort();
|
|
2827
3016
|
return { host, status: "synced", profiles: profiles.sort(), retained, files };
|
|
2828
3017
|
}
|
|
2829
3018
|
function syncGeneratedVariants(opts) {
|
|
@@ -2839,7 +3028,7 @@ function syncGeneratedVariants(opts) {
|
|
|
2839
3028
|
}));
|
|
2840
3029
|
}
|
|
2841
3030
|
const sourceRoot = opts.sourceRoot;
|
|
2842
|
-
const targetHome = opts.targetHome ??
|
|
3031
|
+
const targetHome = opts.targetHome ?? os8.homedir();
|
|
2843
3032
|
const state = readInstallState(defaultStatePath2(targetHome));
|
|
2844
3033
|
const roots = marketplaceRoots2(targetHome, state);
|
|
2845
3034
|
return hosts.map((host) => {
|
|
@@ -2851,14 +3040,14 @@ function syncGeneratedVariants(opts) {
|
|
|
2851
3040
|
});
|
|
2852
3041
|
}
|
|
2853
3042
|
// ../../packages/shared/dist/profile-switch/repo-root.js
|
|
2854
|
-
import
|
|
2855
|
-
import
|
|
3043
|
+
import fs9 from "fs";
|
|
3044
|
+
import path13 from "path";
|
|
2856
3045
|
function findRepoRootWithMarker(startDir, marker, maxLevels) {
|
|
2857
3046
|
let dir = startDir;
|
|
2858
3047
|
for (let i = 0;i <= maxLevels; i++) {
|
|
2859
|
-
if (
|
|
3048
|
+
if (fs9.existsSync(path13.join(dir, marker)))
|
|
2860
3049
|
return dir;
|
|
2861
|
-
const parent =
|
|
3050
|
+
const parent = path13.dirname(dir);
|
|
2862
3051
|
if (parent === dir)
|
|
2863
3052
|
break;
|
|
2864
3053
|
dir = parent;
|
|
@@ -2953,7 +3142,7 @@ function assertKnownRuleId(id) {
|
|
|
2953
3142
|
}
|
|
2954
3143
|
// ../../packages/shared/dist/bootstrap/state.js
|
|
2955
3144
|
init_config_loader();
|
|
2956
|
-
import
|
|
3145
|
+
import fs10 from "fs";
|
|
2957
3146
|
var BOOTSTRAP_STATE_KEY = "bootstrap";
|
|
2958
3147
|
var BOOTSTRAP_RULES_KEY = "rules";
|
|
2959
3148
|
var BOOTSTRAP_STATE_PATH = `${BOOTSTRAP_STATE_KEY}.${BOOTSTRAP_RULES_KEY}`;
|
|
@@ -2987,7 +3176,7 @@ function resolveBootstrapState(doc) {
|
|
|
2987
3176
|
}
|
|
2988
3177
|
function readConfigBytes() {
|
|
2989
3178
|
try {
|
|
2990
|
-
return
|
|
3179
|
+
return fs10.readFileSync(getConfigPath(), "utf-8");
|
|
2991
3180
|
} catch (error) {
|
|
2992
3181
|
if (error?.code === "ENOENT")
|
|
2993
3182
|
return "";
|
|
@@ -3035,7 +3224,7 @@ function setBootstrapRuleEnabled(id, enabled) {
|
|
|
3035
3224
|
};
|
|
3036
3225
|
}
|
|
3037
3226
|
// ../../packages/shared/dist/bootstrap/render.js
|
|
3038
|
-
import
|
|
3227
|
+
import path14 from "path";
|
|
3039
3228
|
var BOOTSTRAP_BLOCK_START = "<!-- massa-ai:bootstrap:start -->";
|
|
3040
3229
|
var BOOTSTRAP_BLOCK_END = "<!-- massa-ai:bootstrap:end -->";
|
|
3041
3230
|
var CONTRACT_FILENAME = "MASSA-AI.md";
|
|
@@ -3067,19 +3256,19 @@ var HOST_CONFIG_DIR = {
|
|
|
3067
3256
|
function resolveHostRoot(host, targetHome, hostRoot) {
|
|
3068
3257
|
requireAbsoluteTargetHome(targetHome);
|
|
3069
3258
|
if (hostRoot === undefined)
|
|
3070
|
-
return
|
|
3071
|
-
const relative =
|
|
3072
|
-
if (!
|
|
3259
|
+
return path14.join(targetHome, ...HOST_CONFIG_DIR[host]);
|
|
3260
|
+
const relative = path14.relative(targetHome, hostRoot);
|
|
3261
|
+
if (!path14.isAbsolute(hostRoot) || relative === "" || relative.startsWith("..") || path14.isAbsolute(relative)) {
|
|
3073
3262
|
throw new BootstrapRenderError("HostRootOutsideTargetHomeError", `hostRoot must be an absolute directory inside targetHome, got "${hostRoot}" for targetHome "${targetHome}"`, [hostRoot, targetHome]);
|
|
3074
3263
|
}
|
|
3075
3264
|
return hostRoot;
|
|
3076
3265
|
}
|
|
3077
3266
|
function bootstrapContractPath(host, targetHome, hostRoot) {
|
|
3078
|
-
return
|
|
3267
|
+
return path14.join(resolveHostRoot(host, targetHome, hostRoot), CONTRACT_FILENAME);
|
|
3079
3268
|
}
|
|
3080
3269
|
function bootstrapStateFilePath(targetHome) {
|
|
3081
3270
|
requireAbsoluteTargetHome(targetHome);
|
|
3082
|
-
return
|
|
3271
|
+
return path14.join(targetHome, ".config", "massa-ai", "config.json");
|
|
3083
3272
|
}
|
|
3084
3273
|
function renderBootstrap(options) {
|
|
3085
3274
|
const { source, state, host, targetHome, hostRoot } = options;
|
|
@@ -3102,7 +3291,7 @@ ${body}`;
|
|
|
3102
3291
|
return { contract, pointer };
|
|
3103
3292
|
}
|
|
3104
3293
|
function requireAbsoluteTargetHome(targetHome) {
|
|
3105
|
-
if (!
|
|
3294
|
+
if (!path14.isAbsolute(targetHome)) {
|
|
3106
3295
|
throw new BootstrapRenderError("TargetHomeNotAbsoluteError", `targetHome must be an absolute path, got "${targetHome}"`, [targetHome]);
|
|
3107
3296
|
}
|
|
3108
3297
|
}
|
|
@@ -3255,8 +3444,8 @@ function buildBootstrapReport(input) {
|
|
|
3255
3444
|
}
|
|
3256
3445
|
// ../../packages/shared/dist/bootstrap/engine.js
|
|
3257
3446
|
init_config_loader();
|
|
3258
|
-
import
|
|
3259
|
-
import
|
|
3447
|
+
import fs11 from "fs";
|
|
3448
|
+
import path15 from "path";
|
|
3260
3449
|
var INSTALL_STATE_FILENAME = "install-state.json";
|
|
3261
3450
|
var WIRING_REMEDY = "scripts/install-skills.sh --apply";
|
|
3262
3451
|
|
|
@@ -3273,7 +3462,7 @@ function applyBootstrapState(options) {
|
|
|
3273
3462
|
const dryRun = options.dryRun ?? false;
|
|
3274
3463
|
const warn = options.onWarning ?? ((message) => console.warn(message));
|
|
3275
3464
|
const configPath = bootstrapStateFilePath(targetHome);
|
|
3276
|
-
const installStatePath =
|
|
3465
|
+
const installStatePath = path15.join(path15.dirname(configPath), INSTALL_STATE_FILENAME);
|
|
3277
3466
|
const { platforms } = readInstallState(installStatePath);
|
|
3278
3467
|
const installed = HOSTS.filter((host) => platforms[host] !== undefined);
|
|
3279
3468
|
if (installed.length === 0) {
|
|
@@ -3366,22 +3555,22 @@ function applyHost(input) {
|
|
|
3366
3555
|
}
|
|
3367
3556
|
function wiringArtifact(host, targetHome, hostRoot) {
|
|
3368
3557
|
const root = resolveHostRoot(host, targetHome, hostRoot);
|
|
3369
|
-
const contractPath =
|
|
3558
|
+
const contractPath = path15.join(root, CONTRACT_FILENAME);
|
|
3370
3559
|
switch (host) {
|
|
3371
3560
|
case "claude":
|
|
3372
|
-
return { file:
|
|
3561
|
+
return { file: path15.join(root, "CLAUDE.md"), token: `@${CONTRACT_FILENAME}` };
|
|
3373
3562
|
case "codex":
|
|
3374
3563
|
case "cursor":
|
|
3375
|
-
return { file:
|
|
3564
|
+
return { file: path15.join(root, "AGENTS.md"), token: contractPath };
|
|
3376
3565
|
case "opencode":
|
|
3377
3566
|
return { file: openCodeConfigPath(root), token: `"${contractPath}"` };
|
|
3378
3567
|
}
|
|
3379
3568
|
}
|
|
3380
3569
|
function openCodeConfigPath(root) {
|
|
3381
|
-
const json =
|
|
3382
|
-
if (
|
|
3570
|
+
const json = path15.join(root, "opencode.json");
|
|
3571
|
+
if (fs11.existsSync(json))
|
|
3383
3572
|
return json;
|
|
3384
|
-
return
|
|
3573
|
+
return path15.join(root, "opencode.jsonc");
|
|
3385
3574
|
}
|
|
3386
3575
|
function isWired(host, targetHome, hostRoot) {
|
|
3387
3576
|
const artifact = wiringArtifact(host, targetHome, hostRoot);
|
|
@@ -3394,7 +3583,7 @@ function notWiredReason(host, targetHome, hostRoot) {
|
|
|
3394
3583
|
}
|
|
3395
3584
|
function readFileOrNull(filePath) {
|
|
3396
3585
|
try {
|
|
3397
|
-
return
|
|
3586
|
+
return fs11.readFileSync(filePath, "utf-8");
|
|
3398
3587
|
} catch {
|
|
3399
3588
|
return null;
|
|
3400
3589
|
}
|
|
@@ -3440,11 +3629,11 @@ function formatBootstrapReport(report) {
|
|
|
3440
3629
|
init_config_loader();
|
|
3441
3630
|
// src/config-cli.ts
|
|
3442
3631
|
init_inference_providers();
|
|
3443
|
-
import { promises as
|
|
3444
|
-
import
|
|
3445
|
-
import
|
|
3632
|
+
import { promises as fs12 } from "fs";
|
|
3633
|
+
import path16 from "path";
|
|
3634
|
+
import os9 from "os";
|
|
3446
3635
|
import { fileURLToPath } from "url";
|
|
3447
|
-
var __dirname2 =
|
|
3636
|
+
var __dirname2 = path16.dirname(fileURLToPath(import.meta.url));
|
|
3448
3637
|
var WRITABLE_PROVIDERS = ["ollama", "lmstudio", "mistral", "openai", "google", "cohere"];
|
|
3449
3638
|
var GENERATOR_MARKER = "scripts/generate-subagent-artifacts.ts";
|
|
3450
3639
|
var GENERATOR_MARKER_MAX_LEVELS = 6;
|
|
@@ -3722,18 +3911,18 @@ Using defaults:`);
|
|
|
3722
3911
|
return 1;
|
|
3723
3912
|
}
|
|
3724
3913
|
const scope = typeof options.project === "boolean" ? "project" : "user";
|
|
3725
|
-
const agentsDir = scope === "project" ?
|
|
3726
|
-
const sourceAgentsDir =
|
|
3914
|
+
const agentsDir = scope === "project" ? path16.join(process.cwd(), ".opencode/agents") : path16.join(process.env.XDG_CONFIG_HOME && process.env.XDG_CONFIG_HOME.trim() || path16.join(os9.homedir(), ".config"), "opencode", "agents");
|
|
3915
|
+
const sourceAgentsDir = path16.resolve(__dirname2, "..", "agents");
|
|
3727
3916
|
if (subcommand === "install") {
|
|
3728
|
-
await
|
|
3917
|
+
await fs12.mkdir(agentsDir, { recursive: true });
|
|
3729
3918
|
let count = 0;
|
|
3730
|
-
const entries = await
|
|
3919
|
+
const entries = await fs12.readdir(sourceAgentsDir);
|
|
3731
3920
|
for (const entry of entries) {
|
|
3732
3921
|
if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
|
|
3733
3922
|
continue;
|
|
3734
|
-
const src =
|
|
3735
|
-
const dest =
|
|
3736
|
-
await
|
|
3923
|
+
const src = path16.join(sourceAgentsDir, entry);
|
|
3924
|
+
const dest = path16.join(agentsDir, entry);
|
|
3925
|
+
await fs12.copyFile(src, dest);
|
|
3737
3926
|
count++;
|
|
3738
3927
|
}
|
|
3739
3928
|
console.log(`+ ${count} subagent specialists (generated from skills/agents/*/SKILL.md)`);
|
|
@@ -3741,14 +3930,14 @@ Using defaults:`);
|
|
|
3741
3930
|
} else {
|
|
3742
3931
|
let removed = 0;
|
|
3743
3932
|
try {
|
|
3744
|
-
const entries = await
|
|
3933
|
+
const entries = await fs12.readdir(agentsDir);
|
|
3745
3934
|
for (const entry of entries) {
|
|
3746
3935
|
if (!entry.startsWith("massa-ai-") || !entry.endsWith(".md"))
|
|
3747
3936
|
continue;
|
|
3748
|
-
const filePath =
|
|
3749
|
-
const content = await
|
|
3937
|
+
const filePath = path16.join(agentsDir, entry);
|
|
3938
|
+
const content = await fs12.readFile(filePath, "utf8");
|
|
3750
3939
|
if (content.includes("massa-ai-owned: true")) {
|
|
3751
|
-
await
|
|
3940
|
+
await fs12.unlink(filePath);
|
|
3752
3941
|
removed++;
|
|
3753
3942
|
}
|
|
3754
3943
|
}
|
|
@@ -3825,9 +4014,9 @@ Using defaults:`);
|
|
|
3825
4014
|
return 1;
|
|
3826
4015
|
}
|
|
3827
4016
|
const targetOpt = typeof options.target === "string" ? options.target : undefined;
|
|
3828
|
-
const targetHome = targetOpt === undefined ?
|
|
3829
|
-
if (targetHome !==
|
|
3830
|
-
console.error(`Error: --target ${targetHome} is not your home (${
|
|
4017
|
+
const targetHome = targetOpt === undefined ? os9.homedir() : path16.resolve(targetOpt);
|
|
4018
|
+
if (targetHome !== os9.homedir() && options.yes !== true) {
|
|
4019
|
+
console.error(`Error: --target ${targetHome} is not your home (${os9.homedir()}) \u2014 pass --yes to confirm writing there`);
|
|
3831
4020
|
return 1;
|
|
3832
4021
|
}
|
|
3833
4022
|
const dryRun = options["dry-run"] === true;
|
|
@@ -3845,7 +4034,7 @@ Using defaults:`);
|
|
|
3845
4034
|
const report = applyBootstrapState({
|
|
3846
4035
|
targetHome,
|
|
3847
4036
|
dryRun,
|
|
3848
|
-
sourcePath: repoRoot === null ? undefined :
|
|
4037
|
+
sourcePath: repoRoot === null ? undefined : path16.join(repoRoot, "skills", "AGENTS.md")
|
|
3849
4038
|
});
|
|
3850
4039
|
console.log(formatBootstrapReport(report));
|
|
3851
4040
|
return bootstrapReportSucceeded(report) ? 0 : 1;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@massa-ai/opencode-plugin",
|
|
3
|
-
"version": "1.60.
|
|
3
|
+
"version": "1.60.1",
|
|
4
4
|
"description": "massa-ai plugin for OpenCode - Semantic code search, memory, and context compression",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -24,8 +24,8 @@
|
|
|
24
24
|
"dependencies": {
|
|
25
25
|
"@opencode-ai/plugin": "^1.2.15",
|
|
26
26
|
"@opencode-ai/sdk": "^1.2.15",
|
|
27
|
-
"@massa-ai/core": "^1.60.
|
|
28
|
-
"@massa-ai/shared": "^1.60.
|
|
27
|
+
"@massa-ai/core": "^1.60.1",
|
|
28
|
+
"@massa-ai/shared": "^1.60.1"
|
|
29
29
|
},
|
|
30
30
|
"devDependencies": {
|
|
31
31
|
"@types/node": "^22.10.5",
|