oh-my-opencode-slim 0.3.0 → 0.3.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.
@@ -1,10 +1,15 @@
1
1
  import type { ConfigMergeResult, DetectedConfig, InstallConfig } from "./types";
2
2
  export declare function isOpenCodeInstalled(): Promise<boolean>;
3
+ export declare function isTmuxInstalled(): Promise<boolean>;
3
4
  export declare function getOpenCodeVersion(): Promise<string | null>;
4
5
  export declare function fetchLatestVersion(packageName: string): Promise<string | null>;
5
6
  export declare function addPluginToOpenCodeConfig(): Promise<ConfigMergeResult>;
6
7
  export declare function addAuthPlugins(installConfig: InstallConfig): Promise<ConfigMergeResult>;
7
8
  export declare function addProviderConfig(installConfig: InstallConfig): ConfigMergeResult;
9
+ /**
10
+ * Add server configuration to opencode.json for tmux integration
11
+ */
12
+ export declare function addServerConfig(installConfig: InstallConfig): ConfigMergeResult;
8
13
  export declare function generateLiteConfig(installConfig: InstallConfig): Record<string, unknown>;
9
14
  export declare function writeLiteConfig(installConfig: InstallConfig): ConfigMergeResult;
10
15
  export declare function detectCurrentConfig(): DetectedConfig;
package/dist/cli/index.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // src/cli/install.ts
5
- import * as readline from "readline";
5
+ import * as readline from "readline/promises";
6
6
 
7
7
  // src/cli/config-manager.ts
8
8
  import { existsSync, mkdirSync, readFileSync, writeFileSync, statSync } from "fs";
@@ -51,6 +51,18 @@ async function isOpenCodeInstalled() {
51
51
  return false;
52
52
  }
53
53
  }
54
+ async function isTmuxInstalled() {
55
+ try {
56
+ const proc = Bun.spawn(["tmux", "-V"], {
57
+ stdout: "pipe",
58
+ stderr: "pipe"
59
+ });
60
+ await proc.exited;
61
+ return proc.exitCode === 0;
62
+ } catch {
63
+ return false;
64
+ }
65
+ }
54
66
  async function getOpenCodeVersion() {
55
67
  try {
56
68
  const proc = Bun.spawn(["opencode", "--version"], {
@@ -181,6 +193,29 @@ function addProviderConfig(installConfig) {
181
193
  };
182
194
  }
183
195
  }
196
+ function addServerConfig(installConfig) {
197
+ const configPath = getConfigJson();
198
+ try {
199
+ ensureConfigDir();
200
+ let config = parseConfig(configPath) ?? {};
201
+ if (installConfig.hasTmux) {
202
+ const server = config.server ?? {};
203
+ if (server.port === undefined) {
204
+ server.port = 4096;
205
+ }
206
+ config.server = server;
207
+ }
208
+ writeFileSync(configPath, JSON.stringify(config, null, 2) + `
209
+ `);
210
+ return { success: true, configPath };
211
+ } catch (err) {
212
+ return {
213
+ success: false,
214
+ configPath,
215
+ error: `Failed to add server config: ${err}`
216
+ };
217
+ }
218
+ }
184
219
  var MODEL_MAPPINGS = {
185
220
  antigravity: {
186
221
  orchestrator: "google/claude-opus-4-5-thinking",
@@ -215,21 +250,29 @@ var MODEL_MAPPINGS = {
215
250
  };
216
251
  function generateLiteConfig(installConfig) {
217
252
  const baseProvider = installConfig.hasAntigravity ? "antigravity" : installConfig.hasOpenAI ? "openai" : installConfig.hasCerebras ? "cerebras" : null;
218
- if (!baseProvider) {
219
- return { agents: {} };
220
- }
221
- const agents = Object.fromEntries(Object.entries(MODEL_MAPPINGS[baseProvider]).map(([k, v]) => [k, { model: v }]));
222
- if (installConfig.hasAntigravity) {
223
- if (installConfig.hasOpenAI) {
224
- agents["oracle"] = { model: "openai/gpt-5.2-codex" };
225
- }
226
- if (installConfig.hasCerebras) {
253
+ const config = { agents: {} };
254
+ if (baseProvider) {
255
+ const agents = Object.fromEntries(Object.entries(MODEL_MAPPINGS[baseProvider]).map(([k, v]) => [k, { model: v }]));
256
+ if (installConfig.hasAntigravity) {
257
+ if (installConfig.hasOpenAI) {
258
+ agents["oracle"] = { model: "openai/gpt-5.2-codex" };
259
+ }
260
+ if (installConfig.hasCerebras) {
261
+ agents["explore"] = { model: "cerebras/zai-glm-4.6" };
262
+ }
263
+ } else if (installConfig.hasOpenAI && installConfig.hasCerebras) {
227
264
  agents["explore"] = { model: "cerebras/zai-glm-4.6" };
228
265
  }
229
- } else if (installConfig.hasOpenAI && installConfig.hasCerebras) {
230
- agents["explore"] = { model: "cerebras/zai-glm-4.6" };
266
+ config.agents = agents;
267
+ }
268
+ if (installConfig.hasTmux) {
269
+ config.tmux = {
270
+ enabled: true,
271
+ layout: "main-vertical",
272
+ main_pane_size: 60
273
+ };
231
274
  }
232
- return { agents };
275
+ return config;
233
276
  }
234
277
  function writeLiteConfig(installConfig) {
235
278
  const configPath = getLiteConfig();
@@ -252,7 +295,8 @@ function detectCurrentConfig() {
252
295
  isInstalled: false,
253
296
  hasAntigravity: false,
254
297
  hasOpenAI: false,
255
- hasCerebras: false
298
+ hasCerebras: false,
299
+ hasTmux: false
256
300
  };
257
301
  const config = parseConfig(getConfigJson());
258
302
  if (!config)
@@ -262,60 +306,21 @@ function detectCurrentConfig() {
262
306
  result.hasAntigravity = plugins.some((p) => p.startsWith("opencode-antigravity-auth"));
263
307
  const liteConfig = parseConfig(getLiteConfig());
264
308
  if (liteConfig && typeof liteConfig === "object") {
265
- const agents = liteConfig.agents;
309
+ const configObj = liteConfig;
310
+ const agents = configObj.agents;
266
311
  if (agents) {
267
312
  const models = Object.values(agents).map((a) => a?.model).filter(Boolean);
268
313
  result.hasOpenAI = models.some((m) => m?.startsWith("openai/"));
269
314
  result.hasCerebras = models.some((m) => m?.startsWith("cerebras/"));
270
315
  }
316
+ if (configObj.tmux && typeof configObj.tmux === "object") {
317
+ result.hasTmux = configObj.tmux.enabled === true;
318
+ }
271
319
  }
272
320
  return result;
273
321
  }
274
322
 
275
323
  // src/cli/install.ts
276
- var lineReader = null;
277
- var lineBuffer = [];
278
- var lineResolvers = [];
279
- function initLineReader() {
280
- if (lineReader)
281
- return;
282
- lineReader = readline.createInterface({
283
- input: process.stdin,
284
- output: process.stdout,
285
- terminal: process.stdin.isTTY ?? false
286
- });
287
- lineReader.on("line", (line) => {
288
- if (lineResolvers.length > 0) {
289
- const resolve = lineResolvers.shift();
290
- resolve(line);
291
- } else {
292
- lineBuffer.push(line);
293
- }
294
- });
295
- lineReader.on("close", () => {
296
- while (lineResolvers.length > 0) {
297
- const resolve = lineResolvers.shift();
298
- resolve("");
299
- }
300
- });
301
- }
302
- async function readLine() {
303
- initLineReader();
304
- if (lineBuffer.length > 0) {
305
- return lineBuffer.shift();
306
- }
307
- return new Promise((resolve) => {
308
- lineResolvers.push(resolve);
309
- });
310
- }
311
- function closeLineReader() {
312
- if (lineReader) {
313
- lineReader.close();
314
- lineReader = null;
315
- lineBuffer = [];
316
- lineResolvers = [];
317
- }
318
- }
319
324
  var GREEN = "\x1B[32m";
320
325
  var BLUE = "\x1B[34m";
321
326
  var YELLOW = "\x1B[33m";
@@ -333,9 +338,8 @@ var SYMBOLS = {
333
338
  star: `${YELLOW}\u2605${RESET}`
334
339
  };
335
340
  function printHeader(isUpdate) {
336
- const mode = isUpdate ? "Update" : "Install";
337
341
  console.log();
338
- console.log(`${BOLD}oh-my-opencode-slim ${mode}${RESET}`);
342
+ console.log(`${BOLD}oh-my-opencode-slim ${isUpdate ? "Update" : "Install"}${RESET}`);
339
343
  console.log("=".repeat(30));
340
344
  console.log();
341
345
  }
@@ -381,40 +385,35 @@ function formatConfigSummary(config) {
381
385
  lines.push(` ${config.hasAntigravity ? SYMBOLS.check : DIM + "\u25CB" + RESET} Antigravity`);
382
386
  lines.push(` ${config.hasOpenAI ? SYMBOLS.check : DIM + "\u25CB" + RESET} OpenAI`);
383
387
  lines.push(` ${config.hasCerebras ? SYMBOLS.check : DIM + "\u25CB" + RESET} Cerebras`);
388
+ lines.push(` ${config.hasTmux ? SYMBOLS.check : DIM + "\u25CB" + RESET} Tmux Integration`);
384
389
  return lines.join(`
385
390
  `);
386
391
  }
387
- function validateNonTuiArgs(args) {
388
- const requiredArgs = ["antigravity", "openai", "cerebras"];
389
- const errors = requiredArgs.flatMap((key) => {
390
- const value = args[key];
391
- if (value === undefined)
392
- return [`--${key} is required (values: yes, no)`];
393
- if (!["yes", "no"].includes(value))
394
- return [`Invalid --${key} value: ${value} (expected: yes, no)`];
395
- return [];
396
- });
397
- return { valid: errors.length === 0, errors };
392
+ function printAgentModels(config) {
393
+ const liteConfig = generateLiteConfig(config);
394
+ const agents = liteConfig.agents;
395
+ if (!agents || Object.keys(agents).length === 0)
396
+ return;
397
+ console.log(`${BOLD}Agent Model Configuration:${RESET}`);
398
+ console.log();
399
+ const maxAgentLen = Math.max(...Object.keys(agents).map((a) => a.length));
400
+ for (const [agent, info] of Object.entries(agents)) {
401
+ const padding = " ".repeat(maxAgentLen - agent.length);
402
+ console.log(` ${DIM}${agent}${RESET}${padding} ${SYMBOLS.arrow} ${BLUE}${info.model}${RESET}`);
403
+ }
404
+ console.log();
398
405
  }
399
406
  function argsToConfig(args) {
400
407
  return {
401
408
  hasAntigravity: args.antigravity === "yes",
402
409
  hasOpenAI: args.openai === "yes",
403
- hasCerebras: args.cerebras === "yes"
404
- };
405
- }
406
- function detectedToInitialValues(detected) {
407
- return {
408
- antigravity: detected.hasAntigravity ? "yes" : "no",
409
- openai: detected.hasOpenAI ? "yes" : "no",
410
- cerebras: detected.hasCerebras ? "yes" : "no"
410
+ hasCerebras: args.cerebras === "yes",
411
+ hasTmux: args.tmux === "yes"
411
412
  };
412
413
  }
413
- async function askYesNo(promptText, defaultValue = "no") {
414
- const defaultHint = defaultValue === "yes" ? "[Y/n]" : "[y/N]";
415
- const fullPrompt = `${BLUE}${promptText}${RESET} ${defaultHint}: `;
416
- process.stdout.write(fullPrompt);
417
- const answer = (await readLine()).trim().toLowerCase();
414
+ async function askYesNo(rl, prompt, defaultValue = "no") {
415
+ const hint = defaultValue === "yes" ? "[Y/n]" : "[y/N]";
416
+ const answer = (await rl.question(`${BLUE}${prompt}${RESET} ${hint}: `)).trim().toLowerCase();
418
417
  if (answer === "")
419
418
  return defaultValue;
420
419
  if (answer === "y" || answer === "yes")
@@ -423,29 +422,48 @@ async function askYesNo(promptText, defaultValue = "no") {
423
422
  return "no";
424
423
  return defaultValue;
425
424
  }
426
- async function runTuiMode(detected) {
427
- const initial = detectedToInitialValues(detected);
428
- console.log(`${BOLD}Question 1/3:${RESET}`);
429
- const antigravity = await askYesNo("Do you have an Antigravity subscription?", initial.antigravity);
430
- console.log();
431
- console.log(`${BOLD}Question 2/3:${RESET}`);
432
- const openai = await askYesNo("Do you have access to OpenAI API?", initial.openai);
433
- console.log();
434
- console.log(`${BOLD}Question 3/3:${RESET}`);
435
- const cerebras = await askYesNo("Do you have access to Cerebras API?", initial.cerebras);
436
- console.log();
437
- closeLineReader();
438
- return {
439
- hasAntigravity: antigravity === "yes",
440
- hasOpenAI: openai === "yes",
441
- hasCerebras: cerebras === "yes"
442
- };
425
+ async function runInteractiveMode(detected) {
426
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
427
+ const tmuxInstalled = await isTmuxInstalled();
428
+ const totalQuestions = tmuxInstalled ? 4 : 3;
429
+ try {
430
+ console.log(`${BOLD}Question 1/${totalQuestions}:${RESET}`);
431
+ printInfo("The Pantheon is tuned for Antigravity's model routing. Other models work, but results may vary.");
432
+ const antigravity = await askYesNo(rl, "Do you have an Antigravity subscription?", "yes");
433
+ console.log();
434
+ console.log(`${BOLD}Question 2/${totalQuestions}:${RESET}`);
435
+ const openai = await askYesNo(rl, "Do you have access to OpenAI API?", detected.hasOpenAI ? "yes" : "no");
436
+ console.log();
437
+ console.log(`${BOLD}Question 3/${totalQuestions}:${RESET}`);
438
+ const cerebras = await askYesNo(rl, "Do you have access to Cerebras API?", detected.hasCerebras ? "yes" : "no");
439
+ console.log();
440
+ let tmux = "no";
441
+ if (tmuxInstalled) {
442
+ console.log(`${BOLD}Question 4/4:${RESET}`);
443
+ printInfo(`${BOLD}Tmux detected!${RESET} We can enable tmux integration for you.`);
444
+ printInfo("This will spawn new panes for sub-agents, letting you watch them work in real-time.");
445
+ tmux = await askYesNo(rl, "Enable tmux integration?", detected.hasTmux ? "yes" : "no");
446
+ console.log();
447
+ }
448
+ return {
449
+ hasAntigravity: antigravity === "yes",
450
+ hasOpenAI: openai === "yes",
451
+ hasCerebras: cerebras === "yes",
452
+ hasTmux: tmux === "yes"
453
+ };
454
+ } finally {
455
+ rl.close();
456
+ }
443
457
  }
444
458
  async function runInstall(config) {
445
459
  const detected = detectCurrentConfig();
446
460
  const isUpdate = detected.isInstalled;
447
461
  printHeader(isUpdate);
448
- const totalSteps = config.hasAntigravity ? 5 : 3;
462
+ let totalSteps = 3;
463
+ if (config.hasAntigravity)
464
+ totalSteps += 2;
465
+ if (config.hasTmux)
466
+ totalSteps += 1;
449
467
  let step = 1;
450
468
  printStep(step++, totalSteps, "Checking OpenCode installation...");
451
469
  const { ok } = await checkOpenCodeInstalled();
@@ -465,6 +483,12 @@ async function runInstall(config) {
465
483
  if (!handleStepResult(providerResult, "Providers configured"))
466
484
  return 1;
467
485
  }
486
+ if (config.hasTmux) {
487
+ printStep(step++, totalSteps, "Configuring OpenCode HTTP server for tmux...");
488
+ const serverResult = addServerConfig(config);
489
+ if (!handleStepResult(serverResult, "Server configured"))
490
+ return 1;
491
+ }
468
492
  printStep(step++, totalSteps, "Writing oh-my-opencode-slim configuration...");
469
493
  const liteResult = writeLiteConfig(config);
470
494
  if (!handleStepResult(liteResult, "Config written"))
@@ -472,6 +496,7 @@ async function runInstall(config) {
472
496
  console.log();
473
497
  console.log(formatConfigSummary(config));
474
498
  console.log();
499
+ printAgentModels(config);
475
500
  if (!config.hasAntigravity && !config.hasOpenAI && !config.hasCerebras) {
476
501
  printWarning("No providers configured. At least one provider is required.");
477
502
  return 1;
@@ -480,30 +505,40 @@ async function runInstall(config) {
480
505
  console.log();
481
506
  console.log(`${BOLD}Next steps:${RESET}`);
482
507
  console.log();
483
- console.log(` 1. Authenticate with your providers:`);
508
+ let nextStep = 1;
509
+ console.log(` ${nextStep++}. Authenticate with your providers:`);
484
510
  console.log(` ${BLUE}$ opencode auth login${RESET}`);
485
511
  console.log();
486
- console.log(` 2. Start OpenCode:`);
487
- console.log(` ${BLUE}$ opencode${RESET}`);
512
+ if (config.hasTmux) {
513
+ console.log(` ${nextStep++}. Run OpenCode inside tmux:`);
514
+ console.log(` ${BLUE}$ tmux${RESET}`);
515
+ console.log(` ${BLUE}$ opencode${RESET}`);
516
+ } else {
517
+ console.log(` ${nextStep++}. Start OpenCode:`);
518
+ console.log(` ${BLUE}$ opencode${RESET}`);
519
+ }
488
520
  console.log();
489
521
  return 0;
490
522
  }
491
523
  async function install(args) {
492
524
  if (!args.tui) {
493
- const validation = validateNonTuiArgs(args);
494
- if (!validation.valid) {
525
+ const requiredArgs = ["antigravity", "openai", "cerebras", "tmux"];
526
+ const errors = requiredArgs.filter((key) => {
527
+ const value = args[key];
528
+ return value === undefined || !["yes", "no"].includes(value);
529
+ });
530
+ if (errors.length > 0) {
495
531
  printHeader(false);
496
- printError("Validation failed:");
497
- for (const err of validation.errors) {
498
- console.log(` ${SYMBOLS.bullet} ${err}`);
532
+ printError("Missing or invalid arguments:");
533
+ for (const key of errors) {
534
+ console.log(` ${SYMBOLS.bullet} --${key}=<yes|no>`);
499
535
  }
500
536
  console.log();
501
- printInfo("Usage: bunx oh-my-opencode-slim install --no-tui --antigravity=<yes|no> --openai=<yes|no> --cerebras=<yes|no>");
537
+ printInfo("Usage: bunx oh-my-opencode-slim install --no-tui --antigravity=<yes|no> --openai=<yes|no> --cerebras=<yes|no> --tmux=<yes|no>");
502
538
  console.log();
503
539
  return 1;
504
540
  }
505
- const config2 = argsToConfig(args);
506
- return runInstall(config2);
541
+ return runInstall(argsToConfig(args));
507
542
  }
508
543
  const detected = detectCurrentConfig();
509
544
  printHeader(detected.isInstalled);
@@ -512,9 +547,7 @@ async function install(args) {
512
547
  if (!ok)
513
548
  return 1;
514
549
  console.log();
515
- const config = await runTuiMode(detected);
516
- if (!config)
517
- return 1;
550
+ const config = await runInteractiveMode(detected);
518
551
  return runInstall(config);
519
552
  }
520
553
 
@@ -534,6 +567,8 @@ function parseArgs(args) {
534
567
  result.openai = arg.split("=")[1];
535
568
  } else if (arg.startsWith("--cerebras=")) {
536
569
  result.cerebras = arg.split("=")[1];
570
+ } else if (arg.startsWith("--tmux=")) {
571
+ result.tmux = arg.split("=")[1];
537
572
  } else if (arg === "-h" || arg === "--help") {
538
573
  printHelp();
539
574
  process.exit(0);
@@ -551,13 +586,14 @@ Options:
551
586
  --antigravity=yes|no Antigravity subscription (yes/no)
552
587
  --openai=yes|no OpenAI API access (yes/no)
553
588
  --cerebras=yes|no Cerebras API access (yes/no)
589
+ --tmux=yes|no Enable tmux integration (yes/no)
554
590
  --no-tui Non-interactive mode (requires all flags)
555
591
  --skip-auth Skip authentication reminder
556
592
  -h, --help Show this help message
557
593
 
558
594
  Examples:
559
595
  bunx oh-my-opencode-slim install
560
- bunx oh-my-opencode-slim install --no-tui --antigravity=yes --openai=yes --cerebras=no
596
+ bunx oh-my-opencode-slim install --no-tui --antigravity=yes --openai=yes --cerebras=no --tmux=yes
561
597
  `);
562
598
  }
563
599
  async function main() {
@@ -4,12 +4,14 @@ export interface InstallArgs {
4
4
  antigravity?: BooleanArg;
5
5
  openai?: BooleanArg;
6
6
  cerebras?: BooleanArg;
7
+ tmux?: BooleanArg;
7
8
  skipAuth?: boolean;
8
9
  }
9
10
  export interface InstallConfig {
10
11
  hasAntigravity: boolean;
11
12
  hasOpenAI: boolean;
12
13
  hasCerebras: boolean;
14
+ hasTmux: boolean;
13
15
  }
14
16
  export interface ConfigMergeResult {
15
17
  success: boolean;
@@ -21,4 +23,5 @@ export interface DetectedConfig {
21
23
  hasAntigravity: boolean;
22
24
  hasOpenAI: boolean;
23
25
  hasCerebras: boolean;
26
+ hasTmux: boolean;
24
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Lightweight agent orchestration plugin for OpenCode - a slimmed-down fork of oh-my-opencode",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",