Skip to content

Commit 5a63d6e

Browse files
committed
feat: add OpenCode AI coding tool support
Add support for detecting OpenCode AI coding tool through environment variable OPENCODE=1. Changes: - Create src/ai-tools/opencode.ts configuration file - Add OpenCode to AI tools index with CLI type (highest priority) - Update test expectations from 9 to 10 tools - Add comprehensive tests for OpenCode environment variable: * Integration test for OPENCODE=1 * Unit test in exec.test.ts for getCoDevelopedBy function * Production build test for compiled code * Installed package test for npm pack scenarios The OpenCode tool is detected when OPENCODE=1 is set, and will add "Co-developed-by: OpenCode <noreply@opencode.ai>" to commit messages. Change-Id: If8f616e4d6429c59f15e298f4f49191f05f5868e Co-developed-by: Cursor <noreply@cursor.com>
1 parent ba0d711 commit 5a63d6e

6 files changed

Lines changed: 111 additions & 3 deletions

File tree

src/ai-tools/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,7 @@ import cursorConfig from './cursor.js';
7474
import geminiConfig from './gemini.js';
7575
import iflowConfig from './iflow.js';
7676
import kiroConfig from './kiro.js';
77+
import opencodeConfig from './opencode.js';
7778
import qoderCliConfig from './qoder-cli.js';
7879
import qoderIdeConfig from './qoder-ide.js';
7980
import qwenCodeConfig from './qwen-code.js';
@@ -86,6 +87,7 @@ const allConfigs: AIToolConfig[] = [
8687
claudeConfig,
8788
codexConfig,
8889
iflowConfig,
90+
opencodeConfig,
8991
qwenCodeConfig,
9092
geminiConfig,
9193
qoderCliConfig,

src/ai-tools/opencode.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import type { AIToolConfig } from './index.js';
2+
3+
const config: AIToolConfig = {
4+
type: 'cli',
5+
userName: 'OpenCode',
6+
userEmail: 'noreply@opencode.ai',
7+
envVars: [{ key: 'OPENCODE', value: '1' }],
8+
};
9+
10+
export default config;

test/commands/exec.test.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -801,6 +801,13 @@ describe('exec command utilities', () => {
801801
expect(getCoDevelopedBy()).toBe('Codex <noreply@openai.com>');
802802
});
803803

804+
it('should return OpenCode CoDevelopedBy when OPENCODE=1 is set', () => {
805+
// Clear all environment variables to ensure proper order testing
806+
clearCoDevelopedByEnvVars();
807+
process.env.OPENCODE = '1';
808+
expect(getCoDevelopedBy()).toBe('OpenCode <noreply@opencode.ai>');
809+
});
810+
804811
it('should return Kiro CoDevelopedBy when __CFBundleIdentifier=dev.kiro.desktop is set', () => {
805812
// Clear all environment variables to ensure proper order testing
806813
clearCoDevelopedByEnvVars();

test/integration.test.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -412,6 +412,59 @@ This feature was developed using Codex via bun.`;
412412
);
413413
});
414414

415+
it('should work with OPENCODE environment variable', () => {
416+
// Create a commit message file
417+
const messageFile = path.join(tempDir, 'opencode-message.txt');
418+
const commitMessage = `feat: implement feature with OpenCode
419+
420+
This feature was developed using OpenCode.`;
421+
writeFileSync(messageFile, commitMessage, 'utf8');
422+
423+
// Test with OPENCODE environment variable, but first unset higher priority variables
424+
const env = {
425+
...process.env,
426+
CLAUDECODE: undefined,
427+
CODEX_MANAGED_BY_NPM: undefined,
428+
CODEX_MANAGED_BY_BUN: undefined,
429+
OPENCODE: '1',
430+
QWEN_CODE: undefined,
431+
GEMINI_CLI: undefined,
432+
VSCODE_BRAND: undefined,
433+
CURSOR_TRACE_ID: undefined,
434+
};
435+
436+
// Execute the commit-msg hook directly
437+
const execResult = spawnSync(
438+
'node',
439+
[path.join(originalCwd, 'dist/bin/commit-msg.js'), 'exec', messageFile],
440+
{
441+
cwd: testRepoDir,
442+
encoding: 'utf-8',
443+
timeout: 30000,
444+
env: env,
445+
}
446+
);
447+
448+
expect(execResult.status).toBe(0);
449+
450+
// Read the processed commit message
451+
const processedMessage = readFileSync(messageFile, 'utf8');
452+
453+
// Should have added Change-Id
454+
expect(processedMessage).toMatch(/Change-Id: I[a-f0-9]{8,}/);
455+
456+
// Should have added Co-developed-by for OpenCode
457+
expect(processedMessage).toContain(
458+
'Co-developed-by: OpenCode <noreply@opencode.ai>'
459+
);
460+
461+
// Verify original content is preserved
462+
expect(processedMessage).toContain('feat: implement feature with OpenCode');
463+
expect(processedMessage).toContain(
464+
'This feature was developed using OpenCode.'
465+
);
466+
});
467+
415468
it('should work with disabled CoDevelopedBy configuration', () => {
416469
// Create a commit message file
417470
const messageFile = path.join(tempDir, 'no-co-developed-message.txt');

test/pack.test.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,6 +469,34 @@ describe('commit-msg CLI npm pack tests', () => {
469469
);
470470
});
471471

472+
it('should generate Co-developed-by for OPENCODE in installed package', () => {
473+
const messageFile = path.join(tempDir, 'codevelopedby-opencode.txt');
474+
const commitMessage = 'feat: test opencode co-developed-by';
475+
writeFileSync(messageFile, commitMessage, 'utf8');
476+
477+
const env = {
478+
...process.env,
479+
OPENCODE: '1',
480+
};
481+
482+
const execResult = spawnSync(
483+
'node',
484+
[installedBinPath, 'exec', messageFile],
485+
{
486+
encoding: 'utf-8',
487+
timeout: 30000,
488+
env: env,
489+
}
490+
);
491+
492+
expect(execResult.status).toBe(0);
493+
494+
const processedMessage = readFileSync(messageFile, 'utf8');
495+
expect(processedMessage).toContain(
496+
'Co-developed-by: OpenCode <noreply@opencode.ai>'
497+
);
498+
});
499+
472500
it('should respect priority order (CLI over IDE) in installed package', () => {
473501
const messageFile = path.join(tempDir, 'priority-test.txt');
474502
const commitMessage = 'feat: test priority';
@@ -518,7 +546,7 @@ describe('commit-msg CLI npm pack tests', () => {
518546
const configs = installedAITools.getAllToolConfigs();
519547
expect(configs).toBeDefined();
520548
expect(Array.isArray(configs)).toBe(true);
521-
expect(configs.length).toBe(9); // Should have 9 tools
549+
expect(configs.length).toBe(10); // Should have 10 tools
522550

523551
// Verify all configs have required fields
524552
for (const config of configs) {

test/production-build.test.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ describe('Production Build Tests', () => {
5252
describe('Configuration Structure Tests', () => {
5353
it('should have correct number of tool configurations', () => {
5454
const configs = productionAITools.getAllToolConfigs();
55-
// Should have 9 tools: claude, codex, iflow, qwen-code, gemini, qoder-cli, cursor, kiro, qoder-ide
56-
expect(configs.length).toBe(9);
55+
// Should have 10 tools: claude, codex, iflow, opencode, qwen-code, gemini, qoder-cli, cursor, kiro, qoder-ide
56+
expect(configs.length).toBe(10);
5757
});
5858

5959
it('should have all required fields in each configuration', () => {
@@ -126,6 +126,7 @@ describe('Production Build Tests', () => {
126126
'Codex',
127127
'Claude',
128128
'iFlow',
129+
'OpenCode',
129130
'Qwen-Coder',
130131
'Gemini',
131132
'Qoder CLI',
@@ -211,6 +212,13 @@ describe('Production Build Tests', () => {
211212
expect(result).toBe('Codex <noreply@openai.com>');
212213
});
213214

215+
it('should return OpenCode CoDevelopedBy when OPENCODE=1 is set', () => {
216+
productionExec.clearCoDevelopedByEnvVars();
217+
process.env.OPENCODE = '1';
218+
const result = productionExec.getCoDevelopedBy();
219+
expect(result).toBe('OpenCode <noreply@opencode.ai>');
220+
});
221+
214222
it('should return Kiro CoDevelopedBy when __CFBundleIdentifier=dev.kiro.desktop is set', () => {
215223
productionExec.clearCoDevelopedByEnvVars();
216224
process.env.__CFBundleIdentifier = 'dev.kiro.desktop';

0 commit comments

Comments
 (0)