@hilbras/omninode 2.0.0-alpha.14 → 2.0.0-alpha.15
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/README.md +9 -2
- package/dist/cli/index.js +401 -184
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -23,7 +23,49 @@ import { realpathSync } from "fs";
|
|
|
23
23
|
import { Command } from "commander";
|
|
24
24
|
|
|
25
25
|
// src/version.ts
|
|
26
|
-
var OMNINODE_VERSION = "2.0.0-alpha.
|
|
26
|
+
var OMNINODE_VERSION = "2.0.0-alpha.15";
|
|
27
|
+
|
|
28
|
+
// src/cli/exit-codes.ts
|
|
29
|
+
var EXIT = {
|
|
30
|
+
success: 0,
|
|
31
|
+
general: 1,
|
|
32
|
+
invalidInput: 2,
|
|
33
|
+
config: 3,
|
|
34
|
+
provider: 4,
|
|
35
|
+
agent: 5,
|
|
36
|
+
timeout: 6,
|
|
37
|
+
cancelled: 7,
|
|
38
|
+
notFound: 8,
|
|
39
|
+
notImplemented: 9
|
|
40
|
+
};
|
|
41
|
+
function exitCodeFor(error) {
|
|
42
|
+
if (!isOmniNodeError(error)) return EXIT.general;
|
|
43
|
+
const code = error.code;
|
|
44
|
+
if (code === "NOT_IMPLEMENTED") return EXIT.notImplemented;
|
|
45
|
+
if (code === "AGENT_TIMEOUT") return EXIT.timeout;
|
|
46
|
+
if (code.startsWith("CONFIG_")) return EXIT.config;
|
|
47
|
+
if (code.startsWith("PROVIDER_") || code === "MODEL_NOT_FOUND") return EXIT.provider;
|
|
48
|
+
if (code.startsWith("AGENT_")) return EXIT.agent;
|
|
49
|
+
if (code.endsWith("_NOT_FOUND")) return EXIT.notFound;
|
|
50
|
+
if (code.endsWith("_INVALID") || code === "CLI_USAGE" || code === "PROTOCOL_VIOLATION") {
|
|
51
|
+
return EXIT.invalidInput;
|
|
52
|
+
}
|
|
53
|
+
return EXIT.general;
|
|
54
|
+
}
|
|
55
|
+
function exitCodeForStatus(status) {
|
|
56
|
+
switch (status) {
|
|
57
|
+
case "completed":
|
|
58
|
+
return EXIT.success;
|
|
59
|
+
case "cancelled":
|
|
60
|
+
return EXIT.cancelled;
|
|
61
|
+
case "timed_out":
|
|
62
|
+
return EXIT.timeout;
|
|
63
|
+
case "failed":
|
|
64
|
+
return EXIT.general;
|
|
65
|
+
default:
|
|
66
|
+
return EXIT.success;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
27
69
|
|
|
28
70
|
// src/config/schema.ts
|
|
29
71
|
import { z } from "zod";
|
|
@@ -357,6 +399,10 @@ function registerProjectCommands(program) {
|
|
|
357
399
|
project.command("init").description("Alias of `omninode init`.").option("-f, --force", "Overwrite an existing omninode.yaml.").option("-n, --name <name>", "Project name to write into the configuration.", "My Project").action(async (options) => runInit(options));
|
|
358
400
|
}
|
|
359
401
|
|
|
402
|
+
// src/cli/commands/providers.ts
|
|
403
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
404
|
+
import { parseDocument as parseDocument2 } from "yaml";
|
|
405
|
+
|
|
360
406
|
// src/registry/index.ts
|
|
361
407
|
function registryKey(provider, id) {
|
|
362
408
|
return `${provider}:${id}`;
|
|
@@ -409,12 +455,6 @@ var ModelRegistry = class {
|
|
|
409
455
|
// src/cli/commands/shared.ts
|
|
410
456
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
411
457
|
import { parseDocument } from "yaml";
|
|
412
|
-
function notImplemented(command, when) {
|
|
413
|
-
return new OmniNodeError(
|
|
414
|
-
"NOT_IMPLEMENTED",
|
|
415
|
-
`${command} is planned for ${when}. Edit omninode.yaml directly for now.`
|
|
416
|
-
);
|
|
417
|
-
}
|
|
418
458
|
function truncate(text, max) {
|
|
419
459
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
420
460
|
}
|
|
@@ -1977,9 +2017,13 @@ function registerProviderInspect(providerGroup) {
|
|
|
1977
2017
|
// src/cli/commands/providers.ts
|
|
1978
2018
|
function registerProviderCommands(program) {
|
|
1979
2019
|
const provider = program.command("provider").description("Manage AI providers configured in omninode.yaml.");
|
|
1980
|
-
provider.command("list").description("List configured providers.").action(async () => {
|
|
2020
|
+
provider.command("list").description("List configured providers.").option("--json", "Emit providers as JSON.").action(async (options) => {
|
|
1981
2021
|
const config = loadConfig();
|
|
1982
2022
|
const providers = config.project.providers;
|
|
2023
|
+
if (options.json) {
|
|
2024
|
+
console.log(JSON.stringify(providers, null, 2));
|
|
2025
|
+
return;
|
|
2026
|
+
}
|
|
1983
2027
|
if (providers.length === 0) {
|
|
1984
2028
|
console.log(
|
|
1985
2029
|
"No providers configured. Add one with `omninode provider add` or edit omninode.yaml."
|
|
@@ -2008,8 +2052,8 @@ function registerProviderCommands(program) {
|
|
|
2008
2052
|
"--connect",
|
|
2009
2053
|
"Run the full provider connect flow (authenticate, fetch, validate, register) where the adapter supports it."
|
|
2010
2054
|
).action(async (name, options) => testProvider(name, options));
|
|
2011
|
-
provider.command("remove <name>").description("Remove a provider
|
|
2012
|
-
|
|
2055
|
+
provider.command("remove <name>").description("Remove a provider from omninode.yaml.").action((name) => {
|
|
2056
|
+
removeProvider(name);
|
|
2013
2057
|
});
|
|
2014
2058
|
registerProviderInspect(provider);
|
|
2015
2059
|
}
|
|
@@ -2046,6 +2090,26 @@ async function addProvider(options) {
|
|
|
2046
2090
|
console.log(`Run \`omninode provider test ${newProvider.name}\` to verify connectivity.`);
|
|
2047
2091
|
}
|
|
2048
2092
|
}
|
|
2093
|
+
function removeProvider(name) {
|
|
2094
|
+
const configPath = findConfigFile();
|
|
2095
|
+
if (!configPath) {
|
|
2096
|
+
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found.");
|
|
2097
|
+
}
|
|
2098
|
+
const config = loadConfig();
|
|
2099
|
+
if (!config.project.providers.some((p) => p.name === name)) {
|
|
2100
|
+
throw new ProviderError("PROVIDER_NOT_FOUND", `No provider "${name}" is configured.`);
|
|
2101
|
+
}
|
|
2102
|
+
const doc = parseDocument2(readFileSync3(configPath, "utf8"));
|
|
2103
|
+
const current = doc.getIn(["project", "providers"]);
|
|
2104
|
+
const remaining = (current === void 0 || current === null ? [] : doc.get("project")?.providers ?? []).filter((entry2) => entry2?.name !== name);
|
|
2105
|
+
doc.setIn(["project", "providers"], remaining);
|
|
2106
|
+
const validated = appConfigSchema.safeParse(doc.toJS());
|
|
2107
|
+
if (!validated.success) {
|
|
2108
|
+
throw new OmniNodeError("CONFIG_INVALID", "Removing this provider would produce an invalid configuration.");
|
|
2109
|
+
}
|
|
2110
|
+
writeFileSync3(configPath, doc.toString(), "utf8");
|
|
2111
|
+
console.log(`Removed provider "${name}" from ${configPath}.`);
|
|
2112
|
+
}
|
|
2049
2113
|
async function testProvider(name, options = {}) {
|
|
2050
2114
|
const config = loadConfig();
|
|
2051
2115
|
const providerConfig = config.project.providers.find((p) => p.name === name);
|
|
@@ -2081,169 +2145,6 @@ function printProviderStatus(status) {
|
|
|
2081
2145
|
if (status.message) console.log(` detail: ${status.message}`);
|
|
2082
2146
|
}
|
|
2083
2147
|
|
|
2084
|
-
// src/cli/commands/models.ts
|
|
2085
|
-
var CAPABILITIES = [
|
|
2086
|
-
"chat",
|
|
2087
|
-
"coding",
|
|
2088
|
-
"tools",
|
|
2089
|
-
"vision",
|
|
2090
|
-
"embeddings"
|
|
2091
|
-
];
|
|
2092
|
-
function registerModelsCommand(program) {
|
|
2093
|
-
program.command("models").description("Discover and list models from configured providers (model registry, \xA78).").argument("[provider]", "Only discover from this provider.").option("-c, --capability <capability>", `Filter by capability: ${CAPABILITIES.join(", ")}.`).action(async (providerName, options) => {
|
|
2094
|
-
const capability = options.capability;
|
|
2095
|
-
if (capability !== void 0 && !CAPABILITIES.includes(capability)) {
|
|
2096
|
-
throw new OmniNodeError(
|
|
2097
|
-
"CLI_USAGE",
|
|
2098
|
-
`Unknown capability "${options.capability}". Valid capabilities: ${CAPABILITIES.join(", ")}.`
|
|
2099
|
-
);
|
|
2100
|
-
}
|
|
2101
|
-
const config = loadConfig();
|
|
2102
|
-
const selected = config.project.providers.filter(
|
|
2103
|
-
(p) => providerName ? p.name === providerName : true
|
|
2104
|
-
);
|
|
2105
|
-
if (selected.length === 0) {
|
|
2106
|
-
throw new OmniNodeError(
|
|
2107
|
-
"PROVIDER_NOT_FOUND",
|
|
2108
|
-
providerName ? `Provider "${providerName}" is not configured.` : "No providers configured. Add one with `omninode provider add`."
|
|
2109
|
-
);
|
|
2110
|
-
}
|
|
2111
|
-
const registry = new ModelRegistry();
|
|
2112
|
-
for (const providerConfig of selected) {
|
|
2113
|
-
try {
|
|
2114
|
-
const provider = createProvider(providerConfig);
|
|
2115
|
-
const discovered = await registry.discoverFrom(provider);
|
|
2116
|
-
console.log(`${providerConfig.name}: ${discovered.length} model(s)`);
|
|
2117
|
-
} catch (error) {
|
|
2118
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2119
|
-
console.log(`${providerConfig.name}: discovery failed \u2014 ${message}`);
|
|
2120
|
-
}
|
|
2121
|
-
}
|
|
2122
|
-
const models = registry.list({
|
|
2123
|
-
...providerName !== void 0 ? { provider: providerName } : {},
|
|
2124
|
-
...capability !== void 0 ? { capability } : {}
|
|
2125
|
-
});
|
|
2126
|
-
if (models.length === 0) {
|
|
2127
|
-
console.log("No models found.");
|
|
2128
|
-
return;
|
|
2129
|
-
}
|
|
2130
|
-
console.log("");
|
|
2131
|
-
for (const model of models) {
|
|
2132
|
-
console.log(`${model.provider}:${model.id}`);
|
|
2133
|
-
}
|
|
2134
|
-
});
|
|
2135
|
-
}
|
|
2136
|
-
|
|
2137
|
-
// src/cli/commands/agents.ts
|
|
2138
|
-
var collectArgs = (value, previous) => [...previous, value];
|
|
2139
|
-
function registerAgentCommands(program) {
|
|
2140
|
-
const agent = program.command("agent").description("Manage CLI agents registered with OmniNode.");
|
|
2141
|
-
agent.command("list").description("List configured agents.").action(async () => {
|
|
2142
|
-
const config = loadConfig();
|
|
2143
|
-
const agents = config.project.agents;
|
|
2144
|
-
if (agents.length === 0) {
|
|
2145
|
-
console.log("No agents configured. Add one with `omninode agent add` or edit omninode.yaml.");
|
|
2146
|
-
return;
|
|
2147
|
-
}
|
|
2148
|
-
for (const agent2 of agents) {
|
|
2149
|
-
const command = agent2.command ?? "(no command)";
|
|
2150
|
-
console.log(
|
|
2151
|
-
`${agent2.name} integration=${agent2.integration} command=${command} input=${agent2.inputMode ?? "stdin"}`
|
|
2152
|
-
);
|
|
2153
|
-
}
|
|
2154
|
-
});
|
|
2155
|
-
agent.command("add").description(
|
|
2156
|
-
"Register a CLI agent in omninode.yaml. Agents run as child processes (stdin/stdout); native adapters come in a later phase."
|
|
2157
|
-
).requiredOption("--name <name>", "Unique agent name.").requiredOption("--command <command>", "Executable command, e.g. opencode").option("--arg <value>", "Additional command argument (repeatable).", collectArgs, []).option("--cwd <dir>", "Working directory for the agent process.").option(
|
|
2158
|
-
"--input-mode <mode>",
|
|
2159
|
-
"How the task is delivered: stdin (prompt on stdin), arg (prompt as last argument) or protocol (\xA721 JSON-lines task protocol).",
|
|
2160
|
-
"stdin"
|
|
2161
|
-
).option("--env-policy <policy>", "Environment policy: inherit, allowlist, denylist or explicit.").option("--allow-env <name>", "Variable to forward under the allowlist policy (repeatable).", collectArgs).option("--deny-env <name>", "Variable to remove under the denylist policy (repeatable).", collectArgs).option("--max-output-bytes <n>", "Per-stream output budget in bytes (default 5 MiB).", (value) => Number(value)).option("--allow-external-cwd", "Allow a working directory outside the project root.").option(
|
|
2162
|
-
"--timeout-ms <ms>",
|
|
2163
|
-
"Per-task timeout in milliseconds.",
|
|
2164
|
-
(value) => Number(value)
|
|
2165
|
-
).action(async (options) => addAgent(options));
|
|
2166
|
-
agent.command("test <name>").description("Verify the OmniNode \u2194 agent connection by sending a trivial task.").action(async (name) => testAgent(name));
|
|
2167
|
-
registerAgentInspect(agent);
|
|
2168
|
-
}
|
|
2169
|
-
async function addAgent(options) {
|
|
2170
|
-
const candidate = agentConfigSchema.safeParse({
|
|
2171
|
-
name: options.name,
|
|
2172
|
-
type: "cli",
|
|
2173
|
-
command: options.command,
|
|
2174
|
-
...options.arg !== void 0 && options.arg.length > 0 ? { args: options.arg } : {},
|
|
2175
|
-
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
2176
|
-
input_mode: options.inputMode,
|
|
2177
|
-
...options.timeoutMs !== void 0 ? { timeout_ms: options.timeoutMs } : {},
|
|
2178
|
-
...options.envPolicy !== void 0 ? { env_policy: options.envPolicy } : {},
|
|
2179
|
-
...options.allowEnv !== void 0 && options.allowEnv.length > 0 ? { env_allowlist: options.allowEnv } : {},
|
|
2180
|
-
...options.denyEnv !== void 0 && options.denyEnv.length > 0 ? { env_denylist: options.denyEnv } : {},
|
|
2181
|
-
...options.maxOutputBytes !== void 0 ? { max_output_bytes: options.maxOutputBytes } : {},
|
|
2182
|
-
...options.allowExternalCwd !== void 0 ? { allow_external_cwd: options.allowExternalCwd } : {}
|
|
2183
|
-
});
|
|
2184
|
-
if (!candidate.success) {
|
|
2185
|
-
const issues = candidate.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
2186
|
-
throw new OmniNodeError("CLI_USAGE", `Invalid agent options: ${issues}`);
|
|
2187
|
-
}
|
|
2188
|
-
const newAgent = candidate.data;
|
|
2189
|
-
const existing = loadConfig();
|
|
2190
|
-
if (existing.project.agents.some((a) => a.name === newAgent.name)) {
|
|
2191
|
-
throw new OmniNodeError(
|
|
2192
|
-
"CLI_USAGE",
|
|
2193
|
-
`Agent "${newAgent.name}" already exists in omninode.yaml. Remove it first or pick another name.`
|
|
2194
|
-
);
|
|
2195
|
-
}
|
|
2196
|
-
const configPath = appendToConfigList("agents", newAgent);
|
|
2197
|
-
console.log(`Added agent "${newAgent.name}" (${newAgent.type}, input=${newAgent.input_mode}) to ${configPath}.`);
|
|
2198
|
-
console.log(`Run \`omninode agent test ${newAgent.name}\` to verify the connection.`);
|
|
2199
|
-
}
|
|
2200
|
-
async function testAgent(name) {
|
|
2201
|
-
const config = loadConfig();
|
|
2202
|
-
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
2203
|
-
if (!agentConfig) {
|
|
2204
|
-
const available = config.project.agents.map((a) => a.name).join(", ") || "(none)";
|
|
2205
|
-
throw new AgentError(
|
|
2206
|
-
"AGENT_NOT_FOUND",
|
|
2207
|
-
`Agent "${name}" is not configured. Configured agents: ${available}.`
|
|
2208
|
-
);
|
|
2209
|
-
}
|
|
2210
|
-
const agent = createAgent(agentConfig);
|
|
2211
|
-
console.log(
|
|
2212
|
-
`Testing agent "${name}" (${agent.info.integration}, input=${agentConfig.inputMode ?? "stdin"}) \u2014 sending a trivial task.`
|
|
2213
|
-
);
|
|
2214
|
-
const result = await agent.run({
|
|
2215
|
-
taskId: `test-${Date.now()}`,
|
|
2216
|
-
objective: "Reply with the single word: OK"
|
|
2217
|
-
});
|
|
2218
|
-
console.log(` status: ${result.status}`);
|
|
2219
|
-
if (result.exitCode !== void 0) console.log(` exit: ${result.exitCode}`);
|
|
2220
|
-
if (result.summary) console.log(` output: ${result.summary}`);
|
|
2221
|
-
if (result.rawOutput && result.rawOutput !== result.summary) {
|
|
2222
|
-
console.log(` raw: ${result.rawOutput.slice(0, 200).replace(/\n/g, " ")}`);
|
|
2223
|
-
}
|
|
2224
|
-
if (result.error) console.log(` error: ${result.error}`);
|
|
2225
|
-
if (result.status !== "completed") process.exitCode = 1;
|
|
2226
|
-
}
|
|
2227
|
-
|
|
2228
|
-
// src/cli/commands/roles.ts
|
|
2229
|
-
function registerRoleCommands(program) {
|
|
2230
|
-
const role = program.command("role").description("Inspect roles defined in omninode.yaml.");
|
|
2231
|
-
role.command("list").description("List configured roles.").action(async () => {
|
|
2232
|
-
const config = loadConfig();
|
|
2233
|
-
const roles = config.project.roles;
|
|
2234
|
-
if (roles.length === 0) {
|
|
2235
|
-
console.log("No roles configured. Add one under `project.roles` in omninode.yaml.");
|
|
2236
|
-
return;
|
|
2237
|
-
}
|
|
2238
|
-
for (const role2 of roles) {
|
|
2239
|
-
console.log(`${role2.id} ${role2.name}`);
|
|
2240
|
-
for (const responsibility of role2.responsibilities) {
|
|
2241
|
-
console.log(` - ${responsibility}`);
|
|
2242
|
-
}
|
|
2243
|
-
}
|
|
2244
|
-
});
|
|
2245
|
-
}
|
|
2246
|
-
|
|
2247
2148
|
// src/tasks/engine.ts
|
|
2248
2149
|
import { randomBytes } from "crypto";
|
|
2249
2150
|
var ALLOWED_TRANSITIONS = {
|
|
@@ -2587,6 +2488,139 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
2587
2488
|
});
|
|
2588
2489
|
}
|
|
2589
2490
|
|
|
2491
|
+
// src/cli/commands/agents.ts
|
|
2492
|
+
var collectArgs = (value, previous) => [...previous, value];
|
|
2493
|
+
function registerAgentCommands(program) {
|
|
2494
|
+
const agent = program.command("agent").description("Manage CLI agents registered with OmniNode.");
|
|
2495
|
+
agent.command("list").description("List configured agents.").option("--json", "Emit agents as JSON.").action(async (options) => {
|
|
2496
|
+
const config = loadConfig();
|
|
2497
|
+
const agents = config.project.agents;
|
|
2498
|
+
if (options.json) {
|
|
2499
|
+
console.log(JSON.stringify(config.project.agents, null, 2));
|
|
2500
|
+
return;
|
|
2501
|
+
}
|
|
2502
|
+
if (agents.length === 0) {
|
|
2503
|
+
console.log("No agents configured. Add one with `omninode agent add` or edit omninode.yaml.");
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
for (const agent2 of agents) {
|
|
2507
|
+
const command = agent2.command ?? "(no command)";
|
|
2508
|
+
console.log(
|
|
2509
|
+
`${agent2.name} integration=${agent2.integration} command=${command} input=${agent2.inputMode ?? "stdin"}`
|
|
2510
|
+
);
|
|
2511
|
+
}
|
|
2512
|
+
});
|
|
2513
|
+
agent.command("add").description(
|
|
2514
|
+
"Register a CLI agent in omninode.yaml. Agents run as child processes (stdin/stdout); native adapters come in a later phase."
|
|
2515
|
+
).requiredOption("--name <name>", "Unique agent name.").requiredOption("--command <command>", "Executable command, e.g. opencode").option("--arg <value>", "Additional command argument (repeatable).", collectArgs, []).option("--cwd <dir>", "Working directory for the agent process.").option(
|
|
2516
|
+
"--input-mode <mode>",
|
|
2517
|
+
"How the task is delivered: stdin (prompt on stdin), arg (prompt as last argument) or protocol (\xA721 JSON-lines task protocol).",
|
|
2518
|
+
"stdin"
|
|
2519
|
+
).option("--env-policy <policy>", "Environment policy: inherit, allowlist, denylist or explicit.").option("--allow-env <name>", "Variable to forward under the allowlist policy (repeatable).", collectArgs).option("--deny-env <name>", "Variable to remove under the denylist policy (repeatable).", collectArgs).option("--max-output-bytes <n>", "Per-stream output budget in bytes (default 5 MiB).", (value) => Number(value)).option("--allow-external-cwd", "Allow a working directory outside the project root.").option(
|
|
2520
|
+
"--timeout-ms <ms>",
|
|
2521
|
+
"Per-task timeout in milliseconds.",
|
|
2522
|
+
(value) => Number(value)
|
|
2523
|
+
).action(async (options) => addAgent(options));
|
|
2524
|
+
agent.command("run <name> <objective>").description("Run a single task through this agent immediately.").action(async (name, objective) => {
|
|
2525
|
+
const config = loadConfig();
|
|
2526
|
+
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
2527
|
+
if (!agentConfig) {
|
|
2528
|
+
throw new AgentError("AGENT_NOT_FOUND", `No agent "${name}" is configured.`);
|
|
2529
|
+
}
|
|
2530
|
+
const engine = createTaskEngine(config);
|
|
2531
|
+
const task = await engine.create({ objective, agent: name, project: config.project.name });
|
|
2532
|
+
const finished = await engine.run(task.id);
|
|
2533
|
+
console.log(` id: ${finished.id}`);
|
|
2534
|
+
console.log(` status: ${finished.status}`);
|
|
2535
|
+
if (finished.result?.summary) console.log(` summary: ${finished.result.summary}`);
|
|
2536
|
+
if (finished.result?.error) console.log(` error: ${finished.result.error}`);
|
|
2537
|
+
process.exitCode = exitCodeForStatus(finished.status);
|
|
2538
|
+
});
|
|
2539
|
+
agent.command("test <name>").description("Verify the OmniNode \u2194 agent connection by sending a trivial task.").action(async (name) => testAgent(name));
|
|
2540
|
+
registerAgentInspect(agent);
|
|
2541
|
+
}
|
|
2542
|
+
async function addAgent(options) {
|
|
2543
|
+
const candidate = agentConfigSchema.safeParse({
|
|
2544
|
+
name: options.name,
|
|
2545
|
+
type: "cli",
|
|
2546
|
+
command: options.command,
|
|
2547
|
+
...options.arg !== void 0 && options.arg.length > 0 ? { args: options.arg } : {},
|
|
2548
|
+
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
2549
|
+
input_mode: options.inputMode,
|
|
2550
|
+
...options.timeoutMs !== void 0 ? { timeout_ms: options.timeoutMs } : {},
|
|
2551
|
+
...options.envPolicy !== void 0 ? { env_policy: options.envPolicy } : {},
|
|
2552
|
+
...options.allowEnv !== void 0 && options.allowEnv.length > 0 ? { env_allowlist: options.allowEnv } : {},
|
|
2553
|
+
...options.denyEnv !== void 0 && options.denyEnv.length > 0 ? { env_denylist: options.denyEnv } : {},
|
|
2554
|
+
...options.maxOutputBytes !== void 0 ? { max_output_bytes: options.maxOutputBytes } : {},
|
|
2555
|
+
...options.allowExternalCwd !== void 0 ? { allow_external_cwd: options.allowExternalCwd } : {}
|
|
2556
|
+
});
|
|
2557
|
+
if (!candidate.success) {
|
|
2558
|
+
const issues = candidate.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
2559
|
+
throw new OmniNodeError("CLI_USAGE", `Invalid agent options: ${issues}`);
|
|
2560
|
+
}
|
|
2561
|
+
const newAgent = candidate.data;
|
|
2562
|
+
const existing = loadConfig();
|
|
2563
|
+
if (existing.project.agents.some((a) => a.name === newAgent.name)) {
|
|
2564
|
+
throw new OmniNodeError(
|
|
2565
|
+
"CLI_USAGE",
|
|
2566
|
+
`Agent "${newAgent.name}" already exists in omninode.yaml. Remove it first or pick another name.`
|
|
2567
|
+
);
|
|
2568
|
+
}
|
|
2569
|
+
const configPath = appendToConfigList("agents", newAgent);
|
|
2570
|
+
console.log(`Added agent "${newAgent.name}" (${newAgent.type}, input=${newAgent.input_mode}) to ${configPath}.`);
|
|
2571
|
+
console.log(`Run \`omninode agent test ${newAgent.name}\` to verify the connection.`);
|
|
2572
|
+
}
|
|
2573
|
+
async function testAgent(name) {
|
|
2574
|
+
const config = loadConfig();
|
|
2575
|
+
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
2576
|
+
if (!agentConfig) {
|
|
2577
|
+
const available = config.project.agents.map((a) => a.name).join(", ") || "(none)";
|
|
2578
|
+
throw new AgentError(
|
|
2579
|
+
"AGENT_NOT_FOUND",
|
|
2580
|
+
`Agent "${name}" is not configured. Configured agents: ${available}.`
|
|
2581
|
+
);
|
|
2582
|
+
}
|
|
2583
|
+
const agent = createAgent(agentConfig);
|
|
2584
|
+
console.log(
|
|
2585
|
+
`Testing agent "${name}" (${agent.info.integration}, input=${agentConfig.inputMode ?? "stdin"}) \u2014 sending a trivial task.`
|
|
2586
|
+
);
|
|
2587
|
+
const result = await agent.run({
|
|
2588
|
+
taskId: `test-${Date.now()}`,
|
|
2589
|
+
objective: "Reply with the single word: OK"
|
|
2590
|
+
});
|
|
2591
|
+
console.log(` status: ${result.status}`);
|
|
2592
|
+
if (result.exitCode !== void 0) console.log(` exit: ${result.exitCode}`);
|
|
2593
|
+
if (result.summary) console.log(` output: ${result.summary}`);
|
|
2594
|
+
if (result.rawOutput && result.rawOutput !== result.summary) {
|
|
2595
|
+
console.log(` raw: ${result.rawOutput.slice(0, 200).replace(/\n/g, " ")}`);
|
|
2596
|
+
}
|
|
2597
|
+
if (result.error) console.log(` error: ${result.error}`);
|
|
2598
|
+
if (result.status !== "completed") process.exitCode = 1;
|
|
2599
|
+
}
|
|
2600
|
+
|
|
2601
|
+
// src/cli/commands/roles.ts
|
|
2602
|
+
function registerRoleCommands(program) {
|
|
2603
|
+
const role = program.command("role").description("Inspect roles defined in omninode.yaml.");
|
|
2604
|
+
role.command("list").description("List configured roles.").option("--json", "Emit roles as JSON.").action(async (options) => {
|
|
2605
|
+
const config = loadConfig();
|
|
2606
|
+
const roles = config.project.roles;
|
|
2607
|
+
if (options.json) {
|
|
2608
|
+
console.log(JSON.stringify(config.project.roles, null, 2));
|
|
2609
|
+
return;
|
|
2610
|
+
}
|
|
2611
|
+
if (roles.length === 0) {
|
|
2612
|
+
console.log("No roles configured. Add one under `project.roles` in omninode.yaml.");
|
|
2613
|
+
return;
|
|
2614
|
+
}
|
|
2615
|
+
for (const role2 of roles) {
|
|
2616
|
+
console.log(`${role2.id} ${role2.name}`);
|
|
2617
|
+
for (const responsibility of role2.responsibilities) {
|
|
2618
|
+
console.log(` - ${responsibility}`);
|
|
2619
|
+
}
|
|
2620
|
+
}
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
|
|
2590
2624
|
// src/cli/commands/tasks.ts
|
|
2591
2625
|
var collectFiles = (value, previous) => [...previous, value];
|
|
2592
2626
|
function registerTaskCommands(program) {
|
|
@@ -2614,7 +2648,7 @@ function registerTaskCommands(program) {
|
|
|
2614
2648
|
console.log(`Running task ${taskId}...`);
|
|
2615
2649
|
const finished = await engine.run(taskId);
|
|
2616
2650
|
printOutcome(finished);
|
|
2617
|
-
|
|
2651
|
+
process.exitCode = exitCodeForStatus(finished.status);
|
|
2618
2652
|
});
|
|
2619
2653
|
task.command("status <task-id>").description("Show task details and current status.").action(async (taskId) => {
|
|
2620
2654
|
const config = loadConfig();
|
|
@@ -2628,12 +2662,16 @@ function registerTaskCommands(program) {
|
|
|
2628
2662
|
}
|
|
2629
2663
|
printOutcome(found, { verbose: true });
|
|
2630
2664
|
});
|
|
2631
|
-
task.command("list").description("List tasks in the project's task store.").option("-s, --status <status>", "Filter by status (created, queued, running, completed, failed, cancelled).").action(async (options) => {
|
|
2665
|
+
task.command("list").description("List tasks in the project's task store.").option("-s, --status <status>", "Filter by status (created, queued, running, completed, failed, cancelled).").option("--json", "Emit tasks as JSON.").action(async (options) => {
|
|
2632
2666
|
const config = loadConfig();
|
|
2633
2667
|
const engine = createTaskEngine(config);
|
|
2634
2668
|
const tasks = await engine.list(
|
|
2635
2669
|
options.status !== void 0 ? { status: options.status } : {}
|
|
2636
2670
|
);
|
|
2671
|
+
if (options.json) {
|
|
2672
|
+
console.log(JSON.stringify(tasks, null, 2));
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2637
2675
|
if (tasks.length === 0) {
|
|
2638
2676
|
console.log("No tasks found. Create one with `omninode task create`.");
|
|
2639
2677
|
return;
|
|
@@ -2657,7 +2695,7 @@ function registerTaskCommands(program) {
|
|
|
2657
2695
|
console.log(`Running task ${reset.id}...`);
|
|
2658
2696
|
const finished = await engine.run(reset.id);
|
|
2659
2697
|
printOutcome(finished);
|
|
2660
|
-
|
|
2698
|
+
process.exitCode = exitCodeForStatus(finished.status);
|
|
2661
2699
|
});
|
|
2662
2700
|
registerTaskInspect(task);
|
|
2663
2701
|
}
|
|
@@ -3128,12 +3166,16 @@ function service() {
|
|
|
3128
3166
|
}
|
|
3129
3167
|
function registerReportCommands(program) {
|
|
3130
3168
|
const report = program.command("report").description("Inspect AI reports collected from tasks and pipelines (\xA716\u2013\xA717).");
|
|
3131
|
-
report.command("list").description("List stored reports.").option("--task <taskId>", "Only reports for this task.").option("--agent <name>", "Only reports from this agent.").action(async (options) => {
|
|
3169
|
+
report.command("list").description("List stored reports.").option("--task <taskId>", "Only reports for this task.").option("--agent <name>", "Only reports from this agent.").option("--json", "Emit reports as JSON.").action(async (options) => {
|
|
3132
3170
|
const svc = service();
|
|
3133
3171
|
const reports = await svc.storeRef.listReports({
|
|
3134
3172
|
...options.task !== void 0 ? { taskId: options.task } : {},
|
|
3135
3173
|
...options.agent !== void 0 ? { agent: options.agent } : {}
|
|
3136
3174
|
});
|
|
3175
|
+
if (options.json) {
|
|
3176
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
3177
|
+
return;
|
|
3178
|
+
}
|
|
3137
3179
|
if (reports.length === 0) {
|
|
3138
3180
|
console.log("No reports stored yet. Run a pipeline to collect some.");
|
|
3139
3181
|
return;
|
|
@@ -3142,11 +3184,15 @@ function registerReportCommands(program) {
|
|
|
3142
3184
|
console.log(`${r.id} ${r.agent.padEnd(10)} ${truncate(r.summary, 60)}`);
|
|
3143
3185
|
}
|
|
3144
3186
|
});
|
|
3145
|
-
report.command("combined").description("List combined reports (aggregated intelligence, \xA717).").option("--run <pipelineRunId>", "Only combined reports for this pipeline run.").action(async (options) => {
|
|
3187
|
+
report.command("combined").description("List combined reports (aggregated intelligence, \xA717).").option("--run <pipelineRunId>", "Only combined reports for this pipeline run.").option("--json", "Emit combined reports as JSON.").action(async (options) => {
|
|
3146
3188
|
const svc = service();
|
|
3147
3189
|
const combined = await svc.storeRef.listCombined({
|
|
3148
3190
|
...options.run !== void 0 ? { pipelineRunId: options.run } : {}
|
|
3149
3191
|
});
|
|
3192
|
+
if (options.json) {
|
|
3193
|
+
console.log(JSON.stringify(combined, null, 2));
|
|
3194
|
+
return;
|
|
3195
|
+
}
|
|
3150
3196
|
if (combined.length === 0) {
|
|
3151
3197
|
console.log("No combined reports stored yet. Run a pipeline to generate one.");
|
|
3152
3198
|
return;
|
|
@@ -3226,6 +3272,10 @@ function registerReportCommands(program) {
|
|
|
3226
3272
|
});
|
|
3227
3273
|
}
|
|
3228
3274
|
|
|
3275
|
+
// src/cli/commands/pipelines.ts
|
|
3276
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
3277
|
+
import { parseDocument as parseDocument3 } from "yaml";
|
|
3278
|
+
|
|
3229
3279
|
// src/pipelines/engine.ts
|
|
3230
3280
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
3231
3281
|
|
|
@@ -4177,9 +4227,13 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
4177
4227
|
// src/cli/commands/pipelines.ts
|
|
4178
4228
|
function registerPipelineCommands(program) {
|
|
4179
4229
|
const pipeline = program.command("pipeline").description("Run multi-step AI pipelines defined in omninode.yaml (\xA714).");
|
|
4180
|
-
pipeline.command("list").description("List configured pipelines.").action(async () => {
|
|
4230
|
+
pipeline.command("list").description("List configured pipelines.").option("--json", "Emit pipelines as JSON.").action(async (options) => {
|
|
4181
4231
|
const config = loadConfig();
|
|
4182
4232
|
const pipelines = config.project.pipelines;
|
|
4233
|
+
if (options.json) {
|
|
4234
|
+
console.log(JSON.stringify(config.project.pipelines, null, 2));
|
|
4235
|
+
return;
|
|
4236
|
+
}
|
|
4183
4237
|
if (pipelines.length === 0) {
|
|
4184
4238
|
console.log("No pipelines configured. Add one under `project.pipelines` in omninode.yaml.");
|
|
4185
4239
|
return;
|
|
@@ -4220,6 +4274,9 @@ function registerPipelineCommands(program) {
|
|
|
4220
4274
|
console.log(`Cancellation requested for run ${runId}.`);
|
|
4221
4275
|
console.log("A run executing in another process stops at its next check point.");
|
|
4222
4276
|
});
|
|
4277
|
+
pipeline.command("create <id>").description("Create a pipeline from a YAML/JSON file and append it to omninode.yaml.").requiredOption("--from <file>", "File containing { objective?, steps: [...] } (YAML or JSON).").action(async (id, options) => {
|
|
4278
|
+
await createPipelineFromFile(id, options.from);
|
|
4279
|
+
});
|
|
4223
4280
|
pipeline.command("validate <id>").description("Validate a pipeline definition before running it (\xA79.5).").action(async (id) => {
|
|
4224
4281
|
const config = loadConfig();
|
|
4225
4282
|
const def = findPipeline(config, id);
|
|
@@ -4303,11 +4360,45 @@ async function runPipelineAction(id, objective) {
|
|
|
4303
4360
|
console.log(` result: ${finished.resultSummary}`);
|
|
4304
4361
|
}
|
|
4305
4362
|
console.log(`Pipeline ${finished.status}. Run id: ${finished.id}`);
|
|
4306
|
-
|
|
4363
|
+
process.exitCode = exitCodeForStatus(finished.status);
|
|
4307
4364
|
}
|
|
4308
4365
|
function registerRunAlias(program) {
|
|
4309
4366
|
program.command("run <id> [objective]").description("Alias of `omninode pipeline run` \u2014 execute a configured pipeline end to end.").action(async (id, objective) => runPipelineAction(id, objective));
|
|
4310
4367
|
}
|
|
4368
|
+
async function createPipelineFromFile(id, file) {
|
|
4369
|
+
const configPath = findConfigFile();
|
|
4370
|
+
if (!configPath) {
|
|
4371
|
+
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found. Run `omninode init` first.");
|
|
4372
|
+
}
|
|
4373
|
+
let definition;
|
|
4374
|
+
try {
|
|
4375
|
+
definition = parseDocument3(readFileSync4(file, "utf8")).toJS();
|
|
4376
|
+
} catch (error) {
|
|
4377
|
+
throw new PipelineError("PIPELINE_INVALID", `Could not read pipeline definition from ${file}.`, {
|
|
4378
|
+
cause: error
|
|
4379
|
+
});
|
|
4380
|
+
}
|
|
4381
|
+
const candidate = pipelineConfigSchema.safeParse({ id, ...definition });
|
|
4382
|
+
if (!candidate.success) {
|
|
4383
|
+
const issues = candidate.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
4384
|
+
throw new PipelineError("PIPELINE_INVALID", `Invalid pipeline definition: ${issues}`);
|
|
4385
|
+
}
|
|
4386
|
+
const engine = buildPipelineEngine(loadConfig());
|
|
4387
|
+
engine.validate({ id, ...candidate.data });
|
|
4388
|
+
const doc = parseDocument3(readFileSync4(configPath, "utf8"));
|
|
4389
|
+
if (doc.getIn(["project", "pipelines"]) === void 0) {
|
|
4390
|
+
doc.setIn(["project", "pipelines"], [candidate.data]);
|
|
4391
|
+
} else {
|
|
4392
|
+
doc.addIn(["project", "pipelines"], candidate.data);
|
|
4393
|
+
}
|
|
4394
|
+
const validated = appConfigSchema.safeParse(doc.toJS());
|
|
4395
|
+
if (!validated.success) {
|
|
4396
|
+
throw new PipelineError("PIPELINE_INVALID", "Adding this pipeline would produce an invalid configuration.");
|
|
4397
|
+
}
|
|
4398
|
+
writeFileSync4(configPath, doc.toString(), "utf8");
|
|
4399
|
+
console.log(`Created pipeline "${id}" (${candidate.data.steps.length} step(s)) in ${configPath}.`);
|
|
4400
|
+
console.log(`Run it with: omninode pipeline run ${id} "<objective>"`);
|
|
4401
|
+
}
|
|
4311
4402
|
function isInterrupted(run2) {
|
|
4312
4403
|
return run2.status === "running" && run2.finishedAt === void 0;
|
|
4313
4404
|
}
|
|
@@ -4341,7 +4432,7 @@ function registerMemoryCommands(program) {
|
|
|
4341
4432
|
console.log(` ${scope}: ${count}`);
|
|
4342
4433
|
}
|
|
4343
4434
|
});
|
|
4344
|
-
memory.command("query <text>").description("Search memory for entries relevant to the text.").option("-s, --scope <scope>", "Restrict to a scope: project, task, role or knowledge.").option("-n, --limit <count>", "Maximum entries to return.", (value) => Number(value), 10).action(async (text, options) => {
|
|
4435
|
+
memory.command("query <text>").description("Search memory for entries relevant to the text.").option("-s, --scope <scope>", "Restrict to a scope: project, task, role or knowledge.").option("-n, --limit <count>", "Maximum entries to return.", (value) => Number(value), 10).option("--json", "Emit entries as JSON.").action(async (text, options) => {
|
|
4345
4436
|
const config = loadConfig();
|
|
4346
4437
|
const svc = new MemoryService(createMemoryProvider(config.project.memory));
|
|
4347
4438
|
const entries = await svc.providerRef.query({
|
|
@@ -4349,6 +4440,10 @@ function registerMemoryCommands(program) {
|
|
|
4349
4440
|
...options.scope !== void 0 ? { scope: options.scope } : {},
|
|
4350
4441
|
limit: options.limit
|
|
4351
4442
|
});
|
|
4443
|
+
if (options.json) {
|
|
4444
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
4445
|
+
return;
|
|
4446
|
+
}
|
|
4352
4447
|
if (entries.length === 0) {
|
|
4353
4448
|
console.log("No relevant memory found.");
|
|
4354
4449
|
return;
|
|
@@ -4379,11 +4474,15 @@ function registerMemoryCommands(program) {
|
|
|
4379
4474
|
// src/cli/commands/plan.ts
|
|
4380
4475
|
function registerPlanCommands(program) {
|
|
4381
4476
|
const plan = program.command("plan").description("Inspect implementation plans produced by the planner layer (\xA718).");
|
|
4382
|
-
plan.command("list").description("List stored plans.").option("--run <pipelineRunId>", "Only plans for this pipeline run.").action(async (options) => {
|
|
4477
|
+
plan.command("list").description("List stored plans.").option("--run <pipelineRunId>", "Only plans for this pipeline run.").option("--json", "Emit plans as JSON.").action(async (options) => {
|
|
4383
4478
|
const store = new FilePlanStore();
|
|
4384
4479
|
const plans = await store.list({
|
|
4385
4480
|
...options.run !== void 0 ? { pipelineRunId: options.run } : {}
|
|
4386
4481
|
});
|
|
4482
|
+
if (options.json) {
|
|
4483
|
+
console.log(JSON.stringify(plans, null, 2));
|
|
4484
|
+
return;
|
|
4485
|
+
}
|
|
4387
4486
|
if (plans.length === 0) {
|
|
4388
4487
|
console.log("No plans stored yet. Run a pipeline with a plan step to generate one.");
|
|
4389
4488
|
return;
|
|
@@ -4650,6 +4749,122 @@ Trust model: agents execute with the permissions of the OS user running OmniNode
|
|
|
4650
4749
|
});
|
|
4651
4750
|
}
|
|
4652
4751
|
|
|
4752
|
+
// src/cli/commands/config.ts
|
|
4753
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4754
|
+
function registerConfigCommands(program) {
|
|
4755
|
+
const config = program.command("config").description("Inspect the project configuration (omninode.yaml).");
|
|
4756
|
+
config.command("path").description("Print the path of the configuration file in use.").action(() => {
|
|
4757
|
+
const path = findConfigFile();
|
|
4758
|
+
console.log(path ?? "no omninode.yaml found");
|
|
4759
|
+
});
|
|
4760
|
+
config.command("show").description("Print the effective configuration (credential references only, never values).").option("--json", "Emit the configuration as JSON.").action((options) => {
|
|
4761
|
+
const resolved = loadConfig();
|
|
4762
|
+
const payload = {
|
|
4763
|
+
project: resolved.project.name,
|
|
4764
|
+
providers: resolved.project.providers.map((p) => ({
|
|
4765
|
+
name: p.name,
|
|
4766
|
+
type: p.type,
|
|
4767
|
+
baseUrl: p.baseUrl,
|
|
4768
|
+
apiKeyEnvVar: p.apiKeyEnvVar ?? null
|
|
4769
|
+
})),
|
|
4770
|
+
agents: resolved.project.agents.map((a) => ({ name: a.name, command: a.command ?? null, inputMode: a.inputMode })),
|
|
4771
|
+
roles: resolved.project.roles.map((r) => r.id),
|
|
4772
|
+
pipelines: resolved.project.pipelines.map((p) => p.id),
|
|
4773
|
+
memory: resolved.project.memory ?? null,
|
|
4774
|
+
logging: resolved.logging ?? null
|
|
4775
|
+
};
|
|
4776
|
+
if (options.json) {
|
|
4777
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4778
|
+
return;
|
|
4779
|
+
}
|
|
4780
|
+
console.log(`project: ${payload.project}`);
|
|
4781
|
+
console.log(`providers: ${payload.providers.map((p) => `${p.name} (${p.type}, key=${p.apiKeyEnvVar ? `$${p.apiKeyEnvVar}` : "none"})`).join(", ") || "none"}`);
|
|
4782
|
+
console.log(`agents: ${payload.agents.map((a) => a.name).join(", ") || "none"}`);
|
|
4783
|
+
console.log(`roles: ${payload.roles.join(", ") || "none"}`);
|
|
4784
|
+
console.log(`pipelines: ${payload.pipelines.join(", ") || "none"}`);
|
|
4785
|
+
console.log(`memory: ${payload.memory ? `${payload.memory.provider}${payload.memory.required ? " (required)" : ""}` : "not configured"}`);
|
|
4786
|
+
});
|
|
4787
|
+
config.command("validate").description("Validate the configuration file, including the inline-secret scan.").action(() => {
|
|
4788
|
+
const path = findConfigFile();
|
|
4789
|
+
if (!path) throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found.");
|
|
4790
|
+
const findings = scanForInlineSecrets(readFileSync5(path, "utf8"));
|
|
4791
|
+
if (findings.length > 0) {
|
|
4792
|
+
throw new OmniNodeError(
|
|
4793
|
+
"CONFIG_INVALID",
|
|
4794
|
+
`${path} contains ${findings.length} inline secret(s) (line ${findings.map((f) => f.line).join(", ")}). Use environment-variable references.`
|
|
4795
|
+
);
|
|
4796
|
+
}
|
|
4797
|
+
const resolved = loadConfig();
|
|
4798
|
+
console.log(`${path} is valid \u2014 project "${resolved.project.name}" with ${resolved.project.providers.length} provider(s), ${resolved.project.agents.length} agent(s), ${resolved.project.pipelines.length} pipeline(s).`);
|
|
4799
|
+
});
|
|
4800
|
+
}
|
|
4801
|
+
|
|
4802
|
+
// src/cli/commands/models.ts
|
|
4803
|
+
var CAPABILITIES = [
|
|
4804
|
+
"chat",
|
|
4805
|
+
"coding",
|
|
4806
|
+
"tools",
|
|
4807
|
+
"vision",
|
|
4808
|
+
"embeddings"
|
|
4809
|
+
];
|
|
4810
|
+
function registerModelsCommand(program) {
|
|
4811
|
+
for (const name of ["models", "model"]) {
|
|
4812
|
+
registerModelGroup(program, name);
|
|
4813
|
+
}
|
|
4814
|
+
}
|
|
4815
|
+
function registerModelGroup(program, name) {
|
|
4816
|
+
program.command(name).description("Discover and list models from configured providers (model registry, \xA78).").argument("[provider]", "Only discover from this provider.").option("-c, --capability <capability>", `Filter by capability: ${CAPABILITIES.join(", ")}.`).option("--json", "Emit the discovered models as JSON.").action(async (providerName, options) => {
|
|
4817
|
+
const capability = options.capability;
|
|
4818
|
+
if (capability !== void 0 && !CAPABILITIES.includes(capability)) {
|
|
4819
|
+
throw new OmniNodeError(
|
|
4820
|
+
"CLI_USAGE",
|
|
4821
|
+
`Unknown capability "${options.capability}". Valid capabilities: ${CAPABILITIES.join(", ")}.`
|
|
4822
|
+
);
|
|
4823
|
+
}
|
|
4824
|
+
const config = loadConfig();
|
|
4825
|
+
const selected = config.project.providers.filter(
|
|
4826
|
+
(p) => providerName ? p.name === providerName : true
|
|
4827
|
+
);
|
|
4828
|
+
if (selected.length === 0) {
|
|
4829
|
+
if (options.json) {
|
|
4830
|
+
console.log("[]");
|
|
4831
|
+
return;
|
|
4832
|
+
}
|
|
4833
|
+
throw new OmniNodeError(
|
|
4834
|
+
"PROVIDER_NOT_FOUND",
|
|
4835
|
+
providerName ? `Provider "${providerName}" is not configured.` : "No providers configured. Add one with `omninode provider add`."
|
|
4836
|
+
);
|
|
4837
|
+
}
|
|
4838
|
+
const registry = new ModelRegistry();
|
|
4839
|
+
for (const providerConfig of selected) {
|
|
4840
|
+
try {
|
|
4841
|
+
const provider = createProvider(providerConfig);
|
|
4842
|
+
const discovered = await registry.discoverFrom(provider);
|
|
4843
|
+
console.log(`${providerConfig.name}: ${discovered.length} model(s)`);
|
|
4844
|
+
} catch (error) {
|
|
4845
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
4846
|
+
console.log(`${providerConfig.name}: discovery failed \u2014 ${message}`);
|
|
4847
|
+
}
|
|
4848
|
+
}
|
|
4849
|
+
const models = registry.list({
|
|
4850
|
+
...providerName !== void 0 ? { provider: providerName } : {},
|
|
4851
|
+
...capability !== void 0 ? { capability } : {}
|
|
4852
|
+
});
|
|
4853
|
+
if (options.json) {
|
|
4854
|
+
console.log(JSON.stringify(models, null, 2));
|
|
4855
|
+
return;
|
|
4856
|
+
}
|
|
4857
|
+
if (models.length === 0) {
|
|
4858
|
+
console.log("No models found.");
|
|
4859
|
+
return;
|
|
4860
|
+
}
|
|
4861
|
+
console.log("");
|
|
4862
|
+
for (const model of models) {
|
|
4863
|
+
console.log(`${model.provider}:${model.id}`);
|
|
4864
|
+
}
|
|
4865
|
+
});
|
|
4866
|
+
}
|
|
4867
|
+
|
|
4653
4868
|
// src/cli/index.ts
|
|
4654
4869
|
function createProgram() {
|
|
4655
4870
|
const program = new Command();
|
|
@@ -4671,6 +4886,7 @@ function createProgram() {
|
|
|
4671
4886
|
registerAuditCommand(program);
|
|
4672
4887
|
registerMigrateCommand(program);
|
|
4673
4888
|
registerSecurityCommand(program);
|
|
4889
|
+
registerConfigCommands(program);
|
|
4674
4890
|
registerRunAlias(program);
|
|
4675
4891
|
return program;
|
|
4676
4892
|
}
|
|
@@ -4693,15 +4909,16 @@ async function run(argv) {
|
|
|
4693
4909
|
}
|
|
4694
4910
|
}
|
|
4695
4911
|
function handleError(error) {
|
|
4912
|
+
const code = exitCodeFor(error);
|
|
4696
4913
|
if (isOmniNodeError(error)) {
|
|
4697
4914
|
console.error(`error: [${error.code}] ${error.message}`);
|
|
4698
4915
|
if (error.details !== void 0) {
|
|
4699
4916
|
console.error(JSON.stringify(error.details, null, 2));
|
|
4700
4917
|
}
|
|
4701
|
-
process.exit(
|
|
4918
|
+
process.exit(code);
|
|
4702
4919
|
}
|
|
4703
4920
|
console.error(error instanceof Error ? error.message : String(error));
|
|
4704
|
-
process.exit(
|
|
4921
|
+
process.exit(code);
|
|
4705
4922
|
}
|
|
4706
4923
|
var entry = process.argv[1] ? pathToFileURL(realpathSync(process.argv[1])).href : "";
|
|
4707
4924
|
if (import.meta.url === entry) {
|