@cortexkit/opencode-magic-context 0.8.9 → 0.8.11

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/README.md CHANGED
@@ -333,7 +333,7 @@ On startup, Magic Context checks for common configuration problems — OpenCode'
333
333
  A companion desktop app for browsing and managing Magic Context state outside of OpenCode.
334
334
 
335
335
  <p align="center">
336
- <a href="https://github.com/cortexkit/opencode-magic-context/releases/tag/dashboard-v0.2.4"><strong>⬇️ Download for macOS · Windows · Linux</strong></a></p>
336
+ <a href="https://github.com/cortexkit/opencode-magic-context/releases/tag/dashboard-v0.2.6"><strong>⬇️ Download for macOS · Windows · Linux</strong></a></p>
337
337
 
338
338
  **Features:**
339
339
  - **Memory Browser** — search, filter, and edit project memories with category and project filtering
@@ -1 +1 @@
1
- {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AA4MA,wBAAsB,SAAS,CAC3B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GACnD,OAAO,CAAC,MAAM,CAAC,CA2MjB"}
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AA4MA,wBAAsB,SAAS,CAC3B,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAO,GACnD,OAAO,CAAC,MAAM,CAAC,CAuNjB"}
package/dist/cli.js CHANGED
@@ -8393,15 +8393,20 @@ function ensureTuiPluginEntry() {
8393
8393
  config = import_comment_json.parse(raw) ?? {};
8394
8394
  }
8395
8395
  const plugins = Array.isArray(config.plugin) ? config.plugin.filter((p) => typeof p === "string") : [];
8396
- if (plugins.some((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`))) {
8397
- return false;
8396
+ const existingIdx = plugins.findIndex((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`));
8397
+ if (existingIdx >= 0) {
8398
+ if (plugins[existingIdx] === PLUGIN_ENTRY) {
8399
+ return false;
8400
+ }
8401
+ plugins[existingIdx] = PLUGIN_ENTRY;
8402
+ } else {
8403
+ plugins.push(PLUGIN_ENTRY);
8398
8404
  }
8399
- plugins.push(PLUGIN_ENTRY);
8400
8405
  config.plugin = plugins;
8401
8406
  mkdirSync2(dirname2(configPath), { recursive: true });
8402
8407
  writeFileSync2(configPath, `${import_comment_json.stringify(config, null, 2)}
8403
8408
  `);
8404
- log(`[magic-context] added TUI plugin entry to ${configPath}`);
8409
+ log(`[magic-context] updated TUI plugin entry in ${configPath}`);
8405
8410
  return true;
8406
8411
  } catch (error) {
8407
8412
  log(`[magic-context] failed to update tui.json: ${error instanceof Error ? error.message : String(error)}`);
@@ -10158,13 +10163,22 @@ async function runDoctor(options = {}) {
10158
10163
  const raw = readFileSync5(paths.opencodeConfig, "utf-8");
10159
10164
  const config = import_comment_json3.parse(raw);
10160
10165
  const plugins = Array.isArray(config?.plugin) ? config.plugin : [];
10161
- const hasPlugin = plugins.some((p) => typeof p === "string" && (p === PLUGIN_NAME3 || p.startsWith(`${PLUGIN_NAME3}@`) || p.includes("opencode-magic-context")));
10166
+ const pluginList = plugins.filter((p) => typeof p === "string");
10167
+ const existingIdx = pluginList.findIndex((p) => p === PLUGIN_NAME3 || p.startsWith(`${PLUGIN_NAME3}@`) || p.includes("opencode-magic-context"));
10162
10168
  const configName = paths.opencodeConfigFormat === "jsonc" ? "opencode.jsonc" : "opencode.json";
10163
- if (hasPlugin) {
10169
+ if (existingIdx >= 0 && pluginList[existingIdx] === PLUGIN_ENTRY_WITH_VERSION2) {
10164
10170
  R2.success(`Plugin registered in ${configName}`);
10171
+ } else if (existingIdx >= 0) {
10172
+ const oldEntry = pluginList[existingIdx];
10173
+ pluginList[existingIdx] = PLUGIN_ENTRY_WITH_VERSION2;
10174
+ config.plugin = pluginList;
10175
+ writeFileSync4(paths.opencodeConfig, `${import_comment_json3.stringify(config, null, 2)}
10176
+ `);
10177
+ R2.success(`Upgraded plugin entry in ${configName}: ${oldEntry} → ${PLUGIN_ENTRY_WITH_VERSION2}`);
10178
+ fixed++;
10165
10179
  } else {
10166
- const updatedPlugins = [...plugins, PLUGIN_ENTRY_WITH_VERSION2];
10167
- config.plugin = updatedPlugins;
10180
+ pluginList.push(PLUGIN_ENTRY_WITH_VERSION2);
10181
+ config.plugin = pluginList;
10168
10182
  writeFileSync4(paths.opencodeConfig, `${import_comment_json3.stringify(config, null, 2)}
10169
10183
  `);
10170
10184
  R2.success(`Added plugin to ${configName}`);
package/dist/index.js CHANGED
@@ -16916,7 +16916,7 @@ function isThinkingPart(part) {
16916
16916
  var encoder, TAG_PREFIX_REGEX;
16917
16917
  var init_tag_content_primitives = __esm(() => {
16918
16918
  encoder = new TextEncoder;
16919
- TAG_PREFIX_REGEX = /^\u00A7\d+\u00A7\s*/;
16919
+ TAG_PREFIX_REGEX = /^(?:\u00A7\d+\u00A7\s*)+/;
16920
16920
  });
16921
16921
 
16922
16922
  // src/hooks/magic-context/tag-part-guards.ts
@@ -28223,15 +28223,20 @@ function ensureTuiPluginEntry() {
28223
28223
  config2 = import_comment_json.parse(raw) ?? {};
28224
28224
  }
28225
28225
  const plugins = Array.isArray(config2.plugin) ? config2.plugin.filter((p) => typeof p === "string") : [];
28226
- if (plugins.some((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`))) {
28227
- return false;
28226
+ const existingIdx = plugins.findIndex((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`));
28227
+ if (existingIdx >= 0) {
28228
+ if (plugins[existingIdx] === PLUGIN_ENTRY) {
28229
+ return false;
28230
+ }
28231
+ plugins[existingIdx] = PLUGIN_ENTRY;
28232
+ } else {
28233
+ plugins.push(PLUGIN_ENTRY);
28228
28234
  }
28229
- plugins.push(PLUGIN_ENTRY);
28230
28235
  config2.plugin = plugins;
28231
28236
  mkdirSync5(dirname2(configPath), { recursive: true });
28232
28237
  writeFileSync3(configPath, `${import_comment_json.stringify(config2, null, 2)}
28233
28238
  `);
28234
- log(`[magic-context] added TUI plugin entry to ${configPath}`);
28239
+ log(`[magic-context] updated TUI plugin entry in ${configPath}`);
28235
28240
  return true;
28236
28241
  } catch (error48) {
28237
28242
  log(`[magic-context] failed to update tui.json: ${error48 instanceof Error ? error48.message : String(error48)}`);
@@ -30221,28 +30226,66 @@ function getModelsJsonPath() {
30221
30226
  }
30222
30227
  return join11(cacheBase, "opencode", "models.json");
30223
30228
  }
30229
+ function getOpencodeConfigPath() {
30230
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
30231
+ const configDir = envDir ? envDir : platform2() === "win32" ? join11(homedir5(), ".config", "opencode") : join11(process.env.XDG_CONFIG_HOME || join11(homedir5(), ".config"), "opencode");
30232
+ const jsonc = join11(configDir, "opencode.jsonc");
30233
+ if (existsSync5(jsonc))
30234
+ return jsonc;
30235
+ const json2 = join11(configDir, "opencode.json");
30236
+ if (existsSync5(json2))
30237
+ return json2;
30238
+ return null;
30239
+ }
30224
30240
  function loadModelsDevLimits() {
30225
30241
  const limits = new Map;
30226
- const filePath = getModelsJsonPath();
30242
+ const modelsJsonPath = getModelsJsonPath();
30227
30243
  try {
30228
- if (!existsSync5(filePath)) {
30229
- return limits;
30230
- }
30231
- const raw = readFileSync4(filePath, "utf-8");
30232
- const data = JSON.parse(raw);
30233
- for (const [providerId, provider2] of Object.entries(data)) {
30234
- if (!provider2?.models || typeof provider2.models !== "object")
30235
- continue;
30236
- for (const [modelId, model] of Object.entries(provider2.models)) {
30237
- const context = model?.limit?.context;
30238
- if (typeof context === "number" && context > 0) {
30239
- limits.set(`${providerId}/${modelId}`, context);
30244
+ if (existsSync5(modelsJsonPath)) {
30245
+ const raw = readFileSync4(modelsJsonPath, "utf-8");
30246
+ const data = JSON.parse(raw);
30247
+ for (const [providerId, provider2] of Object.entries(data)) {
30248
+ if (!provider2?.models || typeof provider2.models !== "object")
30249
+ continue;
30250
+ for (const [modelId, model] of Object.entries(provider2.models)) {
30251
+ const context = model?.limit?.context;
30252
+ if (typeof context === "number" && context > 0) {
30253
+ limits.set(`${providerId}/${modelId}`, context);
30254
+ const modes = model?.experimental?.modes;
30255
+ if (modes && typeof modes === "object") {
30256
+ for (const mode of Object.keys(modes)) {
30257
+ limits.set(`${providerId}/${modelId}-${mode}`, context);
30258
+ }
30259
+ }
30260
+ }
30240
30261
  }
30241
30262
  }
30242
30263
  }
30243
30264
  } catch (error48) {
30244
30265
  sessionLog("global", "models-dev-cache: failed to read models.json:", error48 instanceof Error ? error48.message : String(error48));
30245
30266
  }
30267
+ try {
30268
+ const configPath = getOpencodeConfigPath();
30269
+ if (configPath && existsSync5(configPath)) {
30270
+ let raw = readFileSync4(configPath, "utf-8");
30271
+ raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) => match.startsWith('"') ? match : "");
30272
+ const config2 = JSON.parse(raw);
30273
+ if (config2.provider && typeof config2.provider === "object") {
30274
+ for (const [providerId, provider2] of Object.entries(config2.provider)) {
30275
+ if (!provider2?.models || typeof provider2.models !== "object")
30276
+ continue;
30277
+ for (const [modelId, model] of Object.entries(provider2.models)) {
30278
+ const context = model?.limit?.context;
30279
+ if (typeof context === "number" && context > 0) {
30280
+ limits.set(`${providerId}/${modelId}`, context);
30281
+ }
30282
+ }
30283
+ }
30284
+ }
30285
+ }
30286
+ } catch (error48) {
30287
+ sessionLog("global", "models-dev-cache: failed to read opencode config for custom models:", error48 instanceof Error ? error48.message : String(error48));
30288
+ }
30246
30289
  return limits;
30247
30290
  }
30248
30291
  function getModelsDevContextLimit(providerID, modelID) {
@@ -1 +1 @@
1
- {"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AAuEA;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAShG;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
1
+ {"version":3,"file":"models-dev-cache.d.ts","sourceRoot":"","sources":["../../src/shared/models-dev-cache.ts"],"names":[],"mappings":"AA6IA;;;;GAIG;AACH,wBAAgB,wBAAwB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAShG;AAED,8CAA8C;AAC9C,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
@@ -1 +1 @@
1
- {"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CA+B9C"}
1
+ {"version":3,"file":"tui-config.d.ts","sourceRoot":"","sources":["../../src/shared/tui-config.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAqBH;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAsC9C"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.8.9",
3
+ "version": "0.8.11",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
@@ -34,27 +34,58 @@ function getModelsJsonPath(): string {
34
34
  return join(cacheBase, "opencode", "models.json");
35
35
  }
36
36
 
37
+ function getOpencodeConfigPath(): string | null {
38
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
39
+ const configDir = envDir
40
+ ? envDir
41
+ : platform() === "win32"
42
+ ? join(homedir(), ".config", "opencode")
43
+ : join(process.env.XDG_CONFIG_HOME || join(homedir(), ".config"), "opencode");
44
+
45
+ // Check jsonc first, then json (matches OpenCode's own lookup order)
46
+ const jsonc = join(configDir, "opencode.jsonc");
47
+ if (existsSync(jsonc)) return jsonc;
48
+ const json = join(configDir, "opencode.json");
49
+ if (existsSync(json)) return json;
50
+ return null;
51
+ }
52
+
37
53
  function loadModelsDevLimits(): Map<string, number> {
38
54
  const limits = new Map<string, number>();
39
- const filePath = getModelsJsonPath();
40
55
 
56
+ // 1. Load from OpenCode's models.dev cache (base layer — all known public models)
57
+ const modelsJsonPath = getModelsJsonPath();
41
58
  try {
42
- if (!existsSync(filePath)) {
43
- return limits;
44
- }
59
+ if (existsSync(modelsJsonPath)) {
60
+ const raw = readFileSync(modelsJsonPath, "utf-8");
61
+ const data = JSON.parse(raw) as Record<
62
+ string,
63
+ {
64
+ models?: Record<
65
+ string,
66
+ {
67
+ limit?: { context?: number };
68
+ experimental?: { modes?: Record<string, unknown> };
69
+ }
70
+ >;
71
+ }
72
+ >;
45
73
 
46
- const raw = readFileSync(filePath, "utf-8");
47
- const data = JSON.parse(raw) as Record<
48
- string,
49
- { models?: Record<string, { limit?: { context?: number } }> }
50
- >;
51
-
52
- for (const [providerId, provider] of Object.entries(data)) {
53
- if (!provider?.models || typeof provider.models !== "object") continue;
54
- for (const [modelId, model] of Object.entries(provider.models)) {
55
- const context = model?.limit?.context;
56
- if (typeof context === "number" && context > 0) {
57
- limits.set(`${providerId}/${modelId}`, context);
74
+ for (const [providerId, provider] of Object.entries(data)) {
75
+ if (!provider?.models || typeof provider.models !== "object") continue;
76
+ for (const [modelId, model] of Object.entries(provider.models)) {
77
+ const context = model?.limit?.context;
78
+ if (typeof context === "number" && context > 0) {
79
+ limits.set(`${providerId}/${modelId}`, context);
80
+ // OpenCode creates derived model IDs from experimental.modes
81
+ // e.g. gpt-5.4 + modes.fast gpt-5.4-fast (inherits parent limit)
82
+ const modes = model?.experimental?.modes;
83
+ if (modes && typeof modes === "object") {
84
+ for (const mode of Object.keys(modes)) {
85
+ limits.set(`${providerId}/${modelId}-${mode}`, context);
86
+ }
87
+ }
88
+ }
58
89
  }
59
90
  }
60
91
  }
@@ -66,6 +97,45 @@ function loadModelsDevLimits(): Map<string, number> {
66
97
  );
67
98
  }
68
99
 
100
+ // 2. Overlay custom provider models from OpenCode config (higher priority).
101
+ // Users define custom/proxy models via provider.<id>.models.<name>.limit.context
102
+ // in opencode.json(c). These override models.dev entries for the same key.
103
+ try {
104
+ const configPath = getOpencodeConfigPath();
105
+ if (configPath && existsSync(configPath)) {
106
+ let raw = readFileSync(configPath, "utf-8");
107
+ // Strip JSONC single-line comments while preserving // inside strings.
108
+ // Match strings first (to skip them), then match comments outside strings.
109
+ raw = raw.replace(/"(?:[^"\\]|\\.)*"|\/\/.*$/gm, (match) =>
110
+ match.startsWith('"') ? match : "",
111
+ );
112
+ const config = JSON.parse(raw) as {
113
+ provider?: Record<
114
+ string,
115
+ { models?: Record<string, { limit?: { context?: number } }> }
116
+ >;
117
+ };
118
+
119
+ if (config.provider && typeof config.provider === "object") {
120
+ for (const [providerId, provider] of Object.entries(config.provider)) {
121
+ if (!provider?.models || typeof provider.models !== "object") continue;
122
+ for (const [modelId, model] of Object.entries(provider.models)) {
123
+ const context = model?.limit?.context;
124
+ if (typeof context === "number" && context > 0) {
125
+ limits.set(`${providerId}/${modelId}`, context);
126
+ }
127
+ }
128
+ }
129
+ }
130
+ }
131
+ } catch (error) {
132
+ sessionLog(
133
+ "global",
134
+ "models-dev-cache: failed to read opencode config for custom models:",
135
+ error instanceof Error ? error.message : String(error),
136
+ );
137
+ }
138
+
69
139
  return limits;
70
140
  }
71
141
 
@@ -40,16 +40,23 @@ export function ensureTuiPluginEntry(): boolean {
40
40
  ? config.plugin.filter((p): p is string => typeof p === "string")
41
41
  : [];
42
42
 
43
- if (plugins.some((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`))) {
44
- return false; // Already present
43
+ const existingIdx = plugins.findIndex(
44
+ (p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`),
45
+ );
46
+ if (existingIdx >= 0) {
47
+ if (plugins[existingIdx] === PLUGIN_ENTRY) {
48
+ return false; // Already @latest
49
+ }
50
+ // Upgrade pinned version to @latest
51
+ plugins[existingIdx] = PLUGIN_ENTRY;
52
+ } else {
53
+ plugins.push(PLUGIN_ENTRY);
45
54
  }
46
-
47
- plugins.push(PLUGIN_ENTRY);
48
55
  config.plugin = plugins;
49
56
 
50
57
  mkdirSync(dirname(configPath), { recursive: true });
51
58
  writeFileSync(configPath, `${stringify(config, null, 2)}\n`);
52
- log(`[magic-context] added TUI plugin entry to ${configPath}`);
59
+ log(`[magic-context] updated TUI plugin entry in ${configPath}`);
53
60
  return true;
54
61
  } catch (error) {
55
62
  log(