@omnidev-ai/cli 0.1.1 → 0.3.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnidev-ai/cli",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -9,7 +9,9 @@
9
9
  "directory": "packages/cli"
10
10
  },
11
11
  "exports": {
12
- ".": "./src/index.ts"
12
+ ".": "./src/index.ts",
13
+ "./commands/init": "./src/commands/init.ts",
14
+ "./commands/sync": "./src/commands/sync.ts"
13
15
  },
14
16
  "bin": {
15
17
  "omnidev": "./src/index.ts"
@@ -28,7 +30,8 @@
28
30
  },
29
31
  "dependencies": {
30
32
  "@inquirer/prompts": "^8.1.0",
31
- "@omnidev-ai/core": "^0.1.1",
33
+ "@omnidev-ai/adapters": "^0.3.0",
34
+ "@omnidev-ai/core": "^0.3.0",
32
35
  "@stricli/core": "^1.2.5"
33
36
  },
34
37
  "devDependencies": {}
@@ -10,13 +10,11 @@ Static CLI commands built with Stricli framework for OmniDev project management.
10
10
  | Command | File | Purpose |
11
11
  |---------|------|---------|
12
12
  | `omnidev init` | init.ts | Creates .omni/ directory, config.toml, provider.toml, instructions.md, internal gitignore |
13
- | `omnidev serve` | serve.ts | Starts MCP server via @omnidev-ai/mcp, optional --profile flag sets active profile |
14
13
  | `omnidev doctor` | doctor.ts | Validates Bun version (≥1.0), .omni/ directory, config.toml, internal gitignore |
15
14
  | `omnidev capability` | capability.ts | Subcommands: list, enable <name>, disable <name> (auto-syncs on enable/disable) |
16
15
  | `omnidev profile` | profile.ts | Subcommands: list, set <name> (auto-syncs on set, shows active with ● indicator) |
17
16
  | `omnidev ralph` | ralph.ts | Orchestrator: init, start [--agent/--iterations/--prd], stop, status; prd/story/spec/log/patterns subcommands |
18
17
  | `omnidev sync` | sync.ts | Manual agent configuration sync (capabilities, skills, rules, .omni/.gitignore) |
19
- | `omnidev mcp status` | mcp.ts | Shows MCP controller status from .omni/state/mcp-status.json |
20
18
 
21
19
  ## CONVENTIONS
22
20
 
@@ -1,20 +1,14 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, rmSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { tmpdir } from "@omnidev-ai/core/test-utils";
2
+ import { mkdirSync } from "node:fs";
3
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
5
4
  import { runCapabilityDisable, runCapabilityEnable, runCapabilityList } from "./capability";
6
5
 
7
6
  describe("capability list command", () => {
8
- let testDir: string;
9
- let originalCwd: string;
7
+ setupTestDir("capability-test-", { chdir: true });
10
8
  let originalExit: typeof process.exit;
11
9
  let exitCode: number | undefined;
12
10
 
13
11
  beforeEach(() => {
14
- originalCwd = process.cwd();
15
- testDir = tmpdir("capability-test-");
16
- process.chdir(testDir);
17
-
18
12
  // Mock process.exit
19
13
  exitCode = undefined;
20
14
  originalExit = process.exit;
@@ -26,10 +20,6 @@ describe("capability list command", () => {
26
20
 
27
21
  afterEach(() => {
28
22
  process.exit = originalExit;
29
- process.chdir(originalCwd);
30
- if (existsSync(testDir)) {
31
- rmSync(testDir, { recursive: true, force: true });
32
- }
33
23
  });
34
24
 
35
25
  test("shows message when no capabilities found", async () => {
@@ -307,17 +297,11 @@ capabilities = ["alpha", "beta", "gamma"]
307
297
  });
308
298
 
309
299
  describe("capability enable command", () => {
310
- let testDir: string;
311
- let originalCwd: string;
300
+ setupTestDir("capability-enable-test-", { chdir: true });
312
301
  let originalExit: typeof process.exit;
313
302
  let exitCode: number | undefined;
314
303
 
315
304
  beforeEach(() => {
316
- originalCwd = process.cwd();
317
- testDir = join(import.meta.dir, `test-capability-enable-${Date.now()}`);
318
- mkdirSync(testDir, { recursive: true });
319
- process.chdir(testDir);
320
-
321
305
  // Mock process.exit
322
306
  exitCode = undefined;
323
307
  originalExit = process.exit;
@@ -329,10 +313,6 @@ describe("capability enable command", () => {
329
313
 
330
314
  afterEach(() => {
331
315
  process.exit = originalExit;
332
- process.chdir(originalCwd);
333
- if (existsSync(testDir)) {
334
- rmSync(testDir, { recursive: true, force: true });
335
- }
336
316
  });
337
317
 
338
318
  test("enables a capability", async () => {
@@ -419,17 +399,11 @@ capabilities = []
419
399
  });
420
400
 
421
401
  describe("capability disable command", () => {
422
- let testDir: string;
423
- let originalCwd: string;
402
+ setupTestDir("capability-disable-test-", { chdir: true });
424
403
  let originalExit: typeof process.exit;
425
404
  let _exitCode: number | undefined;
426
405
 
427
406
  beforeEach(() => {
428
- originalCwd = process.cwd();
429
- testDir = join(import.meta.dir, `test-capability-disable-${Date.now()}`);
430
- mkdirSync(testDir, { recursive: true });
431
- process.chdir(testDir);
432
-
433
407
  // Mock process.exit
434
408
  _exitCode = undefined;
435
409
  originalExit = process.exit;
@@ -441,10 +415,6 @@ describe("capability disable command", () => {
441
415
 
442
416
  afterEach(() => {
443
417
  process.exit = originalExit;
444
- process.chdir(originalCwd);
445
- if (existsSync(testDir)) {
446
- rmSync(testDir, { recursive: true, force: true });
447
- }
448
418
  });
449
419
 
450
420
  test("disables a capability", async () => {
@@ -1,3 +1,4 @@
1
+ import { getEnabledAdapters } from "@omnidev-ai/adapters";
1
2
  import {
2
3
  disableCapability,
3
4
  discoverCapabilities,
@@ -75,8 +76,9 @@ export async function runCapabilityEnable(
75
76
  console.log(`✓ Enabled capability: ${name}`);
76
77
  console.log("");
77
78
 
78
- // Auto-sync agent configuration
79
- await syncAgentConfiguration();
79
+ // Auto-sync agent configuration with enabled adapters
80
+ const adapters = await getEnabledAdapters();
81
+ await syncAgentConfiguration({ adapters });
80
82
  } catch (error) {
81
83
  console.error("Error enabling capability:", error);
82
84
  process.exit(1);
@@ -95,8 +97,9 @@ export async function runCapabilityDisable(
95
97
  console.log(`✓ Disabled capability: ${name}`);
96
98
  console.log("");
97
99
 
98
- // Auto-sync agent configuration
99
- await syncAgentConfiguration();
100
+ // Auto-sync agent configuration with enabled adapters
101
+ const adapters = await getEnabledAdapters();
102
+ await syncAgentConfiguration({ adapters });
100
103
  } catch (error) {
101
104
  console.error("Error disabling capability:", error);
102
105
  process.exit(1);
@@ -1,11 +1,10 @@
1
1
  import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "@omnidev-ai/core/test-utils";
2
+ import { mkdirSync, writeFileSync } from "node:fs";
3
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
4
4
  import { runDoctor } from "./doctor";
5
5
 
6
6
  describe("doctor command", () => {
7
- let testDir: string;
8
- let originalCwd: string;
7
+ setupTestDir("doctor-test-", { chdir: true });
9
8
  let originalExit: typeof process.exit;
10
9
  let exitCalled: boolean;
11
10
  let exitCode: number;
@@ -29,23 +28,15 @@ capabilities = []
29
28
  `,
30
29
  );
31
30
  writeFileSync(
32
- ".omni/.gitignore",
33
- `# OmniDev Core
34
- .env
35
- generated/
36
- state/
37
- sandbox/
38
- *.log
31
+ ".gitignore",
32
+ `# OmniDev
33
+ .omni/
34
+ omni.local.toml
39
35
  `,
40
36
  );
41
37
  }
42
38
 
43
39
  beforeEach(() => {
44
- // Create test directory in /tmp
45
- originalCwd = process.cwd();
46
- testDir = tmpdir("doctor-test-");
47
- process.chdir(testDir);
48
-
49
40
  // Mock process.exit
50
41
  exitCalled = false;
51
42
  exitCode = 0;
@@ -59,14 +50,6 @@ sandbox/
59
50
  afterEach(() => {
60
51
  // Restore process.exit
61
52
  process.exit = originalExit;
62
-
63
- // Restore working directory
64
- process.chdir(originalCwd);
65
-
66
- // Clean up test directory
67
- if (existsSync(testDir)) {
68
- rmSync(testDir, { recursive: true, force: true });
69
- }
70
53
  });
71
54
 
72
55
  test("should pass all checks when setup is complete", async () => {
@@ -156,7 +139,29 @@ sandbox/
156
139
  expect(exitCalled).toBe(false);
157
140
  });
158
141
 
159
- test("should validate internal .gitignore exists", async () => {
142
+ test("should validate root .gitignore has OmniDev entries", async () => {
143
+ mkdirSync(".omni", { recursive: true });
144
+ writeFileSync(
145
+ "omni.toml",
146
+ `project = "test"
147
+ active_profile = "default"
148
+
149
+ [providers]
150
+ enabled = ["claude"]
151
+
152
+ [profiles.default]
153
+ capabilities = []
154
+ `,
155
+ );
156
+ // Missing root .gitignore
157
+
158
+ await runDoctor();
159
+
160
+ expect(exitCalled).toBe(true);
161
+ expect(exitCode).toBe(1);
162
+ });
163
+
164
+ test("should fail when root .gitignore is missing OmniDev entries", async () => {
160
165
  mkdirSync(".omni", { recursive: true });
161
166
  writeFileSync(
162
167
  "omni.toml",
@@ -170,7 +175,8 @@ enabled = ["claude"]
170
175
  capabilities = []
171
176
  `,
172
177
  );
173
- // Missing .omni/.gitignore
178
+ // .gitignore exists but missing OmniDev entries
179
+ writeFileSync(".gitignore", "node_modules/\n");
174
180
 
175
181
  await runDoctor();
176
182
 
@@ -20,7 +20,7 @@ export async function runDoctor(): Promise<void> {
20
20
  checkBunVersion(),
21
21
  checkOmniLocalDir(),
22
22
  checkConfig(),
23
- checkInternalGitignore(),
23
+ checkRootGitignore(),
24
24
  checkCapabilitiesDir(),
25
25
  ];
26
26
 
@@ -128,21 +128,38 @@ async function checkConfig(): Promise<Check> {
128
128
  }
129
129
  }
130
130
 
131
- async function checkInternalGitignore(): Promise<Check> {
132
- const gitignorePath = ".omni/.gitignore";
131
+ async function checkRootGitignore(): Promise<Check> {
132
+ const gitignorePath = ".gitignore";
133
133
  if (!existsSync(gitignorePath)) {
134
134
  return {
135
- name: "Internal .gitignore",
135
+ name: "Root .gitignore",
136
136
  passed: false,
137
- message: ".omni/.gitignore not found",
137
+ message: ".gitignore not found",
138
+ fix: "Run: omnidev init",
139
+ };
140
+ }
141
+
142
+ const content = await Bun.file(gitignorePath).text();
143
+ const lines = content.split("\n").map((line) => line.trim());
144
+ const hasOmniDir = lines.includes(".omni/");
145
+ const hasLocalToml = lines.includes("omni.local.toml");
146
+
147
+ if (!hasOmniDir || !hasLocalToml) {
148
+ const missing: string[] = [];
149
+ if (!hasOmniDir) missing.push(".omni/");
150
+ if (!hasLocalToml) missing.push("omni.local.toml");
151
+ return {
152
+ name: "Root .gitignore",
153
+ passed: false,
154
+ message: `Missing entries: ${missing.join(", ")}`,
138
155
  fix: "Run: omnidev init",
139
156
  };
140
157
  }
141
158
 
142
159
  return {
143
- name: "Internal .gitignore",
160
+ name: "Root .gitignore",
144
161
  passed: true,
145
- message: "Found",
162
+ message: "Found with OmniDev entries",
146
163
  };
147
164
  }
148
165
 
@@ -1,34 +1,20 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
3
- import { tmpdir } from "@omnidev-ai/core/test-utils";
1
+ import { describe, expect, test } from "bun:test";
2
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
3
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
4
4
  import { runInit } from "./init";
5
5
 
6
6
  describe("init command", () => {
7
- let testDir: string;
8
- let originalCwd: string;
9
-
10
- beforeEach(() => {
11
- originalCwd = process.cwd();
12
- testDir = tmpdir("init-test-");
13
- process.chdir(testDir);
14
- });
15
-
16
- afterEach(() => {
17
- process.chdir(originalCwd);
18
- if (existsSync(testDir)) {
19
- rmSync(testDir, { recursive: true, force: true });
20
- }
21
- });
7
+ setupTestDir("init-test-", { chdir: true });
22
8
 
23
9
  test("creates .omni/ directory", async () => {
24
- await runInit({}, "claude");
10
+ await runInit({}, "claude-code");
25
11
 
26
12
  expect(existsSync(".omni")).toBe(true);
27
13
  expect(existsSync(".omni/capabilities")).toBe(true);
28
14
  });
29
15
 
30
16
  test("creates omni.toml with default config", async () => {
31
- await runInit({}, "claude");
17
+ await runInit({}, "claude-code");
32
18
 
33
19
  expect(existsSync("omni.toml")).toBe(true);
34
20
 
@@ -40,14 +26,14 @@ describe("init command", () => {
40
26
  expect(content).toContain("[profiles.default]");
41
27
  expect(content).toContain("[profiles.planning]");
42
28
  expect(content).toContain("[profiles.coding]");
43
- // providers should be in config.toml
44
- expect(content).toContain("[providers]");
29
+ // providers are stored in local state, not config.toml
30
+ expect(content).not.toContain("[providers]");
45
31
  // should have documentation comments
46
32
  expect(content).toContain("# OmniDev Configuration");
47
33
  });
48
34
 
49
35
  test("creates active profile in state file", async () => {
50
- await runInit({}, "claude");
36
+ await runInit({}, "claude-code");
51
37
 
52
38
  expect(existsSync(".omni/state/active-profile")).toBe(true);
53
39
 
@@ -55,37 +41,47 @@ describe("init command", () => {
55
41
  expect(content).toBe("default");
56
42
  });
57
43
 
44
+ test("stores enabled providers in local state file", async () => {
45
+ await runInit({}, "claude-code");
46
+
47
+ expect(existsSync(".omni/state/providers.json")).toBe(true);
48
+
49
+ const content = readFileSync(".omni/state/providers.json", "utf-8");
50
+ const state = JSON.parse(content);
51
+ expect(state.enabled).toContain("claude-code");
52
+ });
53
+
58
54
  test("does not create separate capabilities.toml file", async () => {
59
- await runInit({}, "claude");
55
+ await runInit({}, "claude-code");
60
56
 
61
57
  // All config is unified in config.toml
62
58
  expect(existsSync(".omni/capabilities.toml")).toBe(false);
63
59
  });
64
60
 
65
61
  test("does not create separate profiles.toml file", async () => {
66
- await runInit({}, "claude");
62
+ await runInit({}, "claude-code");
67
63
 
68
64
  // Profiles are in config.toml
69
65
  expect(existsSync(".omni/profiles.toml")).toBe(false);
70
66
  });
71
67
 
72
68
  test("creates .omni/ directory with subdirectories", async () => {
73
- await runInit({}, "claude");
69
+ await runInit({}, "claude-code");
74
70
 
75
71
  expect(existsSync(".omni")).toBe(true);
76
72
  expect(existsSync(".omni/state")).toBe(true);
77
- expect(existsSync(".omni/sandbox")).toBe(true);
78
73
  });
79
74
 
80
75
  test("does not create separate provider.toml file", async () => {
81
- await runInit({}, "claude");
76
+ await runInit({}, "claude-code");
82
77
 
83
- // Providers are in config.toml
78
+ // Provider state is in .omni/state/providers.json
84
79
  expect(existsSync(".omni/provider.toml")).toBe(false);
85
80
 
86
- // Verify provider is in config.toml instead
87
- const content = readFileSync("omni.toml", "utf-8");
88
- expect(content).toContain('enabled = ["claude"]');
81
+ // Verify provider is in state file instead
82
+ const content = readFileSync(".omni/state/providers.json", "utf-8");
83
+ const state = JSON.parse(content);
84
+ expect(state.enabled).toContain("claude-code");
89
85
  });
90
86
 
91
87
  test("creates AGENTS.md for Codex provider", async () => {
@@ -111,14 +107,14 @@ describe("init command", () => {
111
107
  expect(content).toContain("No capabilities enabled yet");
112
108
  });
113
109
 
114
- test("does not create AGENTS.md for Claude provider", async () => {
115
- await runInit({}, "claude");
110
+ test("does not create AGENTS.md for Claude Code provider", async () => {
111
+ await runInit({}, "claude-code");
116
112
 
117
113
  expect(existsSync("AGENTS.md")).toBe(false);
118
114
  });
119
115
 
120
- test("creates CLAUDE.md for Claude provider", async () => {
121
- await runInit({}, "claude");
116
+ test("creates CLAUDE.md for Claude Code provider", async () => {
117
+ await runInit({}, "claude-code");
122
118
 
123
119
  expect(existsSync("CLAUDE.md")).toBe(true);
124
120
 
@@ -133,15 +129,12 @@ describe("init command", () => {
133
129
  expect(existsSync("CLAUDE.md")).toBe(false);
134
130
  });
135
131
 
136
- test("creates both AGENTS.md and CLAUDE.md for 'both' providers", async () => {
132
+ test("creates both CLAUDE.md and .cursor/rules/ for 'both' providers", async () => {
133
+ // "both" maps to claude-code and cursor
137
134
  await runInit({}, "both");
138
135
 
139
- expect(existsSync("AGENTS.md")).toBe(true);
140
136
  expect(existsSync("CLAUDE.md")).toBe(true);
141
-
142
- const agentsContent = readFileSync("AGENTS.md", "utf-8");
143
- expect(agentsContent).toContain("# Project Instructions");
144
- expect(agentsContent).toContain("@import .omni/instructions.md");
137
+ expect(existsSync(".cursor/rules")).toBe(true);
145
138
 
146
139
  const claudeContent = readFileSync("CLAUDE.md", "utf-8");
147
140
  expect(claudeContent).toContain("# Project Instructions");
@@ -152,7 +145,7 @@ describe("init command", () => {
152
145
  const existingContent = "# My Existing Config\n\nExisting content here.\n";
153
146
  await Bun.write("CLAUDE.md", existingContent);
154
147
 
155
- await runInit({}, "claude");
148
+ await runInit({}, "claude-code");
156
149
 
157
150
  const content = readFileSync("CLAUDE.md", "utf-8");
158
151
  expect(content).toBe(existingContent);
@@ -168,47 +161,58 @@ describe("init command", () => {
168
161
  expect(content).toBe(existingContent);
169
162
  });
170
163
 
171
- test("creates .omni/.gitignore with internal patterns", async () => {
172
- await runInit({}, "claude");
173
-
174
- expect(existsSync(".omni/.gitignore")).toBe(true);
164
+ test("does not create .omni/.gitignore", async () => {
165
+ await runInit({}, "claude-code");
175
166
 
176
- const content = readFileSync(".omni/.gitignore", "utf-8");
177
- expect(content).toContain("# OmniDev working files - always ignored");
178
- expect(content).toContain(".env");
179
- expect(content).toContain("state/");
180
- expect(content).toContain("sandbox/");
181
- expect(content).toContain("*.log");
167
+ // The whole .omni/ directory is gitignored, no need for internal .gitignore
168
+ expect(existsSync(".omni/.gitignore")).toBe(false);
182
169
  });
183
170
 
184
- test("does not modify project's root .gitignore", async () => {
171
+ test("updates root .gitignore with omnidev entries", async () => {
185
172
  // Create a root .gitignore with custom content
186
173
  await Bun.write(".gitignore", "node_modules/\n*.log\n");
187
174
 
188
- await runInit({}, "claude");
175
+ await runInit({}, "claude-code");
189
176
 
190
- // Verify .gitignore was not modified
191
177
  const content = readFileSync(".gitignore", "utf-8");
192
- expect(content).toBe("node_modules/\n*.log\n");
193
- expect(content).not.toContain(".omni/");
178
+ expect(content).toContain("node_modules/");
179
+ expect(content).toContain("*.log");
180
+ expect(content).toContain("# OmniDev");
181
+ expect(content).toContain(".omni/");
182
+ expect(content).toContain("omni.local.toml");
194
183
  });
195
184
 
196
- test("does not create root .gitignore if it doesn't exist", async () => {
197
- await runInit({}, "claude");
185
+ test("creates root .gitignore if it doesn't exist", async () => {
186
+ await runInit({}, "claude-code");
187
+
188
+ expect(existsSync(".gitignore")).toBe(true);
189
+
190
+ const content = readFileSync(".gitignore", "utf-8");
191
+ expect(content).toContain("# OmniDev");
192
+ expect(content).toContain(".omni/");
193
+ expect(content).toContain("omni.local.toml");
194
+ });
195
+
196
+ test("does not duplicate gitignore entries on multiple runs", async () => {
197
+ await runInit({}, "claude-code");
198
+ await runInit({}, "claude-code");
198
199
 
199
- expect(existsSync(".gitignore")).toBe(false);
200
+ const content = readFileSync(".gitignore", "utf-8");
201
+ // Should only have one occurrence of each entry
202
+ expect(content.match(/\.omni\//g)?.length).toBe(1);
203
+ expect(content.match(/omni\.local\.toml/g)?.length).toBe(1);
200
204
  });
201
205
 
202
206
  test("is idempotent - safe to run multiple times", async () => {
203
- await runInit({}, "claude");
204
- await runInit({}, "claude");
205
- await runInit({}, "claude");
207
+ await runInit({}, "claude-code");
208
+ await runInit({}, "claude-code");
209
+ await runInit({}, "claude-code");
206
210
 
207
211
  expect(existsSync("omni.toml")).toBe(true);
208
212
  expect(existsSync(".omni")).toBe(true);
209
213
  expect(existsSync("CLAUDE.md")).toBe(true);
210
214
 
211
- // Should not create AGENTS.md for Claude
215
+ // Should not create AGENTS.md for Claude Code
212
216
  expect(existsSync("AGENTS.md")).toBe(false);
213
217
  });
214
218
 
@@ -217,7 +221,7 @@ describe("init command", () => {
217
221
  mkdirSync(".omni", { recursive: true });
218
222
  await Bun.write("omni.toml", customConfig);
219
223
 
220
- await runInit({}, "claude");
224
+ await runInit({}, "claude-code");
221
225
 
222
226
  const content = readFileSync("omni.toml", "utf-8");
223
227
  expect(content).toBe(customConfig);
@@ -236,12 +240,11 @@ describe("init command", () => {
236
240
  test("creates all directories even if some already exist", async () => {
237
241
  mkdirSync(".omni", { recursive: true });
238
242
 
239
- await runInit({}, "claude");
243
+ await runInit({}, "claude-code");
240
244
 
241
245
  expect(existsSync(".omni/capabilities")).toBe(true);
242
246
  expect(existsSync(".omni")).toBe(true);
243
247
  expect(existsSync(".omni/state")).toBe(true);
244
- expect(existsSync(".omni/sandbox")).toBe(true);
245
248
  });
246
249
 
247
250
  test("accepts provider via positional parameter", async () => {
@@ -249,8 +252,9 @@ describe("init command", () => {
249
252
 
250
253
  expect(existsSync(".omni/provider.toml")).toBe(false);
251
254
 
252
- const content = readFileSync("omni.toml", "utf-8");
253
- expect(content).toContain('enabled = ["codex"]');
255
+ const content = readFileSync(".omni/state/providers.json", "utf-8");
256
+ const state = JSON.parse(content);
257
+ expect(state.enabled).toContain("codex");
254
258
  });
255
259
 
256
260
  test("accepts 'both' as provider parameter", async () => {
@@ -258,7 +262,28 @@ describe("init command", () => {
258
262
 
259
263
  expect(existsSync(".omni/provider.toml")).toBe(false);
260
264
 
261
- const content = readFileSync("omni.toml", "utf-8");
262
- expect(content).toContain('enabled = ["claude", "codex"]');
265
+ const content = readFileSync(".omni/state/providers.json", "utf-8");
266
+ const state = JSON.parse(content);
267
+ // "both" maps to claude-code and cursor
268
+ expect(state.enabled).toContain("claude-code");
269
+ expect(state.enabled).toContain("cursor");
270
+ });
271
+
272
+ test("supports legacy 'claude' name mapping to claude-code", async () => {
273
+ await runInit({}, "claude");
274
+
275
+ const content = readFileSync(".omni/state/providers.json", "utf-8");
276
+ const state = JSON.parse(content);
277
+ expect(state.enabled).toContain("claude-code");
278
+ });
279
+
280
+ test("supports comma-separated providers", async () => {
281
+ await runInit({}, "claude-code,codex,cursor");
282
+
283
+ const content = readFileSync(".omni/state/providers.json", "utf-8");
284
+ const state = JSON.parse(content);
285
+ expect(state.enabled).toContain("claude-code");
286
+ expect(state.enabled).toContain("codex");
287
+ expect(state.enabled).toContain("cursor");
263
288
  });
264
289
  });