@cortexkit/opencode-magic-context 0.2.1 → 0.2.3

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
@@ -25,20 +25,54 @@
25
25
 
26
26
  ---
27
27
 
28
+ ## What is Magic Context?
29
+
30
+ Your agent should never stop working to manage its own context. Magic Context is an OpenCode plugin that handles it entirely in the background:
31
+
32
+ **1.** Transparent context compaction via a background historian — the main agent keeps working while a separate model compresses older conversation. All operations are **cache-aware** and deferred to avoid wasting cached prefixes.
33
+
34
+ **2.** Cross-session project memory — architecture decisions, constraints, and preferences persist across conversations.
35
+
36
+ **3.** Overnight dreamer agent that consolidates, deduplicates, and promotes memories into canonical facts, plus maintains codebase documentation.
37
+
38
+ **4.** On-demand sidekick that augments prompts with relevant project context.
39
+
40
+ ---
41
+
28
42
  ## Get Started
29
43
 
30
- Add to your OpenCode config (`opencode.json` or `opencode.jsonc`):
44
+ ### Quick Setup (Recommended)
31
45
 
32
- ```jsonc
33
- {
34
- "plugins": ["@cortexkit/opencode-magic-context@latest"]
35
- }
46
+ Run the interactive setup wizard — it detects your models, configures everything, and handles compatibility:
47
+
48
+ **macOS / Linux:**
49
+ ```bash
50
+ curl -fsSL https://raw.githubusercontent.com/cortexkit/opencode-magic-context/master/scripts/install.sh | bash
51
+ ```
52
+
53
+ **Windows (PowerShell):**
54
+ ```powershell
55
+ irm https://raw.githubusercontent.com/cortexkit/opencode-magic-context/master/scripts/install.ps1 | iex
56
+ ```
57
+
58
+ **Or run directly (any OS):**
59
+ ```bash
60
+ bunx @cortexkit/opencode-magic-context setup
36
61
  ```
37
62
 
38
- Magic Context conflicts with OpenCode's built-in compaction — the two cannot run together. To disable it:
63
+ The wizard will:
64
+ 1. Check your OpenCode installation and available models
65
+ 2. Add the plugin and disable built-in compaction
66
+ 3. Help you pick models for historian, dreamer, and sidekick
67
+ 4. Handle oh-my-opencode compatibility if needed
68
+
69
+ ### Manual Setup
70
+
71
+ Add to your OpenCode config (`opencode.json` or `opencode.jsonc`):
39
72
 
40
73
  ```jsonc
41
74
  {
75
+ "plugin": ["@cortexkit/opencode-magic-context"],
42
76
  "compaction": {
43
77
  "auto": false,
44
78
  "prune": false
@@ -46,6 +80,8 @@ Magic Context conflicts with OpenCode's built-in compaction — the two cannot r
46
80
  }
47
81
  ```
48
82
 
83
+ > **Why disable compaction?** Magic Context manages context itself — built-in compaction interferes with its cache-aware deferred operations and would cause duplicate compression.
84
+
49
85
  Create `magic-context.jsonc` in your project root, `.opencode/`, or `~/.config/opencode/`:
50
86
 
51
87
  ```jsonc
@@ -63,19 +99,21 @@ Create `magic-context.jsonc` in your project root, `.opencode/`, or `~/.config/o
63
99
 
64
100
  That's it. Everything else has sensible defaults. Project config merges on top of user-wide settings.
65
101
 
66
- ---
102
+ ### Oh-My-OpenCode Users
67
103
 
68
- ## What is Magic Context?
69
-
70
- AI coding agents forget everything the moment a conversation gets long enough. Context windows fill up, old messages get dropped, and the agent loses track of decisions it made twenty minutes ago.
71
-
72
- Magic Context fixes this with a background historian — a separate, lightweight model that compresses older conversation into structured summaries and durable facts while the main agent keeps working. The agent never stops to summarize its own history. It never notices the rewriting happening beneath it.
73
-
74
- Every mutation is **cache-aware**. Drops and rewrites are queued until the provider's cached prefix expires, so you're not paying to throw away work that's already cached.
104
+ If you use [oh-my-opencode](https://github.com/code-yeongyu/oh-my-opencode), disable the hooks that conflict with Magic Context in your `oh-my-opencode.json`:
75
105
 
76
- Across sessions, architecture decisions, constraints, and preferences persist in a **cross-session memory** system. A new conversation starts with everything the previous one learned, without replaying old transcripts.
106
+ ```json
107
+ {
108
+ "disabled_hooks": [
109
+ "context-window-monitor",
110
+ "preemptive-compaction",
111
+ "anthropic-context-window-limit-recovery"
112
+ ]
113
+ }
114
+ ```
77
115
 
78
- And overnight, a **dreamer** agent consolidates, verifies, and improves memories checking them against the actual codebase and merging duplicates into clean canonical facts.
116
+ The setup wizard handles this automatically if it detects an oh-my-opencode config.
79
117
 
80
118
  ---
81
119
 
@@ -0,0 +1,9 @@
1
+ export interface ConfigPaths {
2
+ configDir: string;
3
+ opencodeConfig: string;
4
+ opencodeConfigFormat: "json" | "jsonc" | "none";
5
+ magicContextConfig: string;
6
+ omoConfig: string | null;
7
+ }
8
+ export declare function detectConfigPaths(): ConfigPaths;
9
+ //# sourceMappingURL=config-paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-paths.d.ts","sourceRoot":"","sources":["../../src/cli/config-paths.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,oBAAoB,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;IAChD,kBAAkB,EAAE,MAAM,CAAC;IAC3B,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CAC5B;AA+BD,wBAAgB,iBAAiB,IAAI,WAAW,CA2B/C"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":""}
@@ -0,0 +1,19 @@
1
+ export declare function isOpenCodeInstalled(): boolean;
2
+ export declare function getOpenCodeVersion(): string | null;
3
+ export declare function getAvailableModels(): string[];
4
+ /** Group models by provider for display */
5
+ export declare function groupModelsByProvider(models: string[]): Map<string, string[]>;
6
+ /** Get unique providers from model list */
7
+ export declare function getProviders(models: string[]): string[];
8
+ /** Filter models matching any of the given patterns */
9
+ export declare function filterModels(models: string[], patterns: string[]): string[];
10
+ /**
11
+ * Build a curated model selection list for a given role.
12
+ * Returns models ordered by recommendation priority.
13
+ */
14
+ export declare function buildModelSelection(allModels: string[], role: "historian" | "dreamer" | "sidekick"): {
15
+ label: string;
16
+ value: string;
17
+ recommended?: boolean;
18
+ }[];
19
+ //# sourceMappingURL=opencode-helpers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"opencode-helpers.d.ts","sourceRoot":"","sources":["../../src/cli/opencode-helpers.ts"],"names":[],"mappings":"AAEA,wBAAgB,mBAAmB,IAAI,OAAO,CAO7C;AAED,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAMlD;AAED,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAU7C;AAED,2CAA2C;AAC3C,wBAAgB,qBAAqB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC,CAU7E;AAED,2CAA2C;AAC3C,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CASvD;AAED,uDAAuD;AACvD,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAE3E;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAC/B,SAAS,EAAE,MAAM,EAAE,EACnB,IAAI,EAAE,WAAW,GAAG,SAAS,GAAG,UAAU,GAC3C;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,CA0E3D"}
@@ -0,0 +1,12 @@
1
+ export declare function closePrompts(): void;
2
+ export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
3
+ export declare function selectOne(message: string, options: {
4
+ label: string;
5
+ value: string;
6
+ recommended?: boolean;
7
+ }[]): Promise<string>;
8
+ export declare function selectMultiple(message: string, options: {
9
+ label: string;
10
+ value: string;
11
+ }[]): Promise<string[]>;
12
+ //# sourceMappingURL=prompts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"prompts.d.ts","sourceRoot":"","sources":["../../src/cli/prompts.ts"],"names":[],"mappings":"AAwBA,wBAAgB,YAAY,IAAI,IAAI,CAEnC;AAED,wBAAsB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,UAAU,UAAO,GAAG,OAAO,CAAC,OAAO,CAAC,CAKlF;AAED,wBAAsB,SAAS,CAC3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,OAAO,CAAA;CAAE,EAAE,GACnE,OAAO,CAAC,MAAM,CAAC,CAmBjB;AAED,wBAAsB,cAAc,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,EAAE,GAC5C,OAAO,CAAC,MAAM,EAAE,CAAC,CAcnB"}
@@ -0,0 +1,2 @@
1
+ export declare function runSetup(): Promise<number>;
2
+ //# sourceMappingURL=setup.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/cli/setup.ts"],"names":[],"mappings":"AAmJA,wBAAsB,QAAQ,IAAI,OAAO,CAAC,MAAM,CAAC,CA8KhD"}
package/dist/cli.js ADDED
@@ -0,0 +1,451 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/setup.ts
4
+ import { existsSync as existsSync2, mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
+ import { dirname } from "node:path";
6
+
7
+ // src/cli/config-paths.ts
8
+ import { existsSync } from "node:fs";
9
+ import { homedir } from "node:os";
10
+ import { join } from "node:path";
11
+ function getConfigDir() {
12
+ const envDir = process.env.OPENCODE_CONFIG_DIR?.trim();
13
+ if (envDir)
14
+ return envDir;
15
+ switch (process.platform) {
16
+ case "win32": {
17
+ const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming");
18
+ return join(appData, "opencode");
19
+ }
20
+ case "darwin":
21
+ case "linux":
22
+ default: {
23
+ const xdgConfig = process.env.XDG_CONFIG_HOME || join(homedir(), ".config");
24
+ return join(xdgConfig, "opencode");
25
+ }
26
+ }
27
+ }
28
+ function findOmoConfig(configDir) {
29
+ const locations = [
30
+ join(configDir, "oh-my-opencode.jsonc"),
31
+ join(configDir, "oh-my-opencode.json")
32
+ ];
33
+ for (const loc of locations) {
34
+ if (existsSync(loc))
35
+ return loc;
36
+ }
37
+ return null;
38
+ }
39
+ function detectConfigPaths() {
40
+ const configDir = getConfigDir();
41
+ let opencodeConfig;
42
+ let opencodeConfigFormat;
43
+ const jsoncPath = join(configDir, "opencode.jsonc");
44
+ const jsonPath = join(configDir, "opencode.json");
45
+ if (existsSync(jsoncPath)) {
46
+ opencodeConfig = jsoncPath;
47
+ opencodeConfigFormat = "jsonc";
48
+ } else if (existsSync(jsonPath)) {
49
+ opencodeConfig = jsonPath;
50
+ opencodeConfigFormat = "json";
51
+ } else {
52
+ opencodeConfig = jsonPath;
53
+ opencodeConfigFormat = "none";
54
+ }
55
+ return {
56
+ configDir,
57
+ opencodeConfig,
58
+ opencodeConfigFormat,
59
+ magicContextConfig: join(configDir, "magic-context.jsonc"),
60
+ omoConfig: findOmoConfig(configDir)
61
+ };
62
+ }
63
+
64
+ // src/cli/opencode-helpers.ts
65
+ import { execSync } from "node:child_process";
66
+ function isOpenCodeInstalled() {
67
+ try {
68
+ execSync("opencode --version", { stdio: "pipe" });
69
+ return true;
70
+ } catch {
71
+ return false;
72
+ }
73
+ }
74
+ function getOpenCodeVersion() {
75
+ try {
76
+ return execSync("opencode --version", { stdio: "pipe" }).toString().trim();
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+ function getAvailableModels() {
82
+ try {
83
+ const output = execSync("opencode models", { stdio: "pipe" }).toString().trim();
84
+ return output.split(`
85
+ `).map((l) => l.trim()).filter(Boolean);
86
+ } catch {
87
+ return [];
88
+ }
89
+ }
90
+ function buildModelSelection(allModels, role) {
91
+ const result = [];
92
+ const added = new Set;
93
+ const addIfAvailable = (pattern, recommended = false) => {
94
+ const matches = allModels.filter((m) => m === pattern || m.endsWith(`/${pattern}`));
95
+ for (const m of matches) {
96
+ if (!added.has(m)) {
97
+ added.add(m);
98
+ result.push({
99
+ label: m,
100
+ value: m,
101
+ recommended: recommended && result.length === 0
102
+ });
103
+ }
104
+ }
105
+ };
106
+ if (role === "historian") {
107
+ addIfAvailable("claude-sonnet-4-6", true);
108
+ addIfAvailable("claude-sonnet-4-5");
109
+ addIfAvailable("gemini-3.1-pro");
110
+ addIfAvailable("gpt-5.4");
111
+ addIfAvailable("glm-5");
112
+ addIfAvailable("minimax-m2.7");
113
+ for (const m of allModels.filter((m2) => m2.startsWith("github-copilot/"))) {
114
+ if (!added.has(m)) {
115
+ added.add(m);
116
+ result.push({ label: `${m} (free with Copilot)`, value: m });
117
+ }
118
+ }
119
+ } else if (role === "dreamer") {
120
+ for (const m of allModels.filter((m2) => m2.startsWith("ollama/"))) {
121
+ if (!added.has(m)) {
122
+ added.add(m);
123
+ result.push({ label: `${m} (local)`, value: m, recommended: result.length === 0 });
124
+ }
125
+ }
126
+ addIfAvailable("claude-sonnet-4-6", result.length === 0);
127
+ addIfAvailable("gemini-3-flash");
128
+ addIfAvailable("glm-5");
129
+ addIfAvailable("minimax-m2.7");
130
+ addIfAvailable("gpt-5.4-mini");
131
+ for (const m of allModels.filter((m2) => m2.startsWith("github-copilot/"))) {
132
+ if (!added.has(m)) {
133
+ added.add(m);
134
+ result.push({ label: `${m} (free with Copilot)`, value: m });
135
+ }
136
+ }
137
+ } else if (role === "sidekick") {
138
+ for (const m of allModels.filter((m2) => m2.startsWith("cerebras/"))) {
139
+ if (!added.has(m)) {
140
+ added.add(m);
141
+ result.push({
142
+ label: `${m} (fastest)`,
143
+ value: m,
144
+ recommended: result.length === 0
145
+ });
146
+ }
147
+ }
148
+ addIfAvailable("gpt-5-nano");
149
+ addIfAvailable("gemini-3-flash");
150
+ addIfAvailable("gpt-5.4-mini");
151
+ }
152
+ return result;
153
+ }
154
+
155
+ // src/cli/prompts.ts
156
+ import { createReadStream } from "node:fs";
157
+ import { createInterface } from "node:readline";
158
+ function getInput() {
159
+ if (!process.stdin.isTTY) {
160
+ try {
161
+ return createReadStream("/dev/tty");
162
+ } catch {}
163
+ }
164
+ return process.stdin;
165
+ }
166
+ var rl = createInterface({ input: getInput(), output: process.stdout });
167
+ function ask(question) {
168
+ return new Promise((resolve) => {
169
+ rl.question(question, (answer) => resolve(answer.trim()));
170
+ });
171
+ }
172
+ function closePrompts() {
173
+ rl.close();
174
+ }
175
+ async function confirm(message, defaultYes = true) {
176
+ const hint = defaultYes ? "Y/n" : "y/N";
177
+ const answer = await ask(` ${message} [${hint}] `);
178
+ if (answer === "")
179
+ return defaultYes;
180
+ return answer.toLowerCase().startsWith("y");
181
+ }
182
+ async function selectOne(message, options) {
183
+ console.log(` ${message}`);
184
+ console.log("");
185
+ for (let i = 0;i < options.length; i++) {
186
+ const opt = options[i];
187
+ const rec = opt.recommended ? " (recommended)" : "";
188
+ const num = `${i + 1}`.padStart(3);
189
+ console.log(` ${num}) ${opt.label}${rec}`);
190
+ }
191
+ console.log("");
192
+ while (true) {
193
+ const answer = await ask(" Enter number: ");
194
+ const idx = Number.parseInt(answer, 10) - 1;
195
+ if (idx >= 0 && idx < options.length) {
196
+ return options[idx].value;
197
+ }
198
+ console.log(" Invalid selection, try again.");
199
+ }
200
+ }
201
+
202
+ // src/cli/setup.ts
203
+ var PLUGIN_NAME = "@cortexkit/opencode-magic-context";
204
+ function printStep(step, total, message) {
205
+ console.log(` [${step}/${total}] ${message}`);
206
+ }
207
+ function printSuccess(message) {
208
+ console.log(` ✓ ${message}`);
209
+ }
210
+ function printWarning(message) {
211
+ console.log(` ⚠ ${message}`);
212
+ }
213
+ function printInfo(message) {
214
+ console.log(` → ${message}`);
215
+ }
216
+ function ensureDir(dir) {
217
+ if (!existsSync2(dir)) {
218
+ mkdirSync(dir, { recursive: true });
219
+ }
220
+ }
221
+ function stripJsoncComments(text) {
222
+ return text.replace(/\/\/.*$/gm, "").replace(/\/\*[\s\S]*?\*\//g, "");
223
+ }
224
+ function readJsonc(path) {
225
+ const content = readFileSync(path, "utf-8");
226
+ try {
227
+ return JSON.parse(stripJsoncComments(content));
228
+ } catch {
229
+ return {};
230
+ }
231
+ }
232
+ function writeJsonc(path, data) {
233
+ writeFileSync(path, JSON.stringify(data, null, 2) + `
234
+ `);
235
+ }
236
+ function addPluginToOpenCodeConfig(configPath, format) {
237
+ ensureDir(dirname(configPath));
238
+ if (format === "none") {
239
+ const config2 = {
240
+ plugin: [PLUGIN_NAME],
241
+ compaction: { auto: false, prune: false }
242
+ };
243
+ writeJsonc(configPath, config2);
244
+ return;
245
+ }
246
+ const config = readJsonc(configPath);
247
+ const plugins = config.plugin ?? [];
248
+ const hasPlugin = plugins.some((p) => p === PLUGIN_NAME || p.startsWith(`${PLUGIN_NAME}@`));
249
+ if (!hasPlugin) {
250
+ plugins.push(PLUGIN_NAME);
251
+ config.plugin = plugins;
252
+ }
253
+ const compaction = config.compaction ?? {};
254
+ compaction.auto = false;
255
+ compaction.prune = false;
256
+ config.compaction = compaction;
257
+ writeJsonc(configPath, config);
258
+ }
259
+ function writeMagicContextConfig(configPath, options) {
260
+ const config = {};
261
+ if (options.historianModel) {
262
+ config.historian = { model: options.historianModel };
263
+ }
264
+ if (options.dreamerEnabled) {
265
+ const dreamer = { enabled: true };
266
+ if (options.dreamerModel) {
267
+ dreamer.model = options.dreamerModel;
268
+ }
269
+ config.dreamer = dreamer;
270
+ } else {
271
+ config.dreamer = { enabled: false };
272
+ }
273
+ if (options.sidekickEnabled) {
274
+ const sidekick = { enabled: true };
275
+ if (options.sidekickModel) {
276
+ sidekick.model = options.sidekickModel;
277
+ }
278
+ config.sidekick = sidekick;
279
+ }
280
+ writeJsonc(configPath, config);
281
+ }
282
+ function disableOmoHooks(omoConfigPath) {
283
+ const config = readJsonc(omoConfigPath);
284
+ const disabledHooks = config.disabled_hooks ?? [];
285
+ const hooksToDisable = [
286
+ "context-window-monitor",
287
+ "preemptive-compaction",
288
+ "anthropic-context-window-limit-recovery"
289
+ ];
290
+ for (const hook of hooksToDisable) {
291
+ if (!disabledHooks.includes(hook)) {
292
+ disabledHooks.push(hook);
293
+ }
294
+ }
295
+ config.disabled_hooks = disabledHooks;
296
+ writeJsonc(omoConfigPath, config);
297
+ }
298
+ async function runSetup() {
299
+ console.log("");
300
+ console.log(" ✨ Magic Context — Setup Wizard");
301
+ console.log(" ═══════════════════════════════");
302
+ console.log("");
303
+ const totalSteps = 8;
304
+ let step = 1;
305
+ printStep(step++, totalSteps, "Checking OpenCode installation...");
306
+ const installed = isOpenCodeInstalled();
307
+ if (!installed) {
308
+ printWarning("OpenCode not found on PATH.");
309
+ const shouldContinue = await confirm("Continue setup anyway?", false);
310
+ if (!shouldContinue) {
311
+ console.log("");
312
+ printInfo("Install OpenCode: https://opencode.ai");
313
+ console.log("");
314
+ closePrompts();
315
+ return 1;
316
+ }
317
+ } else {
318
+ const version = getOpenCodeVersion();
319
+ printSuccess(`OpenCode ${version ?? ""} detected`);
320
+ }
321
+ console.log("");
322
+ printStep(step++, totalSteps, "Fetching available models...");
323
+ const allModels = installed ? getAvailableModels() : [];
324
+ if (allModels.length > 0) {
325
+ printSuccess(`Found ${allModels.length} models`);
326
+ } else {
327
+ printWarning("No models found (OpenCode not installed or no providers configured)");
328
+ printInfo("You can configure models manually in magic-context.jsonc later");
329
+ }
330
+ console.log("");
331
+ const paths = detectConfigPaths();
332
+ printStep(step++, totalSteps, "Configuring OpenCode...");
333
+ addPluginToOpenCodeConfig(paths.opencodeConfig, paths.opencodeConfigFormat);
334
+ printSuccess(`Plugin added to ${paths.opencodeConfig}`);
335
+ printInfo("Disabled built-in compaction (auto=false, prune=false)");
336
+ printInfo("Magic Context handles context management — built-in compaction would interfere");
337
+ console.log("");
338
+ printStep(step++, totalSteps, "Configure historian (background context compressor)...");
339
+ console.log("");
340
+ let historianModel = null;
341
+ if (allModels.length > 0) {
342
+ const historianOptions = buildModelSelection(allModels, "historian");
343
+ if (historianOptions.length > 0) {
344
+ historianModel = await selectOne("Select a model for historian (compresses conversation history):", historianOptions);
345
+ printSuccess(`Historian model: ${historianModel}`);
346
+ } else {
347
+ printInfo("No suitable historian models found. Using built-in fallback chain.");
348
+ }
349
+ } else {
350
+ printInfo("Skipping model selection (no models available). Using built-in fallback chain.");
351
+ }
352
+ console.log("");
353
+ printStep(step++, totalSteps, "Configure dreamer (overnight memory maintenance)...");
354
+ console.log("");
355
+ printInfo("The dreamer runs overnight to consolidate, verify, and maintain project memories.");
356
+ const dreamerEnabled = await confirm("Enable dreamer?", true);
357
+ let dreamerModel = null;
358
+ if (dreamerEnabled && allModels.length > 0) {
359
+ const dreamerOptions = buildModelSelection(allModels, "dreamer");
360
+ if (dreamerOptions.length > 0) {
361
+ dreamerModel = await selectOne("Select a model for dreamer (runs in background, local LLMs ideal):", dreamerOptions);
362
+ printSuccess(`Dreamer model: ${dreamerModel}`);
363
+ } else {
364
+ printInfo("No suitable dreamer models found. Using built-in fallback chain.");
365
+ }
366
+ } else if (dreamerEnabled) {
367
+ printInfo("Using built-in fallback chain for dreamer.");
368
+ }
369
+ console.log("");
370
+ printStep(step++, totalSteps, "Configure sidekick (on-demand prompt augmentation)...");
371
+ console.log("");
372
+ printInfo("Sidekick augments prompts with project context via /ctx-aug command.");
373
+ const sidekickEnabled = await confirm("Enable sidekick?", false);
374
+ let sidekickModel = null;
375
+ if (sidekickEnabled && allModels.length > 0) {
376
+ const sidekickOptions = buildModelSelection(allModels, "sidekick");
377
+ if (sidekickOptions.length > 0) {
378
+ sidekickModel = await selectOne("Select a model for sidekick (fast models preferred):", sidekickOptions);
379
+ printSuccess(`Sidekick model: ${sidekickModel}`);
380
+ } else {
381
+ printInfo("No suitable sidekick models found. Using built-in fallback chain.");
382
+ }
383
+ } else if (sidekickEnabled) {
384
+ printInfo("Using built-in fallback chain for sidekick.");
385
+ }
386
+ console.log("");
387
+ writeMagicContextConfig(paths.magicContextConfig, {
388
+ historianModel,
389
+ dreamerEnabled,
390
+ dreamerModel,
391
+ sidekickEnabled,
392
+ sidekickModel
393
+ });
394
+ printSuccess(`Config written to ${paths.magicContextConfig}`);
395
+ console.log("");
396
+ printStep(step++, totalSteps, "Checking for oh-my-opencode...");
397
+ if (paths.omoConfig) {
398
+ printWarning(`Found oh-my-opencode config: ${paths.omoConfig}`);
399
+ printInfo("The following hooks may interfere with Magic Context:");
400
+ console.log(" • context-window-monitor");
401
+ console.log(" • preemptive-compaction");
402
+ console.log(" • anthropic-context-window-limit-recovery");
403
+ console.log("");
404
+ const shouldDisable = await confirm("Disable these hooks in oh-my-opencode?", true);
405
+ if (shouldDisable) {
406
+ disableOmoHooks(paths.omoConfig);
407
+ printSuccess("Hooks disabled in oh-my-opencode config");
408
+ } else {
409
+ printWarning("Skipped — you may experience context management conflicts");
410
+ }
411
+ } else {
412
+ printSuccess("No oh-my-opencode config found (no conflicts)");
413
+ }
414
+ console.log("");
415
+ console.log(" ═══════════════════════════════");
416
+ console.log(" ✨ Setup complete!");
417
+ console.log("");
418
+ console.log(" What was configured:");
419
+ console.log(` • Plugin: ${PLUGIN_NAME}`);
420
+ console.log(` • Compaction: disabled (Magic Context manages context)`);
421
+ if (historianModel)
422
+ console.log(` • Historian: ${historianModel}`);
423
+ if (dreamerEnabled)
424
+ console.log(` • Dreamer: enabled${dreamerModel ? ` (${dreamerModel})` : ""}`);
425
+ if (sidekickEnabled)
426
+ console.log(` • Sidekick: enabled${sidekickModel ? ` (${sidekickModel})` : ""}`);
427
+ console.log("");
428
+ console.log(" Run 'opencode' to start!");
429
+ console.log("");
430
+ closePrompts();
431
+ return 0;
432
+ }
433
+
434
+ // src/cli/index.ts
435
+ var command = process.argv[2];
436
+ if (command === "setup") {
437
+ runSetup().then((code) => process.exit(code));
438
+ } else {
439
+ console.log("");
440
+ console.log(" Magic Context CLI");
441
+ console.log(" ─────────────────");
442
+ console.log("");
443
+ console.log(" Commands:");
444
+ console.log(" setup Interactive setup wizard");
445
+ console.log("");
446
+ console.log(" Usage:");
447
+ console.log(" bunx @cortexkit/opencode-magic-context setup");
448
+ console.log(" npx @cortexkit/opencode-magic-context setup");
449
+ console.log("");
450
+ process.exit(command ? 1 : 0);
451
+ }
package/package.json CHANGED
@@ -1,10 +1,13 @@
1
1
  {
2
2
  "name": "@cortexkit/opencode-magic-context",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "OpenCode plugin for Magic Context — cross-session memory and context management",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
+ "bin": {
9
+ "opencode-magic-context": "dist/cli.js"
10
+ },
8
11
  "author": "ualtinok",
9
12
  "license": "MIT",
10
13
  "keywords": [
@@ -23,10 +26,11 @@
23
26
  },
24
27
  "files": [
25
28
  "dist",
29
+ "scripts/install.sh",
26
30
  "README.md"
27
31
  ],
28
32
  "scripts": {
29
- "build": "bun build src/index.ts --outdir dist --target bun --format esm --external @huggingface/transformers --external @opencode-ai/plugin && tsc --emitDeclarationOnly",
33
+ "build": "bun build src/index.ts --outdir dist --target bun --format esm --external @huggingface/transformers --external @opencode-ai/plugin && bun build src/cli/index.ts --outfile dist/cli.js --target node --format esm && tsc --emitDeclarationOnly",
30
34
  "typecheck": "tsc --noEmit && tsc -p tsconfig.scripts.json",
31
35
  "test": "bun test",
32
36
  "lint": "biome check .",
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Magic Context — Interactive Setup
5
+ # Usage: curl -fsSL https://raw.githubusercontent.com/cortexkit/opencode-magic-context/master/scripts/install.sh | bash
6
+
7
+ PACKAGE="@cortexkit/opencode-magic-context"
8
+
9
+ main() {
10
+ echo ""
11
+ echo " ✨ Magic Context — Setup"
12
+ echo " ────────────────────────"
13
+ echo ""
14
+
15
+ # Detect runtime
16
+ if command -v bun &>/dev/null; then
17
+ echo " → Using bun"
18
+ echo ""
19
+ bunx "$PACKAGE" setup </dev/tty
20
+ elif command -v npx &>/dev/null; then
21
+ echo " → Using npx"
22
+ echo ""
23
+ npx -y "$PACKAGE" setup </dev/tty
24
+ else
25
+ echo " ✗ Neither bun nor npx found."
26
+ echo ""
27
+ echo " Install one of:"
28
+ echo " • bun: curl -fsSL https://bun.sh/install | bash"
29
+ echo " • node: https://nodejs.org"
30
+ echo ""
31
+ exit 1
32
+ fi
33
+ }
34
+
35
+ main "$@"