Browse Source

Merge pull request #969 from alvinunreal/codex/set-opencode_enable_exa-during-install

feat(installer): persist OPENCODE_ENABLE_EXA alongside background-subagents to enable built-in websearch
Alvin 1 week ago
parent
commit
5f622f7125
5 changed files with 114 additions and 25 deletions
  1. 7 4
      docs/installation.md
  2. 4 0
      docs/mcps.md
  3. 81 4
      src/cli/background-subagents.test.ts
  4. 10 7
      src/cli/background-subagents.ts
  5. 12 10
      src/cli/install.ts

+ 7 - 4
docs/installation.md

@@ -36,8 +36,8 @@ The installer supports the following options:
 | `--skills=yes|no` | Install bundled skills (default: yes) |
 | `--companion=ask\|yes\|no` | Install and enable the desktop Companion (`ask` by default; prompt defaults to no) |
 | `--preset=<name>` | Active generated config preset: `openai` or `opencode-go` (default: `openai`) |
-| `--background-subagents=ask\|yes\|no` | Configure the required background-subagents environment export (`ask` by default; prompt defaults to yes) |
-| `--background-subagents-target=<path>` | Write the background-subagents export to a specific shell/profile file |
+| `--background-subagents=ask\|yes\|no` | Configure the required background-subagents and Exa websearch environment exports (`ask` by default; prompt defaults to yes) |
+| `--background-subagents-target=<path>` | Write the background-subagents and websearch exports to a specific shell/profile file |
 | `--no-tui` | Non-interactive mode |
 | `--dry-run` | Simulate install without writing files |
 | `--reset` | Force overwrite of existing configuration |
@@ -49,10 +49,13 @@ background subagents, which are enabled by this environment variable:
 
 ```bash
 OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true
+OPENCODE_ENABLE_EXA=1
 ```
 
-The installer asks before adding that export to your shell startup file. The
+The installer asks before adding those exports to your shell startup file. The
 prompt defaults to `yes` because V2's default orchestration depends on it.
+The Exa flag enables OpenCode's built-in `websearch` tool without requiring an
+API key.
 
 ```bash
 bunx oh-my-opencode-slim@latest install
@@ -76,7 +79,7 @@ source ~/.bashrc
 For a one-shot manual launch without restarting your terminal:
 
 ```bash
-OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode
+OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true OPENCODE_ENABLE_EXA=1 opencode
 ```
 
 ### Non-Destructive Behavior

+ 4 - 0
docs/mcps.md

@@ -14,6 +14,10 @@ Enable the built-in tool by setting these environment variables in your shell pr
 env OPENCODE_ENABLE_EXA=true OPENCODE_ENABLE_PARALLEL=true opencode
 ```
 
+The oh-my-opencode-slim installer sets `OPENCODE_ENABLE_EXA=1` alongside its
+background-subagents export when you accept the environment setup. Parallel
+search remains opt-in.
+
 The built-in tool is Exa-backed (optionally Parallel), needs no API key, and is only available when using the `opencode` provider OR when those flags are set. Control access per agent with `permission: { "websearch": "allow" }` (all tools are allowed by default).
 
 ---

+ 81 - 4
src/cli/background-subagents.test.ts

@@ -54,12 +54,16 @@ describe('background subagents helpers', () => {
   });
 
   test('builds shell-specific managed blocks with true', () => {
-    expect(getBackgroundSubagentsBlock('/tmp/.bashrc')).toContain(
+    const bashBlock = getBackgroundSubagentsBlock('/tmp/.bashrc');
+    const fishBlock = getBackgroundSubagentsBlock('/tmp/config.fish');
+    expect(bashBlock).toContain(
       'export OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true',
     );
-    expect(getBackgroundSubagentsBlock('/tmp/config.fish')).toContain(
+    expect(bashBlock).toContain('export OPENCODE_ENABLE_EXA=1');
+    expect(fishBlock).toContain(
       'set -gx OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS true',
     );
+    expect(fishBlock).toContain('set -gx OPENCODE_ENABLE_EXA 1');
   });
 
   test('prints fish manual instructions for fish targets', () => {
@@ -71,8 +75,9 @@ describe('background subagents helpers', () => {
       'set -gx OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS true',
     );
     expect(instructions).toContain(
-      'env OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode',
+      'env OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true OPENCODE_ENABLE_EXA=1 opencode',
     );
+    expect(instructions).toContain('set -gx OPENCODE_ENABLE_EXA 1');
   });
 
   test('expands tilde target paths', () => {
@@ -164,6 +169,7 @@ describe('configureBackgroundSubagents', () => {
   let tempDir: string | undefined;
   const originalBackgroundEnv =
     process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS;
+  const originalExaEnv = process.env.OPENCODE_ENABLE_EXA;
 
   afterEach(() => {
     if (tempDir) rmSync(tempDir, { recursive: true, force: true });
@@ -174,8 +180,76 @@ describe('configureBackgroundSubagents', () => {
       process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS =
         originalBackgroundEnv;
     }
+    if (originalExaEnv === undefined) {
+      delete process.env.OPENCODE_ENABLE_EXA;
+    } else {
+      process.env.OPENCODE_ENABLE_EXA = originalExaEnv;
+    }
   });
 
+  test.each(['true', '1', 'TRUE'])(
+    'returns already enabled without writing the target when Exa is %s',
+    async (exaValue) => {
+      tempDir = mkdtempSync(join(tmpdir(), 'omoo-bg-'));
+      const target = join(tempDir, '.zshrc');
+      writeFileSync(target, 'preserve=true\n');
+      process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS = 'true';
+      process.env.OPENCODE_ENABLE_EXA = exaValue;
+      const log = spyOn(console, 'log').mockImplementation(() => undefined);
+
+      try {
+        const result = await configureBackgroundSubagents({
+          installCustomSkills: false,
+          promptForStar: false,
+          reset: false,
+          backgroundSubagents: 'yes',
+          backgroundSubagentsTarget: target,
+        });
+
+        expect(result).toEqual({ enabledNow: true });
+        expect(readFileSync(target, 'utf8')).toBe('preserve=true\n');
+        expect(log.mock.calls.join('\n')).toContain(
+          'already enabled in environment',
+        );
+      } finally {
+        log.mockRestore();
+      }
+    },
+  );
+
+  test.each([undefined, 'yes', 'false'])(
+    'writes Exa configuration when Exa is %s',
+    async (exaValue) => {
+      tempDir = mkdtempSync(join(tmpdir(), 'omoo-bg-'));
+      const target = join(tempDir, '.zshrc');
+      process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS = 'true';
+      if (exaValue === undefined) {
+        delete process.env.OPENCODE_ENABLE_EXA;
+      } else {
+        process.env.OPENCODE_ENABLE_EXA = exaValue;
+      }
+      const log = spyOn(console, 'log').mockImplementation(() => undefined);
+
+      try {
+        const result = await configureBackgroundSubagents({
+          installCustomSkills: false,
+          promptForStar: false,
+          reset: false,
+          backgroundSubagents: 'yes',
+          backgroundSubagentsTarget: target,
+        });
+
+        expect(result).toEqual({ enabledNow: false, configuredTarget: target });
+        expect(readFileSync(target, 'utf8')).toContain('OPENCODE_ENABLE_EXA=1');
+        expect(log.mock.calls.join('\n')).not.toContain(
+          'already enabled in environment',
+        );
+      } finally {
+        log.mockRestore();
+      }
+    },
+  );
+
   test('writes shell config without prompting', async () => {
     tempDir = mkdtempSync(join(tmpdir(), 'omoo-bg-'));
     delete process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS;
@@ -197,8 +271,11 @@ describe('configureBackgroundSubagents', () => {
       expect(readFileSync(join(tempDir, '.zshrc'), 'utf8')).toContain(
         'OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS',
       );
+      expect(readFileSync(join(tempDir, '.zshrc'), 'utf8')).toContain(
+        'OPENCODE_ENABLE_EXA=1',
+      );
       expect(log.mock.calls.join('\n')).toContain(
-        'Background subagents enabled',
+        'Background subagents and Exa websearch enabled',
       );
     } finally {
       process.env.SHELL = originalShell;

+ 10 - 7
src/cli/background-subagents.ts

@@ -6,6 +6,7 @@ export type BackgroundSubagentsMode = 'ask' | 'yes' | 'no';
 export type ShellKind = 'bash' | 'fish' | 'zsh';
 
 const ENV_NAME = 'OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS';
+const EXA_ENV_NAME = 'OPENCODE_ENABLE_EXA';
 const START_MARKER = '# >>> oh-my-opencode-slim background subagents >>>';
 const END_MARKER = '# <<< oh-my-opencode-slim background subagents <<<';
 
@@ -46,11 +47,11 @@ export function detectBackgroundSubagentsTarget(
 
 export function getBackgroundSubagentsBlock(targetPath: string): string {
   const isFish = targetPath.endsWith('.fish');
-  const command = isFish
-    ? `set -gx ${ENV_NAME} true`
-    : `export ${ENV_NAME}=true`;
+  const commands = isFish
+    ? `set -gx ${ENV_NAME} true\nset -gx ${EXA_ENV_NAME} 1`
+    : `export ${ENV_NAME}=true\nexport ${EXA_ENV_NAME}=1`;
 
-  return `${START_MARKER}\n${command}\n${END_MARKER}`;
+  return `${START_MARKER}\n${commands}\n${END_MARKER}`;
 }
 
 export function manualBackgroundSubagentsInstructions(options?: {
@@ -63,16 +64,18 @@ export function manualBackgroundSubagentsInstructions(options?: {
     detectShellKind(options?.targetPath);
   const bashZshSnippet = `export ${ENV_NAME}=true`;
   const fishSnippet = `set -gx ${ENV_NAME} true`;
+  const bashZshExaSnippet = `export ${EXA_ENV_NAME}=1`;
+  const fishExaSnippet = `set -gx ${EXA_ENV_NAME} 1`;
 
   if (shell === 'fish') {
-    return `Start OpenCode with background subagents enabled:\n  env ${ENV_NAME}=true opencode\n\nOr add this to your fish startup file:\n  ${fishSnippet}`;
+    return `Start OpenCode with background subagents and websearch enabled:\n  env ${ENV_NAME}=true ${EXA_ENV_NAME}=1 opencode\n\nOr add these to your fish startup file:\n  ${fishSnippet}\n  ${fishExaSnippet}`;
   }
 
   if (shell === 'bash' || shell === 'zsh') {
-    return `Start OpenCode with background subagents enabled:\n  ${ENV_NAME}=true opencode\n\nOr add this to your shell startup file:\n  ${bashZshSnippet}`;
+    return `Start OpenCode with background subagents and websearch enabled:\n  ${ENV_NAME}=true ${EXA_ENV_NAME}=1 opencode\n\nOr add these to your shell startup file:\n  ${bashZshSnippet}\n  ${bashZshExaSnippet}`;
   }
 
-  return `Start OpenCode with background subagents enabled:\n  ${ENV_NAME}=true opencode\n\nOr add one of these to your shell startup file:\n  bash/zsh: ${bashZshSnippet}\n  fish: ${fishSnippet}`;
+  return `Start OpenCode with background subagents and websearch enabled:\n  ${ENV_NAME}=true ${EXA_ENV_NAME}=1 opencode\n\nOr add the matching pair to your shell startup file:\n  bash/zsh: ${bashZshSnippet}\n            ${bashZshExaSnippet}\n  fish: ${fishSnippet}\n        ${fishExaSnippet}`;
 }
 
 export function expandHomePath(targetPath: string): string {

+ 12 - 10
src/cli/install.ts

@@ -165,13 +165,15 @@ async function checkOpenCodeInstalled(): Promise<{
 export async function configureBackgroundSubagents(
   config: InstallConfig,
 ): Promise<{ enabledNow: boolean; configuredTarget?: string }> {
-  if (
-    isBackgroundSubagentsEnabled(
-      process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS,
-    )
-  ) {
+  const backgroundSubagentsEnabled = isBackgroundSubagentsEnabled(
+    process.env.OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS,
+  );
+  const exaEnabled = ['true', '1'].includes(
+    process.env.OPENCODE_ENABLE_EXA?.toLowerCase() ?? '',
+  );
+  if (backgroundSubagentsEnabled && exaEnabled) {
     printSuccess(
-      'OpenCode background subagents already enabled in environment',
+      'OpenCode background subagents and Exa websearch already enabled in environment',
     );
     return { enabledNow: true };
   }
@@ -218,10 +220,10 @@ export async function configureBackgroundSubagents(
       'V2 requires OpenCode background subagents for default orchestration.',
     );
     printInfo(
-      `The installer can add the required environment export to ${target}.`,
+      `The installer can add the required environment exports to ${target}.`,
     );
     const shouldWrite = await confirm(
-      'Add OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true now?',
+      'Enable background subagents and Exa websearch now?',
       true,
     );
     if (!shouldWrite) {
@@ -244,7 +246,7 @@ export async function configureBackgroundSubagents(
   }
 
   printSuccess(
-    `Background subagents enabled ${SYMBOLS.arrow} ${DIM}${target}${RESET}`,
+    `Background subagents and Exa websearch enabled ${SYMBOLS.arrow} ${DIM}${target}${RESET}`,
   );
   return { enabledNow: false, configuredTarget: target };
 }
@@ -537,7 +539,7 @@ async function runInstall(config: InstallConfig): Promise<number> {
     );
   } else {
     console.log(
-      `     ${BLUE}$ OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true opencode${RESET}`,
+      `     ${BLUE}$ OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS=true OPENCODE_ENABLE_EXA=1 opencode${RESET}`,
     );
   }
   console.log();