@omnidev-ai/cli 0.1.1 → 0.4.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.
@@ -1,351 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, test } from "bun:test";
2
- import { existsSync, mkdirSync, rmSync } from "node:fs";
3
- import { tmpdir } from "@omnidev-ai/core/test-utils";
4
- import { runProfileList, runProfileSet } from "./profile";
5
-
6
- describe("profile commands", () => {
7
- let testDir: string;
8
- let originalCwd: string;
9
- let originalExit: typeof process.exit;
10
- let exitCode: number | undefined;
11
- let consoleOutput: string[];
12
- let consoleErrors: string[];
13
-
14
- beforeEach(() => {
15
- // Create test directory in /tmp
16
- originalCwd = process.cwd();
17
- testDir = tmpdir("profile-test-");
18
- process.chdir(testDir);
19
-
20
- // Mock process.exit
21
- exitCode = undefined;
22
- originalExit = process.exit;
23
- process.exit = ((code?: number) => {
24
- exitCode = code ?? 0;
25
- throw new Error(`process.exit(${code})`);
26
- }) as typeof process.exit;
27
-
28
- // Mock console
29
- consoleOutput = [];
30
- consoleErrors = [];
31
- const originalLog = console.log;
32
- const originalError = console.error;
33
- console.log = (...args: unknown[]) => {
34
- consoleOutput.push(args.join(" "));
35
- };
36
- console.error = (...args: unknown[]) => {
37
- consoleErrors.push(args.join(" "));
38
- };
39
-
40
- // Restore after test (in afterEach)
41
- return () => {
42
- console.log = originalLog;
43
- console.error = originalError;
44
- };
45
- });
46
-
47
- afterEach(() => {
48
- process.exit = originalExit;
49
- process.chdir(originalCwd);
50
- if (existsSync(testDir)) {
51
- rmSync(testDir, { recursive: true, force: true });
52
- }
53
- });
54
-
55
- describe("runProfileList", () => {
56
- test("should show error when config file does not exist", async () => {
57
- try {
58
- await runProfileList();
59
- } catch {
60
- // Expected to throw due to process.exit mock
61
- }
62
-
63
- expect(exitCode).toBe(1);
64
- expect(consoleOutput.join("\n")).toContain("No config file found");
65
- expect(consoleOutput.join("\n")).toContain("Run: omnidev init");
66
- });
67
-
68
- test("should show message when no profiles defined", async () => {
69
- // Create minimal config without profiles
70
- mkdirSync(".omni", { recursive: true });
71
- await Bun.write(
72
- "omni.toml",
73
- `project = "test-project"
74
- active_profile = "default"
75
- `,
76
- );
77
-
78
- await runProfileList();
79
-
80
- expect(exitCode).toBeUndefined();
81
- expect(consoleOutput.join("\n")).toContain("No profiles defined");
82
- expect(consoleOutput.join("\n")).toContain("Using default capabilities");
83
- });
84
-
85
- test("should list all profiles from config", async () => {
86
- // Create config with profiles
87
- mkdirSync(".omni", { recursive: true });
88
- await Bun.write(
89
- "omni.toml",
90
- `project = "test-project"
91
- active_profile = "default"
92
-
93
- [profiles.default]
94
- capabilities = []
95
-
96
- [profiles.planning]
97
- capabilities = ["tasks", "planner"]
98
-
99
- [profiles.coding]
100
- capabilities = []
101
- `,
102
- );
103
-
104
- await runProfileList();
105
-
106
- expect(exitCode).toBeUndefined();
107
- const output = consoleOutput.join("\n");
108
- expect(output).toContain("Available Profiles:");
109
- expect(output).toContain("default");
110
- expect(output).toContain("planning");
111
- expect(output).toContain("coding");
112
- });
113
-
114
- test("should show active profile with marker", async () => {
115
- // Create config with profiles
116
- mkdirSync(".omni", { recursive: true });
117
- await Bun.write(
118
- "omni.toml",
119
- `project = "test-project"
120
- active_profile = "planning"
121
-
122
- [profiles.default]
123
- capabilities = []
124
-
125
- [profiles.planning]
126
- capabilities = ["planner"]
127
- `,
128
- );
129
-
130
- await runProfileList();
131
-
132
- expect(exitCode).toBeUndefined();
133
- const output = consoleOutput.join("\n");
134
- expect(output).toContain("● planning (active)");
135
- expect(output).toContain("○ default");
136
- });
137
-
138
- test("should show profile capabilities", async () => {
139
- // Create config with profiles
140
- mkdirSync(".omni", { recursive: true });
141
- await Bun.write(
142
- "omni.toml",
143
- `project = "test-project"
144
- active_profile = "default"
145
-
146
- [profiles.default]
147
- capabilities = []
148
-
149
- [profiles.planning]
150
- capabilities = ["planner", "tasks"]
151
- `,
152
- );
153
-
154
- await runProfileList();
155
-
156
- expect(exitCode).toBeUndefined();
157
- const output = consoleOutput.join("\n");
158
- expect(output).toContain("Capabilities: planner, tasks");
159
- });
160
-
161
- test("should use default_profile when no active profile", async () => {
162
- // Create config with active_profile
163
- mkdirSync(".omni", { recursive: true });
164
- await Bun.write(
165
- "omni.toml",
166
- `project = "test-project"
167
- active_profile = "planning"
168
-
169
- [profiles.planning]
170
- capabilities = ["planner"]
171
- `,
172
- );
173
-
174
- await runProfileList();
175
-
176
- expect(exitCode).toBeUndefined();
177
- const output = consoleOutput.join("\n");
178
- expect(output).toContain("● planning (active)");
179
- });
180
-
181
- test("should handle invalid config gracefully", async () => {
182
- // Create invalid config
183
- mkdirSync(".omni", { recursive: true });
184
- await Bun.write("omni.toml", "invalid toml [[[");
185
-
186
- try {
187
- await runProfileList();
188
- } catch {
189
- // Expected to throw due to process.exit mock
190
- }
191
-
192
- expect(exitCode).toBe(1);
193
- expect(consoleErrors.join("\n")).toContain("Error loading profiles");
194
- });
195
- });
196
-
197
- describe("runProfileSet", () => {
198
- test("should show error when config file does not exist", async () => {
199
- try {
200
- await runProfileSet("planning");
201
- } catch {
202
- // Expected to throw due to process.exit mock
203
- }
204
-
205
- expect(exitCode).toBe(1);
206
- expect(consoleOutput.join("\n")).toContain("No config file found");
207
- expect(consoleOutput.join("\n")).toContain("Run: omnidev init");
208
- });
209
-
210
- test("should show error when profile does not exist", async () => {
211
- // Create config without the requested profile
212
- mkdirSync(".omni", { recursive: true });
213
- await Bun.write(
214
- "omni.toml",
215
- `project = "test-project"
216
-
217
- [profiles.default]
218
- capabilities = []
219
- `,
220
- );
221
-
222
- try {
223
- await runProfileSet("nonexistent");
224
- } catch {
225
- // Expected to throw due to process.exit mock
226
- }
227
-
228
- expect(exitCode).toBe(1);
229
- const output = consoleOutput.join("\n");
230
- expect(output).toContain('Profile "nonexistent" not found');
231
- expect(output).toContain("Available profiles:");
232
- expect(output).toContain("- default");
233
- });
234
-
235
- test("should set active profile", async () => {
236
- // Create config with profiles
237
- mkdirSync(".omni", { recursive: true });
238
- await Bun.write(
239
- "omni.toml",
240
- `project = "test-project"
241
-
242
- [profiles.default]
243
- capabilities = []
244
-
245
- [profiles.planning]
246
- capabilities = ["planner"]
247
- `,
248
- );
249
-
250
- await runProfileSet("planning");
251
-
252
- expect(exitCode).toBeUndefined();
253
- expect(consoleOutput.join("\n")).toContain("Active profile set to: planning");
254
-
255
- // Verify active_profile was written to state file (not config.toml)
256
- const stateContent = await Bun.file(".omni/state/active-profile").text();
257
- expect(stateContent).toBe("planning");
258
- });
259
-
260
- test("should trigger agents sync after setting profile", async () => {
261
- // Create config with profiles
262
- mkdirSync(".omni", { recursive: true });
263
- await Bun.write(
264
- "omni.toml",
265
- `project = "test-project"
266
- active_profile = "default"
267
-
268
- [profiles.default]
269
- capabilities = []
270
-
271
- [profiles.planning]
272
- capabilities = []
273
- `,
274
- );
275
-
276
- await runProfileSet("planning");
277
-
278
- expect(exitCode).toBeUndefined();
279
- const output = consoleOutput.join("\n");
280
- expect(output).toContain("Syncing agent configuration");
281
- });
282
-
283
- test("should show list of available profiles when profile not found", async () => {
284
- // Create config with multiple profiles
285
- mkdirSync(".omni", { recursive: true });
286
- await Bun.write(
287
- "omni.toml",
288
- `project = "test-project"
289
-
290
- [profiles.default]
291
- capabilities = []
292
-
293
- [profiles.planning]
294
- capabilities = []
295
-
296
- [profiles.coding]
297
- capabilities = []
298
- `,
299
- );
300
-
301
- try {
302
- await runProfileSet("nonexistent");
303
- } catch {
304
- // Expected to throw due to process.exit mock
305
- }
306
-
307
- expect(exitCode).toBe(1);
308
- const output = consoleOutput.join("\n");
309
- expect(output).toContain("Available profiles:");
310
- expect(output).toContain("- default");
311
- expect(output).toContain("- planning");
312
- expect(output).toContain("- coding");
313
- });
314
-
315
- test("should handle empty profiles config", async () => {
316
- // Create config without any profiles
317
- mkdirSync(".omni", { recursive: true });
318
- await Bun.write(
319
- "omni.toml",
320
- `project = "test-project"
321
- `,
322
- );
323
-
324
- try {
325
- await runProfileSet("default");
326
- } catch {
327
- // Expected to throw due to process.exit mock
328
- }
329
-
330
- expect(exitCode).toBe(1);
331
- const output = consoleOutput.join("\n");
332
- expect(output).toContain('Profile "default" not found');
333
- expect(output).toContain("(none defined)");
334
- });
335
-
336
- test("should handle invalid config gracefully", async () => {
337
- // Create invalid config
338
- mkdirSync(".omni", { recursive: true });
339
- await Bun.write("omni.toml", "invalid toml [[[");
340
-
341
- try {
342
- await runProfileSet("planning");
343
- } catch {
344
- // Expected to throw due to process.exit mock
345
- }
346
-
347
- expect(exitCode).toBe(1);
348
- expect(consoleErrors.join("\n")).toContain("Error setting profile");
349
- });
350
- });
351
- });
@@ -1,151 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import {
3
- getActiveProfile,
4
- loadConfig,
5
- resolveEnabledCapabilities,
6
- setActiveProfile,
7
- syncAgentConfiguration,
8
- } from "@omnidev-ai/core";
9
- import { buildCommand, buildRouteMap } from "@stricli/core";
10
-
11
- const listCommand = buildCommand({
12
- docs: {
13
- brief: "List available profiles",
14
- },
15
- parameters: {},
16
- async func() {
17
- await runProfileList();
18
- },
19
- });
20
-
21
- async function runSetCommand(_flags: Record<string, never>, profileName: string): Promise<void> {
22
- await runProfileSet(profileName);
23
- }
24
-
25
- const setCommand = buildCommand({
26
- docs: {
27
- brief: "Set the active profile",
28
- },
29
- parameters: {
30
- flags: {},
31
- positional: {
32
- kind: "tuple" as const,
33
- parameters: [
34
- {
35
- brief: "Profile name",
36
- parse: String,
37
- },
38
- ],
39
- },
40
- },
41
- func: runSetCommand,
42
- });
43
-
44
- export const profileRoutes = buildRouteMap({
45
- routes: {
46
- list: listCommand,
47
- set: setCommand,
48
- },
49
- docs: {
50
- brief: "Manage capability profiles",
51
- },
52
- });
53
-
54
- export async function runProfileList(): Promise<void> {
55
- try {
56
- // Check if omni.toml exists
57
- if (!existsSync("omni.toml")) {
58
- console.log("✗ No config file found");
59
- console.log(" Run: omnidev init");
60
- process.exit(1);
61
- }
62
-
63
- // Load config
64
- const config = await loadConfig();
65
-
66
- // Get active profile
67
- const activeProfile = (await getActiveProfile()) ?? config.active_profile ?? "default";
68
-
69
- // Check if profiles exist
70
- const profiles = config.profiles ?? {};
71
- const profileNames = Object.keys(profiles);
72
-
73
- if (profileNames.length === 0) {
74
- console.log("No profiles defined in omni.toml");
75
- console.log("");
76
- console.log("Using default capabilities from omni.toml");
77
- return;
78
- }
79
-
80
- // Display profiles
81
- console.log("Available Profiles:");
82
- console.log("");
83
-
84
- for (const name of profileNames) {
85
- const isActive = name === activeProfile;
86
- const icon = isActive ? "●" : "○";
87
- const profile = profiles[name];
88
-
89
- if (profile === undefined) {
90
- continue;
91
- }
92
-
93
- console.log(`${icon} ${name}${isActive ? " (active)" : ""}`);
94
-
95
- // Show capabilities (including always-enabled)
96
- const capabilities = resolveEnabledCapabilities(config, name);
97
- if (capabilities.length > 0) {
98
- console.log(` Capabilities: ${capabilities.join(", ")}`);
99
- } else {
100
- console.log(" Capabilities: none");
101
- }
102
- console.log("");
103
- }
104
- } catch (error) {
105
- console.error("✗ Error loading profiles:", error);
106
- process.exit(1);
107
- }
108
- }
109
-
110
- export async function runProfileSet(profileName: string): Promise<void> {
111
- try {
112
- // Check if omni.toml exists
113
- if (!existsSync("omni.toml")) {
114
- console.log("✗ No config file found");
115
- console.log(" Run: omnidev init");
116
- process.exit(1);
117
- }
118
-
119
- // Load config
120
- const config = await loadConfig();
121
-
122
- // Validate profile exists
123
- const profiles = config.profiles ?? {};
124
- if (!(profileName in profiles)) {
125
- console.log(`✗ Profile "${profileName}" not found in omni.toml`);
126
- console.log("");
127
- console.log("Available profiles:");
128
- const profileNames = Object.keys(profiles);
129
- if (profileNames.length === 0) {
130
- console.log(" (none defined)");
131
- } else {
132
- for (const name of profileNames) {
133
- console.log(` - ${name}`);
134
- }
135
- }
136
- process.exit(1);
137
- }
138
-
139
- // Set active profile
140
- await setActiveProfile(profileName);
141
-
142
- console.log(`✓ Active profile set to: ${profileName}`);
143
- console.log("");
144
-
145
- // Auto-sync agent configuration
146
- await syncAgentConfiguration();
147
- } catch (error) {
148
- console.error("✗ Error setting profile:", error);
149
- process.exit(1);
150
- }
151
- }
@@ -1,181 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { tmpdir } from "@omnidev-ai/core/test-utils";
4
- import { runServe } from "./serve";
5
-
6
- let testDir: string;
7
- const originalCwd = process.cwd();
8
-
9
- beforeEach(() => {
10
- // Create test directory in /tmp
11
- testDir = tmpdir("serve-test-");
12
- process.chdir(testDir);
13
- });
14
-
15
- afterEach(() => {
16
- // Return to original directory and clean up
17
- process.chdir(originalCwd);
18
- if (existsSync(testDir)) {
19
- rmSync(testDir, { recursive: true, force: true });
20
- }
21
- });
22
-
23
- describe("serve command", () => {
24
- test("should fail when OmniDev is not initialized", async () => {
25
- const mockExit = mock((code?: number) => {
26
- throw new Error(`process.exit: ${code}`);
27
- }) as typeof process.exit;
28
- const originalExit = process.exit;
29
- process.exit = mockExit;
30
-
31
- try {
32
- await expect(runServe({})).rejects.toThrow("process.exit: 1");
33
- expect(mockExit).toHaveBeenCalledWith(1);
34
- } finally {
35
- process.exit = originalExit;
36
- }
37
- });
38
-
39
- test("should fail when .omni/ directory is missing", async () => {
40
- // Don't create .omni/ - test expects it to be missing
41
-
42
- const mockExit = mock((code?: number) => {
43
- throw new Error(`process.exit: ${code}`);
44
- }) as typeof process.exit;
45
- const originalExit = process.exit;
46
- process.exit = mockExit;
47
-
48
- try {
49
- await expect(runServe({})).rejects.toThrow("process.exit: 1");
50
- expect(mockExit).toHaveBeenCalledWith(1);
51
- } finally {
52
- process.exit = originalExit;
53
- }
54
- });
55
-
56
- test("should fail when profile does not exist", async () => {
57
- // Set up directories
58
- mkdirSync(".omni", { recursive: true });
59
- mkdirSync(".omni", { recursive: true });
60
-
61
- // Create a config without the requested profile
62
- writeFileSync(
63
- "omni.toml",
64
- `
65
- [capability]
66
- project = "test"
67
-
68
- [profiles.default]
69
- `,
70
- );
71
-
72
- const mockExit = mock((code?: number) => {
73
- throw new Error(`process.exit: ${code}`);
74
- }) as typeof process.exit;
75
- const originalExit = process.exit;
76
- process.exit = mockExit;
77
-
78
- try {
79
- await expect(runServe({ profile: "nonexistent" })).rejects.toThrow("process.exit: 1");
80
- expect(mockExit).toHaveBeenCalledWith(1);
81
- } finally {
82
- process.exit = originalExit;
83
- }
84
- });
85
-
86
- test("should set profile when provided and valid", async () => {
87
- // Set up directories
88
- mkdirSync(".omni", { recursive: true });
89
- mkdirSync(".omni", { recursive: true });
90
-
91
- // Create config with profiles
92
- writeFileSync(
93
- "omni.toml",
94
- `project = "test"
95
- active_profile = "default"
96
-
97
- [profiles.default]
98
- capabilities = []
99
-
100
- [profiles.testing]
101
- capabilities = []
102
- `,
103
- );
104
-
105
- // Mock startServer to prevent actual server start
106
- const mockStartServer = mock(async () => {
107
- // Server started successfully, do nothing
108
- });
109
-
110
- // Mock the import of @omnidev-ai/mcp
111
- const originalImport = globalThis[Symbol.for("Bun.lazy")];
112
- // biome-ignore lint/suspicious/noExplicitAny: Testing requires dynamic mocking
113
- (globalThis as any).import = mock(async (module: string) => {
114
- if (module === "@omnidev-ai/mcp") {
115
- return { startServer: mockStartServer };
116
- }
117
- throw new Error(`Unexpected import: ${module}`);
118
- });
119
-
120
- const mockExit = mock((code?: number) => {
121
- throw new Error(`process.exit: ${code}`);
122
- }) as typeof process.exit;
123
- const originalExit = process.exit;
124
- process.exit = mockExit;
125
-
126
- try {
127
- // This should fail because startServer will actually run, but that's OK for this test
128
- // We just want to verify that setActiveProfile was called
129
- await runServe({ profile: "testing" }).catch(() => {
130
- // Ignore the error from startServer
131
- });
132
-
133
- // Check that active profile was written to state file (not config.toml)
134
- const stateContent = await Bun.file(".omni/state/active-profile").text();
135
- expect(stateContent).toBe("testing");
136
- } finally {
137
- process.exit = originalExit;
138
- // biome-ignore lint/suspicious/noExplicitAny: Restore original import
139
- if (originalImport) (globalThis as any).import = originalImport;
140
- }
141
- });
142
-
143
- test("should start server without profile flag", async () => {
144
- // Set up directories
145
- mkdirSync(".omni", { recursive: true });
146
- mkdirSync(".omni", { recursive: true });
147
-
148
- // Create config
149
- writeFileSync(
150
- "omni.toml",
151
- `
152
- [capability]
153
- project = "test"
154
- default_profile = "default"
155
-
156
- [profiles.default]
157
- `,
158
- );
159
-
160
- // We can't actually test server startup without complex mocking,
161
- // so we'll just verify the command passes initial checks
162
- const mockExit = mock((code?: number) => {
163
- throw new Error(`process.exit: ${code}`);
164
- }) as typeof process.exit;
165
- const originalExit = process.exit;
166
- process.exit = mockExit;
167
-
168
- try {
169
- // This will fail at the import stage, but that's expected
170
- await runServe({}).catch((error) => {
171
- // Should fail on import or server start, not on validation
172
- expect(error).toBeDefined();
173
- });
174
-
175
- // No profile should be written when flag not provided
176
- expect(existsSync(".omni/active-profile")).toBe(false);
177
- } finally {
178
- process.exit = originalExit;
179
- }
180
- });
181
- });