@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 +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 -33
- package/src/commands/doctor.ts +24 -7
- package/src/commands/init.test.ts +97 -73
- package/src/commands/init.ts +93 -97
- package/src/commands/profile.test.ts +9 -22
- 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 -184
- 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 {
|
|
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
|
-
|
|
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
|
-
|
|
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,30 +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 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
|
|
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
|
-
//
|
|
178
|
+
// .gitignore exists but missing OmniDev entries
|
|
179
|
+
writeFileSync(".gitignore", "node_modules/\n");
|
|
181
180
|
|
|
182
181
|
await runDoctor();
|
|
183
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,35 +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 = 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
|
|
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
|
-
//
|
|
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
|
|
88
|
-
const content = readFileSync("omni.
|
|
89
|
-
|
|
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
|
|
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("
|
|
173
|
-
await runInit({}, "claude");
|
|
164
|
+
test("does not create .omni/.gitignore", async () => {
|
|
165
|
+
await runInit({}, "claude-code");
|
|
174
166
|
|
|
175
|
-
|
|
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("
|
|
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).
|
|
194
|
-
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");
|
|
195
183
|
});
|
|
196
184
|
|
|
197
|
-
test("
|
|
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
|
-
|
|
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.
|
|
254
|
-
|
|
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.
|
|
263
|
-
|
|
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
|
});
|