@omnidev-ai/cli 0.1.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 +35 -0
- package/src/commands/AGENTS.md +43 -0
- package/src/commands/capability.test.ts +483 -0
- package/src/commands/capability.ts +163 -0
- package/src/commands/doctor.test.ts +197 -0
- package/src/commands/doctor.ts +164 -0
- package/src/commands/init.test.ts +265 -0
- package/src/commands/init.ts +192 -0
- package/src/commands/mcp.ts +113 -0
- package/src/commands/profile.test.ts +352 -0
- package/src/commands/profile.ts +151 -0
- package/src/commands/serve.test.ts +184 -0
- package/src/commands/serve.ts +63 -0
- package/src/commands/sync.ts +43 -0
- package/src/index.ts +55 -0
- package/src/lib/debug.ts +4 -0
- package/src/lib/dynamic-app.ts +182 -0
- package/src/prompts/provider.ts +15 -0
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@omnidev-ai/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Nikola-Milovic/omnidev.git",
|
|
9
|
+
"directory": "packages/cli"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": "./src/index.ts"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"omnidev": "./src/index.ts"
|
|
16
|
+
},
|
|
17
|
+
"files": [
|
|
18
|
+
"src",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"publishConfig": {
|
|
22
|
+
"access": "public",
|
|
23
|
+
"registry": "https://registry.npmjs.org"
|
|
24
|
+
},
|
|
25
|
+
"scripts": {
|
|
26
|
+
"typecheck": "tsc --noEmit",
|
|
27
|
+
"build": "echo 'Build not needed for Bun runtime'"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"@inquirer/prompts": "^8.1.0",
|
|
31
|
+
"@omnidev-ai/core": "workspace:*",
|
|
32
|
+
"@stricli/core": "^1.2.5"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {}
|
|
35
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# CLI COMMANDS
|
|
2
|
+
|
|
3
|
+
**Generated:** 2026-01-12
|
|
4
|
+
**Location:** packages/cli/src/commands/
|
|
5
|
+
|
|
6
|
+
## OVERVIEW
|
|
7
|
+
Static CLI commands built with Stricli framework for OmniDev project management.
|
|
8
|
+
|
|
9
|
+
## WHERE TO LOOK
|
|
10
|
+
| Command | File | Purpose |
|
|
11
|
+
|---------|------|---------|
|
|
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
|
+
| `omnidev doctor` | doctor.ts | Validates Bun version (≥1.0), .omni/ directory, config.toml, internal gitignore |
|
|
15
|
+
| `omnidev capability` | capability.ts | Subcommands: list, enable <name>, disable <name> (auto-syncs on enable/disable) |
|
|
16
|
+
| `omnidev profile` | profile.ts | Subcommands: list, set <name> (auto-syncs on set, shows active with ● indicator) |
|
|
17
|
+
| `omnidev ralph` | ralph.ts | Orchestrator: init, start [--agent/--iterations/--prd], stop, status; prd/story/spec/log/patterns subcommands |
|
|
18
|
+
| `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
|
+
|
|
21
|
+
## CONVENTIONS
|
|
22
|
+
|
|
23
|
+
**Command Structure:**
|
|
24
|
+
- All commands use Stricli's `buildCommand()` or `buildRouteMap()` from `@stricli/core`
|
|
25
|
+
- Commands import from `@omnidev-ai/core` for config loading, capability management
|
|
26
|
+
- Route maps exported as `xxxRoutes` for subcommands (e.g., `capabilityRoutes`, `ralphRoutes`)
|
|
27
|
+
|
|
28
|
+
**State Management:**
|
|
29
|
+
- State-changing commands (capability enable/disable, profile set) auto-call `syncAgentConfiguration()`
|
|
30
|
+
- Commands validate `.omni/` exists before attempting operations
|
|
31
|
+
- Error handling: console.error() + process.exit(1)
|
|
32
|
+
|
|
33
|
+
**Output Style:**
|
|
34
|
+
- ✓ for success, ✗ for failure, ● for active items
|
|
35
|
+
- Brief fix suggestions for doctor check failures
|
|
36
|
+
|
|
37
|
+
## ANTI-PATTERNS
|
|
38
|
+
|
|
39
|
+
- **NEVER** add static commands to app.ts - dynamic capability commands loaded via dynamic-app.ts
|
|
40
|
+
- **NEVER** skip syncAgentConfiguration() after state changes - keeps agent configs in sync
|
|
41
|
+
- **NEVER** assume .omni/ exists - check with existsSync() before loading config
|
|
42
|
+
- **NEVER** use any for flags - Stricli parameters define types explicitly
|
|
43
|
+
- **NEVER** hardcode agent commands in ralph.ts - loaded from capabilities/ralph/index.js
|
|
@@ -0,0 +1,483 @@
|
|
|
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 { runCapabilityDisable, runCapabilityEnable, runCapabilityList } from "./capability";
|
|
5
|
+
|
|
6
|
+
describe("capability list command", () => {
|
|
7
|
+
let testDir: string;
|
|
8
|
+
let originalCwd: string;
|
|
9
|
+
let originalExit: typeof process.exit;
|
|
10
|
+
let exitCode: number | undefined;
|
|
11
|
+
|
|
12
|
+
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
|
+
// Mock process.exit
|
|
19
|
+
exitCode = undefined;
|
|
20
|
+
originalExit = process.exit;
|
|
21
|
+
process.exit = ((code?: number) => {
|
|
22
|
+
exitCode = code;
|
|
23
|
+
throw new Error(`process.exit(${code})`);
|
|
24
|
+
}) as typeof process.exit;
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
process.exit = originalExit;
|
|
29
|
+
process.chdir(originalCwd);
|
|
30
|
+
if (existsSync(testDir)) {
|
|
31
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("shows message when no capabilities found", async () => {
|
|
36
|
+
// Create minimal setup
|
|
37
|
+
mkdirSync(".omni", { recursive: true });
|
|
38
|
+
mkdirSync(".omni/capabilities", { recursive: true });
|
|
39
|
+
await Bun.write(
|
|
40
|
+
"omni.toml",
|
|
41
|
+
`project = "test"
|
|
42
|
+
|
|
43
|
+
[profiles.default]
|
|
44
|
+
capabilities = []
|
|
45
|
+
`,
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
const consoleLogs: string[] = [];
|
|
49
|
+
const originalLog = console.log;
|
|
50
|
+
console.log = (...args: unknown[]) => {
|
|
51
|
+
consoleLogs.push(args.join(" "));
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
await runCapabilityList();
|
|
55
|
+
|
|
56
|
+
console.log = originalLog;
|
|
57
|
+
|
|
58
|
+
expect(consoleLogs.some((log) => log.includes("No capabilities found"))).toBe(true);
|
|
59
|
+
expect(
|
|
60
|
+
consoleLogs.some((log) => log.includes("create directories in omni/capabilities/")),
|
|
61
|
+
).toBe(true);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
test("lists all discovered capabilities with enabled status", async () => {
|
|
65
|
+
// Create test structure
|
|
66
|
+
mkdirSync(".omni/capabilities/tasks", { recursive: true });
|
|
67
|
+
await Bun.write(
|
|
68
|
+
".omni/capabilities/tasks/capability.toml",
|
|
69
|
+
`[capability]
|
|
70
|
+
id = "tasks"
|
|
71
|
+
name = "Task Management"
|
|
72
|
+
version = "1.0.0"
|
|
73
|
+
description = "Task tracking"
|
|
74
|
+
`,
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
mkdirSync(".omni/capabilities/notes", { recursive: true });
|
|
78
|
+
await Bun.write(
|
|
79
|
+
".omni/capabilities/notes/capability.toml",
|
|
80
|
+
`[capability]
|
|
81
|
+
id = "notes"
|
|
82
|
+
name = "Note Taking"
|
|
83
|
+
version = "0.5.0"
|
|
84
|
+
description = "Note management"
|
|
85
|
+
`,
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
await Bun.write(
|
|
89
|
+
"omni.toml",
|
|
90
|
+
`project = "test"
|
|
91
|
+
|
|
92
|
+
[profiles.default]
|
|
93
|
+
capabilities = ["tasks"]
|
|
94
|
+
`,
|
|
95
|
+
);
|
|
96
|
+
|
|
97
|
+
const consoleLogs: string[] = [];
|
|
98
|
+
const originalLog = console.log;
|
|
99
|
+
console.log = (...args: unknown[]) => {
|
|
100
|
+
consoleLogs.push(args.join(" "));
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
await runCapabilityList();
|
|
104
|
+
|
|
105
|
+
console.log = originalLog;
|
|
106
|
+
|
|
107
|
+
const output = consoleLogs.join("\n");
|
|
108
|
+
|
|
109
|
+
// Check that both capabilities are shown
|
|
110
|
+
expect(output).toContain("Task Management");
|
|
111
|
+
expect(output).toContain("tasks");
|
|
112
|
+
expect(output).toContain("1.0.0");
|
|
113
|
+
expect(output).toContain("Note Taking");
|
|
114
|
+
expect(output).toContain("notes");
|
|
115
|
+
expect(output).toContain("0.5.0");
|
|
116
|
+
|
|
117
|
+
// Check enabled/disabled status
|
|
118
|
+
expect(output).toContain("✓ enabled");
|
|
119
|
+
expect(output).toContain("✗ disabled");
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
test("shows capability id and version", async () => {
|
|
123
|
+
mkdirSync(".omni/capabilities/test-cap", { recursive: true });
|
|
124
|
+
await Bun.write(
|
|
125
|
+
".omni/capabilities/test-cap/capability.toml",
|
|
126
|
+
`[capability]
|
|
127
|
+
id = "test-cap"
|
|
128
|
+
name = "Test Capability"
|
|
129
|
+
version = "2.3.4"
|
|
130
|
+
description = "Test"
|
|
131
|
+
`,
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
await Bun.write(
|
|
135
|
+
"omni.toml",
|
|
136
|
+
`project = "test"
|
|
137
|
+
|
|
138
|
+
[profiles.default]
|
|
139
|
+
capabilities = ["test-cap"]
|
|
140
|
+
`,
|
|
141
|
+
);
|
|
142
|
+
|
|
143
|
+
const consoleLogs: string[] = [];
|
|
144
|
+
const originalLog = console.log;
|
|
145
|
+
console.log = (...args: unknown[]) => {
|
|
146
|
+
consoleLogs.push(args.join(" "));
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
await runCapabilityList();
|
|
150
|
+
|
|
151
|
+
console.log = originalLog;
|
|
152
|
+
|
|
153
|
+
const output = consoleLogs.join("\n");
|
|
154
|
+
|
|
155
|
+
expect(output).toContain("ID: test-cap");
|
|
156
|
+
expect(output).toContain("Version: 2.3.4");
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
test("handles invalid capability gracefully", async () => {
|
|
160
|
+
mkdirSync(".omni/capabilities/valid", { recursive: true });
|
|
161
|
+
await Bun.write(
|
|
162
|
+
".omni/capabilities/valid/capability.toml",
|
|
163
|
+
`[capability]
|
|
164
|
+
id = "valid"
|
|
165
|
+
name = "Valid"
|
|
166
|
+
version = "1.0.0"
|
|
167
|
+
description = "Valid capability"
|
|
168
|
+
`,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
mkdirSync(".omni/capabilities/invalid", { recursive: true });
|
|
172
|
+
await Bun.write(".omni/capabilities/invalid/capability.toml", "invalid toml [[[");
|
|
173
|
+
|
|
174
|
+
await Bun.write(
|
|
175
|
+
"omni.toml",
|
|
176
|
+
`project = "test"
|
|
177
|
+
|
|
178
|
+
[profiles.default]
|
|
179
|
+
capabilities = ["valid", "invalid"]
|
|
180
|
+
`,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
const consoleLogs: string[] = [];
|
|
184
|
+
const consoleErrors: string[] = [];
|
|
185
|
+
const originalLog = console.log;
|
|
186
|
+
const originalError = console.error;
|
|
187
|
+
console.log = (...args: unknown[]) => {
|
|
188
|
+
consoleLogs.push(args.join(" "));
|
|
189
|
+
};
|
|
190
|
+
console.error = (...args: unknown[]) => {
|
|
191
|
+
consoleErrors.push(args.join(" "));
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
await runCapabilityList();
|
|
195
|
+
|
|
196
|
+
console.log = originalLog;
|
|
197
|
+
console.error = originalError;
|
|
198
|
+
|
|
199
|
+
// Valid capability should be shown
|
|
200
|
+
expect(consoleLogs.join("\n")).toContain("Valid");
|
|
201
|
+
|
|
202
|
+
// Invalid capability should show error
|
|
203
|
+
expect(consoleErrors.some((log) => log.includes("Failed to load capability"))).toBe(true);
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
test("respects profile when determining enabled status", async () => {
|
|
207
|
+
mkdirSync(".omni/capabilities/tasks", { recursive: true });
|
|
208
|
+
await Bun.write(
|
|
209
|
+
".omni/capabilities/tasks/capability.toml",
|
|
210
|
+
`[capability]
|
|
211
|
+
id = "tasks"
|
|
212
|
+
name = "Tasks"
|
|
213
|
+
version = "1.0.0"
|
|
214
|
+
description = "Task tracking"
|
|
215
|
+
`,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
await Bun.write(
|
|
219
|
+
"omni.toml",
|
|
220
|
+
`project = "test"
|
|
221
|
+
active_profile = "coding"
|
|
222
|
+
|
|
223
|
+
[profiles.coding]
|
|
224
|
+
capabilities = ["tasks"]
|
|
225
|
+
`,
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const consoleLogs: string[] = [];
|
|
229
|
+
const originalLog = console.log;
|
|
230
|
+
console.log = (...args: unknown[]) => {
|
|
231
|
+
consoleLogs.push(args.join(" "));
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
await runCapabilityList();
|
|
235
|
+
|
|
236
|
+
console.log = originalLog;
|
|
237
|
+
|
|
238
|
+
const output = consoleLogs.join("\n");
|
|
239
|
+
|
|
240
|
+
expect(output).toContain("✓ enabled");
|
|
241
|
+
expect(output).toContain("Tasks");
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("exits with code 1 on error", async () => {
|
|
245
|
+
// Create an omni directory but with invalid config to trigger error
|
|
246
|
+
mkdirSync(".omni", { recursive: true });
|
|
247
|
+
mkdirSync(".omni/capabilities", { recursive: true });
|
|
248
|
+
await Bun.write("omni.toml", "invalid toml [[[");
|
|
249
|
+
mkdirSync(".omni", { recursive: true });
|
|
250
|
+
|
|
251
|
+
const originalError = console.error;
|
|
252
|
+
console.error = () => {}; // Suppress error output
|
|
253
|
+
|
|
254
|
+
try {
|
|
255
|
+
await runCapabilityList();
|
|
256
|
+
} catch (_error) {
|
|
257
|
+
// Expected to throw from process.exit mock
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
console.error = originalError;
|
|
261
|
+
|
|
262
|
+
expect(exitCode).toBe(1);
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
test("shows multiple capabilities in order", async () => {
|
|
266
|
+
const capabilities = ["alpha", "beta", "gamma"];
|
|
267
|
+
|
|
268
|
+
for (const cap of capabilities) {
|
|
269
|
+
mkdirSync(`.omni/capabilities/${cap}`, { recursive: true });
|
|
270
|
+
await Bun.write(
|
|
271
|
+
`.omni/capabilities/${cap}/capability.toml`,
|
|
272
|
+
`[capability]
|
|
273
|
+
id = "${cap}"
|
|
274
|
+
name = "${cap.toUpperCase()}"
|
|
275
|
+
version = "1.0.0"
|
|
276
|
+
description = "${cap} capability"
|
|
277
|
+
`,
|
|
278
|
+
);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
await Bun.write(
|
|
282
|
+
"omni.toml",
|
|
283
|
+
`project = "test"
|
|
284
|
+
|
|
285
|
+
[profiles.default]
|
|
286
|
+
capabilities = ["alpha", "beta", "gamma"]
|
|
287
|
+
`,
|
|
288
|
+
);
|
|
289
|
+
|
|
290
|
+
const consoleLogs: string[] = [];
|
|
291
|
+
const originalLog = console.log;
|
|
292
|
+
console.log = (...args: unknown[]) => {
|
|
293
|
+
consoleLogs.push(args.join(" "));
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
await runCapabilityList();
|
|
297
|
+
|
|
298
|
+
console.log = originalLog;
|
|
299
|
+
|
|
300
|
+
const output = consoleLogs.join("\n");
|
|
301
|
+
|
|
302
|
+
for (const cap of capabilities) {
|
|
303
|
+
expect(output).toContain(cap.toUpperCase());
|
|
304
|
+
expect(output).toContain(`ID: ${cap}`);
|
|
305
|
+
}
|
|
306
|
+
});
|
|
307
|
+
});
|
|
308
|
+
|
|
309
|
+
describe("capability enable command", () => {
|
|
310
|
+
let testDir: string;
|
|
311
|
+
let originalCwd: string;
|
|
312
|
+
let originalExit: typeof process.exit;
|
|
313
|
+
let exitCode: number | undefined;
|
|
314
|
+
|
|
315
|
+
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
|
+
// Mock process.exit
|
|
322
|
+
exitCode = undefined;
|
|
323
|
+
originalExit = process.exit;
|
|
324
|
+
process.exit = ((code?: number) => {
|
|
325
|
+
exitCode = code;
|
|
326
|
+
throw new Error(`process.exit(${code})`);
|
|
327
|
+
}) as typeof process.exit;
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
afterEach(() => {
|
|
331
|
+
process.exit = originalExit;
|
|
332
|
+
process.chdir(originalCwd);
|
|
333
|
+
if (existsSync(testDir)) {
|
|
334
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("enables a capability", async () => {
|
|
339
|
+
mkdirSync(".omni/capabilities/tasks", { recursive: true });
|
|
340
|
+
await Bun.write(
|
|
341
|
+
".omni/capabilities/tasks/capability.toml",
|
|
342
|
+
`[capability]
|
|
343
|
+
id = "tasks"
|
|
344
|
+
name = "Tasks"
|
|
345
|
+
version = "1.0.0"
|
|
346
|
+
description = "Task tracking"
|
|
347
|
+
`,
|
|
348
|
+
);
|
|
349
|
+
|
|
350
|
+
await Bun.write(
|
|
351
|
+
"omni.toml",
|
|
352
|
+
`project = "test"
|
|
353
|
+
|
|
354
|
+
[profiles.default]
|
|
355
|
+
capabilities = []
|
|
356
|
+
`,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
await runCapabilityEnable({}, "tasks");
|
|
360
|
+
|
|
361
|
+
const content = await Bun.file("omni.toml").text();
|
|
362
|
+
expect(content).toContain('capabilities = ["tasks"]');
|
|
363
|
+
});
|
|
364
|
+
|
|
365
|
+
test("adds capability to profile when enabling", async () => {
|
|
366
|
+
mkdirSync(".omni/capabilities/tasks", { recursive: true });
|
|
367
|
+
await Bun.write(
|
|
368
|
+
".omni/capabilities/tasks/capability.toml",
|
|
369
|
+
`[capability]
|
|
370
|
+
id = "tasks"
|
|
371
|
+
name = "Tasks"
|
|
372
|
+
version = "1.0.0"
|
|
373
|
+
description = "Task tracking"
|
|
374
|
+
`,
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
await Bun.write(
|
|
378
|
+
"omni.toml",
|
|
379
|
+
`project = "test"
|
|
380
|
+
|
|
381
|
+
[profiles.default]
|
|
382
|
+
capabilities = []
|
|
383
|
+
`,
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
await runCapabilityEnable({}, "tasks");
|
|
387
|
+
|
|
388
|
+
const content = await Bun.file("omni.toml").text();
|
|
389
|
+
expect(content).toContain('capabilities = ["tasks"]');
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
test("exits with error if capability doesn't exist", async () => {
|
|
393
|
+
mkdirSync(".omni", { recursive: true });
|
|
394
|
+
await Bun.write(
|
|
395
|
+
"omni.toml",
|
|
396
|
+
`project = "test"
|
|
397
|
+
|
|
398
|
+
[profiles.default]
|
|
399
|
+
capabilities = []
|
|
400
|
+
`,
|
|
401
|
+
);
|
|
402
|
+
|
|
403
|
+
const originalError = console.error;
|
|
404
|
+
const originalLog = console.log;
|
|
405
|
+
console.error = () => {};
|
|
406
|
+
console.log = () => {};
|
|
407
|
+
|
|
408
|
+
try {
|
|
409
|
+
await runCapabilityEnable({}, "nonexistent");
|
|
410
|
+
} catch (_error) {
|
|
411
|
+
// Expected to throw from process.exit mock
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
console.error = originalError;
|
|
415
|
+
console.log = originalLog;
|
|
416
|
+
|
|
417
|
+
expect(exitCode).toBe(1);
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("capability disable command", () => {
|
|
422
|
+
let testDir: string;
|
|
423
|
+
let originalCwd: string;
|
|
424
|
+
let originalExit: typeof process.exit;
|
|
425
|
+
let _exitCode: number | undefined;
|
|
426
|
+
|
|
427
|
+
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
|
+
// Mock process.exit
|
|
434
|
+
_exitCode = undefined;
|
|
435
|
+
originalExit = process.exit;
|
|
436
|
+
process.exit = ((code?: number) => {
|
|
437
|
+
_exitCode = code;
|
|
438
|
+
throw new Error(`process.exit(${code})`);
|
|
439
|
+
}) as typeof process.exit;
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
afterEach(() => {
|
|
443
|
+
process.exit = originalExit;
|
|
444
|
+
process.chdir(originalCwd);
|
|
445
|
+
if (existsSync(testDir)) {
|
|
446
|
+
rmSync(testDir, { recursive: true, force: true });
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
|
|
450
|
+
test("disables a capability", async () => {
|
|
451
|
+
mkdirSync(".omni", { recursive: true });
|
|
452
|
+
await Bun.write(
|
|
453
|
+
"omni.toml",
|
|
454
|
+
`project = "test"
|
|
455
|
+
|
|
456
|
+
[profiles.default]
|
|
457
|
+
capabilities = ["tasks"]
|
|
458
|
+
`,
|
|
459
|
+
);
|
|
460
|
+
|
|
461
|
+
await runCapabilityDisable({}, "tasks");
|
|
462
|
+
|
|
463
|
+
const content = await Bun.file("omni.toml").text();
|
|
464
|
+
expect(content).toContain("capabilities = []");
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
test("removes capability from profile", async () => {
|
|
468
|
+
mkdirSync(".omni", { recursive: true });
|
|
469
|
+
await Bun.write(
|
|
470
|
+
"omni.toml",
|
|
471
|
+
`project = "test"
|
|
472
|
+
|
|
473
|
+
[profiles.default]
|
|
474
|
+
capabilities = ["tasks", "notes"]
|
|
475
|
+
`,
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
await runCapabilityDisable({}, "tasks");
|
|
479
|
+
|
|
480
|
+
const content = await Bun.file("omni.toml").text();
|
|
481
|
+
expect(content).toContain('capabilities = ["notes"]');
|
|
482
|
+
});
|
|
483
|
+
});
|