@hilbras/omninode 2.0.0-alpha.14 → 2.0.0-alpha.16
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 +16 -2
- package/dist/cli/index.js +658 -251
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +65 -4
- package/dist/index.js +202 -37
- 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.16";
|
|
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";
|
|
@@ -105,6 +147,10 @@ var projectConfigSchema = z.object({
|
|
|
105
147
|
}).strict().optional()
|
|
106
148
|
}).strict();
|
|
107
149
|
var appConfigSchema = z.object({
|
|
150
|
+
/** Active profile; overlays live under `profiles` (§16). */
|
|
151
|
+
profile: z.string().min(1).optional(),
|
|
152
|
+
/** Named partial overlays, e.g. development/production/testing. */
|
|
153
|
+
profiles: z.record(z.unknown()).optional(),
|
|
108
154
|
project: projectConfigSchema,
|
|
109
155
|
logging: z.object({
|
|
110
156
|
level: z.enum(["debug", "info", "warn", "error"]),
|
|
@@ -145,6 +191,85 @@ ${lines}
|
|
|
145
191
|
OmniNode never stores credentials in configuration. Replace each value with an environment-variable reference, e.g. api_key_env_var: MY_API_KEY.`;
|
|
146
192
|
}
|
|
147
193
|
|
|
194
|
+
// src/config/profiles.ts
|
|
195
|
+
import { homedir } from "os";
|
|
196
|
+
import path from "path";
|
|
197
|
+
function userConfigPath(env = process.env) {
|
|
198
|
+
if (env.OMNINODE_USER_CONFIG) return env.OMNINODE_USER_CONFIG;
|
|
199
|
+
const home = env.HOME ?? homedir();
|
|
200
|
+
return path.join(home, ".omninode", "config.yaml");
|
|
201
|
+
}
|
|
202
|
+
function resolveProfile(input, env) {
|
|
203
|
+
if (env.OMNINODE_PROFILE) return env.OMNINODE_PROFILE;
|
|
204
|
+
if (typeof input.profile === "string" && input.profile.length > 0) return input.profile;
|
|
205
|
+
return "default";
|
|
206
|
+
}
|
|
207
|
+
function mergeConfig(base, overlay) {
|
|
208
|
+
if (!isPlainObject(base) || !isPlainObject(overlay)) {
|
|
209
|
+
return overlay === void 0 ? base : overlay;
|
|
210
|
+
}
|
|
211
|
+
const result = { ...base };
|
|
212
|
+
for (const [key, value] of Object.entries(overlay)) {
|
|
213
|
+
const existing = result[key];
|
|
214
|
+
if (Array.isArray(value)) {
|
|
215
|
+
result[key] = value;
|
|
216
|
+
} else if (isPlainObject(value) && isPlainObject(existing)) {
|
|
217
|
+
result[key] = mergeConfig(existing, value);
|
|
218
|
+
} else {
|
|
219
|
+
result[key] = value;
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
return result;
|
|
223
|
+
}
|
|
224
|
+
function isPlainObject(value) {
|
|
225
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
226
|
+
}
|
|
227
|
+
function asObject(value) {
|
|
228
|
+
return isPlainObject(value) ? value : {};
|
|
229
|
+
}
|
|
230
|
+
function applyEnvOverrides(input, env) {
|
|
231
|
+
const applied = [];
|
|
232
|
+
const config = mergeConfig(input, {});
|
|
233
|
+
if (env.OMNINODE_LOG_LEVEL) {
|
|
234
|
+
config.logging = { ...asObject(config.logging), level: env.OMNINODE_LOG_LEVEL };
|
|
235
|
+
applied.push("OMNINODE_LOG_LEVEL");
|
|
236
|
+
}
|
|
237
|
+
if (env.OMNINODE_LOG_FORMAT) {
|
|
238
|
+
config.logging = { ...asObject(config.logging), format: env.OMNINODE_LOG_FORMAT };
|
|
239
|
+
applied.push("OMNINODE_LOG_FORMAT");
|
|
240
|
+
}
|
|
241
|
+
const memoryProvider = env.OMNINODE_MEMORY_PROVIDER;
|
|
242
|
+
const memoryRequired = env.OMNINODE_MEMORY_REQUIRED;
|
|
243
|
+
if (memoryProvider || memoryRequired) {
|
|
244
|
+
const project = asObject(config.project);
|
|
245
|
+
config.project = {
|
|
246
|
+
...project,
|
|
247
|
+
memory: {
|
|
248
|
+
...asObject(project.memory ?? { provider: "local" }),
|
|
249
|
+
...memoryProvider ? { provider: memoryProvider } : {},
|
|
250
|
+
...memoryRequired ? { required: memoryRequired !== "false" } : {}
|
|
251
|
+
}
|
|
252
|
+
};
|
|
253
|
+
if (memoryProvider) applied.push("OMNINODE_MEMORY_PROVIDER");
|
|
254
|
+
if (memoryRequired) applied.push("OMNINODE_MEMORY_REQUIRED");
|
|
255
|
+
}
|
|
256
|
+
return { config, applied };
|
|
257
|
+
}
|
|
258
|
+
function applyDefaults(input) {
|
|
259
|
+
const applied = [];
|
|
260
|
+
const config = mergeConfig(input, {});
|
|
261
|
+
const logging = asObject(config.logging);
|
|
262
|
+
if (logging.level === void 0) {
|
|
263
|
+
config.logging = { ...logging, level: "info" };
|
|
264
|
+
applied.push("logging.level");
|
|
265
|
+
}
|
|
266
|
+
if (logging.format === void 0) {
|
|
267
|
+
config.logging = { ...asObject(config.logging), format: "text" };
|
|
268
|
+
applied.push("logging.format");
|
|
269
|
+
}
|
|
270
|
+
return { config, applied };
|
|
271
|
+
}
|
|
272
|
+
|
|
148
273
|
// src/config/loader.ts
|
|
149
274
|
import { existsSync, readFileSync } from "fs";
|
|
150
275
|
import { parse as parseYaml } from "yaml";
|
|
@@ -171,38 +296,108 @@ function expandEnvRefs(text, env, source = "config") {
|
|
|
171
296
|
}
|
|
172
297
|
);
|
|
173
298
|
}
|
|
174
|
-
function
|
|
299
|
+
function loadConfigDetailed(options = {}) {
|
|
175
300
|
const env = options.env ?? process.env;
|
|
176
|
-
const
|
|
177
|
-
|
|
301
|
+
const explicitPath = options.path;
|
|
302
|
+
let input = {};
|
|
303
|
+
let projectFile;
|
|
304
|
+
let userFile;
|
|
305
|
+
if (!explicitPath) {
|
|
306
|
+
const candidate = userConfigPath(env);
|
|
307
|
+
if (existsSync(candidate)) {
|
|
308
|
+
input = mergeConfig(input, readConfigDocument(candidate, env));
|
|
309
|
+
userFile = candidate;
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
const resolvedProject = explicitPath ?? (options.directory ? findConfigFile(options.directory) : findConfigFile());
|
|
313
|
+
if (resolvedProject) {
|
|
314
|
+
input = mergeConfig(input, readConfigDocument(resolvedProject, env));
|
|
315
|
+
projectFile = resolvedProject;
|
|
316
|
+
}
|
|
317
|
+
if (!projectFile && !userFile) {
|
|
178
318
|
throw new ConfigError(
|
|
179
319
|
"CONFIG_NOT_FOUND",
|
|
180
320
|
"No omninode.yaml found. Run `omninode init` to create one, or pass an explicit path."
|
|
181
321
|
);
|
|
182
322
|
}
|
|
183
|
-
const
|
|
323
|
+
const profile = options.profile ?? resolveProfile(input, env);
|
|
324
|
+
const overlay = input.profiles?.[profile];
|
|
325
|
+
if (overlay !== void 0) {
|
|
326
|
+
const { profiles: _drop, ...base } = input;
|
|
327
|
+
input = mergeConfig(base, overlay);
|
|
328
|
+
} else if (profile !== "default" && profile !== "") {
|
|
329
|
+
throw new ConfigError(
|
|
330
|
+
"CONFIG_INVALID",
|
|
331
|
+
`Profile "${profile}" is not defined in this configuration (known: ${Object.keys(input.profiles ?? {}).join(", ") || "none"}).`
|
|
332
|
+
);
|
|
333
|
+
}
|
|
334
|
+
const withEnv = applyEnvOverrides(input, env);
|
|
335
|
+
const withDefaults = applyDefaults(withEnv.config);
|
|
336
|
+
const parsed = appConfigSchema.safeParse(withDefaults.config);
|
|
337
|
+
if (!parsed.success) {
|
|
338
|
+
const issues = parsed.error.issues.map((issue) => {
|
|
339
|
+
const location = issue.path.join(".") || "(root)";
|
|
340
|
+
return `${location}: ${issue.message}`;
|
|
341
|
+
});
|
|
342
|
+
throw new ConfigError(
|
|
343
|
+
"CONFIG_INVALID",
|
|
344
|
+
`Invalid configuration:
|
|
345
|
+
${issues.join("\n ")}`,
|
|
346
|
+
{ details: { issues } }
|
|
347
|
+
);
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
config: toAppConfig(parsed.data),
|
|
351
|
+
sources: {
|
|
352
|
+
...projectFile !== void 0 ? { projectFile } : {},
|
|
353
|
+
...userFile !== void 0 ? { userFile } : {},
|
|
354
|
+
profile,
|
|
355
|
+
envOverrides: withEnv.applied,
|
|
356
|
+
defaults: withDefaults.applied
|
|
357
|
+
}
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
function readConfigDocument(path2, optionsEnv) {
|
|
361
|
+
const raw = readFileSync(path2, "utf8");
|
|
184
362
|
const secretFindings = scanForInlineSecrets(raw);
|
|
185
363
|
if (secretFindings.length > 0) {
|
|
186
|
-
throw new ConfigError("CONFIG_INVALID", describeSecretFindings(
|
|
364
|
+
throw new ConfigError("CONFIG_INVALID", describeSecretFindings(path2, secretFindings));
|
|
187
365
|
}
|
|
188
|
-
const expanded = expandEnvRefs(raw,
|
|
189
|
-
let data;
|
|
366
|
+
const expanded = expandEnvRefs(raw, optionsEnv, path2);
|
|
190
367
|
try {
|
|
191
|
-
|
|
368
|
+
return parseYaml(expanded);
|
|
192
369
|
} catch (error) {
|
|
193
|
-
throw new ConfigError("CONFIG_INVALID", `Failed to parse ${
|
|
370
|
+
throw new ConfigError("CONFIG_INVALID", `Failed to parse ${path2}.`, { cause: error });
|
|
194
371
|
}
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
}
|
|
372
|
+
}
|
|
373
|
+
function configDiagnostics(config) {
|
|
374
|
+
const notes = [];
|
|
375
|
+
for (const provider of config.project.providers) {
|
|
376
|
+
if (!provider.apiKeyEnvVar && provider.baseUrl.startsWith("https://")) {
|
|
377
|
+
notes.push(
|
|
378
|
+
`Provider "${provider.name}": API key reference is missing \u2014 add api_key_env_var: <ENV_VAR> unless this endpoint is intentionally keyless.`
|
|
379
|
+
);
|
|
380
|
+
}
|
|
204
381
|
}
|
|
205
|
-
|
|
382
|
+
for (const agent of config.project.agents) {
|
|
383
|
+
if (!agent.command && agent.integration !== "custom") {
|
|
384
|
+
notes.push(`Agent "${agent.name}": no command configured \u2014 the process adapter needs one.`);
|
|
385
|
+
}
|
|
386
|
+
if (agent.allowExternalCwd) {
|
|
387
|
+
notes.push(`Agent "${agent.name}": may run outside the project root (allow_external_cwd).`);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
for (const pipeline of config.project.pipelines) {
|
|
391
|
+
const ids = new Set(pipeline.steps.map((s) => s.id));
|
|
392
|
+
for (const step of pipeline.steps) {
|
|
393
|
+
for (const dep of step.dependsOn ?? []) {
|
|
394
|
+
if (!ids.has(dep)) {
|
|
395
|
+
notes.push(`Pipeline "${pipeline.id}": step "${step.id}" depends on unknown step "${dep}".`);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
return notes;
|
|
206
401
|
}
|
|
207
402
|
function toAppConfig(yaml) {
|
|
208
403
|
return {
|
|
@@ -330,6 +525,18 @@ function defaultProjectConfigYaml(projectName = "My Project") {
|
|
|
330
525
|
`;
|
|
331
526
|
}
|
|
332
527
|
|
|
528
|
+
// src/cli/options.ts
|
|
529
|
+
var cliConfigOptions = {};
|
|
530
|
+
function loadProjectConfig() {
|
|
531
|
+
return loadConfigDetailed({
|
|
532
|
+
...cliConfigOptions.path !== void 0 ? { path: cliConfigOptions.path } : {},
|
|
533
|
+
...cliConfigOptions.profile !== void 0 ? { profile: cliConfigOptions.profile } : {}
|
|
534
|
+
}).config;
|
|
535
|
+
}
|
|
536
|
+
function activeConfigPath() {
|
|
537
|
+
return cliConfigOptions.path ?? findConfigFile();
|
|
538
|
+
}
|
|
539
|
+
|
|
333
540
|
// src/cli/commands/init.ts
|
|
334
541
|
import { existsSync as existsSync2, writeFileSync } from "fs";
|
|
335
542
|
async function runInit(options) {
|
|
@@ -357,6 +564,10 @@ function registerProjectCommands(program) {
|
|
|
357
564
|
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
565
|
}
|
|
359
566
|
|
|
567
|
+
// src/cli/commands/providers.ts
|
|
568
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
|
|
569
|
+
import { parseDocument as parseDocument2 } from "yaml";
|
|
570
|
+
|
|
360
571
|
// src/registry/index.ts
|
|
361
572
|
function registryKey(provider, id) {
|
|
362
573
|
return `${provider}:${id}`;
|
|
@@ -409,17 +620,11 @@ var ModelRegistry = class {
|
|
|
409
620
|
// src/cli/commands/shared.ts
|
|
410
621
|
import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
411
622
|
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
623
|
function truncate(text, max) {
|
|
419
624
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
420
625
|
}
|
|
421
626
|
function appendToConfigList(listKey, item) {
|
|
422
|
-
const configPath =
|
|
627
|
+
const configPath = activeConfigPath();
|
|
423
628
|
if (!configPath) {
|
|
424
629
|
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found. Run `omninode init` first.");
|
|
425
630
|
}
|
|
@@ -1325,15 +1530,15 @@ async function writeFileAtomic(target, contents, durability = "rename") {
|
|
|
1325
1530
|
}
|
|
1326
1531
|
}
|
|
1327
1532
|
}
|
|
1328
|
-
async function inspectDocument(
|
|
1533
|
+
async function inspectDocument(path2) {
|
|
1329
1534
|
let raw;
|
|
1330
1535
|
try {
|
|
1331
|
-
await stat(
|
|
1536
|
+
await stat(path2);
|
|
1332
1537
|
} catch {
|
|
1333
1538
|
return { status: "missing" };
|
|
1334
1539
|
}
|
|
1335
1540
|
try {
|
|
1336
|
-
raw = await readFile(
|
|
1541
|
+
raw = await readFile(path2, "utf8");
|
|
1337
1542
|
} catch (error) {
|
|
1338
1543
|
return { status: "corrupt", detail: `unreadable: ${error instanceof Error ? error.message : String(error)}` };
|
|
1339
1544
|
}
|
|
@@ -1353,21 +1558,21 @@ async function inspectDocument(path) {
|
|
|
1353
1558
|
if (version < SCHEMA_VERSION) return { status: "v1-legacy" };
|
|
1354
1559
|
return { status: "current", schemaVersion: version, count: doc.items.length };
|
|
1355
1560
|
}
|
|
1356
|
-
async function readDocument(
|
|
1357
|
-
const state = await inspectDocument(
|
|
1561
|
+
async function readDocument(path2, onCorrupt, quarantine = true) {
|
|
1562
|
+
const state = await inspectDocument(path2);
|
|
1358
1563
|
if (state.status === "missing" || state.status === "empty") return [];
|
|
1359
1564
|
if (state.status === "future") {
|
|
1360
1565
|
const error = new Error(
|
|
1361
|
-
`${
|
|
1566
|
+
`${path2} was written by a newer OmniNode (schema v${state.schemaVersion} > v${SCHEMA_VERSION})`
|
|
1362
1567
|
);
|
|
1363
|
-
onCorrupt?.(
|
|
1568
|
+
onCorrupt?.(path2, error);
|
|
1364
1569
|
return [];
|
|
1365
1570
|
}
|
|
1366
1571
|
let raw;
|
|
1367
1572
|
try {
|
|
1368
|
-
raw = await readFile(
|
|
1573
|
+
raw = await readFile(path2, "utf8");
|
|
1369
1574
|
} catch (error) {
|
|
1370
|
-
onCorrupt?.(
|
|
1575
|
+
onCorrupt?.(path2, error);
|
|
1371
1576
|
return [];
|
|
1372
1577
|
}
|
|
1373
1578
|
try {
|
|
@@ -1376,16 +1581,16 @@ async function readDocument(path, onCorrupt, quarantine = true) {
|
|
|
1376
1581
|
return parsed.items ?? [];
|
|
1377
1582
|
} catch (error) {
|
|
1378
1583
|
if (quarantine) {
|
|
1379
|
-
const quarantinePath = `${
|
|
1584
|
+
const quarantinePath = `${path2}.corrupt-${Date.now()}`;
|
|
1380
1585
|
try {
|
|
1381
|
-
await rename(
|
|
1586
|
+
await rename(path2, quarantinePath);
|
|
1382
1587
|
logger.child({ component: "persistence" }).warn(
|
|
1383
|
-
`Quarantined unreadable store file ${
|
|
1588
|
+
`Quarantined unreadable store file ${path2} \u2192 ${quarantinePath}`
|
|
1384
1589
|
);
|
|
1385
1590
|
} catch {
|
|
1386
1591
|
}
|
|
1387
1592
|
}
|
|
1388
|
-
onCorrupt?.(
|
|
1593
|
+
onCorrupt?.(path2, error);
|
|
1389
1594
|
return [];
|
|
1390
1595
|
}
|
|
1391
1596
|
}
|
|
@@ -1915,7 +2120,7 @@ function registerPipelineInspect(pipelineGroup) {
|
|
|
1915
2120
|
function registerAgentInspect(agentGroup) {
|
|
1916
2121
|
jsonOption(
|
|
1917
2122
|
agentGroup.command("inspect <name>").description("Show an agent's effective configuration and recent tasks.").action(async (name, options) => {
|
|
1918
|
-
const config =
|
|
2123
|
+
const config = loadProjectConfig();
|
|
1919
2124
|
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
1920
2125
|
if (!agentConfig) throw new OmniNodeError("AGENT_NOT_FOUND", `No agent "${name}" is configured.`);
|
|
1921
2126
|
const registry = buildAgentRegistry(config);
|
|
@@ -1945,7 +2150,7 @@ function registerAgentInspect(agentGroup) {
|
|
|
1945
2150
|
function registerProviderInspect(providerGroup) {
|
|
1946
2151
|
jsonOption(
|
|
1947
2152
|
providerGroup.command("inspect <name>").description("Show a provider's authentication status and capabilities.").action(async (name, options) => {
|
|
1948
|
-
const config =
|
|
2153
|
+
const config = loadProjectConfig();
|
|
1949
2154
|
const providerConfig = config.project.providers.find((p) => p.name === name);
|
|
1950
2155
|
if (!providerConfig) throw new OmniNodeError("PROVIDER_NOT_FOUND", `No provider "${name}" is configured.`);
|
|
1951
2156
|
const { createProvider: createProvider2 } = await import("./providers-OYXBAQTX.js");
|
|
@@ -1977,9 +2182,13 @@ function registerProviderInspect(providerGroup) {
|
|
|
1977
2182
|
// src/cli/commands/providers.ts
|
|
1978
2183
|
function registerProviderCommands(program) {
|
|
1979
2184
|
const provider = program.command("provider").description("Manage AI providers configured in omninode.yaml.");
|
|
1980
|
-
provider.command("list").description("List configured providers.").action(async () => {
|
|
1981
|
-
const config =
|
|
2185
|
+
provider.command("list").description("List configured providers.").option("--json", "Emit providers as JSON.").action(async (options) => {
|
|
2186
|
+
const config = loadProjectConfig();
|
|
1982
2187
|
const providers = config.project.providers;
|
|
2188
|
+
if (options.json) {
|
|
2189
|
+
console.log(JSON.stringify(providers, null, 2));
|
|
2190
|
+
return;
|
|
2191
|
+
}
|
|
1983
2192
|
if (providers.length === 0) {
|
|
1984
2193
|
console.log(
|
|
1985
2194
|
"No providers configured. Add one with `omninode provider add` or edit omninode.yaml."
|
|
@@ -2008,13 +2217,13 @@ function registerProviderCommands(program) {
|
|
|
2008
2217
|
"--connect",
|
|
2009
2218
|
"Run the full provider connect flow (authenticate, fetch, validate, register) where the adapter supports it."
|
|
2010
2219
|
).action(async (name, options) => testProvider(name, options));
|
|
2011
|
-
provider.command("remove <name>").description("Remove a provider
|
|
2012
|
-
|
|
2220
|
+
provider.command("remove <name>").description("Remove a provider from omninode.yaml.").action((name) => {
|
|
2221
|
+
removeProvider(name);
|
|
2013
2222
|
});
|
|
2014
2223
|
registerProviderInspect(provider);
|
|
2015
2224
|
}
|
|
2016
2225
|
async function addProvider(options) {
|
|
2017
|
-
const configPath =
|
|
2226
|
+
const configPath = activeConfigPath();
|
|
2018
2227
|
if (!configPath) {
|
|
2019
2228
|
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found. Run `omninode init` first.");
|
|
2020
2229
|
}
|
|
@@ -2029,11 +2238,11 @@ async function addProvider(options) {
|
|
|
2029
2238
|
throw new OmniNodeError("CLI_USAGE", `Invalid provider options: ${issues}`);
|
|
2030
2239
|
}
|
|
2031
2240
|
const newProvider = candidate.data;
|
|
2032
|
-
const existing =
|
|
2241
|
+
const existing = loadProjectConfig();
|
|
2033
2242
|
if (existing.project.providers.some((p) => p.name === newProvider.name)) {
|
|
2034
2243
|
throw new OmniNodeError(
|
|
2035
2244
|
"CLI_USAGE",
|
|
2036
|
-
`Provider "${newProvider.name}" already exists in ${
|
|
2245
|
+
`Provider "${newProvider.name}" already exists in ${activeConfigPath() ?? "omninode.yaml"}.`
|
|
2037
2246
|
);
|
|
2038
2247
|
}
|
|
2039
2248
|
const writtenPath = appendToConfigList("providers", newProvider);
|
|
@@ -2046,8 +2255,28 @@ async function addProvider(options) {
|
|
|
2046
2255
|
console.log(`Run \`omninode provider test ${newProvider.name}\` to verify connectivity.`);
|
|
2047
2256
|
}
|
|
2048
2257
|
}
|
|
2258
|
+
function removeProvider(name) {
|
|
2259
|
+
const configPath = activeConfigPath();
|
|
2260
|
+
if (!configPath) {
|
|
2261
|
+
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found.");
|
|
2262
|
+
}
|
|
2263
|
+
const config = loadProjectConfig();
|
|
2264
|
+
if (!config.project.providers.some((p) => p.name === name)) {
|
|
2265
|
+
throw new ProviderError("PROVIDER_NOT_FOUND", `No provider "${name}" is configured.`);
|
|
2266
|
+
}
|
|
2267
|
+
const doc = parseDocument2(readFileSync3(configPath, "utf8"));
|
|
2268
|
+
const current = doc.getIn(["project", "providers"]);
|
|
2269
|
+
const remaining = (current === void 0 || current === null ? [] : doc.get("project")?.providers ?? []).filter((entry2) => entry2?.name !== name);
|
|
2270
|
+
doc.setIn(["project", "providers"], remaining);
|
|
2271
|
+
const validated = appConfigSchema.safeParse(doc.toJS());
|
|
2272
|
+
if (!validated.success) {
|
|
2273
|
+
throw new OmniNodeError("CONFIG_INVALID", "Removing this provider would produce an invalid configuration.");
|
|
2274
|
+
}
|
|
2275
|
+
writeFileSync3(configPath, doc.toString(), "utf8");
|
|
2276
|
+
console.log(`Removed provider "${name}" from ${configPath}.`);
|
|
2277
|
+
}
|
|
2049
2278
|
async function testProvider(name, options = {}) {
|
|
2050
|
-
const config =
|
|
2279
|
+
const config = loadProjectConfig();
|
|
2051
2280
|
const providerConfig = config.project.providers.find((p) => p.name === name);
|
|
2052
2281
|
if (!providerConfig) {
|
|
2053
2282
|
const available = config.project.providers.map((p) => p.name).join(", ") || "(none)";
|
|
@@ -2081,169 +2310,6 @@ function printProviderStatus(status) {
|
|
|
2081
2310
|
if (status.message) console.log(` detail: ${status.message}`);
|
|
2082
2311
|
}
|
|
2083
2312
|
|
|
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
2313
|
// src/tasks/engine.ts
|
|
2248
2314
|
import { randomBytes } from "crypto";
|
|
2249
2315
|
var ALLOWED_TRANSITIONS = {
|
|
@@ -2587,12 +2653,145 @@ function createTaskEngine(config, log = logger, directory) {
|
|
|
2587
2653
|
});
|
|
2588
2654
|
}
|
|
2589
2655
|
|
|
2656
|
+
// src/cli/commands/agents.ts
|
|
2657
|
+
var collectArgs = (value, previous) => [...previous, value];
|
|
2658
|
+
function registerAgentCommands(program) {
|
|
2659
|
+
const agent = program.command("agent").description("Manage CLI agents registered with OmniNode.");
|
|
2660
|
+
agent.command("list").description("List configured agents.").option("--json", "Emit agents as JSON.").action(async (options) => {
|
|
2661
|
+
const config = loadProjectConfig();
|
|
2662
|
+
const agents = config.project.agents;
|
|
2663
|
+
if (options.json) {
|
|
2664
|
+
console.log(JSON.stringify(config.project.agents, null, 2));
|
|
2665
|
+
return;
|
|
2666
|
+
}
|
|
2667
|
+
if (agents.length === 0) {
|
|
2668
|
+
console.log("No agents configured. Add one with `omninode agent add` or edit omninode.yaml.");
|
|
2669
|
+
return;
|
|
2670
|
+
}
|
|
2671
|
+
for (const agent2 of agents) {
|
|
2672
|
+
const command = agent2.command ?? "(no command)";
|
|
2673
|
+
console.log(
|
|
2674
|
+
`${agent2.name} integration=${agent2.integration} command=${command} input=${agent2.inputMode ?? "stdin"}`
|
|
2675
|
+
);
|
|
2676
|
+
}
|
|
2677
|
+
});
|
|
2678
|
+
agent.command("add").description(
|
|
2679
|
+
"Register a CLI agent in omninode.yaml. Agents run as child processes (stdin/stdout); native adapters come in a later phase."
|
|
2680
|
+
).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(
|
|
2681
|
+
"--input-mode <mode>",
|
|
2682
|
+
"How the task is delivered: stdin (prompt on stdin), arg (prompt as last argument) or protocol (\xA721 JSON-lines task protocol).",
|
|
2683
|
+
"stdin"
|
|
2684
|
+
).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(
|
|
2685
|
+
"--timeout-ms <ms>",
|
|
2686
|
+
"Per-task timeout in milliseconds.",
|
|
2687
|
+
(value) => Number(value)
|
|
2688
|
+
).action(async (options) => addAgent(options));
|
|
2689
|
+
agent.command("run <name> <objective>").description("Run a single task through this agent immediately.").action(async (name, objective) => {
|
|
2690
|
+
const config = loadProjectConfig();
|
|
2691
|
+
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
2692
|
+
if (!agentConfig) {
|
|
2693
|
+
throw new AgentError("AGENT_NOT_FOUND", `No agent "${name}" is configured.`);
|
|
2694
|
+
}
|
|
2695
|
+
const engine = createTaskEngine(config);
|
|
2696
|
+
const task = await engine.create({ objective, agent: name, project: config.project.name });
|
|
2697
|
+
const finished = await engine.run(task.id);
|
|
2698
|
+
console.log(` id: ${finished.id}`);
|
|
2699
|
+
console.log(` status: ${finished.status}`);
|
|
2700
|
+
if (finished.result?.summary) console.log(` summary: ${finished.result.summary}`);
|
|
2701
|
+
if (finished.result?.error) console.log(` error: ${finished.result.error}`);
|
|
2702
|
+
process.exitCode = exitCodeForStatus(finished.status);
|
|
2703
|
+
});
|
|
2704
|
+
agent.command("test <name>").description("Verify the OmniNode \u2194 agent connection by sending a trivial task.").action(async (name) => testAgent(name));
|
|
2705
|
+
registerAgentInspect(agent);
|
|
2706
|
+
}
|
|
2707
|
+
async function addAgent(options) {
|
|
2708
|
+
const candidate = agentConfigSchema.safeParse({
|
|
2709
|
+
name: options.name,
|
|
2710
|
+
type: "cli",
|
|
2711
|
+
command: options.command,
|
|
2712
|
+
...options.arg !== void 0 && options.arg.length > 0 ? { args: options.arg } : {},
|
|
2713
|
+
...options.cwd !== void 0 ? { cwd: options.cwd } : {},
|
|
2714
|
+
input_mode: options.inputMode,
|
|
2715
|
+
...options.timeoutMs !== void 0 ? { timeout_ms: options.timeoutMs } : {},
|
|
2716
|
+
...options.envPolicy !== void 0 ? { env_policy: options.envPolicy } : {},
|
|
2717
|
+
...options.allowEnv !== void 0 && options.allowEnv.length > 0 ? { env_allowlist: options.allowEnv } : {},
|
|
2718
|
+
...options.denyEnv !== void 0 && options.denyEnv.length > 0 ? { env_denylist: options.denyEnv } : {},
|
|
2719
|
+
...options.maxOutputBytes !== void 0 ? { max_output_bytes: options.maxOutputBytes } : {},
|
|
2720
|
+
...options.allowExternalCwd !== void 0 ? { allow_external_cwd: options.allowExternalCwd } : {}
|
|
2721
|
+
});
|
|
2722
|
+
if (!candidate.success) {
|
|
2723
|
+
const issues = candidate.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
2724
|
+
throw new OmniNodeError("CLI_USAGE", `Invalid agent options: ${issues}`);
|
|
2725
|
+
}
|
|
2726
|
+
const newAgent = candidate.data;
|
|
2727
|
+
const existing = loadProjectConfig();
|
|
2728
|
+
if (existing.project.agents.some((a) => a.name === newAgent.name)) {
|
|
2729
|
+
throw new OmniNodeError(
|
|
2730
|
+
"CLI_USAGE",
|
|
2731
|
+
`Agent "${newAgent.name}" already exists in omninode.yaml. Remove it first or pick another name.`
|
|
2732
|
+
);
|
|
2733
|
+
}
|
|
2734
|
+
const configPath = appendToConfigList("agents", newAgent);
|
|
2735
|
+
console.log(`Added agent "${newAgent.name}" (${newAgent.type}, input=${newAgent.input_mode}) to ${configPath}.`);
|
|
2736
|
+
console.log(`Run \`omninode agent test ${newAgent.name}\` to verify the connection.`);
|
|
2737
|
+
}
|
|
2738
|
+
async function testAgent(name) {
|
|
2739
|
+
const config = loadProjectConfig();
|
|
2740
|
+
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
2741
|
+
if (!agentConfig) {
|
|
2742
|
+
const available = config.project.agents.map((a) => a.name).join(", ") || "(none)";
|
|
2743
|
+
throw new AgentError(
|
|
2744
|
+
"AGENT_NOT_FOUND",
|
|
2745
|
+
`Agent "${name}" is not configured. Configured agents: ${available}.`
|
|
2746
|
+
);
|
|
2747
|
+
}
|
|
2748
|
+
const agent = createAgent(agentConfig);
|
|
2749
|
+
console.log(
|
|
2750
|
+
`Testing agent "${name}" (${agent.info.integration}, input=${agentConfig.inputMode ?? "stdin"}) \u2014 sending a trivial task.`
|
|
2751
|
+
);
|
|
2752
|
+
const result = await agent.run({
|
|
2753
|
+
taskId: `test-${Date.now()}`,
|
|
2754
|
+
objective: "Reply with the single word: OK"
|
|
2755
|
+
});
|
|
2756
|
+
console.log(` status: ${result.status}`);
|
|
2757
|
+
if (result.exitCode !== void 0) console.log(` exit: ${result.exitCode}`);
|
|
2758
|
+
if (result.summary) console.log(` output: ${result.summary}`);
|
|
2759
|
+
if (result.rawOutput && result.rawOutput !== result.summary) {
|
|
2760
|
+
console.log(` raw: ${result.rawOutput.slice(0, 200).replace(/\n/g, " ")}`);
|
|
2761
|
+
}
|
|
2762
|
+
if (result.error) console.log(` error: ${result.error}`);
|
|
2763
|
+
if (result.status !== "completed") process.exitCode = 1;
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2766
|
+
// src/cli/commands/roles.ts
|
|
2767
|
+
function registerRoleCommands(program) {
|
|
2768
|
+
const role = program.command("role").description("Inspect roles defined in omninode.yaml.");
|
|
2769
|
+
role.command("list").description("List configured roles.").option("--json", "Emit roles as JSON.").action(async (options) => {
|
|
2770
|
+
const config = loadProjectConfig();
|
|
2771
|
+
const roles = config.project.roles;
|
|
2772
|
+
if (options.json) {
|
|
2773
|
+
console.log(JSON.stringify(config.project.roles, null, 2));
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
if (roles.length === 0) {
|
|
2777
|
+
console.log("No roles configured. Add one under `project.roles` in omninode.yaml.");
|
|
2778
|
+
return;
|
|
2779
|
+
}
|
|
2780
|
+
for (const role2 of roles) {
|
|
2781
|
+
console.log(`${role2.id} ${role2.name}`);
|
|
2782
|
+
for (const responsibility of role2.responsibilities) {
|
|
2783
|
+
console.log(` - ${responsibility}`);
|
|
2784
|
+
}
|
|
2785
|
+
}
|
|
2786
|
+
});
|
|
2787
|
+
}
|
|
2788
|
+
|
|
2590
2789
|
// src/cli/commands/tasks.ts
|
|
2591
2790
|
var collectFiles = (value, previous) => [...previous, value];
|
|
2592
2791
|
function registerTaskCommands(program) {
|
|
2593
2792
|
const task = program.command("task").description("Create, run and inspect tasks (\xA713).");
|
|
2594
2793
|
task.command("create <objective>").description("Create a task. Assign a role and/or agent; run it with `omninode task run`.").option("-r, --role <roleId>", "Role id from project.roles (validated at creation).").option("-a, --agent <name>", "Agent name from project.agents (validated at creation).").option("-f, --file <path>", "File or glob relevant to the task (repeatable).", collectFiles).action(async (objective, options) => {
|
|
2595
|
-
const config =
|
|
2794
|
+
const config = loadProjectConfig();
|
|
2596
2795
|
const engine = createTaskEngine(config);
|
|
2597
2796
|
const created = await engine.create({
|
|
2598
2797
|
objective,
|
|
@@ -2609,15 +2808,15 @@ function registerTaskCommands(program) {
|
|
|
2609
2808
|
console.log(`Run \`omninode task run ${created.id}\` to execute it.`);
|
|
2610
2809
|
});
|
|
2611
2810
|
task.command("run <task-id>").description("Run a task through its assigned agent.").action(async (taskId) => {
|
|
2612
|
-
const config =
|
|
2811
|
+
const config = loadProjectConfig();
|
|
2613
2812
|
const engine = createTaskEngine(config);
|
|
2614
2813
|
console.log(`Running task ${taskId}...`);
|
|
2615
2814
|
const finished = await engine.run(taskId);
|
|
2616
2815
|
printOutcome(finished);
|
|
2617
|
-
|
|
2816
|
+
process.exitCode = exitCodeForStatus(finished.status);
|
|
2618
2817
|
});
|
|
2619
2818
|
task.command("status <task-id>").description("Show task details and current status.").action(async (taskId) => {
|
|
2620
|
-
const config =
|
|
2819
|
+
const config = loadProjectConfig();
|
|
2621
2820
|
const engine = createTaskEngine(config);
|
|
2622
2821
|
const found = await engine.get(taskId);
|
|
2623
2822
|
if (!found) {
|
|
@@ -2628,12 +2827,16 @@ function registerTaskCommands(program) {
|
|
|
2628
2827
|
}
|
|
2629
2828
|
printOutcome(found, { verbose: true });
|
|
2630
2829
|
});
|
|
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) => {
|
|
2632
|
-
const config =
|
|
2830
|
+
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) => {
|
|
2831
|
+
const config = loadProjectConfig();
|
|
2633
2832
|
const engine = createTaskEngine(config);
|
|
2634
2833
|
const tasks = await engine.list(
|
|
2635
2834
|
options.status !== void 0 ? { status: options.status } : {}
|
|
2636
2835
|
);
|
|
2836
|
+
if (options.json) {
|
|
2837
|
+
console.log(JSON.stringify(tasks, null, 2));
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2637
2840
|
if (tasks.length === 0) {
|
|
2638
2841
|
console.log("No tasks found. Create one with `omninode task create`.");
|
|
2639
2842
|
return;
|
|
@@ -2643,13 +2846,13 @@ function registerTaskCommands(program) {
|
|
|
2643
2846
|
}
|
|
2644
2847
|
});
|
|
2645
2848
|
task.command("cancel <task-id>").description("Cancel a task that has not finished yet.").action(async (taskId) => {
|
|
2646
|
-
const config =
|
|
2849
|
+
const config = loadProjectConfig();
|
|
2647
2850
|
const engine = createTaskEngine(config);
|
|
2648
2851
|
const cancelled = await engine.cancel(taskId);
|
|
2649
2852
|
console.log(`Task ${cancelled.id} is now "${cancelled.status}".`);
|
|
2650
2853
|
});
|
|
2651
2854
|
task.command("retry <task-id>").description("Reset a failed, timed out, unknown or cancelled task to created; combine with --run to execute it again.").option("--run", "Run the task again immediately after resetting it.").action(async (taskId, options) => {
|
|
2652
|
-
const config =
|
|
2855
|
+
const config = loadProjectConfig();
|
|
2653
2856
|
const engine = createTaskEngine(config);
|
|
2654
2857
|
const reset = await engine.retry(taskId);
|
|
2655
2858
|
console.log(`Task ${reset.id} reset to "${reset.status}".`);
|
|
@@ -2657,7 +2860,7 @@ function registerTaskCommands(program) {
|
|
|
2657
2860
|
console.log(`Running task ${reset.id}...`);
|
|
2658
2861
|
const finished = await engine.run(reset.id);
|
|
2659
2862
|
printOutcome(finished);
|
|
2660
|
-
|
|
2863
|
+
process.exitCode = exitCodeForStatus(finished.status);
|
|
2661
2864
|
});
|
|
2662
2865
|
registerTaskInspect(task);
|
|
2663
2866
|
}
|
|
@@ -3128,12 +3331,16 @@ function service() {
|
|
|
3128
3331
|
}
|
|
3129
3332
|
function registerReportCommands(program) {
|
|
3130
3333
|
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) => {
|
|
3334
|
+
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
3335
|
const svc = service();
|
|
3133
3336
|
const reports = await svc.storeRef.listReports({
|
|
3134
3337
|
...options.task !== void 0 ? { taskId: options.task } : {},
|
|
3135
3338
|
...options.agent !== void 0 ? { agent: options.agent } : {}
|
|
3136
3339
|
});
|
|
3340
|
+
if (options.json) {
|
|
3341
|
+
console.log(JSON.stringify(reports, null, 2));
|
|
3342
|
+
return;
|
|
3343
|
+
}
|
|
3137
3344
|
if (reports.length === 0) {
|
|
3138
3345
|
console.log("No reports stored yet. Run a pipeline to collect some.");
|
|
3139
3346
|
return;
|
|
@@ -3142,11 +3349,15 @@ function registerReportCommands(program) {
|
|
|
3142
3349
|
console.log(`${r.id} ${r.agent.padEnd(10)} ${truncate(r.summary, 60)}`);
|
|
3143
3350
|
}
|
|
3144
3351
|
});
|
|
3145
|
-
report.command("combined").description("List combined reports (aggregated intelligence, \xA717).").option("--run <pipelineRunId>", "Only combined reports for this pipeline run.").action(async (options) => {
|
|
3352
|
+
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
3353
|
const svc = service();
|
|
3147
3354
|
const combined = await svc.storeRef.listCombined({
|
|
3148
3355
|
...options.run !== void 0 ? { pipelineRunId: options.run } : {}
|
|
3149
3356
|
});
|
|
3357
|
+
if (options.json) {
|
|
3358
|
+
console.log(JSON.stringify(combined, null, 2));
|
|
3359
|
+
return;
|
|
3360
|
+
}
|
|
3150
3361
|
if (combined.length === 0) {
|
|
3151
3362
|
console.log("No combined reports stored yet. Run a pipeline to generate one.");
|
|
3152
3363
|
return;
|
|
@@ -3226,6 +3437,10 @@ function registerReportCommands(program) {
|
|
|
3226
3437
|
});
|
|
3227
3438
|
}
|
|
3228
3439
|
|
|
3440
|
+
// src/cli/commands/pipelines.ts
|
|
3441
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
|
|
3442
|
+
import { parseDocument as parseDocument3 } from "yaml";
|
|
3443
|
+
|
|
3229
3444
|
// src/pipelines/engine.ts
|
|
3230
3445
|
import { randomBytes as randomBytes5 } from "crypto";
|
|
3231
3446
|
|
|
@@ -4177,9 +4392,13 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
4177
4392
|
// src/cli/commands/pipelines.ts
|
|
4178
4393
|
function registerPipelineCommands(program) {
|
|
4179
4394
|
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 () => {
|
|
4181
|
-
const config =
|
|
4395
|
+
pipeline.command("list").description("List configured pipelines.").option("--json", "Emit pipelines as JSON.").action(async (options) => {
|
|
4396
|
+
const config = loadProjectConfig();
|
|
4182
4397
|
const pipelines = config.project.pipelines;
|
|
4398
|
+
if (options.json) {
|
|
4399
|
+
console.log(JSON.stringify(config.project.pipelines, null, 2));
|
|
4400
|
+
return;
|
|
4401
|
+
}
|
|
4183
4402
|
if (pipelines.length === 0) {
|
|
4184
4403
|
console.log("No pipelines configured. Add one under `project.pipelines` in omninode.yaml.");
|
|
4185
4404
|
return;
|
|
@@ -4195,7 +4414,7 @@ function registerPipelineCommands(program) {
|
|
|
4195
4414
|
pipeline.command("retry <run-id> [objective]").description(
|
|
4196
4415
|
"Error recovery: re-run a recorded pipeline run as a new run, reusing its objective."
|
|
4197
4416
|
).action(async (runId, objective) => {
|
|
4198
|
-
const config =
|
|
4417
|
+
const config = loadProjectConfig();
|
|
4199
4418
|
const engine = buildPipelineEngine(config);
|
|
4200
4419
|
const previous = await engine.get(runId);
|
|
4201
4420
|
if (!previous) {
|
|
@@ -4214,14 +4433,17 @@ function registerPipelineCommands(program) {
|
|
|
4214
4433
|
await runPipelineAction(def.id, objective ?? previous.objective);
|
|
4215
4434
|
});
|
|
4216
4435
|
pipeline.command("cancel <run-id>").description("Request cancellation of a pipeline run (stops scheduling and cancels running tasks).").action(async (runId) => {
|
|
4217
|
-
const config =
|
|
4436
|
+
const config = loadProjectConfig();
|
|
4218
4437
|
const engine = buildPipelineEngine(config);
|
|
4219
4438
|
await engine.cancel(runId);
|
|
4220
4439
|
console.log(`Cancellation requested for run ${runId}.`);
|
|
4221
4440
|
console.log("A run executing in another process stops at its next check point.");
|
|
4222
4441
|
});
|
|
4442
|
+
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) => {
|
|
4443
|
+
await createPipelineFromFile(id, options.from);
|
|
4444
|
+
});
|
|
4223
4445
|
pipeline.command("validate <id>").description("Validate a pipeline definition before running it (\xA79.5).").action(async (id) => {
|
|
4224
|
-
const config =
|
|
4446
|
+
const config = loadProjectConfig();
|
|
4225
4447
|
const def = findPipeline(config, id);
|
|
4226
4448
|
const engine = buildPipelineEngine(config);
|
|
4227
4449
|
engine.validate(def);
|
|
@@ -4232,7 +4454,7 @@ function registerPipelineCommands(program) {
|
|
|
4232
4454
|
}
|
|
4233
4455
|
});
|
|
4234
4456
|
pipeline.command("runs <id>").description("List recorded runs of a pipeline.").action(async (id) => {
|
|
4235
|
-
const config =
|
|
4457
|
+
const config = loadProjectConfig();
|
|
4236
4458
|
const engine = buildPipelineEngine(config);
|
|
4237
4459
|
const runs = await engine.listRuns({ pipelineId: id });
|
|
4238
4460
|
if (runs.length === 0) {
|
|
@@ -4245,7 +4467,7 @@ function registerPipelineCommands(program) {
|
|
|
4245
4467
|
}
|
|
4246
4468
|
});
|
|
4247
4469
|
pipeline.command("status <run-id>").description("Show the recorded state of a pipeline run.").action(async (runId) => {
|
|
4248
|
-
const config =
|
|
4470
|
+
const config = loadProjectConfig();
|
|
4249
4471
|
const engine = buildPipelineEngine(config);
|
|
4250
4472
|
const run2 = await engine.get(runId);
|
|
4251
4473
|
if (!run2) {
|
|
@@ -4277,7 +4499,7 @@ function registerPipelineCommands(program) {
|
|
|
4277
4499
|
registerPipelineInspect(pipeline);
|
|
4278
4500
|
}
|
|
4279
4501
|
async function runPipelineAction(id, objective) {
|
|
4280
|
-
const config =
|
|
4502
|
+
const config = loadProjectConfig();
|
|
4281
4503
|
const def = findPipeline(config, id);
|
|
4282
4504
|
const engine = buildPipelineEngine(config);
|
|
4283
4505
|
console.log(
|
|
@@ -4303,11 +4525,45 @@ async function runPipelineAction(id, objective) {
|
|
|
4303
4525
|
console.log(` result: ${finished.resultSummary}`);
|
|
4304
4526
|
}
|
|
4305
4527
|
console.log(`Pipeline ${finished.status}. Run id: ${finished.id}`);
|
|
4306
|
-
|
|
4528
|
+
process.exitCode = exitCodeForStatus(finished.status);
|
|
4307
4529
|
}
|
|
4308
4530
|
function registerRunAlias(program) {
|
|
4309
4531
|
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
4532
|
}
|
|
4533
|
+
async function createPipelineFromFile(id, file) {
|
|
4534
|
+
const configPath = activeConfigPath();
|
|
4535
|
+
if (!configPath) {
|
|
4536
|
+
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found. Run `omninode init` first.");
|
|
4537
|
+
}
|
|
4538
|
+
let definition;
|
|
4539
|
+
try {
|
|
4540
|
+
definition = parseDocument3(readFileSync4(file, "utf8")).toJS();
|
|
4541
|
+
} catch (error) {
|
|
4542
|
+
throw new PipelineError("PIPELINE_INVALID", `Could not read pipeline definition from ${file}.`, {
|
|
4543
|
+
cause: error
|
|
4544
|
+
});
|
|
4545
|
+
}
|
|
4546
|
+
const candidate = pipelineConfigSchema.safeParse({ id, ...definition });
|
|
4547
|
+
if (!candidate.success) {
|
|
4548
|
+
const issues = candidate.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
4549
|
+
throw new PipelineError("PIPELINE_INVALID", `Invalid pipeline definition: ${issues}`);
|
|
4550
|
+
}
|
|
4551
|
+
const engine = buildPipelineEngine(loadProjectConfig());
|
|
4552
|
+
engine.validate({ id, ...candidate.data });
|
|
4553
|
+
const doc = parseDocument3(readFileSync4(configPath, "utf8"));
|
|
4554
|
+
if (doc.getIn(["project", "pipelines"]) === void 0) {
|
|
4555
|
+
doc.setIn(["project", "pipelines"], [candidate.data]);
|
|
4556
|
+
} else {
|
|
4557
|
+
doc.addIn(["project", "pipelines"], candidate.data);
|
|
4558
|
+
}
|
|
4559
|
+
const validated = appConfigSchema.safeParse(doc.toJS());
|
|
4560
|
+
if (!validated.success) {
|
|
4561
|
+
throw new PipelineError("PIPELINE_INVALID", "Adding this pipeline would produce an invalid configuration.");
|
|
4562
|
+
}
|
|
4563
|
+
writeFileSync4(configPath, doc.toString(), "utf8");
|
|
4564
|
+
console.log(`Created pipeline "${id}" (${candidate.data.steps.length} step(s)) in ${configPath}.`);
|
|
4565
|
+
console.log(`Run it with: omninode pipeline run ${id} "<objective>"`);
|
|
4566
|
+
}
|
|
4311
4567
|
function isInterrupted(run2) {
|
|
4312
4568
|
return run2.status === "running" && run2.finishedAt === void 0;
|
|
4313
4569
|
}
|
|
@@ -4327,7 +4583,7 @@ function findPipeline(config, id) {
|
|
|
4327
4583
|
function registerMemoryCommands(program) {
|
|
4328
4584
|
const memory = program.command("memory").description("Inspect and manage persistent project memory (\xA719\u2013\xA720).");
|
|
4329
4585
|
memory.command("status").description("Show the active memory provider and how much is stored.").action(async () => {
|
|
4330
|
-
const config =
|
|
4586
|
+
const config = loadProjectConfig();
|
|
4331
4587
|
const configured = config.project.memory?.provider ?? "local (default \u2014 add a `memory:` section to opt in)";
|
|
4332
4588
|
const svc = new MemoryService(createMemoryProvider(config.project.memory));
|
|
4333
4589
|
const entries = await svc.providerRef.query({ limit: 1e4 });
|
|
@@ -4341,14 +4597,18 @@ function registerMemoryCommands(program) {
|
|
|
4341
4597
|
console.log(` ${scope}: ${count}`);
|
|
4342
4598
|
}
|
|
4343
4599
|
});
|
|
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) => {
|
|
4345
|
-
const config =
|
|
4600
|
+
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) => {
|
|
4601
|
+
const config = loadProjectConfig();
|
|
4346
4602
|
const svc = new MemoryService(createMemoryProvider(config.project.memory));
|
|
4347
4603
|
const entries = await svc.providerRef.query({
|
|
4348
4604
|
text,
|
|
4349
4605
|
...options.scope !== void 0 ? { scope: options.scope } : {},
|
|
4350
4606
|
limit: options.limit
|
|
4351
4607
|
});
|
|
4608
|
+
if (options.json) {
|
|
4609
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
4610
|
+
return;
|
|
4611
|
+
}
|
|
4352
4612
|
if (entries.length === 0) {
|
|
4353
4613
|
console.log("No relevant memory found.");
|
|
4354
4614
|
return;
|
|
@@ -4363,7 +4623,7 @@ function registerMemoryCommands(program) {
|
|
|
4363
4623
|
"Comma-separated tags.",
|
|
4364
4624
|
(value) => value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0)
|
|
4365
4625
|
).option("-k, --key <key>", "Entry key (defaults to a generated note key).").action(async (text, options) => {
|
|
4366
|
-
const config =
|
|
4626
|
+
const config = loadProjectConfig();
|
|
4367
4627
|
const svc = new MemoryService(createMemoryProvider(config.project.memory));
|
|
4368
4628
|
const key = options.key ?? `note:${Date.now().toString(36)}`;
|
|
4369
4629
|
await svc.providerRef.write({
|
|
@@ -4379,11 +4639,15 @@ function registerMemoryCommands(program) {
|
|
|
4379
4639
|
// src/cli/commands/plan.ts
|
|
4380
4640
|
function registerPlanCommands(program) {
|
|
4381
4641
|
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) => {
|
|
4642
|
+
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
4643
|
const store = new FilePlanStore();
|
|
4384
4644
|
const plans = await store.list({
|
|
4385
4645
|
...options.run !== void 0 ? { pipelineRunId: options.run } : {}
|
|
4386
4646
|
});
|
|
4647
|
+
if (options.json) {
|
|
4648
|
+
console.log(JSON.stringify(plans, null, 2));
|
|
4649
|
+
return;
|
|
4650
|
+
}
|
|
4387
4651
|
if (plans.length === 0) {
|
|
4388
4652
|
console.log("No plans stored yet. Run a pipeline with a plan step to generate one.");
|
|
4389
4653
|
return;
|
|
@@ -4442,7 +4706,7 @@ function countBy(items, key) {
|
|
|
4442
4706
|
}
|
|
4443
4707
|
function registerStatusCommand(program) {
|
|
4444
4708
|
program.command("status").description("Show the project's OmniNode state overview (config, tasks, runs, reports, plans, memory).").option("--json", "Emit machine-readable JSON (for scripts and CI).").action(async (options) => {
|
|
4445
|
-
const config =
|
|
4709
|
+
const config = loadProjectConfig();
|
|
4446
4710
|
const tasks = await new FileTaskStore().list();
|
|
4447
4711
|
const runs = await new FilePipelineRunStore().list();
|
|
4448
4712
|
const reportStore = new FileReportStore();
|
|
@@ -4523,8 +4787,8 @@ var STORE_FILES = [
|
|
|
4523
4787
|
async function migrateProjectStores(directory = `${process.cwd()}/.omninode`, options = {}) {
|
|
4524
4788
|
const reports = [];
|
|
4525
4789
|
for (const file of STORE_FILES) {
|
|
4526
|
-
const
|
|
4527
|
-
const state = await inspectDocument(
|
|
4790
|
+
const path2 = `${directory}/${file}`;
|
|
4791
|
+
const state = await inspectDocument(path2);
|
|
4528
4792
|
if (state.status === "missing") {
|
|
4529
4793
|
reports.push({ file, state: state.status, migrated: false });
|
|
4530
4794
|
continue;
|
|
@@ -4546,9 +4810,9 @@ async function migrateProjectStores(directory = `${process.cwd()}/.omninode`, op
|
|
|
4546
4810
|
reports.push({ file, state: state.status, migrated: false, note: state.detail });
|
|
4547
4811
|
continue;
|
|
4548
4812
|
}
|
|
4549
|
-
const items = await readDocument(
|
|
4813
|
+
const items = await readDocument(path2, void 0, false);
|
|
4550
4814
|
if (!options.checkOnly) {
|
|
4551
|
-
await writeFileAtomic(
|
|
4815
|
+
await writeFileAtomic(path2, `${JSON.stringify({ schemaVersion: SCHEMA_VERSION, items }, null, 2)}
|
|
4552
4816
|
`);
|
|
4553
4817
|
}
|
|
4554
4818
|
const beforeCount = items.length;
|
|
@@ -4591,7 +4855,7 @@ function registerSecurityCommand(program) {
|
|
|
4591
4855
|
console.log(`Unknown security action "${action}". Available: audit.`);
|
|
4592
4856
|
return;
|
|
4593
4857
|
}
|
|
4594
|
-
const config =
|
|
4858
|
+
const config = loadProjectConfig();
|
|
4595
4859
|
const findings = [];
|
|
4596
4860
|
for (const agent of config.project.agents) {
|
|
4597
4861
|
const policy = resolveEnvPolicy(agent);
|
|
@@ -4650,12 +4914,155 @@ Trust model: agents execute with the permissions of the OS user running OmniNode
|
|
|
4650
4914
|
});
|
|
4651
4915
|
}
|
|
4652
4916
|
|
|
4917
|
+
// src/cli/commands/config.ts
|
|
4918
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
4919
|
+
import { parse as parseYaml2 } from "yaml";
|
|
4920
|
+
function registerConfigCommands(program) {
|
|
4921
|
+
const config = program.command("config").description("Inspect the project configuration (omninode.yaml).");
|
|
4922
|
+
config.command("path").description("Print the path of the configuration file in use.").action(() => {
|
|
4923
|
+
const path2 = activeConfigPath();
|
|
4924
|
+
console.log(path2 ?? "no omninode.yaml found");
|
|
4925
|
+
});
|
|
4926
|
+
config.command("show").description("Print the effective configuration (credential references only, never values).").option("--json", "Emit the configuration as JSON.").action((options) => {
|
|
4927
|
+
const resolved = loadConfigDetailed().config;
|
|
4928
|
+
const payload = {
|
|
4929
|
+
project: resolved.project.name,
|
|
4930
|
+
providers: resolved.project.providers.map((p) => ({
|
|
4931
|
+
name: p.name,
|
|
4932
|
+
type: p.type,
|
|
4933
|
+
baseUrl: p.baseUrl,
|
|
4934
|
+
apiKeyEnvVar: p.apiKeyEnvVar ?? null
|
|
4935
|
+
})),
|
|
4936
|
+
agents: resolved.project.agents.map((a) => ({ name: a.name, command: a.command ?? null, inputMode: a.inputMode })),
|
|
4937
|
+
roles: resolved.project.roles.map((r) => r.id),
|
|
4938
|
+
pipelines: resolved.project.pipelines.map((p) => p.id),
|
|
4939
|
+
memory: resolved.project.memory ?? null,
|
|
4940
|
+
logging: resolved.logging ?? null
|
|
4941
|
+
};
|
|
4942
|
+
if (options.json) {
|
|
4943
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
4944
|
+
return;
|
|
4945
|
+
}
|
|
4946
|
+
console.log(`project: ${payload.project}`);
|
|
4947
|
+
console.log(`providers: ${payload.providers.map((p) => `${p.name} (${p.type}, key=${p.apiKeyEnvVar ? `$${p.apiKeyEnvVar}` : "none"})`).join(", ") || "none"}`);
|
|
4948
|
+
console.log(`agents: ${payload.agents.map((a) => a.name).join(", ") || "none"}`);
|
|
4949
|
+
console.log(`roles: ${payload.roles.join(", ") || "none"}`);
|
|
4950
|
+
console.log(`pipelines: ${payload.pipelines.join(", ") || "none"}`);
|
|
4951
|
+
console.log(`memory: ${payload.memory ? `${payload.memory.provider}${payload.memory.required ? " (required)" : ""}` : "not configured"}`);
|
|
4952
|
+
});
|
|
4953
|
+
config.command("profiles").description("List the configuration profiles defined in this project.").action(() => {
|
|
4954
|
+
const path2 = activeConfigPath();
|
|
4955
|
+
const raw = path2 ? parseYaml2(readFileSync5(path2, "utf8")) : {};
|
|
4956
|
+
const names = Object.keys(raw.profiles ?? {});
|
|
4957
|
+
const active = loadConfigDetailed().sources.profile;
|
|
4958
|
+
console.log(`active: ${active}`);
|
|
4959
|
+
console.log(`defined: ${names.length > 0 ? names.join(", ") : "none (add a `profiles:` section)"}`);
|
|
4960
|
+
});
|
|
4961
|
+
config.command("validate").description("Validate the configuration file, including the inline-secret scan.").action(() => {
|
|
4962
|
+
const path2 = activeConfigPath();
|
|
4963
|
+
if (!path2) throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found.");
|
|
4964
|
+
const findings = scanForInlineSecrets(readFileSync5(path2, "utf8"));
|
|
4965
|
+
if (findings.length > 0) {
|
|
4966
|
+
throw new OmniNodeError(
|
|
4967
|
+
"CONFIG_INVALID",
|
|
4968
|
+
`${path2} contains ${findings.length} inline secret(s) (line ${findings.map((f) => f.line).join(", ")}). Use environment-variable references.`
|
|
4969
|
+
);
|
|
4970
|
+
}
|
|
4971
|
+
const { config: resolved, sources } = loadConfigDetailed();
|
|
4972
|
+
console.log(
|
|
4973
|
+
`${path2} 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).`
|
|
4974
|
+
);
|
|
4975
|
+
console.log(` profile: ${sources.profile}`);
|
|
4976
|
+
if (sources.userFile) console.log(` user cfg: ${sources.userFile}`);
|
|
4977
|
+
if (sources.envOverrides.length > 0) {
|
|
4978
|
+
console.log(` env overrides: ${sources.envOverrides.join(", ")}`);
|
|
4979
|
+
}
|
|
4980
|
+
if (sources.defaults.length > 0) {
|
|
4981
|
+
console.log(` defaults applied: ${sources.defaults.join(", ")}`);
|
|
4982
|
+
}
|
|
4983
|
+
const diagnostics = configDiagnostics(resolved);
|
|
4984
|
+
if (diagnostics.length > 0) {
|
|
4985
|
+
console.log(" diagnostics:");
|
|
4986
|
+
for (const note of diagnostics) console.log(` - ${note}`);
|
|
4987
|
+
}
|
|
4988
|
+
});
|
|
4989
|
+
}
|
|
4990
|
+
|
|
4991
|
+
// src/cli/commands/models.ts
|
|
4992
|
+
var CAPABILITIES = [
|
|
4993
|
+
"chat",
|
|
4994
|
+
"coding",
|
|
4995
|
+
"tools",
|
|
4996
|
+
"vision",
|
|
4997
|
+
"embeddings"
|
|
4998
|
+
];
|
|
4999
|
+
function registerModelsCommand(program) {
|
|
5000
|
+
for (const name of ["models", "model"]) {
|
|
5001
|
+
registerModelGroup(program, name);
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
function registerModelGroup(program, name) {
|
|
5005
|
+
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) => {
|
|
5006
|
+
const capability = options.capability;
|
|
5007
|
+
if (capability !== void 0 && !CAPABILITIES.includes(capability)) {
|
|
5008
|
+
throw new OmniNodeError(
|
|
5009
|
+
"CLI_USAGE",
|
|
5010
|
+
`Unknown capability "${options.capability}". Valid capabilities: ${CAPABILITIES.join(", ")}.`
|
|
5011
|
+
);
|
|
5012
|
+
}
|
|
5013
|
+
const config = loadProjectConfig();
|
|
5014
|
+
const selected = config.project.providers.filter(
|
|
5015
|
+
(p) => providerName ? p.name === providerName : true
|
|
5016
|
+
);
|
|
5017
|
+
if (selected.length === 0) {
|
|
5018
|
+
if (options.json) {
|
|
5019
|
+
console.log("[]");
|
|
5020
|
+
return;
|
|
5021
|
+
}
|
|
5022
|
+
throw new OmniNodeError(
|
|
5023
|
+
"PROVIDER_NOT_FOUND",
|
|
5024
|
+
providerName ? `Provider "${providerName}" is not configured.` : "No providers configured. Add one with `omninode provider add`."
|
|
5025
|
+
);
|
|
5026
|
+
}
|
|
5027
|
+
const registry = new ModelRegistry();
|
|
5028
|
+
for (const providerConfig of selected) {
|
|
5029
|
+
try {
|
|
5030
|
+
const provider = createProvider(providerConfig);
|
|
5031
|
+
const discovered = await registry.discoverFrom(provider);
|
|
5032
|
+
console.log(`${providerConfig.name}: ${discovered.length} model(s)`);
|
|
5033
|
+
} catch (error) {
|
|
5034
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
5035
|
+
console.log(`${providerConfig.name}: discovery failed \u2014 ${message}`);
|
|
5036
|
+
}
|
|
5037
|
+
}
|
|
5038
|
+
const models = registry.list({
|
|
5039
|
+
...providerName !== void 0 ? { provider: providerName } : {},
|
|
5040
|
+
...capability !== void 0 ? { capability } : {}
|
|
5041
|
+
});
|
|
5042
|
+
if (options.json) {
|
|
5043
|
+
console.log(JSON.stringify(models, null, 2));
|
|
5044
|
+
return;
|
|
5045
|
+
}
|
|
5046
|
+
if (models.length === 0) {
|
|
5047
|
+
console.log("No models found.");
|
|
5048
|
+
return;
|
|
5049
|
+
}
|
|
5050
|
+
console.log("");
|
|
5051
|
+
for (const model of models) {
|
|
5052
|
+
console.log(`${model.provider}:${model.id}`);
|
|
5053
|
+
}
|
|
5054
|
+
});
|
|
5055
|
+
}
|
|
5056
|
+
|
|
4653
5057
|
// src/cli/index.ts
|
|
4654
5058
|
function createProgram() {
|
|
4655
5059
|
const program = new Command();
|
|
4656
5060
|
program.name("omninode").description(
|
|
4657
5061
|
"Provider-agnostic multi-AI orchestration platform: coordinate CLI agents, AI providers, memory, roles, reports and planning in one execution system."
|
|
4658
|
-
).version(OMNINODE_VERSION, "-v, --version", "Print the OmniNode version.")
|
|
5062
|
+
).version(OMNINODE_VERSION, "-v, --version", "Print the OmniNode version.").option("-c, --config <file>", "Use a specific configuration file instead of ./omninode.yaml.").option("-p, --profile <name>", "Apply a named configuration profile (development/production/testing).").hook("preAction", (thisCommand) => {
|
|
5063
|
+
cliConfigOptions.path = thisCommand.opts().config;
|
|
5064
|
+
cliConfigOptions.profile = thisCommand.opts().profile;
|
|
5065
|
+
});
|
|
4659
5066
|
registerInitCommand(program);
|
|
4660
5067
|
registerProjectCommands(program);
|
|
4661
5068
|
registerProviderCommands(program);
|
|
@@ -4671,15 +5078,14 @@ function createProgram() {
|
|
|
4671
5078
|
registerAuditCommand(program);
|
|
4672
5079
|
registerMigrateCommand(program);
|
|
4673
5080
|
registerSecurityCommand(program);
|
|
5081
|
+
registerConfigCommands(program);
|
|
4674
5082
|
registerRunAlias(program);
|
|
4675
5083
|
return program;
|
|
4676
5084
|
}
|
|
4677
5085
|
function applyLoggingConfig() {
|
|
4678
5086
|
try {
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
if (logging) configureLogger(logging);
|
|
4682
|
-
}
|
|
5087
|
+
const resolved = loadProjectConfig();
|
|
5088
|
+
if (resolved.logging) configureLogger(resolved.logging);
|
|
4683
5089
|
} catch {
|
|
4684
5090
|
}
|
|
4685
5091
|
}
|
|
@@ -4693,15 +5099,16 @@ async function run(argv) {
|
|
|
4693
5099
|
}
|
|
4694
5100
|
}
|
|
4695
5101
|
function handleError(error) {
|
|
5102
|
+
const code = exitCodeFor(error);
|
|
4696
5103
|
if (isOmniNodeError(error)) {
|
|
4697
5104
|
console.error(`error: [${error.code}] ${error.message}`);
|
|
4698
5105
|
if (error.details !== void 0) {
|
|
4699
5106
|
console.error(JSON.stringify(error.details, null, 2));
|
|
4700
5107
|
}
|
|
4701
|
-
process.exit(
|
|
5108
|
+
process.exit(code);
|
|
4702
5109
|
}
|
|
4703
5110
|
console.error(error instanceof Error ? error.message : String(error));
|
|
4704
|
-
process.exit(
|
|
5111
|
+
process.exit(code);
|
|
4705
5112
|
}
|
|
4706
5113
|
var entry = process.argv[1] ? pathToFileURL(realpathSync(process.argv[1])).href : "";
|
|
4707
5114
|
if (import.meta.url === entry) {
|