@massa-ai/opencode-plugin 1.52.0 → 1.54.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.
Files changed (2) hide show
  1. package/dist/config-cli.js +119 -9
  2. package/package.json +3 -3
@@ -1906,17 +1906,11 @@ function detectRoute(platform, host) {
1906
1906
  if (route === "file")
1907
1907
  return { kind: "proceed" };
1908
1908
  if (route === "marketplace") {
1909
- if (host === "claude")
1909
+ if (host === "claude" || host === "codex")
1910
1910
  return { kind: "proceed" };
1911
- if (host === "codex") {
1912
- return {
1913
- kind: "refuse",
1914
- reason: "codex marketplace-route installs are refused (in-place bundle rewrite would dirty a checkout " + "and break the drift gate) \u2014 use the dev path: MASSA_AI_MODEL_PROFILE + regenerate."
1915
- };
1916
- }
1917
1911
  return {
1918
1912
  kind: "refuse",
1919
- reason: "claude/codex marketplace-route installs are refused (in-place bundle rewrite would dirty a checkout " + "and break the drift gate) \u2014 use the dev path: MASSA_AI_MODEL_PROFILE + regenerate."
1913
+ reason: "marketplace-route installs need an explicit, supported host before switching " + "(claude and codex proceed; no other host resolves a live write target yet) \u2014 " + "use the dev path: MASSA_AI_MODEL_PROFILE + regenerate."
1920
1914
  };
1921
1915
  }
1922
1916
  return {
@@ -2110,12 +2104,68 @@ import fs6 from "fs";
2110
2104
  import path10 from "path";
2111
2105
  import os6 from "os";
2112
2106
  import crypto3 from "crypto";
2107
+ import { execFileSync as execFileSync2 } from "child_process";
2113
2108
 
2114
2109
  // ../../packages/shared/dist/profile-switch/claude-marketplace.js
2115
2110
  import fs5 from "fs";
2116
2111
  import os5 from "os";
2117
2112
  import path9 from "path";
2118
2113
  var DEFAULT_PLUGIN_KEY = "massa-ai@massa-ai";
2114
+ function splitPluginKey(pluginKey) {
2115
+ const at = pluginKey.indexOf("@");
2116
+ if (at === -1)
2117
+ return null;
2118
+ const pluginName = pluginKey.slice(0, at);
2119
+ const marketplaceName = pluginKey.slice(at + 1);
2120
+ if (!pluginName || !marketplaceName)
2121
+ return null;
2122
+ return { pluginName, marketplaceName };
2123
+ }
2124
+ function isContainedPath(root, candidate) {
2125
+ const rel = path9.relative(root, candidate);
2126
+ return rel === "" || !rel.startsWith("..") && !path9.isAbsolute(rel);
2127
+ }
2128
+ function resolveDirectorySourceRoot(targetHome, pluginKey) {
2129
+ const split = splitPluginKey(pluginKey);
2130
+ if (!split)
2131
+ return;
2132
+ const { pluginName, marketplaceName } = split;
2133
+ const knownMarketplacesPath = path9.join(targetHome, ".claude", "plugins", "known_marketplaces.json");
2134
+ let known;
2135
+ try {
2136
+ known = JSON.parse(fs5.readFileSync(knownMarketplacesPath, "utf8"));
2137
+ } catch {
2138
+ return;
2139
+ }
2140
+ const entry = known?.[marketplaceName];
2141
+ if (!entry || entry.source?.source !== "directory")
2142
+ return;
2143
+ const installLocation = entry.installLocation;
2144
+ if (!installLocation)
2145
+ return null;
2146
+ const manifestPath = path9.join(installLocation, ".claude-plugin", "marketplace.json");
2147
+ let manifest;
2148
+ try {
2149
+ manifest = JSON.parse(fs5.readFileSync(manifestPath, "utf8"));
2150
+ } catch {
2151
+ return null;
2152
+ }
2153
+ const plugin = manifest.plugins?.find((p) => p?.name === pluginName);
2154
+ const relSource = plugin?.source;
2155
+ if (!relSource)
2156
+ return null;
2157
+ const resolvedInstallLocation = path9.resolve(installLocation);
2158
+ const composed = path9.resolve(resolvedInstallLocation, relSource);
2159
+ if (!isContainedPath(resolvedInstallLocation, composed))
2160
+ return null;
2161
+ try {
2162
+ if (!fs5.existsSync(composed))
2163
+ return null;
2164
+ } catch {
2165
+ return null;
2166
+ }
2167
+ return composed;
2168
+ }
2119
2169
  function selectRecord(records) {
2120
2170
  if (records.length === 0)
2121
2171
  return;
@@ -2135,6 +2185,9 @@ function selectRecord(records) {
2135
2185
  function resolveClaudeMarketplaceRoot(opts = {}) {
2136
2186
  const targetHome = opts.targetHome ?? os5.homedir();
2137
2187
  const pluginKey = opts.pluginKey ?? DEFAULT_PLUGIN_KEY;
2188
+ const directoryResult = resolveDirectorySourceRoot(targetHome, pluginKey);
2189
+ if (directoryResult !== undefined)
2190
+ return directoryResult;
2138
2191
  const registryPath = path9.join(targetHome, ".claude", "plugins", "installed_plugins.json");
2139
2192
  let records;
2140
2193
  try {
@@ -2246,6 +2299,48 @@ function matchesGlob(filename, glob) {
2246
2299
  const suffix = glob.slice(starIdx + 1);
2247
2300
  return filename.length >= prefix.length + suffix.length && filename.startsWith(prefix) && filename.endsWith(suffix);
2248
2301
  }
2302
+ function matchingFileNames(dir, glob) {
2303
+ return fs6.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isFile() && matchesGlob(e.name, glob)).map((e) => e.name);
2304
+ }
2305
+ function detectGitAvailability(dir) {
2306
+ try {
2307
+ const out = execFileSync2("git", ["-C", dir, "rev-parse", "--is-inside-work-tree"], {
2308
+ stdio: ["ignore", "pipe", "ignore"]
2309
+ });
2310
+ return out.toString().trim() === "true" ? "in-repo" : "not-a-repo";
2311
+ } catch (err) {
2312
+ return err.code === "ENOENT" ? "no-git" : "not-a-repo";
2313
+ }
2314
+ }
2315
+ function gitTrackedFileNames(dir, filenames) {
2316
+ if (filenames.length === 0)
2317
+ return new Set;
2318
+ try {
2319
+ const out = execFileSync2("git", ["-C", dir, "ls-files", "--", ...filenames], {
2320
+ stdio: ["ignore", "pipe", "ignore"]
2321
+ });
2322
+ return new Set(out.toString().split(`
2323
+ `).map((line) => line.trim()).filter(Boolean));
2324
+ } catch {
2325
+ return new Set;
2326
+ }
2327
+ }
2328
+ var GUARD_PASS = { blocked: false, unchecked: false };
2329
+ var GUARD_UNCHECKED = { blocked: false, unchecked: true };
2330
+ function checkTrackedPathGuard(activeDir, filenames) {
2331
+ if (filenames.length === 0 || !fs6.existsSync(activeDir))
2332
+ return GUARD_PASS;
2333
+ const availability = detectGitAvailability(activeDir);
2334
+ if (availability === "no-git")
2335
+ return GUARD_UNCHECKED;
2336
+ if (availability === "not-a-repo")
2337
+ return GUARD_PASS;
2338
+ const tracked = gitTrackedFileNames(activeDir, filenames);
2339
+ if (tracked.size === 0)
2340
+ return GUARD_PASS;
2341
+ const offending = filenames.find((name) => tracked.has(name));
2342
+ return { blocked: true, path: path10.join(activeDir, offending), unchecked: false };
2343
+ }
2249
2344
  function assertStateWritable(stateFilePath) {
2250
2345
  const dir = path10.dirname(stateFilePath);
2251
2346
  try {
@@ -2375,12 +2470,27 @@ function switchProfile(opts) {
2375
2470
  rows.push({ host: h.host, status: "switched" });
2376
2471
  continue;
2377
2472
  }
2473
+ const candidateNames = matchingFileNames(h.variantDir, h.layout.activeGlob);
2474
+ const guard = checkTrackedPathGuard(h.layout.activeDir, candidateNames);
2475
+ if (guard.blocked) {
2476
+ rows.push({
2477
+ host: h.host,
2478
+ status: "failed",
2479
+ reason: `refusing to write ${guard.path} \u2014 it is tracked by git and this switch would dirty the checkout`
2480
+ });
2481
+ continue;
2482
+ }
2378
2483
  try {
2379
2484
  const filesChanged = copyVariant(h.host, h.layout, h.variantDir);
2380
2485
  updatePlatform(stateFilePath, h.host, {
2381
2486
  modelProfile: { profile: opts.profile, switchedAt: new Date().toISOString() }
2382
2487
  });
2383
- rows.push({ host: h.host, status: "switched", filesChanged });
2488
+ rows.push({
2489
+ host: h.host,
2490
+ status: "switched",
2491
+ filesChanged,
2492
+ ...guard.unchecked ? { reason: "tracked-path guard could not verify (git unavailable) \u2014 proceeded unchecked" } : {}
2493
+ });
2384
2494
  } catch (err) {
2385
2495
  rows.push({ host: h.host, status: "failed", reason: err.message });
2386
2496
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@massa-ai/opencode-plugin",
3
- "version": "1.52.0",
3
+ "version": "1.54.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.52.0",
28
- "@massa-ai/shared": "^1.52.0"
27
+ "@massa-ai/core": "^1.54.1",
28
+ "@massa-ai/shared": "^1.54.1"
29
29
  },
30
30
  "devDependencies": {
31
31
  "@types/node": "^22.10.5",