@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.
@@ -0,0 +1,63 @@
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
+ });
@@ -0,0 +1,43 @@
1
+ import { getActiveProfile, loadConfig, syncAgentConfiguration } from "@omnidev-ai/core";
2
+ import { buildCommand } from "@stricli/core";
3
+
4
+ export const syncCommand = buildCommand({
5
+ docs: {
6
+ brief: "Manually sync all capabilities, roles, instructions, and MCP configuration",
7
+ },
8
+ parameters: {},
9
+ async func() {
10
+ return await runSync();
11
+ },
12
+ });
13
+
14
+ export async function runSync(): Promise<void> {
15
+ console.log("Syncing OmniDev configuration...");
16
+ console.log("");
17
+
18
+ try {
19
+ const config = await loadConfig();
20
+ const activeProfile = (await getActiveProfile()) ?? config.active_profile ?? "default";
21
+
22
+ const result = await syncAgentConfiguration({ silent: false });
23
+
24
+ console.log("");
25
+ console.log("✓ Sync completed successfully!");
26
+ console.log("");
27
+ console.log(`Profile: ${activeProfile}`);
28
+ console.log(`Capabilities: ${result.capabilities.join(", ") || "none"}`);
29
+ console.log("");
30
+ console.log("Synced components:");
31
+ console.log(" • Capability registry");
32
+ console.log(" • Capability sync hooks");
33
+ console.log(" • .omni/.gitignore");
34
+ console.log(" • .omni/instructions.md");
35
+ console.log(" • .claude/skills/");
36
+ console.log(" • .cursor/rules/");
37
+ } catch (error) {
38
+ console.error("");
39
+ console.error("✗ Sync failed:");
40
+ console.error(` ${error instanceof Error ? error.message : String(error)}`);
41
+ process.exit(1);
42
+ }
43
+ }
package/src/index.ts ADDED
@@ -0,0 +1,55 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * @omnidev-ai/cli - Command-line interface for OmniDev
4
+ *
5
+ * This package provides the CLI for managing OmniDev configuration,
6
+ * capabilities, and the MCP server.
7
+ */
8
+
9
+ import { run } from "@stricli/core";
10
+ import { buildDynamicApp } from "./lib/dynamic-app";
11
+ import { debug } from "./lib/debug";
12
+
13
+ // Build app dynamically based on enabled capabilities
14
+ const app = await buildDynamicApp();
15
+
16
+ debug("CLI startup", {
17
+ arguments: process.argv.slice(2),
18
+ cwd: process.cwd(),
19
+ });
20
+
21
+ // Run CLI with error handling
22
+ try {
23
+ run(app, process.argv.slice(2), {
24
+ // biome-ignore lint/suspicious/noExplicitAny: Stricli expects a process-like object with stdin/stdout/stderr
25
+ process: process as any,
26
+ });
27
+ } catch (error) {
28
+ // Provide helpful error messages instead of cryptic stack traces
29
+ if (error instanceof Error) {
30
+ // Check if it's a routing error (command not found)
31
+ if (
32
+ error.message.includes("getRoutingTargetForInput") ||
33
+ error.stack?.includes("@stricli/core")
34
+ ) {
35
+ const args = process.argv.slice(2);
36
+ console.error(`\nError: Command not found or invalid usage.`);
37
+
38
+ if (args.length > 0) {
39
+ console.error(`\nYou tried to run: omnidev ${args.join(" ")}`);
40
+ console.error("\nThis could mean:");
41
+ console.error(" 1. The command doesn't exist");
42
+ console.error(" 2. A required capability is not enabled");
43
+ console.error(" 3. Invalid command syntax\n");
44
+ }
45
+
46
+ console.error("Run 'omnidev --help' to see available commands");
47
+ console.error("\nTo enable capabilities, run: omnidev capability enable <name>");
48
+ console.error("To see enabled capabilities: omnidev capability list");
49
+ process.exit(1);
50
+ } else {
51
+ // Re-throw other errors
52
+ throw error;
53
+ }
54
+ }
55
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Re-export debug from core for backwards compatibility TODO: remove and update imports
3
+ */
4
+ export { debug } from "@omnidev-ai/core";
@@ -0,0 +1,182 @@
1
+ import { existsSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import type { CapabilityExport } from "@omnidev-ai/core";
4
+ import { buildApplication, buildRouteMap } from "@stricli/core";
5
+ import { capabilityRoutes } from "../commands/capability";
6
+ // Core commands
7
+ import { doctorCommand } from "../commands/doctor";
8
+ import { initCommand } from "../commands/init";
9
+ import { mcpRoutes } from "../commands/mcp";
10
+ import { profileRoutes } from "../commands/profile";
11
+ import { serveCommand } from "../commands/serve";
12
+ import { syncCommand } from "../commands/sync";
13
+ import { debug } from "./debug";
14
+
15
+ /**
16
+ * Build CLI app with dynamically loaded capability commands
17
+ */
18
+ export async function buildDynamicApp() {
19
+ // Start with core commands
20
+ const routes: Record<string, unknown> = {
21
+ init: initCommand,
22
+ doctor: doctorCommand,
23
+ serve: serveCommand,
24
+ sync: syncCommand,
25
+ capability: capabilityRoutes,
26
+ profile: profileRoutes,
27
+ mcp: mcpRoutes,
28
+ };
29
+
30
+ debug("Core routes registered", Object.keys(routes));
31
+
32
+ // Only load capability commands if initialized
33
+ if (existsSync(".omni/config.toml")) {
34
+ try {
35
+ const capabilityCommands = await loadCapabilityCommands();
36
+ debug("Capability commands loaded", {
37
+ commands: Object.keys(capabilityCommands),
38
+ details: Object.entries(capabilityCommands).map(([name, cmd]) => ({
39
+ name,
40
+ type: typeof cmd,
41
+ // biome-ignore lint/suspicious/noExplicitAny: Debug: access constructor name
42
+ constructor: (cmd as any)?.constructor?.name,
43
+ keys: Object.keys(cmd as object),
44
+ // biome-ignore lint/suspicious/noExplicitAny: Debug: check for method
45
+ hasGetRoutingTargetForInput: typeof (cmd as any)?.getRoutingTargetForInput,
46
+ })),
47
+ });
48
+ Object.assign(routes, capabilityCommands);
49
+ } catch (error) {
50
+ const errorMessage = error instanceof Error ? error.message : String(error);
51
+ console.warn(`Warning: Failed to load capability commands: ${errorMessage}`);
52
+ debug("Full error loading capabilities", error);
53
+ // Continue with core commands only
54
+ }
55
+ }
56
+
57
+ debug("Final routes", Object.keys(routes));
58
+
59
+ const app = buildApplication(
60
+ buildRouteMap({
61
+ // biome-ignore lint/suspicious/noExplicitAny: Dynamic commands from capabilities
62
+ routes: routes as any,
63
+ docs: {
64
+ brief: "OmniDev commands",
65
+ },
66
+ }),
67
+ {
68
+ name: "omnidev",
69
+ versionInfo: {
70
+ currentVersion: "0.1.0",
71
+ },
72
+ },
73
+ );
74
+
75
+ debug("App built successfully");
76
+
77
+ return app;
78
+ }
79
+
80
+ /**
81
+ * Load CLI commands from enabled capabilities
82
+ */
83
+ async function loadCapabilityCommands(): Promise<Record<string, unknown>> {
84
+ const { buildCapabilityRegistry, installCapabilityDependencies } = await import(
85
+ "@omnidev-ai/core"
86
+ );
87
+
88
+ // Install dependencies first (silent to avoid noise during CLI startup)
89
+ await installCapabilityDependencies(true);
90
+
91
+ const registry = await buildCapabilityRegistry();
92
+ const capabilities = registry.getAllCapabilities();
93
+
94
+ const commands: Record<string, unknown> = {};
95
+
96
+ for (const capability of capabilities) {
97
+ try {
98
+ debug(`Loading capability '${capability.id}'`, { path: capability.path });
99
+ const capabilityExport = await loadCapabilityExport(capability);
100
+
101
+ debug(`Capability '${capability.id}' export`, {
102
+ found: !!capabilityExport,
103
+ hasCLICommands: !!capabilityExport?.cliCommands,
104
+ cliCommands: capabilityExport?.cliCommands ? Object.keys(capabilityExport.cliCommands) : [],
105
+ });
106
+
107
+ // Extract CLI commands from structured export
108
+ if (capabilityExport?.cliCommands) {
109
+ for (const [commandName, command] of Object.entries(capabilityExport.cliCommands)) {
110
+ if (commands[commandName]) {
111
+ console.warn(
112
+ `Command '${commandName}' from capability '${capability.id}' conflicts with existing command. Using '${capability.id}' version.`,
113
+ );
114
+ }
115
+ commands[commandName] = command;
116
+ debug(`Registered command '${commandName}' from '${capability.id}'`, {
117
+ type: typeof command,
118
+ // biome-ignore lint/suspicious/noExplicitAny: Debug: access constructor name
119
+ constructor: (command as any)?.constructor?.name,
120
+ });
121
+ }
122
+ }
123
+ } catch (error) {
124
+ console.error(`Failed to load capability '${capability.id}':`, error);
125
+ // Continue loading other capabilities
126
+ }
127
+ }
128
+
129
+ return commands;
130
+ }
131
+
132
+ /**
133
+ * Load the default export from a capability
134
+ */
135
+ async function loadCapabilityExport(capability: {
136
+ id: string;
137
+ path: string;
138
+ }): Promise<CapabilityExport | null> {
139
+ const capabilityPath = join(process.cwd(), capability.path);
140
+ const indexPath = join(capabilityPath, "index.ts");
141
+
142
+ if (!existsSync(indexPath)) {
143
+ // Try .js extension
144
+ const jsIndexPath = join(capabilityPath, "index.js");
145
+ if (!existsSync(jsIndexPath)) {
146
+ return null;
147
+ }
148
+ // Use .js path
149
+ const module = await import(jsIndexPath);
150
+ if (!module.default) {
151
+ return null;
152
+ }
153
+ return module.default as CapabilityExport;
154
+ }
155
+
156
+ const module = await import(indexPath);
157
+
158
+ // Get default export (structured capability export)
159
+ if (!module.default) {
160
+ return null;
161
+ }
162
+
163
+ const capExport = module.default as CapabilityExport;
164
+
165
+ // Debug: Log the actual structure of CLI commands
166
+ if (capExport.cliCommands) {
167
+ for (const [name, cmd] of Object.entries(capExport.cliCommands)) {
168
+ debug(`CLI command '${name}' structure`, {
169
+ type: typeof cmd,
170
+ // biome-ignore lint/suspicious/noExplicitAny: Debug: access constructor name
171
+ constructor: (cmd as any)?.constructor?.name,
172
+ keys: Object.keys(cmd as object),
173
+ // biome-ignore lint/suspicious/noExplicitAny: Debug: check for method
174
+ hasGetRoutingTargetForInput: typeof (cmd as any)?.getRoutingTargetForInput,
175
+ // biome-ignore lint/suspicious/noExplicitAny: Debug: access routes property
176
+ routesKeys: (cmd as any).routes ? Object.keys((cmd as any).routes) : undefined,
177
+ });
178
+ }
179
+ }
180
+
181
+ return capExport;
182
+ }
@@ -0,0 +1,15 @@
1
+ import { checkbox } from "@inquirer/prompts";
2
+ import type { Provider } from "@omnidev-ai/core";
3
+
4
+ export async function promptForProvider(): Promise<Provider[]> {
5
+ const answers = await checkbox({
6
+ message: "Select your AI provider(s):",
7
+ choices: [
8
+ { name: "Claude (recommended)", value: "claude", checked: true },
9
+ { name: "Codex", value: "codex", checked: false },
10
+ ],
11
+ required: true,
12
+ });
13
+
14
+ return answers as Provider[];
15
+ }