@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 +6 -3
- package/src/commands/AGENTS.md +0 -2
- package/src/commands/capability.test.ts +5 -35
- package/src/commands/capability.ts +7 -4
- package/src/commands/doctor.test.ts +32 -26
- package/src/commands/doctor.ts +24 -7
- package/src/commands/init.test.ts +97 -72
- package/src/commands/init.ts +93 -97
- package/src/commands/profile.test.ts +9 -21
- package/src/commands/profile.ts +4 -2
- package/src/commands/provider.test.ts +128 -0
- package/src/commands/provider.ts +139 -0
- package/src/commands/sync.ts +10 -4
- package/src/index.ts +2 -2
- package/src/lib/dynamic-app.ts +2 -4
- package/src/prompts/provider.ts +6 -4
- package/src/commands/mcp.ts +0 -113
- package/src/commands/serve.test.ts +0 -181
- package/src/commands/serve.ts +0 -63
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnidev-ai/cli",
|
|
3
|
-
"version": "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/
|
|
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": {}
|
package/src/commands/AGENTS.md
CHANGED
|
@@ -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 {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
-
".
|
|
33
|
-
`# OmniDev
|
|
34
|
-
.
|
|
35
|
-
|
|
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
|
|
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
|
-
//
|
|
178
|
+
// .gitignore exists but missing OmniDev entries
|
|
179
|
+
writeFileSync(".gitignore", "node_modules/\n");
|
|
174
180
|
|
|
175
181
|
await runDoctor();
|
|
176
182
|
|
package/src/commands/doctor.ts
CHANGED
|
@@ -20,7 +20,7 @@ export async function runDoctor(): Promise<void> {
|
|
|
20
20
|
checkBunVersion(),
|
|
21
21
|
checkOmniLocalDir(),
|
|
22
22
|
checkConfig(),
|
|
23
|
-
|
|
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
|
|
132
|
-
const gitignorePath = ".
|
|
131
|
+
async function checkRootGitignore(): Promise<Check> {
|
|
132
|
+
const gitignorePath = ".gitignore";
|
|
133
133
|
if (!existsSync(gitignorePath)) {
|
|
134
134
|
return {
|
|
135
|
-
name: "
|
|
135
|
+
name: "Root .gitignore",
|
|
136
136
|
passed: false,
|
|
137
|
-
message: ".
|
|
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: "
|
|
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 {
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync
|
|
3
|
-
import {
|
|
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
|
-
|
|
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
|
|
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
|
-
//
|
|
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
|
|
87
|
-
const content = readFileSync("omni.
|
|
88
|
-
|
|
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
|
|
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("
|
|
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
|
-
|
|
177
|
-
expect(
|
|
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("
|
|
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).
|
|
193
|
-
expect(content).
|
|
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("
|
|
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
|
-
|
|
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.
|
|
253
|
-
|
|
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.
|
|
262
|
-
|
|
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
|
});
|