oh-my-opencode-slim 1.0.4 → 1.0.6

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
@@ -37,9 +37,14 @@ Install and configure oh-my-opencode-slim: https://raw.githubusercontent.com/alv
37
37
  bunx oh-my-opencode-slim@latest install
38
38
  ```
39
39
 
40
+ The installer also registers the companion TUI plugin in OpenCode's
41
+ `tui.json`, which adds a small sidebar showing specialist-agent status plus
42
+ active/reusable task sessions. For manual setups, add `oh-my-opencode-slim` to
43
+ the `plugin` array in both `opencode.json` and `tui.json`.
44
+
40
45
  ### Getting Started
41
46
 
42
- The installer generates an OpenAI preset by default, using `openai/gpt-5.5` for the higher-judgment agents and `openai/gpt-5.4-mini` for the faster scoped agents.
47
+ The installer generates both OpenAI and OpenCode Go presets, with OpenAI active by default. OpenAI uses `openai/gpt-5.5` for the higher-judgment agents and `openai/gpt-5.4-mini` for the faster scoped agents. To make OpenCode Go active during install, run `bunx oh-my-opencode-slim@latest install --preset=opencode-go`.
43
48
 
44
49
  Then:
45
50
 
@@ -60,7 +65,7 @@ Then:
60
65
  > [!TIP]
61
66
  > Want to understand how automatic delegation works in practice? Review the **[Orchestrator prompt](https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/src/agents/orchestrator.ts#L28)** — it contains the delegation rules, specialist routing logic, and the thresholds for when the main agent should hand work off to subagents.
62
67
 
63
- The default generated configuration looks like this:
68
+ The default generated configuration includes both `openai` and `opencode-go` presets. Abbreviated:
64
69
 
65
70
  ```jsonc
66
71
  {
@@ -85,7 +90,7 @@ want to customize how many resumable child-agent sessions are remembered.
85
90
 
86
91
  ### For Alternative Providers
87
92
 
88
- To use Kimi, GitHub Copilot, ZAI Coding Plan, or a mixed-provider setup, use **[Configuration](docs/configuration.md)** for the full reference. If you want a ready-made starting point, check the **[Author's Preset](docs/authors-preset.md)** and **[$30 Preset](docs/thirty-dollars-preset.md)** - the `$30` preset is the best cheap setup.
93
+ To use OpenCode Go, Kimi, GitHub Copilot, ZAI Coding Plan, or a mixed-provider setup, use **[Configuration](docs/configuration.md)** for the full reference. If you want a ready-made starting point, check the **[Author's Preset](docs/authors-preset.md)** and **[$30 Preset](docs/thirty-dollars-preset.md)** - the `$30` preset is the best cheap setup.
89
94
 
90
95
  The configuration guide also covers custom subagents via `agents.<name>`, where
91
96
  you can define both a normal `prompt` and an `orchestratorPrompt` block for
@@ -16,6 +16,7 @@ export declare function parseConfig(path: string): {
16
16
  */
17
17
  export declare function writeConfig(configPath: string, config: OpenCodeConfig): void;
18
18
  export declare function addPluginToOpenCodeConfig(): Promise<ConfigMergeResult>;
19
+ export declare function addPluginToOpenCodeTuiConfig(): Promise<ConfigMergeResult>;
19
20
  export declare function writeLiteConfig(installConfig: InstallConfig, targetPath?: string): ConfigMergeResult;
20
21
  export declare function disableDefaultAgents(): ConfigMergeResult;
21
22
  export declare function enableLspByDefault(): ConfigMergeResult;
package/dist/cli/index.js CHANGED
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env bun
2
2
  // @bun
3
+ import { createRequire } from "node:module";
4
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
3
5
 
4
6
  // src/cli/install.ts
5
7
  import { existsSync as existsSync4 } from "node:fs";
8
+ import { createInterface } from "node:readline/promises";
6
9
 
7
10
  // src/cli/config-io.ts
8
11
  import {
@@ -27,6 +30,10 @@ function getCustomOpenCodeConfigDir() {
27
30
  const configDir = process.env.OPENCODE_CONFIG_DIR?.trim();
28
31
  return configDir || undefined;
29
32
  }
33
+ function getCustomTuiConfigPath() {
34
+ const configPath = process.env.OPENCODE_TUI_CONFIG?.trim();
35
+ return configPath || undefined;
36
+ }
30
37
  function getConfigDir() {
31
38
  const customConfigDir = getCustomOpenCodeConfigDir();
32
39
  if (customConfigDir) {
@@ -50,6 +57,15 @@ function getLiteConfig() {
50
57
  function getLiteConfigJsonc() {
51
58
  return join(getConfigDir(), "oh-my-opencode-slim.jsonc");
52
59
  }
60
+ function getTuiConfig() {
61
+ const customConfigPath = getCustomTuiConfigPath();
62
+ if (customConfigPath)
63
+ return customConfigPath;
64
+ return join(getConfigDir(), "tui.json");
65
+ }
66
+ function getTuiConfigJsonc() {
67
+ return join(getConfigDir(), "tui.jsonc");
68
+ }
53
69
  function getExistingLiteConfigPath() {
54
70
  const jsonPath = getLiteConfig();
55
71
  if (existsSync(jsonPath))
@@ -59,6 +75,18 @@ function getExistingLiteConfigPath() {
59
75
  return jsoncPath;
60
76
  return jsonPath;
61
77
  }
78
+ function getExistingTuiConfigPath() {
79
+ const customConfigPath = getCustomTuiConfigPath();
80
+ if (customConfigPath)
81
+ return customConfigPath;
82
+ const jsonPath = join(getConfigDir(), "tui.json");
83
+ if (existsSync(jsonPath))
84
+ return jsonPath;
85
+ const jsoncPath = getTuiConfigJsonc();
86
+ if (existsSync(jsoncPath))
87
+ return jsoncPath;
88
+ return jsonPath;
89
+ }
62
90
  function getExistingConfigPath() {
63
91
  const jsonPath = getConfigJson();
64
92
  if (existsSync(jsonPath))
@@ -74,6 +102,12 @@ function ensureConfigDir() {
74
102
  mkdirSync(configDir, { recursive: true });
75
103
  }
76
104
  }
105
+ function ensureTuiConfigDir() {
106
+ const configDir = dirname(getTuiConfig());
107
+ if (!existsSync(configDir)) {
108
+ mkdirSync(configDir, { recursive: true });
109
+ }
110
+ }
77
111
  function ensureOpenCodeConfigDir() {
78
112
  const configDir = dirname(getConfigJson());
79
113
  if (!existsSync(configDir)) {
@@ -304,7 +338,6 @@ var PluginConfigSchema = z2.object({
304
338
  setDefaultAgent: z2.boolean().optional(),
305
339
  scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
306
340
  balanceProviderUsage: z2.boolean().optional(),
307
- showStartupToast: z2.boolean().optional().describe("Show the startup activation toast when OpenCode starts. Defaults to true."),
308
341
  autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
309
342
  manualPlan: ManualPlanSchema.optional(),
310
343
  presets: z2.record(z2.string(), PresetSchema).optional(),
@@ -458,6 +491,7 @@ function installSkill(skill) {
458
491
 
459
492
  // src/cli/providers.ts
460
493
  var SCHEMA_URL = "https://unpkg.com/oh-my-opencode-slim@latest/oh-my-opencode-slim.schema.json";
494
+ var GENERATED_PRESETS = ["openai", "opencode-go"];
461
495
  var MODEL_MAPPINGS = {
462
496
  openai: {
463
497
  orchestrator: { model: "openai/gpt-5.5" },
@@ -493,12 +527,31 @@ var MODEL_MAPPINGS = {
493
527
  explorer: { model: "zai-coding-plan/glm-5", variant: "low" },
494
528
  designer: { model: "zai-coding-plan/glm-5", variant: "medium" },
495
529
  fixer: { model: "zai-coding-plan/glm-5", variant: "low" }
530
+ },
531
+ "opencode-go": {
532
+ orchestrator: { model: "opencode-go/glm-5.1" },
533
+ oracle: { model: "opencode-go/deepseek-v4-pro", variant: "max" },
534
+ council: { model: "opencode-go/deepseek-v4-pro", variant: "high" },
535
+ librarian: { model: "opencode-go/minimax-m2.7" },
536
+ explorer: { model: "opencode-go/minimax-m2.7" },
537
+ designer: { model: "opencode-go/kimi-k2.6", variant: "medium" },
538
+ fixer: { model: "opencode-go/deepseek-v4-flash", variant: "high" }
496
539
  }
497
540
  };
541
+ function isGeneratedPresetName(value) {
542
+ return GENERATED_PRESETS.includes(value);
543
+ }
544
+ function getGeneratedPresetNames() {
545
+ return [...GENERATED_PRESETS];
546
+ }
498
547
  function generateLiteConfig(installConfig) {
548
+ const preset = installConfig.preset ?? "openai";
549
+ if (!isGeneratedPresetName(preset)) {
550
+ throw new Error(`Unsupported preset "${preset}". Available generated presets: ${getGeneratedPresetNames().join(", ")}`);
551
+ }
499
552
  const config = {
500
553
  $schema: SCHEMA_URL,
501
- preset: "openai",
554
+ preset,
502
555
  presets: {}
503
556
  };
504
557
  const createAgentConfig = (agentName, modelInfo) => {
@@ -524,7 +577,10 @@ function generateLiteConfig(installConfig) {
524
577
  createAgentConfig(agentName, modelInfo)
525
578
  ]));
526
579
  };
527
- config.presets.openai = buildPreset("openai");
580
+ const presets = config.presets;
581
+ for (const presetName of GENERATED_PRESETS) {
582
+ presets[presetName] = buildPreset(presetName);
583
+ }
528
584
  if (installConfig.hasTmux) {
529
585
  config.tmux = {
530
586
  enabled: true,
@@ -546,6 +602,14 @@ function getPlugins(config) {
546
602
  function getPluginEntries(config) {
547
603
  return getPlugins(config).filter(isString);
548
604
  }
605
+ function getPluginSpec(entry) {
606
+ if (isString(entry))
607
+ return entry;
608
+ if (!Array.isArray(entry))
609
+ return;
610
+ const spec = entry[0];
611
+ return isString(spec) ? spec : undefined;
612
+ }
549
613
  function normalizePathForMatch(path) {
550
614
  return path.replaceAll("\\", "/");
551
615
  }
@@ -590,6 +654,10 @@ function isLocalPackageRootEntry(entry) {
590
654
  function isPluginEntry(entry) {
591
655
  return entry === PACKAGE_NAME || entry.startsWith(`${PACKAGE_NAME}@`) || entry.startsWith("file://") && entry.includes(PACKAGE_NAME) || isLocalPackageRootEntry(entry);
592
656
  }
657
+ function isMatchingPluginEntry(entry) {
658
+ const spec = getPluginSpec(entry);
659
+ return spec ? isPluginEntry(spec) : false;
660
+ }
593
661
  function getPluginEntry() {
594
662
  const cliEntryPath = process.argv[1];
595
663
  if (!cliEntryPath) {
@@ -672,7 +740,7 @@ async function addPluginToOpenCodeConfig() {
672
740
  const config = parsedConfig ?? {};
673
741
  const plugins = getPlugins(config);
674
742
  const pluginEntry = getPluginEntry();
675
- const filteredPlugins = plugins.filter((plugin) => !isString(plugin) || !isPluginEntry(plugin));
743
+ const filteredPlugins = plugins.filter((plugin) => !isMatchingPluginEntry(plugin));
676
744
  filteredPlugins.push(pluginEntry);
677
745
  config.plugin = filteredPlugins;
678
746
  writeConfig(configPath, config);
@@ -685,6 +753,42 @@ async function addPluginToOpenCodeConfig() {
685
753
  };
686
754
  }
687
755
  }
756
+ async function addPluginToOpenCodeTuiConfig() {
757
+ const configPath = getExistingTuiConfigPath();
758
+ try {
759
+ ensureTuiConfigDir();
760
+ } catch (err) {
761
+ return {
762
+ success: false,
763
+ configPath,
764
+ error: `Failed to create config directory: ${err}`
765
+ };
766
+ }
767
+ try {
768
+ const { config: parsedConfig, error } = parseConfig(configPath);
769
+ if (error) {
770
+ return {
771
+ success: false,
772
+ configPath,
773
+ error: `Failed to parse TUI config: ${error}`
774
+ };
775
+ }
776
+ const config = parsedConfig ?? {};
777
+ const plugins = getPlugins(config);
778
+ const pluginEntry = getPluginEntry();
779
+ const filteredPlugins = plugins.filter((plugin) => !isMatchingPluginEntry(plugin));
780
+ filteredPlugins.push(pluginEntry);
781
+ config.plugin = filteredPlugins;
782
+ writeConfig(configPath, config);
783
+ return { success: true, configPath };
784
+ } catch (err) {
785
+ return {
786
+ success: false,
787
+ configPath,
788
+ error: `Failed to update opencode TUI config: ${err}`
789
+ };
790
+ }
791
+ }
688
792
  function writeLiteConfig(installConfig, targetPath) {
689
793
  const configPath = targetPath ?? getLiteConfig();
690
794
  try {
@@ -1005,8 +1109,10 @@ var SYMBOLS = {
1005
1109
  bullet: `${DIM}-${RESET}`,
1006
1110
  info: `${BLUE}[i]${RESET}`,
1007
1111
  warn: `${YELLOW}[!]${RESET}`,
1008
- star: `${YELLOW}*${RESET}`
1112
+ star: `${YELLOW}★${RESET}`
1009
1113
  };
1114
+ var GITHUB_REPO = "alvinunreal/oh-my-opencode-slim";
1115
+ var GITHUB_URL = `https://github.com/${GITHUB_REPO}`;
1010
1116
  function printHeader(isUpdate) {
1011
1117
  console.log();
1012
1118
  console.log(`${BOLD}oh-my-opencode-slim ${isUpdate ? "Update" : "Install"}${RESET}`);
@@ -1025,6 +1131,34 @@ function printError(message) {
1025
1131
  function printInfo(message) {
1026
1132
  console.log(`${SYMBOLS.info} ${message}`);
1027
1133
  }
1134
+ async function confirm(message, defaultYes = true) {
1135
+ const suffix = defaultYes ? " (Y/n) " : " (y/N) ";
1136
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
1137
+ try {
1138
+ const answer = (await rl.question(`${message}${suffix}`)).trim().toLowerCase();
1139
+ if (!answer)
1140
+ return defaultYes;
1141
+ return answer === "y" || answer === "yes";
1142
+ } finally {
1143
+ rl.close();
1144
+ }
1145
+ }
1146
+ async function askToStarRepo(config) {
1147
+ if (!config.promptForStar || config.dryRun || !process.stdin.isTTY)
1148
+ return;
1149
+ console.log();
1150
+ const shouldStar = await confirm(`${SYMBOLS.star} Star the repo on GitHub?`, true);
1151
+ if (!shouldStar)
1152
+ return;
1153
+ try {
1154
+ const { execFileSync } = await import("node:child_process");
1155
+ execFileSync("gh", ["api", "--silent", "--method", "PUT", `/user/starred/${GITHUB_REPO}`], { stdio: "ignore", timeout: 1e4 });
1156
+ printSuccess("Thanks for starring! ★");
1157
+ } catch {
1158
+ printInfo(`Couldn't star automatically. You can star manually:
1159
+ ${BLUE}${GITHUB_URL}${RESET}`);
1160
+ }
1161
+ }
1028
1162
  async function checkOpenCodeInstalled() {
1029
1163
  const installed = await isOpenCodeInstalled();
1030
1164
  if (!installed) {
@@ -1056,7 +1190,7 @@ async function runInstall(config) {
1056
1190
  const detected = detectCurrentConfig();
1057
1191
  const isUpdate = detected.isInstalled;
1058
1192
  printHeader(isUpdate);
1059
- let totalSteps = 5;
1193
+ let totalSteps = 6;
1060
1194
  if (config.installSkills)
1061
1195
  totalSteps += 1;
1062
1196
  if (config.installCustomSkills)
@@ -1078,6 +1212,17 @@ async function runInstall(config) {
1078
1212
  if (!handleStepResult(pluginResult, "Plugin added"))
1079
1213
  return 1;
1080
1214
  }
1215
+ printStep(step++, totalSteps, "Adding TUI version badge...");
1216
+ if (config.dryRun) {
1217
+ printInfo("Dry run mode - skipping TUI plugin installation");
1218
+ } else {
1219
+ const tuiResult = await addPluginToOpenCodeTuiConfig();
1220
+ if (!tuiResult.success) {
1221
+ printInfo(`Skipped TUI badge: ${tuiResult.error}`);
1222
+ } else {
1223
+ handleStepResult(tuiResult, "TUI badge added");
1224
+ }
1225
+ }
1081
1226
  printStep(step++, totalSteps, "Disabling OpenCode default agents...");
1082
1227
  if (config.dryRun) {
1083
1228
  printInfo("Dry run mode - skipping agent disabling");
@@ -1105,7 +1250,7 @@ ${JSON.stringify(liteConfig, null, 2)}
1105
1250
  const configPath2 = getExistingLiteConfigPath();
1106
1251
  const configExists = existsSync4(configPath2);
1107
1252
  if (configExists && !config.reset) {
1108
- printInfo(`Configuration already exists at ${configPath2}. ` + "Use --reset to overwrite.");
1253
+ printInfo(`Configuration already exists at ${configPath2}. Use --reset to overwrite.`);
1109
1254
  } else {
1110
1255
  const liteResult = writeLiteConfig(config, configExists ? configPath2 : undefined);
1111
1256
  if (!handleStepResult(liteResult, configExists ? "Config reset" : "Config written"))
@@ -1176,13 +1321,14 @@ ${JSON.stringify(liteConfig, null, 2)}
1176
1321
  console.log(" 5. Verify the agents are responding:");
1177
1322
  console.log(` ${BLUE}> ping all agents${RESET}`);
1178
1323
  console.log();
1179
- const modelsInfo = "Default configuration uses OpenAI models (gpt-5.5 / gpt-5.4-mini).";
1324
+ const modelsInfo = config.preset && config.preset !== "openai" ? `Generated OpenAI and OpenCode Go presets; ${config.preset} is active.` : "Generated OpenAI and OpenCode Go presets; OpenAI is active by default.";
1180
1325
  console.log(`${modelsInfo}`);
1181
1326
  const altProviders = "For the full configuration reference, see:";
1182
1327
  console.log(altProviders);
1183
- const docsUrl = "https://github.com/alvinunreal/oh-my-opencode-slim/" + "blob/master/docs/configuration.md";
1328
+ const docsUrl = "https://github.com/alvinunreal/oh-my-opencode-slim/blob/master/docs/configuration.md";
1184
1329
  console.log(` ${BLUE}${docsUrl}${RESET}`);
1185
1330
  console.log();
1331
+ await askToStarRepo(config);
1186
1332
  return 0;
1187
1333
  }
1188
1334
  async function install(args) {
@@ -1190,12 +1336,23 @@ async function install(args) {
1190
1336
  hasTmux: false,
1191
1337
  installSkills: args.skills === "yes",
1192
1338
  installCustomSkills: args.skills === "yes",
1339
+ preset: args.preset,
1340
+ promptForStar: args.tui,
1193
1341
  dryRun: args.dryRun,
1194
1342
  reset: args.reset ?? false
1195
1343
  };
1196
1344
  return runInstall(config);
1197
1345
  }
1198
1346
 
1347
+ // src/cli/providers.ts
1348
+ var GENERATED_PRESETS2 = ["openai", "opencode-go"];
1349
+ function isGeneratedPresetName2(value) {
1350
+ return GENERATED_PRESETS2.includes(value);
1351
+ }
1352
+ function getGeneratedPresetNames2() {
1353
+ return [...GENERATED_PRESETS2];
1354
+ }
1355
+
1199
1356
  // src/cli/index.ts
1200
1357
  function parseArgs(args) {
1201
1358
  const result = {
@@ -1207,6 +1364,13 @@ function parseArgs(args) {
1207
1364
  result.tui = false;
1208
1365
  } else if (arg.startsWith("--skills=")) {
1209
1366
  result.skills = arg.split("=")[1];
1367
+ } else if (arg.startsWith("--preset=")) {
1368
+ const preset = arg.split("=")[1];
1369
+ if (!isGeneratedPresetName2(preset)) {
1370
+ console.error(`Unsupported preset: ${preset}. Available presets: ${getGeneratedPresetNames2().join(", ")}`);
1371
+ process.exit(1);
1372
+ }
1373
+ result.preset = preset;
1210
1374
  } else if (arg === "--dry-run") {
1211
1375
  result.dryRun = true;
1212
1376
  } else if (arg === "--reset") {
@@ -1226,17 +1390,22 @@ Usage: bunx oh-my-opencode-slim install [OPTIONS]
1226
1390
 
1227
1391
  Options:
1228
1392
  --skills=yes|no Install recommended and bundled skills (default: yes)
1393
+ --preset=<name> Active generated config preset (default: openai)
1229
1394
  --no-tui Non-interactive mode
1230
1395
  --dry-run Simulate install without writing files
1231
1396
  --reset Force overwrite of existing configuration
1232
1397
  -h, --help Show this help message
1233
1398
 
1234
- The installer generates an OpenAI configuration by default.
1399
+ Available presets: ${getGeneratedPresetNames2().join(", ")}
1400
+
1401
+ The installer generates OpenAI and OpenCode Go presets by default.
1402
+ OpenAI is active unless --preset selects another generated preset.
1235
1403
  For the full config reference, see docs/configuration.md.
1236
1404
 
1237
1405
  Examples:
1238
1406
  bunx oh-my-opencode-slim install
1239
1407
  bunx oh-my-opencode-slim install --no-tui --skills=yes
1408
+ bunx oh-my-opencode-slim install --preset=opencode-go
1240
1409
  bunx oh-my-opencode-slim install --reset
1241
1410
  `);
1242
1411
  }
@@ -22,9 +22,13 @@ export declare function getConfigJson(): string;
22
22
  export declare function getConfigJsonc(): string;
23
23
  export declare function getLiteConfig(): string;
24
24
  export declare function getLiteConfigJsonc(): string;
25
+ export declare function getTuiConfig(): string;
26
+ export declare function getTuiConfigJsonc(): string;
25
27
  export declare function getExistingLiteConfigPath(): string;
28
+ export declare function getExistingTuiConfigPath(): string;
26
29
  export declare function getExistingConfigPath(): string;
27
30
  export declare function ensureConfigDir(): void;
31
+ export declare function ensureTuiConfigDir(): void;
28
32
  /**
29
33
  * Ensure the directory for OpenCode's main config file exists.
30
34
  */
@@ -1,4 +1,5 @@
1
- import type { InstallConfig } from "./types";
1
+ import type { InstallConfig } from './types';
2
+ export declare const GENERATED_PRESETS: readonly ["openai", "opencode-go"];
2
3
  export declare const MODEL_MAPPINGS: {
3
4
  readonly openai: {
4
5
  readonly orchestrator: {
@@ -75,7 +76,7 @@ export declare const MODEL_MAPPINGS: {
75
76
  readonly variant: "low";
76
77
  };
77
78
  };
78
- readonly "zai-plan": {
79
+ readonly 'zai-plan': {
79
80
  readonly orchestrator: {
80
81
  readonly model: "zai-coding-plan/glm-5";
81
82
  };
@@ -100,5 +101,38 @@ export declare const MODEL_MAPPINGS: {
100
101
  readonly variant: "low";
101
102
  };
102
103
  };
104
+ readonly 'opencode-go': {
105
+ readonly orchestrator: {
106
+ readonly model: "opencode-go/glm-5.1";
107
+ };
108
+ readonly oracle: {
109
+ readonly model: "opencode-go/deepseek-v4-pro";
110
+ readonly variant: "max";
111
+ };
112
+ readonly council: {
113
+ readonly model: "opencode-go/deepseek-v4-pro";
114
+ readonly variant: "high";
115
+ };
116
+ readonly librarian: {
117
+ readonly model: "opencode-go/minimax-m2.7";
118
+ };
119
+ readonly explorer: {
120
+ readonly model: "opencode-go/minimax-m2.7";
121
+ };
122
+ readonly designer: {
123
+ readonly model: "opencode-go/kimi-k2.6";
124
+ readonly variant: "medium";
125
+ };
126
+ readonly fixer: {
127
+ readonly model: "opencode-go/deepseek-v4-flash";
128
+ readonly variant: "high";
129
+ };
130
+ };
103
131
  };
132
+ export type PresetName = keyof typeof MODEL_MAPPINGS;
133
+ export type GeneratedPresetName = (typeof GENERATED_PRESETS)[number];
134
+ export declare function isPresetName(value: string): value is PresetName;
135
+ export declare function getPresetNames(): PresetName[];
136
+ export declare function isGeneratedPresetName(value: string): value is GeneratedPresetName;
137
+ export declare function getGeneratedPresetNames(): GeneratedPresetName[];
104
138
  export declare function generateLiteConfig(installConfig: InstallConfig): Record<string, unknown>;
@@ -2,6 +2,7 @@ export type BooleanArg = 'yes' | 'no';
2
2
  export interface InstallArgs {
3
3
  tui: boolean;
4
4
  skills?: BooleanArg;
5
+ preset?: string;
5
6
  dryRun?: boolean;
6
7
  reset?: boolean;
7
8
  }
@@ -15,6 +16,8 @@ export interface InstallConfig {
15
16
  hasTmux: boolean;
16
17
  installSkills: boolean;
17
18
  installCustomSkills: boolean;
19
+ preset?: string;
20
+ promptForStar?: boolean;
18
21
  dryRun?: boolean;
19
22
  reset: boolean;
20
23
  }
@@ -195,7 +195,6 @@ export declare const PluginConfigSchema: z.ZodObject<{
195
195
  v2: "v2";
196
196
  }>>;
197
197
  balanceProviderUsage: z.ZodOptional<z.ZodBoolean>;
198
- showStartupToast: z.ZodOptional<z.ZodBoolean>;
199
198
  autoUpdate: z.ZodOptional<z.ZodBoolean>;
200
199
  manualPlan: z.ZodOptional<z.ZodObject<{
201
200
  orchestrator: z.ZodObject<{
@@ -12,7 +12,6 @@ export interface PackageJson {
12
12
  [key: string]: unknown;
13
13
  }
14
14
  export interface AutoUpdateCheckerOptions {
15
- showStartupToast?: boolean;
16
15
  autoUpdate?: boolean;
17
16
  }
18
17
  export interface PluginEntryInfo {
package/dist/index.js CHANGED
@@ -6246,33 +6246,33 @@ var require_URL = __commonJS((exports, module) => {
6246
6246
  else
6247
6247
  return basepath.substring(0, lastslash + 1) + refpath;
6248
6248
  }
6249
- function remove_dot_segments(path14) {
6250
- if (!path14)
6251
- return path14;
6249
+ function remove_dot_segments(path15) {
6250
+ if (!path15)
6251
+ return path15;
6252
6252
  var output = "";
6253
- while (path14.length > 0) {
6254
- if (path14 === "." || path14 === "..") {
6255
- path14 = "";
6253
+ while (path15.length > 0) {
6254
+ if (path15 === "." || path15 === "..") {
6255
+ path15 = "";
6256
6256
  break;
6257
6257
  }
6258
- var twochars = path14.substring(0, 2);
6259
- var threechars = path14.substring(0, 3);
6260
- var fourchars = path14.substring(0, 4);
6258
+ var twochars = path15.substring(0, 2);
6259
+ var threechars = path15.substring(0, 3);
6260
+ var fourchars = path15.substring(0, 4);
6261
6261
  if (threechars === "../") {
6262
- path14 = path14.substring(3);
6262
+ path15 = path15.substring(3);
6263
6263
  } else if (twochars === "./") {
6264
- path14 = path14.substring(2);
6264
+ path15 = path15.substring(2);
6265
6265
  } else if (threechars === "/./") {
6266
- path14 = "/" + path14.substring(3);
6267
- } else if (twochars === "/." && path14.length === 2) {
6268
- path14 = "/";
6269
- } else if (fourchars === "/../" || threechars === "/.." && path14.length === 3) {
6270
- path14 = "/" + path14.substring(4);
6266
+ path15 = "/" + path15.substring(3);
6267
+ } else if (twochars === "/." && path15.length === 2) {
6268
+ path15 = "/";
6269
+ } else if (fourchars === "/../" || threechars === "/.." && path15.length === 3) {
6270
+ path15 = "/" + path15.substring(4);
6271
6271
  output = output.replace(/\/?[^\/]*$/, "");
6272
6272
  } else {
6273
- var segment = path14.match(/(\/?([^\/]*))/)[0];
6273
+ var segment = path15.match(/(\/?([^\/]*))/)[0];
6274
6274
  output += segment;
6275
- path14 = path14.substring(segment.length);
6275
+ path15 = path15.substring(segment.length);
6276
6276
  }
6277
6277
  }
6278
6278
  return output;
@@ -18150,14 +18150,14 @@ var require_turndown_cjs = __commonJS((exports, module) => {
18150
18150
  } else if (node.nodeType === 1) {
18151
18151
  replacement = replacementForNode.call(self, node);
18152
18152
  }
18153
- return join12(output, replacement);
18153
+ return join13(output, replacement);
18154
18154
  }, "");
18155
18155
  }
18156
18156
  function postProcess(output) {
18157
18157
  var self = this;
18158
18158
  this.rules.forEach(function(rule) {
18159
18159
  if (typeof rule.append === "function") {
18160
- output = join12(output, rule.append(self.options));
18160
+ output = join13(output, rule.append(self.options));
18161
18161
  }
18162
18162
  });
18163
18163
  return output.replace(/^[\t\r\n]+/, "").replace(/[\t\r\n\s]+$/, "");
@@ -18170,7 +18170,7 @@ var require_turndown_cjs = __commonJS((exports, module) => {
18170
18170
  content = content.trim();
18171
18171
  return whitespace.leading + rule.replacement(content, node, this.options) + whitespace.trailing;
18172
18172
  }
18173
- function join12(output, replacement) {
18173
+ function join13(output, replacement) {
18174
18174
  var s1 = trimTrailingNewlines(output);
18175
18175
  var s2 = trimLeadingNewlines(replacement);
18176
18176
  var nls = Math.max(output.length - s1.length, replacement.length - s2.length);
@@ -18570,7 +18570,6 @@ var PluginConfigSchema = z2.object({
18570
18570
  setDefaultAgent: z2.boolean().optional(),
18571
18571
  scoringEngineVersion: z2.enum(["v1", "v2-shadow", "v2"]).optional(),
18572
18572
  balanceProviderUsage: z2.boolean().optional(),
18573
- showStartupToast: z2.boolean().optional().describe("Show the startup activation toast when OpenCode starts. Defaults to true."),
18574
18573
  autoUpdate: z2.boolean().optional().describe("Disable automatic installation of plugin updates when false. Defaults to true."),
18575
18574
  manualPlan: ManualPlanSchema.optional(),
18576
18575
  presets: z2.record(z2.string(), PresetSchema).optional(),
@@ -21995,7 +21994,7 @@ function preparePackageUpdate(version, packageName = PACKAGE_NAME, runtimePackag
21995
21994
 
21996
21995
  // src/hooks/auto-update-checker/index.ts
21997
21996
  function createAutoUpdateCheckerHook(ctx, options = {}) {
21998
- const { showStartupToast = true, autoUpdate = true } = options;
21997
+ const { autoUpdate = true } = options;
21999
21998
  let hasChecked = false;
22000
21999
  return {
22001
22000
  event: ({ event }) => {
@@ -22008,19 +22007,11 @@ function createAutoUpdateCheckerHook(ctx, options = {}) {
22008
22007
  return;
22009
22008
  hasChecked = true;
22010
22009
  setTimeout(async () => {
22011
- const cachedVersion = getCachedVersion();
22012
22010
  const localDevVersion = getLocalDevVersion(ctx.directory);
22013
- const displayVersion = localDevVersion ?? cachedVersion;
22014
22011
  if (localDevVersion) {
22015
- if (showStartupToast) {
22016
- showToast(ctx, `OMO-Slim ${displayVersion} (dev)`, "Running in local development mode.", "info");
22017
- }
22018
22012
  log("[auto-update-checker] Local development mode");
22019
22013
  return;
22020
22014
  }
22021
- if (showStartupToast) {
22022
- showToast(ctx, `OMO-Slim ${displayVersion ?? "unknown"}`, "oh-my-opencode-slim is active.", "info");
22023
- }
22024
22015
  runBackgroundUpdateCheck(ctx, autoUpdate).catch((err) => {
22025
22016
  log("[auto-update-checker] Background update check failed:", err);
22026
22017
  });
@@ -23342,6 +23333,7 @@ function createTaskSessionManagerHook(_ctx, options) {
23342
23333
  const pendingCallOrder = [];
23343
23334
  const contextByTask = new Map;
23344
23335
  const pendingManagedTaskIds = new Set;
23336
+ let anonymousPendingCallId = 0;
23345
23337
  function addTaskContext(taskId, files) {
23346
23338
  if (files.length === 0)
23347
23339
  return;
@@ -23388,6 +23380,9 @@ function createTaskSessionManagerHook(_ctx, options) {
23388
23380
  const firstLine = output.split(/\r?\n/, 1)[0]?.trim().toLowerCase() ?? "";
23389
23381
  return firstLine.startsWith("[error]") && firstLine.includes("session") && (firstLine.includes("not found") || firstLine.includes("no session"));
23390
23382
  }
23383
+ function pendingCallId(input) {
23384
+ return input.callID ?? `${input.sessionID ?? "unknown"}:anonymous-${++anonymousPendingCallId}`;
23385
+ }
23391
23386
  function rememberPendingCall(call) {
23392
23387
  const existingIndex = pendingCallOrder.indexOf(call.callId);
23393
23388
  if (existingIndex >= 0) {
@@ -23403,17 +23398,23 @@ function createTaskSessionManagerHook(_ctx, options) {
23403
23398
  pendingCalls.delete(evictedCallId);
23404
23399
  }
23405
23400
  }
23406
- function takePendingCall(callId) {
23407
- if (!callId)
23401
+ function takePendingCall(callId, parentSessionId) {
23402
+ const resolvedCallId = callId ?? firstPendingCallForParent(parentSessionId);
23403
+ if (!resolvedCallId)
23408
23404
  return;
23409
- const pending = pendingCalls.get(callId);
23410
- pendingCalls.delete(callId);
23411
- const orderIndex = pendingCallOrder.indexOf(callId);
23405
+ const pending = pendingCalls.get(resolvedCallId);
23406
+ pendingCalls.delete(resolvedCallId);
23407
+ const orderIndex = pendingCallOrder.indexOf(resolvedCallId);
23412
23408
  if (orderIndex >= 0) {
23413
23409
  pendingCallOrder.splice(orderIndex, 1);
23414
23410
  }
23415
23411
  return pending;
23416
23412
  }
23413
+ function firstPendingCallForParent(parentSessionId) {
23414
+ if (!parentSessionId)
23415
+ return;
23416
+ return pendingCallOrder.find((callId) => pendingCalls.get(callId)?.parentSessionId === parentSessionId);
23417
+ }
23417
23418
  return {
23418
23419
  "tool.execute.before": async (input, output) => {
23419
23420
  if (input.tool.toLowerCase() !== "task")
@@ -23431,14 +23432,16 @@ function createTaskSessionManagerHook(_ctx, options) {
23431
23432
  prompt: typeof args.prompt === "string" ? args.prompt : undefined,
23432
23433
  agentType: args.subagent_type
23433
23434
  });
23434
- if (input.callID) {
23435
- rememberPendingCall({
23436
- callId: input.callID,
23437
- parentSessionId: input.sessionID,
23438
- agentType: args.subagent_type,
23439
- label
23440
- });
23441
- }
23435
+ const pendingCall = {
23436
+ callId: pendingCallId({
23437
+ callID: input.callID,
23438
+ sessionID: input.sessionID
23439
+ }),
23440
+ parentSessionId: input.sessionID,
23441
+ agentType: args.subagent_type,
23442
+ label
23443
+ };
23444
+ rememberPendingCall(pendingCall);
23442
23445
  if (typeof args.task_id !== "string" || args.task_id.trim() === "") {
23443
23446
  return;
23444
23447
  }
@@ -23451,15 +23454,8 @@ function createTaskSessionManagerHook(_ctx, options) {
23451
23454
  args.task_id = remembered.taskId;
23452
23455
  pendingManagedTaskIds.add(remembered.taskId);
23453
23456
  sessionManager.markUsed(input.sessionID, args.subagent_type, remembered.taskId);
23454
- if (input.callID) {
23455
- rememberPendingCall({
23456
- callId: input.callID,
23457
- parentSessionId: input.sessionID,
23458
- agentType: args.subagent_type,
23459
- label,
23460
- resumedTaskId: remembered.taskId
23461
- });
23462
- }
23457
+ pendingCall.resumedTaskId = remembered.taskId;
23458
+ rememberPendingCall(pendingCall);
23463
23459
  },
23464
23460
  "tool.execute.after": async (input, output) => {
23465
23461
  if (input.tool.toLowerCase() === "read") {
@@ -23470,7 +23466,7 @@ function createTaskSessionManagerHook(_ctx, options) {
23470
23466
  }
23471
23467
  if (input.tool.toLowerCase() !== "task")
23472
23468
  return;
23473
- const pending = takePendingCall(input.callID);
23469
+ const pending = takePendingCall(input.callID, input.sessionID);
23474
23470
  if (!pending || typeof output.output !== "string")
23475
23471
  return;
23476
23472
  const taskId = parseTaskIdFromTaskOutput(output.output);
@@ -23538,8 +23534,8 @@ function createTaskSessionManagerHook(_ctx, options) {
23538
23534
  const sessionId = input.event.properties?.info?.id ?? input.event.properties?.sessionID;
23539
23535
  if (!sessionId)
23540
23536
  return;
23541
- sessionManager.clearParent(sessionId);
23542
23537
  sessionManager.dropTask(sessionId);
23538
+ sessionManager.clearParent(sessionId);
23543
23539
  contextByTask.delete(sessionId);
23544
23540
  pendingManagedTaskIds.delete(sessionId);
23545
23541
  pruneContext();
@@ -29622,6 +29618,74 @@ Returns the councillor responses with a summary footer.`,
29622
29618
  });
29623
29619
  return { council_session };
29624
29620
  }
29621
+ // src/tui-state.ts
29622
+ import * as fs9 from "node:fs";
29623
+ import * as os4 from "node:os";
29624
+ import * as path13 from "node:path";
29625
+ var STATE_DIR = "oh-my-opencode-slim";
29626
+ var STATE_FILE = "tui-state.json";
29627
+ function dataDir() {
29628
+ return process.env.XDG_DATA_HOME ?? path13.join(os4.homedir(), ".local", "share");
29629
+ }
29630
+ function getTuiStatePath() {
29631
+ return path13.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
29632
+ }
29633
+ function emptySnapshot() {
29634
+ return {
29635
+ version: 1,
29636
+ updatedAt: Date.now(),
29637
+ agentModels: {}
29638
+ };
29639
+ }
29640
+ function parseSnapshot(value) {
29641
+ const parsed = JSON.parse(value);
29642
+ if (parsed?.version !== 1)
29643
+ return emptySnapshot();
29644
+ return {
29645
+ version: 1,
29646
+ updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
29647
+ agentModels: parsed.agentModels ?? {}
29648
+ };
29649
+ }
29650
+ function readTuiSnapshot() {
29651
+ try {
29652
+ return parseSnapshot(fs9.readFileSync(getTuiStatePath(), "utf8"));
29653
+ } catch {
29654
+ return emptySnapshot();
29655
+ }
29656
+ }
29657
+ async function readTuiSnapshotAsync() {
29658
+ try {
29659
+ return parseSnapshot(await fs9.promises.readFile(getTuiStatePath(), "utf8"));
29660
+ } catch {
29661
+ return emptySnapshot();
29662
+ }
29663
+ }
29664
+ function writeTuiSnapshot(snapshot) {
29665
+ try {
29666
+ const filePath = getTuiStatePath();
29667
+ fs9.mkdirSync(path13.dirname(filePath), { recursive: true });
29668
+ fs9.writeFileSync(filePath, `${JSON.stringify(snapshot)}
29669
+ `);
29670
+ } catch {}
29671
+ }
29672
+ function updateSnapshot(mutator) {
29673
+ const snapshot = readTuiSnapshot();
29674
+ mutator(snapshot);
29675
+ snapshot.updatedAt = Date.now();
29676
+ writeTuiSnapshot(snapshot);
29677
+ }
29678
+ function recordTuiAgentModels(input) {
29679
+ updateSnapshot((snapshot) => {
29680
+ snapshot.agentModels = { ...input.agentModels };
29681
+ });
29682
+ }
29683
+ function recordTuiAgentModel(input) {
29684
+ updateSnapshot((snapshot) => {
29685
+ snapshot.agentModels[input.agentName] = input.model;
29686
+ });
29687
+ }
29688
+
29625
29689
  // src/tools/preset-manager.ts
29626
29690
  var COMMAND_NAME3 = "preset";
29627
29691
  function createPresetManager(ctx, config) {
@@ -29698,6 +29762,14 @@ function createPresetManager(ctx, config) {
29698
29762
  await ctx.client.config.update({
29699
29763
  body: { agent: allUpdates }
29700
29764
  });
29765
+ const snapshot = readTuiSnapshot();
29766
+ const agentModels = { ...snapshot.agentModels };
29767
+ for (const [agentName, agentConfig] of Object.entries(allUpdates)) {
29768
+ if (typeof agentConfig.model === "string") {
29769
+ agentModels[agentName] = agentConfig.model;
29770
+ }
29771
+ }
29772
+ recordTuiAgentModels({ agentModels });
29701
29773
  activePreset = presetName;
29702
29774
  const summaryParts = [];
29703
29775
  for (const [name, cfg] of Object.entries(agentUpdates)) {
@@ -29808,15 +29880,15 @@ var BINARY_PREFIXES = [
29808
29880
  ];
29809
29881
  var WEBFETCH_DESCRIPTION = "Fetch a URL with better extraction for static/docs pages. Supports llms.txt probing, content-focused HTML extraction, metadata, redirects, and an optional prompt processed by a cheap secondary model.";
29810
29882
  // src/tools/smartfetch/tool.ts
29811
- import os4 from "node:os";
29812
- import path16 from "node:path";
29883
+ import os5 from "node:os";
29884
+ import path17 from "node:path";
29813
29885
  import {
29814
29886
  tool as tool4
29815
29887
  } from "@opencode-ai/plugin";
29816
29888
 
29817
29889
  // src/tools/smartfetch/binary.ts
29818
29890
  import { mkdir as mkdir2, writeFile as writeFile2 } from "node:fs/promises";
29819
- import path13 from "node:path";
29891
+ import path14 from "node:path";
29820
29892
  function extensionForMime(contentType) {
29821
29893
  const mime = contentType.split(";")[0]?.trim().toLowerCase();
29822
29894
  const map = {
@@ -29837,10 +29909,10 @@ function buildBinaryResultMessage(fetchResult, savedPath) {
29837
29909
  async function saveBinary(binaryDir, data, contentType, filename) {
29838
29910
  await mkdir2(binaryDir, { recursive: true });
29839
29911
  const initialName = filename || `webfetch-${Date.now()}.${extensionForMime(contentType)}`;
29840
- const parsed = path13.parse(initialName);
29912
+ const parsed = path14.parse(initialName);
29841
29913
  for (let attempt = 0;attempt < 1000; attempt++) {
29842
29914
  const candidateName = attempt === 0 ? initialName : `${parsed.name}-${attempt}${parsed.ext || `.${extensionForMime(contentType)}`}`;
29843
- const file = path13.join(binaryDir, candidateName);
29915
+ const file = path14.join(binaryDir, candidateName);
29844
29916
  try {
29845
29917
  await writeFile2(file, data, { flag: "wx" });
29846
29918
  return file;
@@ -30494,7 +30566,7 @@ var L = class u2 {
30494
30566
  };
30495
30567
 
30496
30568
  // src/tools/smartfetch/network.ts
30497
- import path14 from "node:path";
30569
+ import path15 from "node:path";
30498
30570
 
30499
30571
  // src/tools/smartfetch/utils.ts
30500
30572
  var import_readability = __toESM(require_readability(), 1);
@@ -31219,7 +31291,7 @@ function inferFilenameFromUrl(url) {
31219
31291
  function truncateFilename(name, maxLength = 180) {
31220
31292
  if (name.length <= maxLength)
31221
31293
  return name;
31222
- const parsed = path14.parse(name);
31294
+ const parsed = path15.parse(name);
31223
31295
  const ext = parsed.ext || "";
31224
31296
  const baseLimit = Math.max(1, maxLength - ext.length);
31225
31297
  return `${parsed.name.slice(0, baseLimit)}${ext}`;
@@ -31391,7 +31463,7 @@ function isInvalidLlmsResult(fetchResult) {
31391
31463
  // src/tools/smartfetch/secondary-model.ts
31392
31464
  import { existsSync as existsSync9 } from "node:fs";
31393
31465
  import { readFile as readFile4 } from "node:fs/promises";
31394
- import path15 from "node:path";
31466
+ import path16 from "node:path";
31395
31467
  function parseModelRef(value) {
31396
31468
  if (!value)
31397
31469
  return;
@@ -31417,7 +31489,7 @@ function pickAgentModelRef(value) {
31417
31489
  }
31418
31490
  function findPreferredOpenCodeConfigPath(baseDir) {
31419
31491
  for (const file of ["opencode.jsonc", "opencode.json"]) {
31420
- const fullPath = path15.join(baseDir, file);
31492
+ const fullPath = path16.join(baseDir, file);
31421
31493
  if (existsSync9(fullPath))
31422
31494
  return fullPath;
31423
31495
  }
@@ -31434,7 +31506,7 @@ async function readOpenCodeConfigFile(configPath) {
31434
31506
  }
31435
31507
  }
31436
31508
  async function readEffectiveOpenCodeConfig(directory) {
31437
- const projectDir = path15.join(directory, ".opencode");
31509
+ const projectDir = path16.join(directory, ".opencode");
31438
31510
  const userDirs = getConfigSearchDirs();
31439
31511
  const projectPath = findPreferredOpenCodeConfigPath(projectDir);
31440
31512
  const userPath = userDirs.map((configDir) => findPreferredOpenCodeConfigPath(configDir)).find(Boolean);
@@ -31595,7 +31667,7 @@ async function runSecondaryModelWithFallback(client, directory, models, prompt,
31595
31667
  // src/tools/smartfetch/tool.ts
31596
31668
  var z5 = tool4.schema;
31597
31669
  function createWebfetchTool(pluginCtx, options = {}) {
31598
- const binaryDir = options.binaryDir || path16.join(os4.tmpdir(), "opencode-smartfetch");
31670
+ const binaryDir = options.binaryDir || path17.join(os5.tmpdir(), "opencode-smartfetch");
31599
31671
  return tool4({
31600
31672
  description: WEBFETCH_DESCRIPTION,
31601
31673
  args: {
@@ -32258,7 +32330,6 @@ var OhMyOpenCodeLite = async (ctx) => {
32258
32330
  webfetch = createWebfetchTool(ctx);
32259
32331
  multiplexerSessionManager = new MultiplexerSessionManager(ctx, multiplexerConfig);
32260
32332
  autoUpdateChecker = createAutoUpdateCheckerHook(ctx, {
32261
- showStartupToast: config.showStartupToast ?? true,
32262
32333
  autoUpdate: config.autoUpdate ?? true
32263
32334
  });
32264
32335
  phaseReminderHook = createPhaseReminderHook();
@@ -32478,6 +32549,15 @@ var OhMyOpenCodeLite = async (ctx) => {
32478
32549
  }
32479
32550
  }
32480
32551
  }
32552
+ const tuiAgentModels = {};
32553
+ for (const agentDef of agentDefs) {
32554
+ if (agentDef.name === "councillor")
32555
+ continue;
32556
+ const entry = configAgent[agentDef.name];
32557
+ const resolvedModel = typeof entry?.model === "string" ? entry.model : runtimeChains[agentDef.name]?.[0] ? runtimeChains[agentDef.name][0] : typeof agentDef.config.model === "string" ? agentDef.config.model : undefined;
32558
+ tuiAgentModels[agentDef.name] = resolvedModel ?? "default";
32559
+ }
32560
+ recordTuiAgentModels({ agentModels: tuiAgentModels });
32481
32561
  const configMcp = opencodeConfig.mcp;
32482
32562
  if (!configMcp) {
32483
32563
  opencodeConfig.mcp = { ...mcps };
@@ -32521,6 +32601,15 @@ var OhMyOpenCodeLite = async (ctx) => {
32521
32601
  },
32522
32602
  event: async (input) => {
32523
32603
  const event = input.event;
32604
+ if (event.type === "message.updated") {
32605
+ const info = event.properties?.info;
32606
+ if (typeof info?.agent === "string" && typeof info.providerID === "string" && typeof info.modelID === "string") {
32607
+ recordTuiAgentModel({
32608
+ agentName: resolveRuntimeAgentName(config, info.agent),
32609
+ model: `${info.providerID}/${info.modelID}`
32610
+ });
32611
+ }
32612
+ }
32524
32613
  if (event.type === "session.created") {
32525
32614
  const childSessionId = event.properties?.info?.id;
32526
32615
  const parentSessionId = event.properties?.info?.parentID;
@@ -0,0 +1,15 @@
1
+ export interface TuiSnapshot {
2
+ version: 1;
3
+ updatedAt: number;
4
+ agentModels: Record<string, string>;
5
+ }
6
+ export declare function getTuiStatePath(): string;
7
+ export declare function readTuiSnapshot(): TuiSnapshot;
8
+ export declare function readTuiSnapshotAsync(): Promise<TuiSnapshot>;
9
+ export declare function recordTuiAgentModels(input: {
10
+ agentModels: Record<string, string>;
11
+ }): void;
12
+ export declare function recordTuiAgentModel(input: {
13
+ agentName: string;
14
+ model: string;
15
+ }): void;
package/dist/tui.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ import type { TuiPluginModule } from '@opencode-ai/plugin/tui';
2
+ import { type TuiSnapshot } from './tui-state';
3
+ export declare function formatSidebarModelName(model: string): string;
4
+ export declare function getSidebarAgentNames(snapshot: TuiSnapshot): string[];
5
+ declare const plugin: TuiPluginModule & {
6
+ id: string;
7
+ };
8
+ export default plugin;
package/dist/tui.js ADDED
@@ -0,0 +1,252 @@
1
+ import { createRequire } from "node:module";
2
+ var __create = Object.create;
3
+ var __getProtoOf = Object.getPrototypeOf;
4
+ var __defProp = Object.defineProperty;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ function __accessProp(key) {
8
+ return this[key];
9
+ }
10
+ var __toESMCache_node;
11
+ var __toESMCache_esm;
12
+ var __toESM = (mod, isNodeMode, target) => {
13
+ var canCache = mod != null && typeof mod === "object";
14
+ if (canCache) {
15
+ var cache = isNodeMode ? __toESMCache_node ??= new WeakMap : __toESMCache_esm ??= new WeakMap;
16
+ var cached = cache.get(mod);
17
+ if (cached)
18
+ return cached;
19
+ }
20
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
21
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
22
+ for (let key of __getOwnPropNames(mod))
23
+ if (!__hasOwnProp.call(to, key))
24
+ __defProp(to, key, {
25
+ get: __accessProp.bind(mod, key),
26
+ enumerable: true
27
+ });
28
+ if (canCache)
29
+ cache.set(mod, to);
30
+ return to;
31
+ };
32
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
33
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
34
+
35
+ // src/tui.ts
36
+ import { createElement, insert, setProp } from "@opentui/solid";
37
+
38
+ // src/config/constants.ts
39
+ var AGENT_ALIASES = {
40
+ explore: "explorer",
41
+ "frontend-ui-ux-engineer": "designer"
42
+ };
43
+ var SUBAGENT_NAMES = [
44
+ "explorer",
45
+ "librarian",
46
+ "oracle",
47
+ "designer",
48
+ "fixer",
49
+ "observer",
50
+ "council",
51
+ "councillor"
52
+ ];
53
+ var ORCHESTRATOR_NAME = "orchestrator";
54
+ var ALL_AGENT_NAMES = [ORCHESTRATOR_NAME, ...SUBAGENT_NAMES];
55
+ var PROTECTED_AGENTS = new Set(["orchestrator", "councillor"]);
56
+ var DEFAULT_MODELS = {
57
+ orchestrator: undefined,
58
+ oracle: "openai/gpt-5.5",
59
+ librarian: "openai/gpt-5.4-mini",
60
+ explorer: "openai/gpt-5.4-mini",
61
+ designer: "openai/gpt-5.4-mini",
62
+ fixer: "openai/gpt-5.4-mini",
63
+ observer: "openai/gpt-5.4-mini",
64
+ council: "openai/gpt-5.4-mini",
65
+ councillor: "openai/gpt-5.4-mini"
66
+ };
67
+ var POLL_INTERVAL_BACKGROUND_MS = 2000;
68
+ var DEFAULT_TIMEOUT_MS = 2 * 60 * 1000;
69
+ var MAX_POLL_TIME_MS = 5 * 60 * 1000;
70
+ var DEFAULT_MAX_SUBAGENT_DEPTH = 3;
71
+ var PHASE_REMINDER_TEXT = `!IMPORTANT! Recall the workflow rules:
72
+ Understand → choose the best parallelized path based on your capabilities and agents delegation rules → recall session reuse rules → execute → verify.
73
+ If delegating, launch the specialist in the same turn you mention it !END!`;
74
+ var TMUX_SPAWN_DELAY_MS = 500;
75
+ var COUNCILLOR_STAGGER_MS = 250;
76
+ var DEFAULT_DISABLED_AGENTS = ["observer"];
77
+
78
+ // src/tui-state.ts
79
+ import * as fs from "node:fs";
80
+ import * as os from "node:os";
81
+ import * as path from "node:path";
82
+ var STATE_DIR = "oh-my-opencode-slim";
83
+ var STATE_FILE = "tui-state.json";
84
+ function dataDir() {
85
+ return process.env.XDG_DATA_HOME ?? path.join(os.homedir(), ".local", "share");
86
+ }
87
+ function getTuiStatePath() {
88
+ return path.join(dataDir(), "opencode", "storage", STATE_DIR, STATE_FILE);
89
+ }
90
+ function emptySnapshot() {
91
+ return {
92
+ version: 1,
93
+ updatedAt: Date.now(),
94
+ agentModels: {}
95
+ };
96
+ }
97
+ function parseSnapshot(value) {
98
+ const parsed = JSON.parse(value);
99
+ if (parsed?.version !== 1)
100
+ return emptySnapshot();
101
+ return {
102
+ version: 1,
103
+ updatedAt: typeof parsed.updatedAt === "number" ? parsed.updatedAt : Date.now(),
104
+ agentModels: parsed.agentModels ?? {}
105
+ };
106
+ }
107
+ function readTuiSnapshot() {
108
+ try {
109
+ return parseSnapshot(fs.readFileSync(getTuiStatePath(), "utf8"));
110
+ } catch {
111
+ return emptySnapshot();
112
+ }
113
+ }
114
+ async function readTuiSnapshotAsync() {
115
+ try {
116
+ return parseSnapshot(await fs.promises.readFile(getTuiStatePath(), "utf8"));
117
+ } catch {
118
+ return emptySnapshot();
119
+ }
120
+ }
121
+ function writeTuiSnapshot(snapshot) {
122
+ try {
123
+ const filePath = getTuiStatePath();
124
+ fs.mkdirSync(path.dirname(filePath), { recursive: true });
125
+ fs.writeFileSync(filePath, `${JSON.stringify(snapshot)}
126
+ `);
127
+ } catch {}
128
+ }
129
+ function updateSnapshot(mutator) {
130
+ const snapshot = readTuiSnapshot();
131
+ mutator(snapshot);
132
+ snapshot.updatedAt = Date.now();
133
+ writeTuiSnapshot(snapshot);
134
+ }
135
+ function recordTuiAgentModels(input) {
136
+ updateSnapshot((snapshot) => {
137
+ snapshot.agentModels = { ...input.agentModels };
138
+ });
139
+ }
140
+ function recordTuiAgentModel(input) {
141
+ updateSnapshot((snapshot) => {
142
+ snapshot.agentModels[input.agentName] = input.model;
143
+ });
144
+ }
145
+
146
+ // src/tui.ts
147
+ var PLUGIN_NAME = "oh-my-opencode-slim";
148
+ var FALLBACK_SIDEBAR_AGENTS = SUBAGENT_NAMES.filter((agent) => agent !== "councillor" && agent !== "council" && !DEFAULT_DISABLED_AGENTS.includes(agent));
149
+ var BORDER = { type: "single" };
150
+ async function readPackageVersion() {
151
+ try {
152
+ const packageJson = await Bun.file(new URL("../package.json", import.meta.url)).json();
153
+ return typeof packageJson.version === "string" ? packageJson.version : undefined;
154
+ } catch {
155
+ return;
156
+ }
157
+ }
158
+ function element(tag, props, children = []) {
159
+ const node = createElement(tag);
160
+ for (const [key, value] of Object.entries(props)) {
161
+ if (value !== undefined)
162
+ setProp(node, key, value);
163
+ }
164
+ for (const child of children) {
165
+ if (child === null || child === undefined || child === false)
166
+ continue;
167
+ insert(node, child);
168
+ }
169
+ return node;
170
+ }
171
+ function text(props, children) {
172
+ return element("text", props, children);
173
+ }
174
+ function box(props, children = []) {
175
+ return element("box", props, children);
176
+ }
177
+ function truncate(value, max = 24) {
178
+ return value.length > max ? `${value.slice(0, max - 1)}…` : value;
179
+ }
180
+ function formatSidebarModelName(model) {
181
+ const lastSlash = model.lastIndexOf("/");
182
+ return lastSlash === -1 ? model : model.slice(lastSlash + 1);
183
+ }
184
+ function getSidebarAgentNames(snapshot) {
185
+ const configuredAgents = Object.keys(snapshot.agentModels);
186
+ return configuredAgents.length > 0 ? configuredAgents : FALLBACK_SIDEBAR_AGENTS;
187
+ }
188
+ function row(label, value, theme, valueColor) {
189
+ return box({ width: "100%", flexDirection: "row", justifyContent: "space-between" }, [
190
+ text({ fg: theme.textMuted }, [label]),
191
+ text({ fg: valueColor ?? theme.text }, [value])
192
+ ]);
193
+ }
194
+ function renderSidebar(snapshot, version, theme) {
195
+ return box({
196
+ width: "100%",
197
+ flexDirection: "column",
198
+ border: BORDER,
199
+ borderColor: theme.borderActive,
200
+ paddingTop: 1,
201
+ paddingBottom: 1,
202
+ paddingLeft: 1,
203
+ paddingRight: 1
204
+ }, [
205
+ box({
206
+ width: "100%",
207
+ flexDirection: "row",
208
+ justifyContent: "space-between",
209
+ alignItems: "center"
210
+ }, [
211
+ box({ paddingLeft: 1, paddingRight: 1, backgroundColor: theme.accent }, [text({ fg: theme.background }, ["omo-slim"])]),
212
+ text({ fg: theme.textMuted }, [`v${version}`])
213
+ ]),
214
+ box({ width: "100%", marginTop: 1 }, [
215
+ text({ fg: theme.text }, ["Agents"])
216
+ ]),
217
+ ...getSidebarAgentNames(snapshot).map((agentName) => {
218
+ const model = snapshot.agentModels[agentName] ?? "pending";
219
+ return row(agentName, truncate(formatSidebarModelName(model), 26), theme, theme.textMuted);
220
+ })
221
+ ]);
222
+ }
223
+ var plugin = {
224
+ id: `${PLUGIN_NAME}:tui`,
225
+ tui: async (api, _options, meta) => {
226
+ const version = meta.version ?? await readPackageVersion() ?? "dev";
227
+ let snapshot = readTuiSnapshot();
228
+ const renderTimer = setInterval(async () => {
229
+ try {
230
+ snapshot = await readTuiSnapshotAsync();
231
+ api.renderer.requestRender();
232
+ } catch {}
233
+ }, 1000);
234
+ api.lifecycle.onDispose(() => {
235
+ clearInterval(renderTimer);
236
+ });
237
+ api.slots.register({
238
+ order: 900,
239
+ slots: {
240
+ sidebar_content() {
241
+ return renderSidebar(snapshot, version, api.theme.current);
242
+ }
243
+ }
244
+ });
245
+ }
246
+ };
247
+ var tui_default = plugin;
248
+ export {
249
+ getSidebarAgentNames,
250
+ formatSidebarModelName,
251
+ tui_default as default
252
+ };
@@ -19,10 +19,6 @@
19
19
  "balanceProviderUsage": {
20
20
  "type": "boolean"
21
21
  },
22
- "showStartupToast": {
23
- "description": "Show the startup activation toast when OpenCode starts. Defaults to true.",
24
- "type": "boolean"
25
- },
26
22
  "autoUpdate": {
27
23
  "description": "Disable automatic installation of plugin updates when false. Defaults to true.",
28
24
  "type": "boolean"
package/package.json CHANGED
@@ -1,9 +1,19 @@
1
1
  {
2
2
  "name": "oh-my-opencode-slim",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
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",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./tui": {
13
+ "import": "./dist/tui.js",
14
+ "types": "./dist/tui.d.ts"
15
+ }
16
+ },
7
17
  "bin": {
8
18
  "oh-my-opencode-slim": "./dist/cli/index.js"
9
19
  },
@@ -36,7 +46,7 @@
36
46
  "LICENSE"
37
47
  ],
38
48
  "scripts": {
39
- "build:plugin": "bun build src/index.ts --outdir dist --target node --format esm --external @ast-grep/napi --external @opencode-ai/plugin --external @opencode-ai/sdk --external jsdom --external zod",
49
+ "build:plugin": "bun build src/index.ts src/tui.ts --outdir dist --target node --format esm --external @ast-grep/napi --external @opencode-ai/plugin --external @opencode-ai/sdk --external @opentui/core --external @opentui/solid --external jsdom --external zod",
40
50
  "build:cli": "bun build src/cli/index.ts --outdir dist/cli --target node --format esm --external @ast-grep/napi --external @opencode-ai/plugin --external @opencode-ai/sdk --external jsdom --external zod",
41
51
  "build": "bun run build:plugin && bun run build:cli && tsc --emitDeclarationOnly && bun run generate-schema",
42
52
  "prepare": "bun run build",
@@ -68,6 +78,9 @@
68
78
  "lru-cache": "^11.3.3",
69
79
  "turndown": "^7.2.4"
70
80
  },
81
+ "optionalDependencies": {
82
+ "@opentui/solid": "^0.1.97"
83
+ },
71
84
  "devDependencies": {
72
85
  "@biomejs/biome": "2.4.11",
73
86
  "@types/jsdom": "^21.1.7",