@hilbras/omninode 2.0.0-alpha.15 → 2.0.0-alpha.17
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 +18 -2
- package/dist/cli/index.js +299 -87
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +83 -4
- package/dist/index.js +224 -37
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -23,7 +23,7 @@ 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.17";
|
|
27
27
|
|
|
28
28
|
// src/cli/exit-codes.ts
|
|
29
29
|
var EXIT = {
|
|
@@ -147,6 +147,10 @@ var projectConfigSchema = z.object({
|
|
|
147
147
|
}).strict().optional()
|
|
148
148
|
}).strict();
|
|
149
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(),
|
|
150
154
|
project: projectConfigSchema,
|
|
151
155
|
logging: z.object({
|
|
152
156
|
level: z.enum(["debug", "info", "warn", "error"]),
|
|
@@ -187,6 +191,85 @@ ${lines}
|
|
|
187
191
|
OmniNode never stores credentials in configuration. Replace each value with an environment-variable reference, e.g. api_key_env_var: MY_API_KEY.`;
|
|
188
192
|
}
|
|
189
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
|
+
|
|
190
273
|
// src/config/loader.ts
|
|
191
274
|
import { existsSync, readFileSync } from "fs";
|
|
192
275
|
import { parse as parseYaml } from "yaml";
|
|
@@ -213,38 +296,129 @@ function expandEnvRefs(text, env, source = "config") {
|
|
|
213
296
|
}
|
|
214
297
|
);
|
|
215
298
|
}
|
|
216
|
-
function
|
|
299
|
+
function loadConfigDetailed(options = {}) {
|
|
217
300
|
const env = options.env ?? process.env;
|
|
218
|
-
const
|
|
219
|
-
|
|
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) {
|
|
220
318
|
throw new ConfigError(
|
|
221
319
|
"CONFIG_NOT_FOUND",
|
|
222
320
|
"No omninode.yaml found. Run `omninode init` to create one, or pass an explicit path."
|
|
223
321
|
);
|
|
224
322
|
}
|
|
225
|
-
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
|
+
assertUniqueNames(parsed.data);
|
|
350
|
+
return {
|
|
351
|
+
config: toAppConfig(parsed.data),
|
|
352
|
+
sources: {
|
|
353
|
+
...projectFile !== void 0 ? { projectFile } : {},
|
|
354
|
+
...userFile !== void 0 ? { userFile } : {},
|
|
355
|
+
profile,
|
|
356
|
+
envOverrides: withEnv.applied,
|
|
357
|
+
defaults: withDefaults.applied
|
|
358
|
+
}
|
|
359
|
+
};
|
|
360
|
+
}
|
|
361
|
+
function assertUniqueNames(data) {
|
|
362
|
+
const groups = [
|
|
363
|
+
["provider", data.project.providers.map((p) => p.name)],
|
|
364
|
+
["agent", data.project.agents.map((a) => a.name)],
|
|
365
|
+
["role", data.project.roles.map((r) => r.id)],
|
|
366
|
+
["pipeline", data.project.pipelines.map((p) => p.id)]
|
|
367
|
+
];
|
|
368
|
+
for (const [kind, names] of groups) {
|
|
369
|
+
const seen = /* @__PURE__ */ new Set();
|
|
370
|
+
for (const name of names) {
|
|
371
|
+
if (seen.has(name)) {
|
|
372
|
+
throw new ConfigError(
|
|
373
|
+
"CONFIG_INVALID",
|
|
374
|
+
`Duplicate ${kind} name "${name}" \u2014 every ${kind} must have a unique name.`
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
seen.add(name);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function readConfigDocument(path2, optionsEnv) {
|
|
382
|
+
const raw = readFileSync(path2, "utf8");
|
|
226
383
|
const secretFindings = scanForInlineSecrets(raw);
|
|
227
384
|
if (secretFindings.length > 0) {
|
|
228
|
-
throw new ConfigError("CONFIG_INVALID", describeSecretFindings(
|
|
385
|
+
throw new ConfigError("CONFIG_INVALID", describeSecretFindings(path2, secretFindings));
|
|
229
386
|
}
|
|
230
|
-
const expanded = expandEnvRefs(raw,
|
|
231
|
-
let data;
|
|
387
|
+
const expanded = expandEnvRefs(raw, optionsEnv, path2);
|
|
232
388
|
try {
|
|
233
|
-
|
|
389
|
+
return parseYaml(expanded);
|
|
234
390
|
} catch (error) {
|
|
235
|
-
throw new ConfigError("CONFIG_INVALID", `Failed to parse ${
|
|
391
|
+
throw new ConfigError("CONFIG_INVALID", `Failed to parse ${path2}.`, { cause: error });
|
|
236
392
|
}
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
}
|
|
393
|
+
}
|
|
394
|
+
function configDiagnostics(config) {
|
|
395
|
+
const notes = [];
|
|
396
|
+
for (const provider of config.project.providers) {
|
|
397
|
+
if (!provider.apiKeyEnvVar && provider.baseUrl.startsWith("https://")) {
|
|
398
|
+
notes.push(
|
|
399
|
+
`Provider "${provider.name}": API key reference is missing \u2014 add api_key_env_var: <ENV_VAR> unless this endpoint is intentionally keyless.`
|
|
400
|
+
);
|
|
401
|
+
}
|
|
246
402
|
}
|
|
247
|
-
|
|
403
|
+
for (const agent of config.project.agents) {
|
|
404
|
+
if (!agent.command && agent.integration !== "custom") {
|
|
405
|
+
notes.push(`Agent "${agent.name}": no command configured \u2014 the process adapter needs one.`);
|
|
406
|
+
}
|
|
407
|
+
if (agent.allowExternalCwd) {
|
|
408
|
+
notes.push(`Agent "${agent.name}": may run outside the project root (allow_external_cwd).`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
for (const pipeline of config.project.pipelines) {
|
|
412
|
+
const ids = new Set(pipeline.steps.map((s) => s.id));
|
|
413
|
+
for (const step of pipeline.steps) {
|
|
414
|
+
for (const dep of step.dependsOn ?? []) {
|
|
415
|
+
if (!ids.has(dep)) {
|
|
416
|
+
notes.push(`Pipeline "${pipeline.id}": step "${step.id}" depends on unknown step "${dep}".`);
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
return notes;
|
|
248
422
|
}
|
|
249
423
|
function toAppConfig(yaml) {
|
|
250
424
|
return {
|
|
@@ -372,6 +546,18 @@ function defaultProjectConfigYaml(projectName = "My Project") {
|
|
|
372
546
|
`;
|
|
373
547
|
}
|
|
374
548
|
|
|
549
|
+
// src/cli/options.ts
|
|
550
|
+
var cliConfigOptions = {};
|
|
551
|
+
function loadProjectConfig() {
|
|
552
|
+
return loadConfigDetailed({
|
|
553
|
+
...cliConfigOptions.path !== void 0 ? { path: cliConfigOptions.path } : {},
|
|
554
|
+
...cliConfigOptions.profile !== void 0 ? { profile: cliConfigOptions.profile } : {}
|
|
555
|
+
}).config;
|
|
556
|
+
}
|
|
557
|
+
function activeConfigPath() {
|
|
558
|
+
return cliConfigOptions.path ?? findConfigFile();
|
|
559
|
+
}
|
|
560
|
+
|
|
375
561
|
// src/cli/commands/init.ts
|
|
376
562
|
import { existsSync as existsSync2, writeFileSync } from "fs";
|
|
377
563
|
async function runInit(options) {
|
|
@@ -459,7 +645,7 @@ function truncate(text, max) {
|
|
|
459
645
|
return text.length > max ? `${text.slice(0, max - 1)}\u2026` : text;
|
|
460
646
|
}
|
|
461
647
|
function appendToConfigList(listKey, item) {
|
|
462
|
-
const configPath =
|
|
648
|
+
const configPath = activeConfigPath();
|
|
463
649
|
if (!configPath) {
|
|
464
650
|
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found. Run `omninode init` first.");
|
|
465
651
|
}
|
|
@@ -1365,15 +1551,15 @@ async function writeFileAtomic(target, contents, durability = "rename") {
|
|
|
1365
1551
|
}
|
|
1366
1552
|
}
|
|
1367
1553
|
}
|
|
1368
|
-
async function inspectDocument(
|
|
1554
|
+
async function inspectDocument(path2) {
|
|
1369
1555
|
let raw;
|
|
1370
1556
|
try {
|
|
1371
|
-
await stat(
|
|
1557
|
+
await stat(path2);
|
|
1372
1558
|
} catch {
|
|
1373
1559
|
return { status: "missing" };
|
|
1374
1560
|
}
|
|
1375
1561
|
try {
|
|
1376
|
-
raw = await readFile(
|
|
1562
|
+
raw = await readFile(path2, "utf8");
|
|
1377
1563
|
} catch (error) {
|
|
1378
1564
|
return { status: "corrupt", detail: `unreadable: ${error instanceof Error ? error.message : String(error)}` };
|
|
1379
1565
|
}
|
|
@@ -1393,21 +1579,21 @@ async function inspectDocument(path) {
|
|
|
1393
1579
|
if (version < SCHEMA_VERSION) return { status: "v1-legacy" };
|
|
1394
1580
|
return { status: "current", schemaVersion: version, count: doc.items.length };
|
|
1395
1581
|
}
|
|
1396
|
-
async function readDocument(
|
|
1397
|
-
const state = await inspectDocument(
|
|
1582
|
+
async function readDocument(path2, onCorrupt, quarantine = true) {
|
|
1583
|
+
const state = await inspectDocument(path2);
|
|
1398
1584
|
if (state.status === "missing" || state.status === "empty") return [];
|
|
1399
1585
|
if (state.status === "future") {
|
|
1400
1586
|
const error = new Error(
|
|
1401
|
-
`${
|
|
1587
|
+
`${path2} was written by a newer OmniNode (schema v${state.schemaVersion} > v${SCHEMA_VERSION})`
|
|
1402
1588
|
);
|
|
1403
|
-
onCorrupt?.(
|
|
1589
|
+
onCorrupt?.(path2, error);
|
|
1404
1590
|
return [];
|
|
1405
1591
|
}
|
|
1406
1592
|
let raw;
|
|
1407
1593
|
try {
|
|
1408
|
-
raw = await readFile(
|
|
1594
|
+
raw = await readFile(path2, "utf8");
|
|
1409
1595
|
} catch (error) {
|
|
1410
|
-
onCorrupt?.(
|
|
1596
|
+
onCorrupt?.(path2, error);
|
|
1411
1597
|
return [];
|
|
1412
1598
|
}
|
|
1413
1599
|
try {
|
|
@@ -1416,16 +1602,16 @@ async function readDocument(path, onCorrupt, quarantine = true) {
|
|
|
1416
1602
|
return parsed.items ?? [];
|
|
1417
1603
|
} catch (error) {
|
|
1418
1604
|
if (quarantine) {
|
|
1419
|
-
const quarantinePath = `${
|
|
1605
|
+
const quarantinePath = `${path2}.corrupt-${Date.now()}`;
|
|
1420
1606
|
try {
|
|
1421
|
-
await rename(
|
|
1607
|
+
await rename(path2, quarantinePath);
|
|
1422
1608
|
logger.child({ component: "persistence" }).warn(
|
|
1423
|
-
`Quarantined unreadable store file ${
|
|
1609
|
+
`Quarantined unreadable store file ${path2} \u2192 ${quarantinePath}`
|
|
1424
1610
|
);
|
|
1425
1611
|
} catch {
|
|
1426
1612
|
}
|
|
1427
1613
|
}
|
|
1428
|
-
onCorrupt?.(
|
|
1614
|
+
onCorrupt?.(path2, error);
|
|
1429
1615
|
return [];
|
|
1430
1616
|
}
|
|
1431
1617
|
}
|
|
@@ -1955,7 +2141,7 @@ function registerPipelineInspect(pipelineGroup) {
|
|
|
1955
2141
|
function registerAgentInspect(agentGroup) {
|
|
1956
2142
|
jsonOption(
|
|
1957
2143
|
agentGroup.command("inspect <name>").description("Show an agent's effective configuration and recent tasks.").action(async (name, options) => {
|
|
1958
|
-
const config =
|
|
2144
|
+
const config = loadProjectConfig();
|
|
1959
2145
|
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
1960
2146
|
if (!agentConfig) throw new OmniNodeError("AGENT_NOT_FOUND", `No agent "${name}" is configured.`);
|
|
1961
2147
|
const registry = buildAgentRegistry(config);
|
|
@@ -1985,7 +2171,7 @@ function registerAgentInspect(agentGroup) {
|
|
|
1985
2171
|
function registerProviderInspect(providerGroup) {
|
|
1986
2172
|
jsonOption(
|
|
1987
2173
|
providerGroup.command("inspect <name>").description("Show a provider's authentication status and capabilities.").action(async (name, options) => {
|
|
1988
|
-
const config =
|
|
2174
|
+
const config = loadProjectConfig();
|
|
1989
2175
|
const providerConfig = config.project.providers.find((p) => p.name === name);
|
|
1990
2176
|
if (!providerConfig) throw new OmniNodeError("PROVIDER_NOT_FOUND", `No provider "${name}" is configured.`);
|
|
1991
2177
|
const { createProvider: createProvider2 } = await import("./providers-OYXBAQTX.js");
|
|
@@ -2018,7 +2204,7 @@ function registerProviderInspect(providerGroup) {
|
|
|
2018
2204
|
function registerProviderCommands(program) {
|
|
2019
2205
|
const provider = program.command("provider").description("Manage AI providers configured in omninode.yaml.");
|
|
2020
2206
|
provider.command("list").description("List configured providers.").option("--json", "Emit providers as JSON.").action(async (options) => {
|
|
2021
|
-
const config =
|
|
2207
|
+
const config = loadProjectConfig();
|
|
2022
2208
|
const providers = config.project.providers;
|
|
2023
2209
|
if (options.json) {
|
|
2024
2210
|
console.log(JSON.stringify(providers, null, 2));
|
|
@@ -2058,7 +2244,7 @@ function registerProviderCommands(program) {
|
|
|
2058
2244
|
registerProviderInspect(provider);
|
|
2059
2245
|
}
|
|
2060
2246
|
async function addProvider(options) {
|
|
2061
|
-
const configPath =
|
|
2247
|
+
const configPath = activeConfigPath();
|
|
2062
2248
|
if (!configPath) {
|
|
2063
2249
|
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found. Run `omninode init` first.");
|
|
2064
2250
|
}
|
|
@@ -2073,11 +2259,11 @@ async function addProvider(options) {
|
|
|
2073
2259
|
throw new OmniNodeError("CLI_USAGE", `Invalid provider options: ${issues}`);
|
|
2074
2260
|
}
|
|
2075
2261
|
const newProvider = candidate.data;
|
|
2076
|
-
const existing =
|
|
2262
|
+
const existing = loadProjectConfig();
|
|
2077
2263
|
if (existing.project.providers.some((p) => p.name === newProvider.name)) {
|
|
2078
2264
|
throw new OmniNodeError(
|
|
2079
2265
|
"CLI_USAGE",
|
|
2080
|
-
`Provider "${newProvider.name}" already exists in ${
|
|
2266
|
+
`Provider "${newProvider.name}" already exists in ${activeConfigPath() ?? "omninode.yaml"}.`
|
|
2081
2267
|
);
|
|
2082
2268
|
}
|
|
2083
2269
|
const writtenPath = appendToConfigList("providers", newProvider);
|
|
@@ -2091,11 +2277,11 @@ async function addProvider(options) {
|
|
|
2091
2277
|
}
|
|
2092
2278
|
}
|
|
2093
2279
|
function removeProvider(name) {
|
|
2094
|
-
const configPath =
|
|
2280
|
+
const configPath = activeConfigPath();
|
|
2095
2281
|
if (!configPath) {
|
|
2096
2282
|
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found.");
|
|
2097
2283
|
}
|
|
2098
|
-
const config =
|
|
2284
|
+
const config = loadProjectConfig();
|
|
2099
2285
|
if (!config.project.providers.some((p) => p.name === name)) {
|
|
2100
2286
|
throw new ProviderError("PROVIDER_NOT_FOUND", `No provider "${name}" is configured.`);
|
|
2101
2287
|
}
|
|
@@ -2111,7 +2297,7 @@ function removeProvider(name) {
|
|
|
2111
2297
|
console.log(`Removed provider "${name}" from ${configPath}.`);
|
|
2112
2298
|
}
|
|
2113
2299
|
async function testProvider(name, options = {}) {
|
|
2114
|
-
const config =
|
|
2300
|
+
const config = loadProjectConfig();
|
|
2115
2301
|
const providerConfig = config.project.providers.find((p) => p.name === name);
|
|
2116
2302
|
if (!providerConfig) {
|
|
2117
2303
|
const available = config.project.providers.map((p) => p.name).join(", ") || "(none)";
|
|
@@ -2314,6 +2500,7 @@ var TaskEngine = class {
|
|
|
2314
2500
|
const result = {
|
|
2315
2501
|
...output.summary !== void 0 ? { summary: output.summary } : {},
|
|
2316
2502
|
...output.reports !== void 0 && output.reports.length > 0 ? { reports: output.reports } : {},
|
|
2503
|
+
...output.protocol !== void 0 ? { protocol: output.protocol } : {},
|
|
2317
2504
|
// Keep the verbatim output (bounded) for report extraction and audit.
|
|
2318
2505
|
...output.rawOutput !== void 0 && output.rawOutput.length > 0 ? { rawOutput: output.rawOutput.slice(0, 1e4) } : {},
|
|
2319
2506
|
...output.error !== void 0 ? { error: output.error } : {},
|
|
@@ -2493,7 +2680,7 @@ var collectArgs = (value, previous) => [...previous, value];
|
|
|
2493
2680
|
function registerAgentCommands(program) {
|
|
2494
2681
|
const agent = program.command("agent").description("Manage CLI agents registered with OmniNode.");
|
|
2495
2682
|
agent.command("list").description("List configured agents.").option("--json", "Emit agents as JSON.").action(async (options) => {
|
|
2496
|
-
const config =
|
|
2683
|
+
const config = loadProjectConfig();
|
|
2497
2684
|
const agents = config.project.agents;
|
|
2498
2685
|
if (options.json) {
|
|
2499
2686
|
console.log(JSON.stringify(config.project.agents, null, 2));
|
|
@@ -2522,7 +2709,7 @@ function registerAgentCommands(program) {
|
|
|
2522
2709
|
(value) => Number(value)
|
|
2523
2710
|
).action(async (options) => addAgent(options));
|
|
2524
2711
|
agent.command("run <name> <objective>").description("Run a single task through this agent immediately.").action(async (name, objective) => {
|
|
2525
|
-
const config =
|
|
2712
|
+
const config = loadProjectConfig();
|
|
2526
2713
|
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
2527
2714
|
if (!agentConfig) {
|
|
2528
2715
|
throw new AgentError("AGENT_NOT_FOUND", `No agent "${name}" is configured.`);
|
|
@@ -2559,7 +2746,7 @@ async function addAgent(options) {
|
|
|
2559
2746
|
throw new OmniNodeError("CLI_USAGE", `Invalid agent options: ${issues}`);
|
|
2560
2747
|
}
|
|
2561
2748
|
const newAgent = candidate.data;
|
|
2562
|
-
const existing =
|
|
2749
|
+
const existing = loadProjectConfig();
|
|
2563
2750
|
if (existing.project.agents.some((a) => a.name === newAgent.name)) {
|
|
2564
2751
|
throw new OmniNodeError(
|
|
2565
2752
|
"CLI_USAGE",
|
|
@@ -2571,7 +2758,7 @@ async function addAgent(options) {
|
|
|
2571
2758
|
console.log(`Run \`omninode agent test ${newAgent.name}\` to verify the connection.`);
|
|
2572
2759
|
}
|
|
2573
2760
|
async function testAgent(name) {
|
|
2574
|
-
const config =
|
|
2761
|
+
const config = loadProjectConfig();
|
|
2575
2762
|
const agentConfig = config.project.agents.find((a) => a.name === name);
|
|
2576
2763
|
if (!agentConfig) {
|
|
2577
2764
|
const available = config.project.agents.map((a) => a.name).join(", ") || "(none)";
|
|
@@ -2602,7 +2789,7 @@ async function testAgent(name) {
|
|
|
2602
2789
|
function registerRoleCommands(program) {
|
|
2603
2790
|
const role = program.command("role").description("Inspect roles defined in omninode.yaml.");
|
|
2604
2791
|
role.command("list").description("List configured roles.").option("--json", "Emit roles as JSON.").action(async (options) => {
|
|
2605
|
-
const config =
|
|
2792
|
+
const config = loadProjectConfig();
|
|
2606
2793
|
const roles = config.project.roles;
|
|
2607
2794
|
if (options.json) {
|
|
2608
2795
|
console.log(JSON.stringify(config.project.roles, null, 2));
|
|
@@ -2626,7 +2813,7 @@ var collectFiles = (value, previous) => [...previous, value];
|
|
|
2626
2813
|
function registerTaskCommands(program) {
|
|
2627
2814
|
const task = program.command("task").description("Create, run and inspect tasks (\xA713).");
|
|
2628
2815
|
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) => {
|
|
2629
|
-
const config =
|
|
2816
|
+
const config = loadProjectConfig();
|
|
2630
2817
|
const engine = createTaskEngine(config);
|
|
2631
2818
|
const created = await engine.create({
|
|
2632
2819
|
objective,
|
|
@@ -2643,7 +2830,7 @@ function registerTaskCommands(program) {
|
|
|
2643
2830
|
console.log(`Run \`omninode task run ${created.id}\` to execute it.`);
|
|
2644
2831
|
});
|
|
2645
2832
|
task.command("run <task-id>").description("Run a task through its assigned agent.").action(async (taskId) => {
|
|
2646
|
-
const config =
|
|
2833
|
+
const config = loadProjectConfig();
|
|
2647
2834
|
const engine = createTaskEngine(config);
|
|
2648
2835
|
console.log(`Running task ${taskId}...`);
|
|
2649
2836
|
const finished = await engine.run(taskId);
|
|
@@ -2651,7 +2838,7 @@ function registerTaskCommands(program) {
|
|
|
2651
2838
|
process.exitCode = exitCodeForStatus(finished.status);
|
|
2652
2839
|
});
|
|
2653
2840
|
task.command("status <task-id>").description("Show task details and current status.").action(async (taskId) => {
|
|
2654
|
-
const config =
|
|
2841
|
+
const config = loadProjectConfig();
|
|
2655
2842
|
const engine = createTaskEngine(config);
|
|
2656
2843
|
const found = await engine.get(taskId);
|
|
2657
2844
|
if (!found) {
|
|
@@ -2663,7 +2850,7 @@ function registerTaskCommands(program) {
|
|
|
2663
2850
|
printOutcome(found, { verbose: true });
|
|
2664
2851
|
});
|
|
2665
2852
|
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) => {
|
|
2666
|
-
const config =
|
|
2853
|
+
const config = loadProjectConfig();
|
|
2667
2854
|
const engine = createTaskEngine(config);
|
|
2668
2855
|
const tasks = await engine.list(
|
|
2669
2856
|
options.status !== void 0 ? { status: options.status } : {}
|
|
@@ -2681,13 +2868,13 @@ function registerTaskCommands(program) {
|
|
|
2681
2868
|
}
|
|
2682
2869
|
});
|
|
2683
2870
|
task.command("cancel <task-id>").description("Cancel a task that has not finished yet.").action(async (taskId) => {
|
|
2684
|
-
const config =
|
|
2871
|
+
const config = loadProjectConfig();
|
|
2685
2872
|
const engine = createTaskEngine(config);
|
|
2686
2873
|
const cancelled = await engine.cancel(taskId);
|
|
2687
2874
|
console.log(`Task ${cancelled.id} is now "${cancelled.status}".`);
|
|
2688
2875
|
});
|
|
2689
2876
|
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) => {
|
|
2690
|
-
const config =
|
|
2877
|
+
const config = loadProjectConfig();
|
|
2691
2878
|
const engine = createTaskEngine(config);
|
|
2692
2879
|
const reset = await engine.retry(taskId);
|
|
2693
2880
|
console.log(`Task ${reset.id} reset to "${reset.status}".`);
|
|
@@ -4228,7 +4415,7 @@ function buildPipelineEngine(config, log = logger) {
|
|
|
4228
4415
|
function registerPipelineCommands(program) {
|
|
4229
4416
|
const pipeline = program.command("pipeline").description("Run multi-step AI pipelines defined in omninode.yaml (\xA714).");
|
|
4230
4417
|
pipeline.command("list").description("List configured pipelines.").option("--json", "Emit pipelines as JSON.").action(async (options) => {
|
|
4231
|
-
const config =
|
|
4418
|
+
const config = loadProjectConfig();
|
|
4232
4419
|
const pipelines = config.project.pipelines;
|
|
4233
4420
|
if (options.json) {
|
|
4234
4421
|
console.log(JSON.stringify(config.project.pipelines, null, 2));
|
|
@@ -4249,7 +4436,7 @@ function registerPipelineCommands(program) {
|
|
|
4249
4436
|
pipeline.command("retry <run-id> [objective]").description(
|
|
4250
4437
|
"Error recovery: re-run a recorded pipeline run as a new run, reusing its objective."
|
|
4251
4438
|
).action(async (runId, objective) => {
|
|
4252
|
-
const config =
|
|
4439
|
+
const config = loadProjectConfig();
|
|
4253
4440
|
const engine = buildPipelineEngine(config);
|
|
4254
4441
|
const previous = await engine.get(runId);
|
|
4255
4442
|
if (!previous) {
|
|
@@ -4268,7 +4455,7 @@ function registerPipelineCommands(program) {
|
|
|
4268
4455
|
await runPipelineAction(def.id, objective ?? previous.objective);
|
|
4269
4456
|
});
|
|
4270
4457
|
pipeline.command("cancel <run-id>").description("Request cancellation of a pipeline run (stops scheduling and cancels running tasks).").action(async (runId) => {
|
|
4271
|
-
const config =
|
|
4458
|
+
const config = loadProjectConfig();
|
|
4272
4459
|
const engine = buildPipelineEngine(config);
|
|
4273
4460
|
await engine.cancel(runId);
|
|
4274
4461
|
console.log(`Cancellation requested for run ${runId}.`);
|
|
@@ -4278,7 +4465,7 @@ function registerPipelineCommands(program) {
|
|
|
4278
4465
|
await createPipelineFromFile(id, options.from);
|
|
4279
4466
|
});
|
|
4280
4467
|
pipeline.command("validate <id>").description("Validate a pipeline definition before running it (\xA79.5).").action(async (id) => {
|
|
4281
|
-
const config =
|
|
4468
|
+
const config = loadProjectConfig();
|
|
4282
4469
|
const def = findPipeline(config, id);
|
|
4283
4470
|
const engine = buildPipelineEngine(config);
|
|
4284
4471
|
engine.validate(def);
|
|
@@ -4289,7 +4476,7 @@ function registerPipelineCommands(program) {
|
|
|
4289
4476
|
}
|
|
4290
4477
|
});
|
|
4291
4478
|
pipeline.command("runs <id>").description("List recorded runs of a pipeline.").action(async (id) => {
|
|
4292
|
-
const config =
|
|
4479
|
+
const config = loadProjectConfig();
|
|
4293
4480
|
const engine = buildPipelineEngine(config);
|
|
4294
4481
|
const runs = await engine.listRuns({ pipelineId: id });
|
|
4295
4482
|
if (runs.length === 0) {
|
|
@@ -4302,7 +4489,7 @@ function registerPipelineCommands(program) {
|
|
|
4302
4489
|
}
|
|
4303
4490
|
});
|
|
4304
4491
|
pipeline.command("status <run-id>").description("Show the recorded state of a pipeline run.").action(async (runId) => {
|
|
4305
|
-
const config =
|
|
4492
|
+
const config = loadProjectConfig();
|
|
4306
4493
|
const engine = buildPipelineEngine(config);
|
|
4307
4494
|
const run2 = await engine.get(runId);
|
|
4308
4495
|
if (!run2) {
|
|
@@ -4334,7 +4521,7 @@ function registerPipelineCommands(program) {
|
|
|
4334
4521
|
registerPipelineInspect(pipeline);
|
|
4335
4522
|
}
|
|
4336
4523
|
async function runPipelineAction(id, objective) {
|
|
4337
|
-
const config =
|
|
4524
|
+
const config = loadProjectConfig();
|
|
4338
4525
|
const def = findPipeline(config, id);
|
|
4339
4526
|
const engine = buildPipelineEngine(config);
|
|
4340
4527
|
console.log(
|
|
@@ -4366,7 +4553,7 @@ function registerRunAlias(program) {
|
|
|
4366
4553
|
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));
|
|
4367
4554
|
}
|
|
4368
4555
|
async function createPipelineFromFile(id, file) {
|
|
4369
|
-
const configPath =
|
|
4556
|
+
const configPath = activeConfigPath();
|
|
4370
4557
|
if (!configPath) {
|
|
4371
4558
|
throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found. Run `omninode init` first.");
|
|
4372
4559
|
}
|
|
@@ -4383,7 +4570,7 @@ async function createPipelineFromFile(id, file) {
|
|
|
4383
4570
|
const issues = candidate.error.issues.map((i) => `${i.path.join(".") || "(root)"}: ${i.message}`).join("; ");
|
|
4384
4571
|
throw new PipelineError("PIPELINE_INVALID", `Invalid pipeline definition: ${issues}`);
|
|
4385
4572
|
}
|
|
4386
|
-
const engine = buildPipelineEngine(
|
|
4573
|
+
const engine = buildPipelineEngine(loadProjectConfig());
|
|
4387
4574
|
engine.validate({ id, ...candidate.data });
|
|
4388
4575
|
const doc = parseDocument3(readFileSync4(configPath, "utf8"));
|
|
4389
4576
|
if (doc.getIn(["project", "pipelines"]) === void 0) {
|
|
@@ -4418,7 +4605,7 @@ function findPipeline(config, id) {
|
|
|
4418
4605
|
function registerMemoryCommands(program) {
|
|
4419
4606
|
const memory = program.command("memory").description("Inspect and manage persistent project memory (\xA719\u2013\xA720).");
|
|
4420
4607
|
memory.command("status").description("Show the active memory provider and how much is stored.").action(async () => {
|
|
4421
|
-
const config =
|
|
4608
|
+
const config = loadProjectConfig();
|
|
4422
4609
|
const configured = config.project.memory?.provider ?? "local (default \u2014 add a `memory:` section to opt in)";
|
|
4423
4610
|
const svc = new MemoryService(createMemoryProvider(config.project.memory));
|
|
4424
4611
|
const entries = await svc.providerRef.query({ limit: 1e4 });
|
|
@@ -4433,7 +4620,7 @@ function registerMemoryCommands(program) {
|
|
|
4433
4620
|
}
|
|
4434
4621
|
});
|
|
4435
4622
|
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) => {
|
|
4436
|
-
const config =
|
|
4623
|
+
const config = loadProjectConfig();
|
|
4437
4624
|
const svc = new MemoryService(createMemoryProvider(config.project.memory));
|
|
4438
4625
|
const entries = await svc.providerRef.query({
|
|
4439
4626
|
text,
|
|
@@ -4458,7 +4645,7 @@ function registerMemoryCommands(program) {
|
|
|
4458
4645
|
"Comma-separated tags.",
|
|
4459
4646
|
(value) => value.split(",").map((tag) => tag.trim()).filter((tag) => tag.length > 0)
|
|
4460
4647
|
).option("-k, --key <key>", "Entry key (defaults to a generated note key).").action(async (text, options) => {
|
|
4461
|
-
const config =
|
|
4648
|
+
const config = loadProjectConfig();
|
|
4462
4649
|
const svc = new MemoryService(createMemoryProvider(config.project.memory));
|
|
4463
4650
|
const key = options.key ?? `note:${Date.now().toString(36)}`;
|
|
4464
4651
|
await svc.providerRef.write({
|
|
@@ -4541,7 +4728,7 @@ function countBy(items, key) {
|
|
|
4541
4728
|
}
|
|
4542
4729
|
function registerStatusCommand(program) {
|
|
4543
4730
|
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) => {
|
|
4544
|
-
const config =
|
|
4731
|
+
const config = loadProjectConfig();
|
|
4545
4732
|
const tasks = await new FileTaskStore().list();
|
|
4546
4733
|
const runs = await new FilePipelineRunStore().list();
|
|
4547
4734
|
const reportStore = new FileReportStore();
|
|
@@ -4622,8 +4809,8 @@ var STORE_FILES = [
|
|
|
4622
4809
|
async function migrateProjectStores(directory = `${process.cwd()}/.omninode`, options = {}) {
|
|
4623
4810
|
const reports = [];
|
|
4624
4811
|
for (const file of STORE_FILES) {
|
|
4625
|
-
const
|
|
4626
|
-
const state = await inspectDocument(
|
|
4812
|
+
const path2 = `${directory}/${file}`;
|
|
4813
|
+
const state = await inspectDocument(path2);
|
|
4627
4814
|
if (state.status === "missing") {
|
|
4628
4815
|
reports.push({ file, state: state.status, migrated: false });
|
|
4629
4816
|
continue;
|
|
@@ -4645,9 +4832,9 @@ async function migrateProjectStores(directory = `${process.cwd()}/.omninode`, op
|
|
|
4645
4832
|
reports.push({ file, state: state.status, migrated: false, note: state.detail });
|
|
4646
4833
|
continue;
|
|
4647
4834
|
}
|
|
4648
|
-
const items = await readDocument(
|
|
4835
|
+
const items = await readDocument(path2, void 0, false);
|
|
4649
4836
|
if (!options.checkOnly) {
|
|
4650
|
-
await writeFileAtomic(
|
|
4837
|
+
await writeFileAtomic(path2, `${JSON.stringify({ schemaVersion: SCHEMA_VERSION, items }, null, 2)}
|
|
4651
4838
|
`);
|
|
4652
4839
|
}
|
|
4653
4840
|
const beforeCount = items.length;
|
|
@@ -4690,7 +4877,7 @@ function registerSecurityCommand(program) {
|
|
|
4690
4877
|
console.log(`Unknown security action "${action}". Available: audit.`);
|
|
4691
4878
|
return;
|
|
4692
4879
|
}
|
|
4693
|
-
const config =
|
|
4880
|
+
const config = loadProjectConfig();
|
|
4694
4881
|
const findings = [];
|
|
4695
4882
|
for (const agent of config.project.agents) {
|
|
4696
4883
|
const policy = resolveEnvPolicy(agent);
|
|
@@ -4751,14 +4938,15 @@ Trust model: agents execute with the permissions of the OS user running OmniNode
|
|
|
4751
4938
|
|
|
4752
4939
|
// src/cli/commands/config.ts
|
|
4753
4940
|
import { readFileSync as readFileSync5 } from "fs";
|
|
4941
|
+
import { parse as parseYaml2 } from "yaml";
|
|
4754
4942
|
function registerConfigCommands(program) {
|
|
4755
4943
|
const config = program.command("config").description("Inspect the project configuration (omninode.yaml).");
|
|
4756
4944
|
config.command("path").description("Print the path of the configuration file in use.").action(() => {
|
|
4757
|
-
const
|
|
4758
|
-
console.log(
|
|
4945
|
+
const path2 = activeConfigPath();
|
|
4946
|
+
console.log(path2 ?? "no omninode.yaml found");
|
|
4759
4947
|
});
|
|
4760
4948
|
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 =
|
|
4949
|
+
const resolved = loadConfigDetailed().config;
|
|
4762
4950
|
const payload = {
|
|
4763
4951
|
project: resolved.project.name,
|
|
4764
4952
|
providers: resolved.project.providers.map((p) => ({
|
|
@@ -4784,18 +4972,41 @@ function registerConfigCommands(program) {
|
|
|
4784
4972
|
console.log(`pipelines: ${payload.pipelines.join(", ") || "none"}`);
|
|
4785
4973
|
console.log(`memory: ${payload.memory ? `${payload.memory.provider}${payload.memory.required ? " (required)" : ""}` : "not configured"}`);
|
|
4786
4974
|
});
|
|
4975
|
+
config.command("profiles").description("List the configuration profiles defined in this project.").action(() => {
|
|
4976
|
+
const path2 = activeConfigPath();
|
|
4977
|
+
const raw = path2 ? parseYaml2(readFileSync5(path2, "utf8")) : {};
|
|
4978
|
+
const names = Object.keys(raw.profiles ?? {});
|
|
4979
|
+
const active = loadConfigDetailed().sources.profile;
|
|
4980
|
+
console.log(`active: ${active}`);
|
|
4981
|
+
console.log(`defined: ${names.length > 0 ? names.join(", ") : "none (add a `profiles:` section)"}`);
|
|
4982
|
+
});
|
|
4787
4983
|
config.command("validate").description("Validate the configuration file, including the inline-secret scan.").action(() => {
|
|
4788
|
-
const
|
|
4789
|
-
if (!
|
|
4790
|
-
const findings = scanForInlineSecrets(readFileSync5(
|
|
4984
|
+
const path2 = activeConfigPath();
|
|
4985
|
+
if (!path2) throw new OmniNodeError("CONFIG_NOT_FOUND", "No omninode.yaml found.");
|
|
4986
|
+
const findings = scanForInlineSecrets(readFileSync5(path2, "utf8"));
|
|
4791
4987
|
if (findings.length > 0) {
|
|
4792
4988
|
throw new OmniNodeError(
|
|
4793
4989
|
"CONFIG_INVALID",
|
|
4794
|
-
`${
|
|
4990
|
+
`${path2} contains ${findings.length} inline secret(s) (line ${findings.map((f) => f.line).join(", ")}). Use environment-variable references.`
|
|
4795
4991
|
);
|
|
4796
4992
|
}
|
|
4797
|
-
const resolved =
|
|
4798
|
-
console.log(
|
|
4993
|
+
const { config: resolved, sources } = loadConfigDetailed();
|
|
4994
|
+
console.log(
|
|
4995
|
+
`${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).`
|
|
4996
|
+
);
|
|
4997
|
+
console.log(` profile: ${sources.profile}`);
|
|
4998
|
+
if (sources.userFile) console.log(` user cfg: ${sources.userFile}`);
|
|
4999
|
+
if (sources.envOverrides.length > 0) {
|
|
5000
|
+
console.log(` env overrides: ${sources.envOverrides.join(", ")}`);
|
|
5001
|
+
}
|
|
5002
|
+
if (sources.defaults.length > 0) {
|
|
5003
|
+
console.log(` defaults applied: ${sources.defaults.join(", ")}`);
|
|
5004
|
+
}
|
|
5005
|
+
const diagnostics = configDiagnostics(resolved);
|
|
5006
|
+
if (diagnostics.length > 0) {
|
|
5007
|
+
console.log(" diagnostics:");
|
|
5008
|
+
for (const note of diagnostics) console.log(` - ${note}`);
|
|
5009
|
+
}
|
|
4799
5010
|
});
|
|
4800
5011
|
}
|
|
4801
5012
|
|
|
@@ -4821,7 +5032,7 @@ function registerModelGroup(program, name) {
|
|
|
4821
5032
|
`Unknown capability "${options.capability}". Valid capabilities: ${CAPABILITIES.join(", ")}.`
|
|
4822
5033
|
);
|
|
4823
5034
|
}
|
|
4824
|
-
const config =
|
|
5035
|
+
const config = loadProjectConfig();
|
|
4825
5036
|
const selected = config.project.providers.filter(
|
|
4826
5037
|
(p) => providerName ? p.name === providerName : true
|
|
4827
5038
|
);
|
|
@@ -4870,7 +5081,10 @@ function createProgram() {
|
|
|
4870
5081
|
const program = new Command();
|
|
4871
5082
|
program.name("omninode").description(
|
|
4872
5083
|
"Provider-agnostic multi-AI orchestration platform: coordinate CLI agents, AI providers, memory, roles, reports and planning in one execution system."
|
|
4873
|
-
).version(OMNINODE_VERSION, "-v, --version", "Print the OmniNode version.")
|
|
5084
|
+
).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) => {
|
|
5085
|
+
cliConfigOptions.path = thisCommand.opts().config;
|
|
5086
|
+
cliConfigOptions.profile = thisCommand.opts().profile;
|
|
5087
|
+
});
|
|
4874
5088
|
registerInitCommand(program);
|
|
4875
5089
|
registerProjectCommands(program);
|
|
4876
5090
|
registerProviderCommands(program);
|
|
@@ -4892,10 +5106,8 @@ function createProgram() {
|
|
|
4892
5106
|
}
|
|
4893
5107
|
function applyLoggingConfig() {
|
|
4894
5108
|
try {
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
if (logging) configureLogger(logging);
|
|
4898
|
-
}
|
|
5109
|
+
const resolved = loadProjectConfig();
|
|
5110
|
+
if (resolved.logging) configureLogger(resolved.logging);
|
|
4899
5111
|
} catch {
|
|
4900
5112
|
}
|
|
4901
5113
|
}
|