@omnidev-ai/cli 0.1.0 → 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.0",
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": "workspace:*",
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";
2
+ import { mkdirSync } from "node:fs";
3
+ import { setupTestDir } from "@omnidev-ai/core/test-utils";
4
4
  import { runCapabilityDisable, runCapabilityEnable, runCapabilityList } from "./capability";
5
5
 
6
6
  describe("capability list command", () => {
7
- let testDir: string;
8
- let originalCwd: string;
7
+ setupTestDir("capability-test-", { chdir: true });
9
8
  let originalExit: typeof process.exit;
10
9
  let exitCode: number | undefined;
11
10
 
12
11
  beforeEach(() => {
13
- originalCwd = process.cwd();
14
- testDir = join(import.meta.dir, `test-capability-${Date.now()}`);
15
- mkdirSync(testDir, { recursive: true });
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 { join } from "node:path";
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,30 +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 a unique test directory
45
- testDir = join(
46
- process.cwd(),
47
- ".test-tmp",
48
- `doctor-test-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`,
49
- );
50
- mkdirSync(testDir, { recursive: true });
51
-
52
- // Change to test directory
53
- originalCwd = process.cwd();
54
- process.chdir(testDir);
55
-
56
40
  // Mock process.exit
57
41
  exitCalled = false;
58
42
  exitCode = 0;
@@ -66,14 +50,6 @@ sandbox/
66
50
  afterEach(() => {
67
51
  // Restore process.exit
68
52
  process.exit = originalExit;
69
-
70
- // Restore working directory
71
- process.chdir(originalCwd);
72
-
73
- // Clean up test directory
74
- if (existsSync(testDir)) {
75
- rmSync(testDir, { recursive: true, force: true });
76
- }
77
53
  });
78
54
 
79
55
  test("should pass all checks when setup is complete", async () => {
@@ -163,7 +139,29 @@ sandbox/
163
139
  expect(exitCalled).toBe(false);
164
140
  });
165
141
 
166
- 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 () => {
167
165
  mkdirSync(".omni", { recursive: true });
168
166
  writeFileSync(
169
167
  "omni.toml",
@@ -177,7 +175,8 @@ enabled = ["claude"]
177
175
  capabilities = []
178
176
  `,
179
177
  );
180
- // Missing .omni/.gitignore
178
+ // .gitignore exists but missing OmniDev entries
179
+ writeFileSync(".gitignore", "node_modules/\n");
181
180
 
182
181
  await runDoctor();
183
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,35 +1,20 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, readFileSync, rmSync } from "node:fs";
3
- import { join } from "node:path";
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 = join(import.meta.dir, `test-init-${Date.now()}`);
13
- mkdirSync(testDir, { recursive: true });
14
- process.chdir(testDir);
15
- });
16
-
17
- afterEach(() => {
18
- process.chdir(originalCwd);
19
- if (existsSync(testDir)) {
20
- rmSync(testDir, { recursive: true, force: true });
21
- }
22
- });
7
+ setupTestDir("init-test-", { chdir: true });
23
8
 
24
9
  test("creates .omni/ directory", async () => {
25
- await runInit({}, "claude");
10
+ await runInit({}, "claude-code");
26
11
 
27
12
  expect(existsSync(".omni")).toBe(true);
28
13
  expect(existsSync(".omni/capabilities")).toBe(true);
29
14
  });
30
15
 
31
16
  test("creates omni.toml with default config", async () => {
32
- await runInit({}, "claude");
17
+ await runInit({}, "claude-code");
33
18
 
34
19
  expect(existsSync("omni.toml")).toBe(true);
35
20
 
@@ -41,14 +26,14 @@ describe("init command", () => {
41
26
  expect(content).toContain("[profiles.default]");
42
27
  expect(content).toContain("[profiles.planning]");
43
28
  expect(content).toContain("[profiles.coding]");
44
- // providers should be in config.toml
45
- expect(content).toContain("[providers]");
29
+ // providers are stored in local state, not config.toml
30
+ expect(content).not.toContain("[providers]");
46
31
  // should have documentation comments
47
32
  expect(content).toContain("# OmniDev Configuration");
48
33
  });
49
34
 
50
35
  test("creates active profile in state file", async () => {
51
- await runInit({}, "claude");
36
+ await runInit({}, "claude-code");
52
37
 
53
38
  expect(existsSync(".omni/state/active-profile")).toBe(true);
54
39
 
@@ -56,37 +41,47 @@ describe("init command", () => {
56
41
  expect(content).toBe("default");
57
42
  });
58
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
+
59
54
  test("does not create separate capabilities.toml file", async () => {
60
- await runInit({}, "claude");
55
+ await runInit({}, "claude-code");
61
56
 
62
57
  // All config is unified in config.toml
63
58
  expect(existsSync(".omni/capabilities.toml")).toBe(false);
64
59
  });
65
60
 
66
61
  test("does not create separate profiles.toml file", async () => {
67
- await runInit({}, "claude");
62
+ await runInit({}, "claude-code");
68
63
 
69
64
  // Profiles are in config.toml
70
65
  expect(existsSync(".omni/profiles.toml")).toBe(false);
71
66
  });
72
67
 
73
68
  test("creates .omni/ directory with subdirectories", async () => {
74
- await runInit({}, "claude");
69
+ await runInit({}, "claude-code");
75
70
 
76
71
  expect(existsSync(".omni")).toBe(true);
77
72
  expect(existsSync(".omni/state")).toBe(true);
78
- expect(existsSync(".omni/sandbox")).toBe(true);
79
73
  });
80
74
 
81
75
  test("does not create separate provider.toml file", async () => {
82
- await runInit({}, "claude");
76
+ await runInit({}, "claude-code");
83
77
 
84
- // Providers are in config.toml
78
+ // Provider state is in .omni/state/providers.json
85
79
  expect(existsSync(".omni/provider.toml")).toBe(false);
86
80
 
87
- // Verify provider is in config.toml instead
88
- const content = readFileSync("omni.toml", "utf-8");
89
- 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");
90
85
  });
91
86
 
92
87
  test("creates AGENTS.md for Codex provider", async () => {
@@ -112,14 +107,14 @@ describe("init command", () => {
112
107
  expect(content).toContain("No capabilities enabled yet");
113
108
  });
114
109
 
115
- test("does not create AGENTS.md for Claude provider", async () => {
116
- await runInit({}, "claude");
110
+ test("does not create AGENTS.md for Claude Code provider", async () => {
111
+ await runInit({}, "claude-code");
117
112
 
118
113
  expect(existsSync("AGENTS.md")).toBe(false);
119
114
  });
120
115
 
121
- test("creates CLAUDE.md for Claude provider", async () => {
122
- await runInit({}, "claude");
116
+ test("creates CLAUDE.md for Claude Code provider", async () => {
117
+ await runInit({}, "claude-code");
123
118
 
124
119
  expect(existsSync("CLAUDE.md")).toBe(true);
125
120
 
@@ -134,15 +129,12 @@ describe("init command", () => {
134
129
  expect(existsSync("CLAUDE.md")).toBe(false);
135
130
  });
136
131
 
137
- 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
138
134
  await runInit({}, "both");
139
135
 
140
- expect(existsSync("AGENTS.md")).toBe(true);
141
136
  expect(existsSync("CLAUDE.md")).toBe(true);
142
-
143
- const agentsContent = readFileSync("AGENTS.md", "utf-8");
144
- expect(agentsContent).toContain("# Project Instructions");
145
- expect(agentsContent).toContain("@import .omni/instructions.md");
137
+ expect(existsSync(".cursor/rules")).toBe(true);
146
138
 
147
139
  const claudeContent = readFileSync("CLAUDE.md", "utf-8");
148
140
  expect(claudeContent).toContain("# Project Instructions");
@@ -153,7 +145,7 @@ describe("init command", () => {
153
145
  const existingContent = "# My Existing Config\n\nExisting content here.\n";
154
146
  await Bun.write("CLAUDE.md", existingContent);
155
147
 
156
- await runInit({}, "claude");
148
+ await runInit({}, "claude-code");
157
149
 
158
150
  const content = readFileSync("CLAUDE.md", "utf-8");
159
151
  expect(content).toBe(existingContent);
@@ -169,47 +161,58 @@ describe("init command", () => {
169
161
  expect(content).toBe(existingContent);
170
162
  });
171
163
 
172
- test("creates .omni/.gitignore with internal patterns", async () => {
173
- await runInit({}, "claude");
164
+ test("does not create .omni/.gitignore", async () => {
165
+ await runInit({}, "claude-code");
174
166
 
175
- expect(existsSync(".omni/.gitignore")).toBe(true);
176
-
177
- const content = readFileSync(".omni/.gitignore", "utf-8");
178
- expect(content).toContain("# OmniDev working files - always ignored");
179
- expect(content).toContain(".env");
180
- expect(content).toContain("state/");
181
- expect(content).toContain("sandbox/");
182
- expect(content).toContain("*.log");
167
+ // The whole .omni/ directory is gitignored, no need for internal .gitignore
168
+ expect(existsSync(".omni/.gitignore")).toBe(false);
183
169
  });
184
170
 
185
- test("does not modify project's root .gitignore", async () => {
171
+ test("updates root .gitignore with omnidev entries", async () => {
186
172
  // Create a root .gitignore with custom content
187
173
  await Bun.write(".gitignore", "node_modules/\n*.log\n");
188
174
 
189
- await runInit({}, "claude");
175
+ await runInit({}, "claude-code");
190
176
 
191
- // Verify .gitignore was not modified
192
177
  const content = readFileSync(".gitignore", "utf-8");
193
- expect(content).toBe("node_modules/\n*.log\n");
194
- 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");
195
183
  });
196
184
 
197
- test("does not create root .gitignore if it doesn't exist", async () => {
198
- 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");
199
199
 
200
- 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);
201
204
  });
202
205
 
203
206
  test("is idempotent - safe to run multiple times", async () => {
204
- await runInit({}, "claude");
205
- await runInit({}, "claude");
206
- await runInit({}, "claude");
207
+ await runInit({}, "claude-code");
208
+ await runInit({}, "claude-code");
209
+ await runInit({}, "claude-code");
207
210
 
208
211
  expect(existsSync("omni.toml")).toBe(true);
209
212
  expect(existsSync(".omni")).toBe(true);
210
213
  expect(existsSync("CLAUDE.md")).toBe(true);
211
214
 
212
- // Should not create AGENTS.md for Claude
215
+ // Should not create AGENTS.md for Claude Code
213
216
  expect(existsSync("AGENTS.md")).toBe(false);
214
217
  });
215
218
 
@@ -218,7 +221,7 @@ describe("init command", () => {
218
221
  mkdirSync(".omni", { recursive: true });
219
222
  await Bun.write("omni.toml", customConfig);
220
223
 
221
- await runInit({}, "claude");
224
+ await runInit({}, "claude-code");
222
225
 
223
226
  const content = readFileSync("omni.toml", "utf-8");
224
227
  expect(content).toBe(customConfig);
@@ -237,12 +240,11 @@ describe("init command", () => {
237
240
  test("creates all directories even if some already exist", async () => {
238
241
  mkdirSync(".omni", { recursive: true });
239
242
 
240
- await runInit({}, "claude");
243
+ await runInit({}, "claude-code");
241
244
 
242
245
  expect(existsSync(".omni/capabilities")).toBe(true);
243
246
  expect(existsSync(".omni")).toBe(true);
244
247
  expect(existsSync(".omni/state")).toBe(true);
245
- expect(existsSync(".omni/sandbox")).toBe(true);
246
248
  });
247
249
 
248
250
  test("accepts provider via positional parameter", async () => {
@@ -250,8 +252,9 @@ describe("init command", () => {
250
252
 
251
253
  expect(existsSync(".omni/provider.toml")).toBe(false);
252
254
 
253
- const content = readFileSync("omni.toml", "utf-8");
254
- 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");
255
258
  });
256
259
 
257
260
  test("accepts 'both' as provider parameter", async () => {
@@ -259,7 +262,28 @@ describe("init command", () => {
259
262
 
260
263
  expect(existsSync(".omni/provider.toml")).toBe(false);
261
264
 
262
- const content = readFileSync("omni.toml", "utf-8");
263
- 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");
264
288
  });
265
289
  });