@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
|
@@ -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
|
+
}
|
package/src/lib/debug.ts
ADDED
|
@@ -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
|
+
}
|