@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.
@@ -6,9 +6,8 @@ import { capabilityRoutes } from "../commands/capability";
6
6
  // Core commands
7
7
  import { doctorCommand } from "../commands/doctor";
8
8
  import { initCommand } from "../commands/init";
9
- import { mcpRoutes } from "../commands/mcp";
10
9
  import { profileRoutes } from "../commands/profile";
11
- import { serveCommand } from "../commands/serve";
10
+ import { providerRoutes } from "../commands/provider";
12
11
  import { syncCommand } from "../commands/sync";
13
12
  import { debug } from "./debug";
14
13
 
@@ -20,11 +19,10 @@ export async function buildDynamicApp() {
20
19
  const routes: Record<string, unknown> = {
21
20
  init: initCommand,
22
21
  doctor: doctorCommand,
23
- serve: serveCommand,
24
22
  sync: syncCommand,
25
23
  capability: capabilityRoutes,
26
24
  profile: profileRoutes,
27
- mcp: mcpRoutes,
25
+ provider: providerRoutes,
28
26
  };
29
27
 
30
28
  debug("Core routes registered", Object.keys(routes));
@@ -1,15 +1,17 @@
1
1
  import { checkbox } from "@inquirer/prompts";
2
- import type { Provider } from "@omnidev-ai/core";
2
+ import type { ProviderId } from "@omnidev-ai/core";
3
3
 
4
- export async function promptForProvider(): Promise<Provider[]> {
4
+ export async function promptForProviders(): Promise<ProviderId[]> {
5
5
  const answers = await checkbox({
6
6
  message: "Select your AI provider(s):",
7
7
  choices: [
8
- { name: "Claude (recommended)", value: "claude", checked: true },
8
+ { name: "Claude Code (Claude CLI)", value: "claude-code", checked: true },
9
+ { name: "Cursor", value: "cursor", checked: false },
9
10
  { name: "Codex", value: "codex", checked: false },
11
+ { name: "OpenCode", value: "opencode", checked: false },
10
12
  ],
11
13
  required: true,
12
14
  });
13
15
 
14
- return answers as Provider[];
16
+ return answers as ProviderId[];
15
17
  }
@@ -1,113 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { buildCommand, buildRouteMap } from "@stricli/core";
3
-
4
- const STATUS_FILE = ".omni/state/mcp-status.json";
5
-
6
- // Local type definition to avoid circular dependency with @omnidev-ai/mcp
7
- interface McpChildProcess {
8
- capabilityId: string;
9
- pid: number | null;
10
- status: "starting" | "connected" | "disconnected" | "error";
11
- transport: "stdio" | "sse" | "http";
12
- lastHealthCheck?: string;
13
- error?: string;
14
- toolCount?: number;
15
- }
16
-
17
- interface McpStatusFile {
18
- lastUpdated: string;
19
- relayPort: number;
20
- children: McpChildProcess[];
21
- }
22
-
23
- /**
24
- * Run the mcp status command.
25
- */
26
- export async function runMcpStatus(): Promise<void> {
27
- try {
28
- if (!existsSync(STATUS_FILE)) {
29
- console.log("No MCP status found.");
30
- console.log("");
31
- console.log("Is the OmniDev server running? Start it with:");
32
- console.log(" omnidev serve");
33
- return;
34
- }
35
-
36
- const statusText = await Bun.file(STATUS_FILE).text();
37
- const status = JSON.parse(statusText) as McpStatusFile;
38
-
39
- console.log("");
40
- console.log("=== MCP Controller Status ===");
41
- console.log("");
42
- console.log(`Last updated: ${status.lastUpdated}`);
43
- console.log(`Relay port: ${status.relayPort}`);
44
- console.log("");
45
-
46
- if (status.children.length === 0) {
47
- console.log("No MCP children running.");
48
- console.log("");
49
- console.log("Enable a capability with [mcp] configuration to spawn child MCPs.");
50
- return;
51
- }
52
-
53
- console.log(`Child Processes (${status.children.length}):`);
54
- console.log("");
55
-
56
- for (const child of status.children) {
57
- const statusIcon = getStatusIcon(child.status);
58
- console.log(` ${statusIcon} ${child.capabilityId}`);
59
- console.log(` Status: ${child.status}`);
60
- console.log(` Transport: ${child.transport}`);
61
- if (child.pid) {
62
- console.log(` PID: ${child.pid}`);
63
- }
64
- if (child.toolCount !== undefined) {
65
- console.log(` Tools: ${child.toolCount}`);
66
- }
67
- if (child.lastHealthCheck) {
68
- console.log(` Last check: ${child.lastHealthCheck}`);
69
- }
70
- if (child.error) {
71
- console.log(` Error: ${child.error}`);
72
- }
73
- console.log("");
74
- }
75
- } catch (error) {
76
- console.error("Error reading MCP status:", error);
77
- process.exit(1);
78
- }
79
- }
80
-
81
- function getStatusIcon(status: string): string {
82
- switch (status) {
83
- case "connected":
84
- return "\u2713"; // checkmark
85
- case "starting":
86
- return "\u2022"; // bullet
87
- case "disconnected":
88
- return "\u2717"; // x mark
89
- case "error":
90
- return "\u2717"; // x mark
91
- default:
92
- return "?";
93
- }
94
- }
95
-
96
- const statusCommand = buildCommand({
97
- docs: {
98
- brief: "Show MCP controller status",
99
- },
100
- parameters: {},
101
- async func() {
102
- await runMcpStatus();
103
- },
104
- });
105
-
106
- export const mcpRoutes = buildRouteMap({
107
- routes: {
108
- status: statusCommand,
109
- },
110
- docs: {
111
- brief: "MCP controller commands",
112
- },
113
- });
@@ -1,184 +0,0 @@
1
- import { afterEach, beforeEach, describe, expect, mock, test } from "bun:test";
2
- import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
3
- import { join } from "node:path";
4
- import { runServe } from "./serve";
5
-
6
- // Create test fixtures directory
7
- const testDir = join(process.cwd(), "test-fixtures-serve");
8
-
9
- beforeEach(() => {
10
- // Clean up and create fresh test directory
11
- if (existsSync(testDir)) {
12
- rmSync(testDir, { recursive: true, force: true });
13
- }
14
- mkdirSync(testDir, { recursive: true });
15
- process.chdir(testDir);
16
- });
17
-
18
- afterEach(() => {
19
- // Return to original directory and clean up
20
- process.chdir(join(testDir, ".."));
21
- if (existsSync(testDir)) {
22
- rmSync(testDir, { recursive: true, force: true });
23
- }
24
- });
25
-
26
- describe("serve command", () => {
27
- test("should fail when OmniDev is not initialized", async () => {
28
- const mockExit = mock((code?: number) => {
29
- throw new Error(`process.exit: ${code}`);
30
- }) as typeof process.exit;
31
- const originalExit = process.exit;
32
- process.exit = mockExit;
33
-
34
- try {
35
- await expect(runServe({})).rejects.toThrow("process.exit: 1");
36
- expect(mockExit).toHaveBeenCalledWith(1);
37
- } finally {
38
- process.exit = originalExit;
39
- }
40
- });
41
-
42
- test("should fail when .omni/ directory is missing", async () => {
43
- // Don't create .omni/ - test expects it to be missing
44
-
45
- const mockExit = mock((code?: number) => {
46
- throw new Error(`process.exit: ${code}`);
47
- }) as typeof process.exit;
48
- const originalExit = process.exit;
49
- process.exit = mockExit;
50
-
51
- try {
52
- await expect(runServe({})).rejects.toThrow("process.exit: 1");
53
- expect(mockExit).toHaveBeenCalledWith(1);
54
- } finally {
55
- process.exit = originalExit;
56
- }
57
- });
58
-
59
- test("should fail when profile does not exist", async () => {
60
- // Set up directories
61
- mkdirSync(".omni", { recursive: true });
62
- mkdirSync(".omni", { recursive: true });
63
-
64
- // Create a config without the requested profile
65
- writeFileSync(
66
- "omni.toml",
67
- `
68
- [capability]
69
- project = "test"
70
-
71
- [profiles.default]
72
- `,
73
- );
74
-
75
- const mockExit = mock((code?: number) => {
76
- throw new Error(`process.exit: ${code}`);
77
- }) as typeof process.exit;
78
- const originalExit = process.exit;
79
- process.exit = mockExit;
80
-
81
- try {
82
- await expect(runServe({ profile: "nonexistent" })).rejects.toThrow("process.exit: 1");
83
- expect(mockExit).toHaveBeenCalledWith(1);
84
- } finally {
85
- process.exit = originalExit;
86
- }
87
- });
88
-
89
- test("should set profile when provided and valid", async () => {
90
- // Set up directories
91
- mkdirSync(".omni", { recursive: true });
92
- mkdirSync(".omni", { recursive: true });
93
-
94
- // Create config with profiles
95
- writeFileSync(
96
- "omni.toml",
97
- `project = "test"
98
- active_profile = "default"
99
-
100
- [profiles.default]
101
- capabilities = []
102
-
103
- [profiles.testing]
104
- capabilities = []
105
- `,
106
- );
107
-
108
- // Mock startServer to prevent actual server start
109
- const mockStartServer = mock(async () => {
110
- // Server started successfully, do nothing
111
- });
112
-
113
- // Mock the import of @omnidev-ai/mcp
114
- const originalImport = globalThis[Symbol.for("Bun.lazy")];
115
- // biome-ignore lint/suspicious/noExplicitAny: Testing requires dynamic mocking
116
- (globalThis as any).import = mock(async (module: string) => {
117
- if (module === "@omnidev-ai/mcp") {
118
- return { startServer: mockStartServer };
119
- }
120
- throw new Error(`Unexpected import: ${module}`);
121
- });
122
-
123
- const mockExit = mock((code?: number) => {
124
- throw new Error(`process.exit: ${code}`);
125
- }) as typeof process.exit;
126
- const originalExit = process.exit;
127
- process.exit = mockExit;
128
-
129
- try {
130
- // This should fail because startServer will actually run, but that's OK for this test
131
- // We just want to verify that setActiveProfile was called
132
- await runServe({ profile: "testing" }).catch(() => {
133
- // Ignore the error from startServer
134
- });
135
-
136
- // Check that active profile was written to state file (not config.toml)
137
- const stateContent = await Bun.file(".omni/state/active-profile").text();
138
- expect(stateContent).toBe("testing");
139
- } finally {
140
- process.exit = originalExit;
141
- // biome-ignore lint/suspicious/noExplicitAny: Restore original import
142
- if (originalImport) (globalThis as any).import = originalImport;
143
- }
144
- });
145
-
146
- test("should start server without profile flag", async () => {
147
- // Set up directories
148
- mkdirSync(".omni", { recursive: true });
149
- mkdirSync(".omni", { recursive: true });
150
-
151
- // Create config
152
- writeFileSync(
153
- "omni.toml",
154
- `
155
- [capability]
156
- project = "test"
157
- default_profile = "default"
158
-
159
- [profiles.default]
160
- `,
161
- );
162
-
163
- // We can't actually test server startup without complex mocking,
164
- // so we'll just verify the command passes initial checks
165
- const mockExit = mock((code?: number) => {
166
- throw new Error(`process.exit: ${code}`);
167
- }) as typeof process.exit;
168
- const originalExit = process.exit;
169
- process.exit = mockExit;
170
-
171
- try {
172
- // This will fail at the import stage, but that's expected
173
- await runServe({}).catch((error) => {
174
- // Should fail on import or server start, not on validation
175
- expect(error).toBeDefined();
176
- });
177
-
178
- // No profile should be written when flag not provided
179
- expect(existsSync(".omni/active-profile")).toBe(false);
180
- } finally {
181
- process.exit = originalExit;
182
- }
183
- });
184
- });
@@ -1,63 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import { setActiveProfile } from "@omnidev-ai/core";
3
- import { buildCommand } from "@stricli/core";
4
-
5
- interface ServeFlags {
6
- profile?: string;
7
- }
8
-
9
- export async function runServe(flags: ServeFlags): Promise<void> {
10
- console.log("Starting OmniDev MCP server...");
11
-
12
- // Check if OmniDev is initialized
13
- if (!existsSync(".omni")) {
14
- console.error("✗ OmniDev not initialized. Run: omnidev init");
15
- process.exit(1);
16
- }
17
-
18
- // Set profile if provided
19
- if (flags.profile) {
20
- console.log(`Setting profile to: ${flags.profile}`);
21
- try {
22
- // Validate profile exists in config
23
- const { loadConfig } = await import("@omnidev-ai/core");
24
- const config = await loadConfig();
25
- if (!config.profiles?.[flags.profile]) {
26
- console.error(`✗ Profile "${flags.profile}" not found in config`);
27
- process.exit(1);
28
- }
29
- await setActiveProfile(flags.profile);
30
- } catch (error) {
31
- console.error(`✗ Failed to set profile: ${error}`);
32
- process.exit(1);
33
- }
34
- }
35
-
36
- // Import and start the MCP server
37
- try {
38
- const { startServer } = await import("@omnidev-ai/mcp");
39
- console.log("✓ Server starting...");
40
- await startServer();
41
- } catch (error) {
42
- console.error(`✗ Failed to start server: ${error}`);
43
- process.exit(1);
44
- }
45
- }
46
-
47
- export const serveCommand = buildCommand({
48
- docs: {
49
- brief: "Start the OmniDev MCP server",
50
- fullDescription: "Starts the MCP server that exposes omni_query and omni_execute tools to LLMs",
51
- },
52
- parameters: {
53
- flags: {
54
- profile: {
55
- kind: "parsed" as const,
56
- parse: String,
57
- brief: "Set active profile before starting server",
58
- optional: true,
59
- },
60
- },
61
- },
62
- func: runServe,
63
- });