iranti 0.2.10 → 0.2.12
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 +8 -0
- package/dist/scripts/iranti-cli.js +289 -122
- package/dist/scripts/iranti-mcp.js +1 -1
- package/dist/scripts/seed.js +10 -10
- package/dist/src/api/server.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -245,6 +245,14 @@ iranti run --instance local
|
|
|
245
245
|
|
|
246
246
|
If local PostgreSQL is available, setup can bootstrap a localhost database for you. If local PostgreSQL is not available, setup recommends Docker when Docker is installed, and otherwise steers you to managed PostgreSQL with concrete install guidance.
|
|
247
247
|
|
|
248
|
+
If something still fails and you need more detail, use:
|
|
249
|
+
|
|
250
|
+
```bash
|
|
251
|
+
iranti doctor --debug
|
|
252
|
+
iranti run --instance local --debug
|
|
253
|
+
iranti upgrade --verbose
|
|
254
|
+
```
|
|
255
|
+
|
|
248
256
|
Advanced/manual path:
|
|
249
257
|
|
|
250
258
|
```bash
|
|
@@ -21,6 +21,15 @@ const resolutionist_1 = require("../src/resolutionist");
|
|
|
21
21
|
const chat_1 = require("../src/chat");
|
|
22
22
|
const backends_1 = require("../src/library/backends");
|
|
23
23
|
const sdk_1 = require("../src/sdk");
|
|
24
|
+
class CliError extends Error {
|
|
25
|
+
constructor(code, message, hints = [], details) {
|
|
26
|
+
super(message);
|
|
27
|
+
this.name = 'CliError';
|
|
28
|
+
this.code = code;
|
|
29
|
+
this.hints = hints;
|
|
30
|
+
this.details = details;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
24
33
|
const PROVIDER_ENV_KEYS = {
|
|
25
34
|
mock: null,
|
|
26
35
|
ollama: null,
|
|
@@ -43,6 +52,8 @@ const ANSI = {
|
|
|
43
52
|
cyan: '\x1b[36m',
|
|
44
53
|
gray: '\x1b[90m',
|
|
45
54
|
};
|
|
55
|
+
let CLI_DEBUG = process.argv.includes('--debug') || process.env.IRANTI_DEBUG === '1';
|
|
56
|
+
let CLI_VERBOSE = CLI_DEBUG || process.argv.includes('--verbose') || process.env.IRANTI_VERBOSE === '1';
|
|
46
57
|
function useColor() {
|
|
47
58
|
return Boolean(process.stdout.isTTY && !process.env.NO_COLOR);
|
|
48
59
|
}
|
|
@@ -81,6 +92,25 @@ function printNextSteps(steps) {
|
|
|
81
92
|
console.log(` ${index + 1}. ${step}`);
|
|
82
93
|
}
|
|
83
94
|
}
|
|
95
|
+
function setCliDebugFlags(args) {
|
|
96
|
+
CLI_DEBUG = CLI_DEBUG || hasFlag(args, 'debug');
|
|
97
|
+
CLI_VERBOSE = CLI_VERBOSE || CLI_DEBUG || hasFlag(args, 'verbose');
|
|
98
|
+
}
|
|
99
|
+
function debugLog(message, details) {
|
|
100
|
+
if (!CLI_DEBUG)
|
|
101
|
+
return;
|
|
102
|
+
const suffix = details && Object.keys(details).length > 0 ? ` ${JSON.stringify(details)}` : '';
|
|
103
|
+
console.log(`${paint('[DEBUG]', 'gray')} ${message}${suffix}`);
|
|
104
|
+
}
|
|
105
|
+
function verboseLog(message, details) {
|
|
106
|
+
if (!CLI_VERBOSE)
|
|
107
|
+
return;
|
|
108
|
+
const suffix = details && Object.keys(details).length > 0 ? ` ${JSON.stringify(details)}` : '';
|
|
109
|
+
console.log(`${paint('[TRACE]', 'gray')} ${message}${suffix}`);
|
|
110
|
+
}
|
|
111
|
+
function cliError(code, message, hints = [], details) {
|
|
112
|
+
return new CliError(code, message, hints, details);
|
|
113
|
+
}
|
|
84
114
|
function parseArgs(argv) {
|
|
85
115
|
const flags = new Map();
|
|
86
116
|
const positionals = [];
|
|
@@ -197,6 +227,7 @@ function formatSetupBootstrapFailure(error) {
|
|
|
197
227
|
}
|
|
198
228
|
async function handoffToScript(scriptName, rawArgs) {
|
|
199
229
|
const builtPath = builtScriptPath(scriptName);
|
|
230
|
+
debugLog('Handing off to companion script.', { scriptName, builtPath, rawArgs: rawArgs.join(' ') });
|
|
200
231
|
if (fs_1.default.existsSync(builtPath)) {
|
|
201
232
|
await new Promise((resolve, reject) => {
|
|
202
233
|
const child = (0, child_process_1.spawn)(process.execPath, [builtPath, ...rawArgs], {
|
|
@@ -219,7 +250,7 @@ async function handoffToScript(scriptName, rawArgs) {
|
|
|
219
250
|
}
|
|
220
251
|
const sourcePath = path_1.default.resolve(process.cwd(), 'scripts', `${scriptName}.ts`);
|
|
221
252
|
if (!fs_1.default.existsSync(sourcePath)) {
|
|
222
|
-
throw
|
|
253
|
+
throw cliError('IRANTI_SCRIPT_NOT_FOUND', `Unable to locate ${scriptName} implementation.`, ['Run from an installed Iranti package or from the Iranti repo root where scripts/ exists.'], { scriptName, sourcePath, builtPath });
|
|
223
254
|
}
|
|
224
255
|
await new Promise((resolve, reject) => {
|
|
225
256
|
const child = (0, child_process_1.spawn)('npx', ['ts-node', sourcePath, ...rawArgs], {
|
|
@@ -428,8 +459,12 @@ function instancePaths(root, name) {
|
|
|
428
459
|
async function loadInstanceEnv(root, name) {
|
|
429
460
|
const paths = instancePaths(root, name);
|
|
430
461
|
if (!fs_1.default.existsSync(paths.envFile)) {
|
|
431
|
-
throw
|
|
462
|
+
throw cliError('IRANTI_INSTANCE_NOT_FOUND', `Instance '${name}' not found at ${paths.instanceDir}`, [
|
|
463
|
+
'Run `iranti instance list` to see known instances.',
|
|
464
|
+
`Run \`iranti setup\` or \`iranti instance create ${name}\` if this instance does not exist yet.`,
|
|
465
|
+
], { instance: name, root, instanceDir: paths.instanceDir });
|
|
432
466
|
}
|
|
467
|
+
debugLog('Loaded instance env target.', { instance: name, envFile: paths.envFile });
|
|
433
468
|
return {
|
|
434
469
|
...paths,
|
|
435
470
|
env: await readEnvFile(paths.envFile),
|
|
@@ -451,15 +486,15 @@ async function resolveProviderKeyTarget(args) {
|
|
|
451
486
|
const projectPath = path_1.default.resolve(getFlag(args, 'project') ?? process.cwd());
|
|
452
487
|
const bindingFile = path_1.default.join(projectPath, '.env.iranti');
|
|
453
488
|
if (!fs_1.default.existsSync(bindingFile)) {
|
|
454
|
-
throw
|
|
489
|
+
throw cliError('IRANTI_PROJECT_BINDING_MISSING', 'No --instance provided and no .env.iranti found in the current project.', ['Run `iranti project init . --instance <name>` or pass `--instance <name>`.'], { projectPath, bindingFile });
|
|
455
490
|
}
|
|
456
491
|
const binding = await readEnvFile(bindingFile);
|
|
457
492
|
const envFile = binding.IRANTI_INSTANCE_ENV?.trim();
|
|
458
493
|
if (!envFile) {
|
|
459
|
-
throw
|
|
494
|
+
throw cliError('IRANTI_BINDING_INSTANCE_ENV_MISSING', `Project binding is missing IRANTI_INSTANCE_ENV: ${bindingFile}`, ['Run `iranti configure project` to refresh the binding.'], { bindingFile });
|
|
460
495
|
}
|
|
461
496
|
if (!fs_1.default.existsSync(envFile)) {
|
|
462
|
-
throw
|
|
497
|
+
throw cliError('IRANTI_BINDING_INSTANCE_ENV_NOT_FOUND', `Instance env referenced by project binding was not found: ${envFile}`, ['Run `iranti configure project` to refresh the binding or recreate the target instance.'], { bindingFile, envFile });
|
|
463
498
|
}
|
|
464
499
|
return {
|
|
465
500
|
instanceName: binding.IRANTI_INSTANCE?.trim() || undefined,
|
|
@@ -707,7 +742,7 @@ function isSupportedProvider(provider) {
|
|
|
707
742
|
async function promptYesNo(session, prompt, defaultValue) {
|
|
708
743
|
const defaultToken = defaultValue ? 'Y/n' : 'y/N';
|
|
709
744
|
while (true) {
|
|
710
|
-
const answer = (await session.line(`${prompt} (${defaultToken})
|
|
745
|
+
const answer = (await session.line(`${prompt} (${defaultToken})`) ?? '').trim().toLowerCase();
|
|
711
746
|
if (!answer)
|
|
712
747
|
return defaultValue;
|
|
713
748
|
if (['y', 'yes'].includes(answer))
|
|
@@ -730,7 +765,7 @@ async function promptRequiredSecret(session, prompt, currentValue) {
|
|
|
730
765
|
const value = (await session.secretRequired(prompt, currentValue) ?? '').trim();
|
|
731
766
|
if (value.length > 0 && !detectPlaceholder(value))
|
|
732
767
|
return value;
|
|
733
|
-
console.log(`${warnLabel()}
|
|
768
|
+
console.log(`${warnLabel()} ${prompt} is required.`);
|
|
734
769
|
}
|
|
735
770
|
}
|
|
736
771
|
function makeLegacyInstanceApiKey(instanceName) {
|
|
@@ -1466,8 +1501,13 @@ function collectDoctorRemediations(checks, envSource, envFile) {
|
|
|
1466
1501
|
add('Run `iranti setup`, or rerun `iranti doctor` with `--instance <name>` or `--env <file>`.');
|
|
1467
1502
|
}
|
|
1468
1503
|
}
|
|
1469
|
-
if (check.name === 'database configuration' && check.status === 'fail') {
|
|
1470
|
-
|
|
1504
|
+
if ((check.name === 'database configuration' || check.name === 'bound instance database configuration') && check.status === 'fail') {
|
|
1505
|
+
if (check.name === 'bound instance database configuration') {
|
|
1506
|
+
add('Fix the linked instance env, or rerun `iranti setup` / `iranti configure instance` for the bound instance.');
|
|
1507
|
+
}
|
|
1508
|
+
else {
|
|
1509
|
+
add(`Set a real DATABASE_URL in ${envFile ?? 'the target env file'}, or rerun \`iranti setup\` to configure the database again.`);
|
|
1510
|
+
}
|
|
1471
1511
|
}
|
|
1472
1512
|
if (check.name === 'project binding url' && check.status === 'fail') {
|
|
1473
1513
|
add('Run `iranti configure project` to refresh the project binding, or set IRANTI_URL in `.env.iranti`.');
|
|
@@ -1475,15 +1515,18 @@ function collectDoctorRemediations(checks, envSource, envFile) {
|
|
|
1475
1515
|
if (check.name === 'project api key' && check.status === 'fail') {
|
|
1476
1516
|
add('Run `iranti configure project` or set IRANTI_API_KEY in `.env.iranti`.');
|
|
1477
1517
|
}
|
|
1518
|
+
if (check.name === 'bound instance env' && check.status !== 'pass') {
|
|
1519
|
+
add('Run `iranti configure project` to refresh the project binding, or set IRANTI_INSTANCE_ENV in `.env.iranti` so doctor can inspect the bound local instance.');
|
|
1520
|
+
}
|
|
1478
1521
|
if (check.name === 'api key' && check.status !== 'pass') {
|
|
1479
1522
|
add(envSource === 'project-binding'
|
|
1480
1523
|
? 'Set IRANTI_API_KEY in the project binding, or rerun `iranti configure project`.'
|
|
1481
1524
|
: 'Create or rotate an Iranti key with `iranti auth create-key`, then store it in the target env.');
|
|
1482
1525
|
}
|
|
1483
|
-
if (check.name === 'provider credentials' && check.status === 'fail') {
|
|
1526
|
+
if ((check.name === 'provider credentials' || check.name === 'bound instance provider credentials') && check.status === 'fail') {
|
|
1484
1527
|
add('Store or refresh the upstream provider key with `iranti add api-key` or `iranti update api-key`.');
|
|
1485
1528
|
}
|
|
1486
|
-
if (check.name === 'vector backend' && check.status === 'fail') {
|
|
1529
|
+
if ((check.name === 'vector backend' || check.name === 'bound instance vector backend') && check.status === 'fail') {
|
|
1487
1530
|
add('Check the vector backend env vars, or switch back to `IRANTI_VECTOR_BACKEND=pgvector` if the external backend is not ready.');
|
|
1488
1531
|
}
|
|
1489
1532
|
}
|
|
@@ -1498,6 +1541,7 @@ function resolveDoctorEnvTarget(args) {
|
|
|
1498
1541
|
const explicitEnv = getFlag(args, 'env');
|
|
1499
1542
|
const cwd = process.cwd();
|
|
1500
1543
|
if (explicitEnv) {
|
|
1544
|
+
debugLog('Doctor target resolved from explicit env.', { envFile: path_1.default.resolve(explicitEnv) });
|
|
1501
1545
|
return {
|
|
1502
1546
|
envFile: path_1.default.resolve(explicitEnv),
|
|
1503
1547
|
envSource: 'explicit-env',
|
|
@@ -1505,6 +1549,7 @@ function resolveDoctorEnvTarget(args) {
|
|
|
1505
1549
|
}
|
|
1506
1550
|
if (instanceName) {
|
|
1507
1551
|
const root = resolveInstallRoot(args, scope);
|
|
1552
|
+
debugLog('Doctor target resolved from instance.', { instance: instanceName, root });
|
|
1508
1553
|
return {
|
|
1509
1554
|
envFile: path_1.default.join(root, 'instances', instanceName, '.env'),
|
|
1510
1555
|
envSource: `instance:${instanceName}`,
|
|
@@ -1513,11 +1558,14 @@ function resolveDoctorEnvTarget(args) {
|
|
|
1513
1558
|
const repoEnv = path_1.default.join(cwd, '.env');
|
|
1514
1559
|
const projectEnv = path_1.default.join(cwd, '.env.iranti');
|
|
1515
1560
|
if (fs_1.default.existsSync(repoEnv)) {
|
|
1561
|
+
debugLog('Doctor target resolved from repo env.', { envFile: repoEnv });
|
|
1516
1562
|
return { envFile: repoEnv, envSource: 'repo' };
|
|
1517
1563
|
}
|
|
1518
1564
|
if (fs_1.default.existsSync(projectEnv)) {
|
|
1565
|
+
debugLog('Doctor target resolved from project binding.', { envFile: projectEnv });
|
|
1519
1566
|
return { envFile: projectEnv, envSource: 'project-binding' };
|
|
1520
1567
|
}
|
|
1568
|
+
debugLog('Doctor target resolution found no env file.', { cwd });
|
|
1521
1569
|
return { envFile: null, envSource: 'repo' };
|
|
1522
1570
|
}
|
|
1523
1571
|
function resolveUpgradeTarget(raw) {
|
|
@@ -1638,6 +1686,11 @@ function quoteForCmd(arg) {
|
|
|
1638
1686
|
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
1639
1687
|
}
|
|
1640
1688
|
function runCommandCapture(executable, args, cwd, extraEnv) {
|
|
1689
|
+
verboseLog('Running subprocess (capture).', {
|
|
1690
|
+
executable,
|
|
1691
|
+
args: args.join(' '),
|
|
1692
|
+
cwd: cwd ?? process.cwd(),
|
|
1693
|
+
});
|
|
1641
1694
|
const proc = process.platform === 'win32'
|
|
1642
1695
|
? (0, child_process_1.spawnSync)(process.env.ComSpec ?? 'cmd.exe', [
|
|
1643
1696
|
'/d',
|
|
@@ -1661,13 +1714,24 @@ function runCommandCapture(executable, args, cwd, extraEnv) {
|
|
|
1661
1714
|
...extraEnv,
|
|
1662
1715
|
},
|
|
1663
1716
|
});
|
|
1664
|
-
|
|
1717
|
+
const result = {
|
|
1665
1718
|
status: proc.status,
|
|
1666
1719
|
stdout: proc.stdout ?? '',
|
|
1667
1720
|
stderr: proc.stderr ?? '',
|
|
1668
1721
|
};
|
|
1722
|
+
verboseLog('Subprocess finished (capture).', {
|
|
1723
|
+
executable,
|
|
1724
|
+
status: result.status ?? -1,
|
|
1725
|
+
stderr: result.stderr.trim() || null,
|
|
1726
|
+
});
|
|
1727
|
+
return result;
|
|
1669
1728
|
}
|
|
1670
1729
|
function runCommandInteractive(step) {
|
|
1730
|
+
verboseLog('Running subprocess (interactive).', {
|
|
1731
|
+
label: step.label,
|
|
1732
|
+
command: step.display,
|
|
1733
|
+
cwd: step.cwd ?? process.cwd(),
|
|
1734
|
+
});
|
|
1671
1735
|
const proc = process.platform === 'win32'
|
|
1672
1736
|
? (0, child_process_1.spawnSync)(process.env.ComSpec ?? 'cmd.exe', [
|
|
1673
1737
|
'/d',
|
|
@@ -1681,6 +1745,10 @@ function runCommandInteractive(step) {
|
|
|
1681
1745
|
cwd: step.cwd,
|
|
1682
1746
|
stdio: 'inherit',
|
|
1683
1747
|
});
|
|
1748
|
+
verboseLog('Subprocess finished (interactive).', {
|
|
1749
|
+
label: step.label,
|
|
1750
|
+
status: proc.status ?? -1,
|
|
1751
|
+
});
|
|
1684
1752
|
return proc.status;
|
|
1685
1753
|
}
|
|
1686
1754
|
function deriveDatabaseUrlForMode(mode, instanceName, explicitDatabaseUrl) {
|
|
@@ -2341,7 +2409,7 @@ async function setupCommand(args) {
|
|
|
2341
2409
|
await withPromptSession(async (prompt) => {
|
|
2342
2410
|
let setupMode = 'isolated';
|
|
2343
2411
|
while (true) {
|
|
2344
|
-
const chosen = (await prompt.line('
|
|
2412
|
+
const chosen = (await prompt.line('Runtime mode (isolated or shared)', 'isolated') ?? 'isolated').trim().toLowerCase();
|
|
2345
2413
|
if (chosen === 'shared' || chosen === 'isolated') {
|
|
2346
2414
|
setupMode = chosen;
|
|
2347
2415
|
break;
|
|
@@ -2351,12 +2419,12 @@ async function setupCommand(args) {
|
|
|
2351
2419
|
let finalScope = 'user';
|
|
2352
2420
|
let finalRoot = '';
|
|
2353
2421
|
if (setupMode === 'isolated') {
|
|
2354
|
-
finalRoot = path_1.default.resolve(await promptNonEmpty(prompt, '
|
|
2422
|
+
finalRoot = path_1.default.resolve(await promptNonEmpty(prompt, 'Isolated runtime path', explicitRoot ?? path_1.default.join(process.cwd(), '.iranti-runtime')));
|
|
2355
2423
|
finalScope = 'user';
|
|
2356
2424
|
}
|
|
2357
2425
|
else {
|
|
2358
2426
|
while (true) {
|
|
2359
|
-
const chosenScope = (await prompt.line('Install scope
|
|
2427
|
+
const chosenScope = (await prompt.line('Install scope (user or system)', explicitScope ?? 'user') ?? 'user').trim().toLowerCase();
|
|
2360
2428
|
if (chosenScope === 'user' || chosenScope === 'system') {
|
|
2361
2429
|
finalScope = chosenScope;
|
|
2362
2430
|
break;
|
|
@@ -2367,7 +2435,7 @@ async function setupCommand(args) {
|
|
|
2367
2435
|
}
|
|
2368
2436
|
await ensureRuntimeInstalled(finalRoot, finalScope);
|
|
2369
2437
|
console.log(`${okLabel()} Runtime ready at ${finalRoot}`);
|
|
2370
|
-
const instanceName = sanitizeIdentifier(await promptNonEmpty(prompt, '
|
|
2438
|
+
const instanceName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Instance name', setupMode === 'isolated' ? sanitizeIdentifier(path_1.default.basename(process.cwd()), 'local') : 'local'), 'local');
|
|
2371
2439
|
const existingInstance = fs_1.default.existsSync(instancePaths(finalRoot, instanceName).envFile)
|
|
2372
2440
|
? await loadInstanceEnv(finalRoot, instanceName)
|
|
2373
2441
|
: null;
|
|
@@ -2378,7 +2446,7 @@ async function setupCommand(args) {
|
|
|
2378
2446
|
console.log(`${infoLabel()} Creating new instance '${instanceName}'.`);
|
|
2379
2447
|
}
|
|
2380
2448
|
const existingPort = Number.parseInt(existingInstance?.env.IRANTI_PORT ?? '3001', 10);
|
|
2381
|
-
const port = await chooseAvailablePort(prompt, '
|
|
2449
|
+
const port = await chooseAvailablePort(prompt, 'API port', existingPort, Boolean(existingInstance));
|
|
2382
2450
|
const dockerAvailable = hasDockerInstalled();
|
|
2383
2451
|
const psqlAvailable = hasCommandInstalled('psql');
|
|
2384
2452
|
let dbUrl = '';
|
|
@@ -2386,14 +2454,14 @@ async function setupCommand(args) {
|
|
|
2386
2454
|
let databaseMode = recommendedDatabaseMode;
|
|
2387
2455
|
while (true) {
|
|
2388
2456
|
const defaultMode = recommendedDatabaseMode;
|
|
2389
|
-
const dbMode = (await prompt.line('
|
|
2457
|
+
const dbMode = (await prompt.line('Database mode (local, managed, or docker)', defaultMode) ?? defaultMode).trim().toLowerCase();
|
|
2390
2458
|
if (dbMode === 'existing' || dbMode === 'local' || dbMode === 'managed') {
|
|
2391
2459
|
databaseMode = dbMode === 'existing' ? 'local' : dbMode;
|
|
2392
2460
|
const defaultDatabaseUrl = databaseMode === 'local'
|
|
2393
2461
|
? existingInstance?.env.DATABASE_URL ?? deriveDatabaseUrlForMode('local', instanceName)
|
|
2394
2462
|
: existingInstance?.env.DATABASE_URL ?? '';
|
|
2395
2463
|
while (true) {
|
|
2396
|
-
dbUrl = await promptNonEmpty(prompt, '
|
|
2464
|
+
dbUrl = await promptNonEmpty(prompt, 'DATABASE_URL', defaultDatabaseUrl);
|
|
2397
2465
|
if (!detectPlaceholder(dbUrl))
|
|
2398
2466
|
break;
|
|
2399
2467
|
console.log(`${warnLabel()} DATABASE_URL still looks like a placeholder. Enter a real connection string before finishing setup.`);
|
|
@@ -2415,9 +2483,9 @@ async function setupCommand(args) {
|
|
|
2415
2483
|
console.log(`${warnLabel()} Docker is not installed or not on PATH. Choose local or managed instead.`);
|
|
2416
2484
|
continue;
|
|
2417
2485
|
}
|
|
2418
|
-
const dbHostPort = await chooseAvailablePort(prompt, '
|
|
2419
|
-
const dbName = sanitizeIdentifier(await promptNonEmpty(prompt, '
|
|
2420
|
-
const dbPassword = await promptRequiredSecret(prompt, '
|
|
2486
|
+
const dbHostPort = await chooseAvailablePort(prompt, 'Docker PostgreSQL host port', 5432, false);
|
|
2487
|
+
const dbName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Docker PostgreSQL database name', `iranti_${instanceName}`), `iranti_${instanceName}`);
|
|
2488
|
+
const dbPassword = await promptRequiredSecret(prompt, 'Docker PostgreSQL password');
|
|
2421
2489
|
const containerName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Docker container name', `iranti_${instanceName}_db`), `iranti_${instanceName}_db`);
|
|
2422
2490
|
dbUrl = `postgresql://postgres:${dbPassword}@localhost:${dbHostPort}/${dbName}`;
|
|
2423
2491
|
console.log(`${infoLabel()} Docker will be used only for PostgreSQL. Iranti itself does not require Docker once a PostgreSQL database is available.`);
|
|
@@ -2441,7 +2509,7 @@ async function setupCommand(args) {
|
|
|
2441
2509
|
let provider = normalizeProvider(existingInstance?.env.LLM_PROVIDER ?? 'openai') ?? 'openai';
|
|
2442
2510
|
while (true) {
|
|
2443
2511
|
listProviderChoices(provider, existingInstance?.env ?? {});
|
|
2444
|
-
const chosen = normalizeProvider(await promptNonEmpty(prompt, '
|
|
2512
|
+
const chosen = normalizeProvider(await promptNonEmpty(prompt, 'Default LLM provider', provider));
|
|
2445
2513
|
if (chosen && isSupportedProvider(chosen)) {
|
|
2446
2514
|
provider = chosen;
|
|
2447
2515
|
break;
|
|
@@ -2464,7 +2532,7 @@ async function setupCommand(args) {
|
|
|
2464
2532
|
let extraProvider = provider;
|
|
2465
2533
|
while (true) {
|
|
2466
2534
|
listProviderChoices(provider, { ...seedEnv, ...providerKeys });
|
|
2467
|
-
const chosen = normalizeProvider(await promptNonEmpty(prompt, '
|
|
2535
|
+
const chosen = normalizeProvider(await promptNonEmpty(prompt, 'Additional provider', 'claude'));
|
|
2468
2536
|
if (!chosen) {
|
|
2469
2537
|
console.log(`${warnLabel()} Provider is required.`);
|
|
2470
2538
|
continue;
|
|
@@ -2499,9 +2567,9 @@ async function setupCommand(args) {
|
|
|
2499
2567
|
const defaultProjectPath = process.cwd();
|
|
2500
2568
|
let shouldBindProject = await promptYesNo(prompt, 'Bind a project folder to this instance now?', true);
|
|
2501
2569
|
while (shouldBindProject) {
|
|
2502
|
-
const projectPath = path_1.default.resolve(await promptNonEmpty(prompt, '
|
|
2503
|
-
const agentId = sanitizeIdentifier(await promptNonEmpty(prompt, '
|
|
2504
|
-
const memoryEntity = await promptNonEmpty(prompt, '
|
|
2570
|
+
const projectPath = path_1.default.resolve(await promptNonEmpty(prompt, 'Project path to bind', projects.length === 0 ? defaultProjectPath : process.cwd()));
|
|
2571
|
+
const agentId = sanitizeIdentifier(await promptNonEmpty(prompt, 'Project agent ID', projectAgentDefault(projectPath)), 'project_main');
|
|
2572
|
+
const memoryEntity = await promptNonEmpty(prompt, 'Project memory entity', 'user/main');
|
|
2505
2573
|
const claudeCode = await promptYesNo(prompt, 'Create Claude Code project files here now?', true);
|
|
2506
2574
|
projects.push({
|
|
2507
2575
|
path: projectPath,
|
|
@@ -2574,6 +2642,71 @@ async function doctorCommand(args) {
|
|
|
2574
2642
|
const { envFile, envSource } = resolveDoctorEnvTarget(args);
|
|
2575
2643
|
const checks = [];
|
|
2576
2644
|
const version = getPackageVersion();
|
|
2645
|
+
const pushEnvironmentChecks = async (env, prefix = '') => {
|
|
2646
|
+
const databaseUrl = env.DATABASE_URL;
|
|
2647
|
+
checks.push(detectPlaceholder(databaseUrl)
|
|
2648
|
+
? {
|
|
2649
|
+
name: `${prefix}database configuration`,
|
|
2650
|
+
status: 'fail',
|
|
2651
|
+
detail: 'DATABASE_URL is missing or still uses a placeholder value.',
|
|
2652
|
+
}
|
|
2653
|
+
: {
|
|
2654
|
+
name: `${prefix}database configuration`,
|
|
2655
|
+
status: 'pass',
|
|
2656
|
+
detail: 'DATABASE_URL is present and non-placeholder.',
|
|
2657
|
+
});
|
|
2658
|
+
const provider = env.LLM_PROVIDER ?? 'mock';
|
|
2659
|
+
checks.push({
|
|
2660
|
+
name: `${prefix}llm provider`,
|
|
2661
|
+
status: 'pass',
|
|
2662
|
+
detail: `LLM_PROVIDER=${provider}`,
|
|
2663
|
+
});
|
|
2664
|
+
const providerKeyCheck = detectProviderKey(provider, env);
|
|
2665
|
+
checks.push({
|
|
2666
|
+
...providerKeyCheck,
|
|
2667
|
+
name: `${prefix}${providerKeyCheck.name}`,
|
|
2668
|
+
});
|
|
2669
|
+
try {
|
|
2670
|
+
const backendName = (0, backends_1.resolveVectorBackendName)({
|
|
2671
|
+
vectorBackend: env.IRANTI_VECTOR_BACKEND,
|
|
2672
|
+
qdrantUrl: env.IRANTI_QDRANT_URL,
|
|
2673
|
+
qdrantApiKey: env.IRANTI_QDRANT_API_KEY,
|
|
2674
|
+
qdrantCollection: env.IRANTI_QDRANT_COLLECTION,
|
|
2675
|
+
chromaUrl: env.IRANTI_CHROMA_URL,
|
|
2676
|
+
chromaCollection: env.IRANTI_CHROMA_COLLECTION,
|
|
2677
|
+
chromaTenant: env.IRANTI_CHROMA_TENANT,
|
|
2678
|
+
chromaDatabase: env.IRANTI_CHROMA_DATABASE,
|
|
2679
|
+
chromaToken: env.IRANTI_CHROMA_TOKEN,
|
|
2680
|
+
});
|
|
2681
|
+
const backend = (0, backends_1.createVectorBackend)({
|
|
2682
|
+
vectorBackend: backendName,
|
|
2683
|
+
qdrantUrl: env.IRANTI_QDRANT_URL,
|
|
2684
|
+
qdrantApiKey: env.IRANTI_QDRANT_API_KEY,
|
|
2685
|
+
qdrantCollection: env.IRANTI_QDRANT_COLLECTION,
|
|
2686
|
+
chromaUrl: env.IRANTI_CHROMA_URL,
|
|
2687
|
+
chromaCollection: env.IRANTI_CHROMA_COLLECTION,
|
|
2688
|
+
chromaTenant: env.IRANTI_CHROMA_TENANT,
|
|
2689
|
+
chromaDatabase: env.IRANTI_CHROMA_DATABASE,
|
|
2690
|
+
chromaToken: env.IRANTI_CHROMA_TOKEN,
|
|
2691
|
+
});
|
|
2692
|
+
const reachable = await backend.ping();
|
|
2693
|
+
const url = vectorBackendUrl(backendName, env);
|
|
2694
|
+
checks.push({
|
|
2695
|
+
name: `${prefix}vector backend`,
|
|
2696
|
+
status: reachable ? 'pass' : 'warn',
|
|
2697
|
+
detail: url
|
|
2698
|
+
? `${backendName} (${url}) is ${reachable ? 'reachable' : 'unreachable'}`
|
|
2699
|
+
: `${backendName} is ${reachable ? 'reachable' : 'unreachable'}`,
|
|
2700
|
+
});
|
|
2701
|
+
}
|
|
2702
|
+
catch (error) {
|
|
2703
|
+
checks.push({
|
|
2704
|
+
name: `${prefix}vector backend`,
|
|
2705
|
+
status: 'fail',
|
|
2706
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
};
|
|
2577
2710
|
checks.push({
|
|
2578
2711
|
name: 'node version',
|
|
2579
2712
|
status: Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10) >= 18 ? 'pass' : 'fail',
|
|
@@ -2603,24 +2736,15 @@ async function doctorCommand(args) {
|
|
|
2603
2736
|
}
|
|
2604
2737
|
else {
|
|
2605
2738
|
const env = await readEnvFile(envFile);
|
|
2739
|
+
const treatAsProjectBinding = envSource === 'project-binding'
|
|
2740
|
+
|| path_1.default.basename(envFile).toLowerCase() === '.env.iranti'
|
|
2741
|
+
|| (Boolean(env.IRANTI_URL?.trim()) && detectPlaceholder(env.DATABASE_URL));
|
|
2606
2742
|
checks.push({
|
|
2607
2743
|
name: 'environment file',
|
|
2608
2744
|
status: 'pass',
|
|
2609
2745
|
detail: `${envSource} env loaded from ${envFile}`,
|
|
2610
2746
|
});
|
|
2611
|
-
|
|
2612
|
-
checks.push(detectPlaceholder(databaseUrl)
|
|
2613
|
-
? {
|
|
2614
|
-
name: 'database configuration',
|
|
2615
|
-
status: 'fail',
|
|
2616
|
-
detail: 'DATABASE_URL is missing or still uses a placeholder value.',
|
|
2617
|
-
}
|
|
2618
|
-
: {
|
|
2619
|
-
name: 'database configuration',
|
|
2620
|
-
status: 'pass',
|
|
2621
|
-
detail: 'DATABASE_URL is present and non-placeholder.',
|
|
2622
|
-
});
|
|
2623
|
-
if (envSource === 'project-binding') {
|
|
2747
|
+
if (treatAsProjectBinding) {
|
|
2624
2748
|
checks.push(detectPlaceholder(env.IRANTI_URL)
|
|
2625
2749
|
? {
|
|
2626
2750
|
name: 'project binding url',
|
|
@@ -2633,7 +2757,7 @@ async function doctorCommand(args) {
|
|
|
2633
2757
|
detail: `IRANTI_URL=${env.IRANTI_URL}`,
|
|
2634
2758
|
});
|
|
2635
2759
|
}
|
|
2636
|
-
if (
|
|
2760
|
+
if (treatAsProjectBinding) {
|
|
2637
2761
|
checks.push(detectPlaceholder(env.IRANTI_API_KEY)
|
|
2638
2762
|
? {
|
|
2639
2763
|
name: 'project api key',
|
|
@@ -2645,8 +2769,33 @@ async function doctorCommand(args) {
|
|
|
2645
2769
|
status: 'pass',
|
|
2646
2770
|
detail: 'IRANTI_API_KEY is present in .env.iranti.',
|
|
2647
2771
|
});
|
|
2772
|
+
const linkedInstanceEnv = env.IRANTI_INSTANCE_ENV?.trim();
|
|
2773
|
+
if (!linkedInstanceEnv) {
|
|
2774
|
+
checks.push({
|
|
2775
|
+
name: 'bound instance env',
|
|
2776
|
+
status: 'warn',
|
|
2777
|
+
detail: 'IRANTI_INSTANCE_ENV is not set in .env.iranti. Skipping database and provider checks for the bound instance.',
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
else if (!fs_1.default.existsSync(linkedInstanceEnv)) {
|
|
2781
|
+
checks.push({
|
|
2782
|
+
name: 'bound instance env',
|
|
2783
|
+
status: 'warn',
|
|
2784
|
+
detail: `Linked instance env not found: ${linkedInstanceEnv}. Skipping database and provider checks for the bound instance.`,
|
|
2785
|
+
});
|
|
2786
|
+
}
|
|
2787
|
+
else {
|
|
2788
|
+
checks.push({
|
|
2789
|
+
name: 'bound instance env',
|
|
2790
|
+
status: 'pass',
|
|
2791
|
+
detail: `Using ${linkedInstanceEnv} for bound instance diagnostics.`,
|
|
2792
|
+
});
|
|
2793
|
+
const linkedEnv = await readEnvFile(linkedInstanceEnv);
|
|
2794
|
+
await pushEnvironmentChecks(linkedEnv, 'bound instance ');
|
|
2795
|
+
}
|
|
2648
2796
|
}
|
|
2649
2797
|
else {
|
|
2798
|
+
await pushEnvironmentChecks(env);
|
|
2650
2799
|
checks.push(detectPlaceholder(env.IRANTI_API_KEY)
|
|
2651
2800
|
? {
|
|
2652
2801
|
name: 'api key',
|
|
@@ -2659,53 +2808,6 @@ async function doctorCommand(args) {
|
|
|
2659
2808
|
detail: 'IRANTI_API_KEY is present.',
|
|
2660
2809
|
});
|
|
2661
2810
|
}
|
|
2662
|
-
const provider = env.LLM_PROVIDER ?? 'mock';
|
|
2663
|
-
checks.push({
|
|
2664
|
-
name: 'llm provider',
|
|
2665
|
-
status: 'pass',
|
|
2666
|
-
detail: `LLM_PROVIDER=${provider}`,
|
|
2667
|
-
});
|
|
2668
|
-
checks.push(detectProviderKey(provider, env));
|
|
2669
|
-
try {
|
|
2670
|
-
const backendName = (0, backends_1.resolveVectorBackendName)({
|
|
2671
|
-
vectorBackend: env.IRANTI_VECTOR_BACKEND,
|
|
2672
|
-
qdrantUrl: env.IRANTI_QDRANT_URL,
|
|
2673
|
-
qdrantApiKey: env.IRANTI_QDRANT_API_KEY,
|
|
2674
|
-
qdrantCollection: env.IRANTI_QDRANT_COLLECTION,
|
|
2675
|
-
chromaUrl: env.IRANTI_CHROMA_URL,
|
|
2676
|
-
chromaCollection: env.IRANTI_CHROMA_COLLECTION,
|
|
2677
|
-
chromaTenant: env.IRANTI_CHROMA_TENANT,
|
|
2678
|
-
chromaDatabase: env.IRANTI_CHROMA_DATABASE,
|
|
2679
|
-
chromaToken: env.IRANTI_CHROMA_TOKEN,
|
|
2680
|
-
});
|
|
2681
|
-
const backend = (0, backends_1.createVectorBackend)({
|
|
2682
|
-
vectorBackend: backendName,
|
|
2683
|
-
qdrantUrl: env.IRANTI_QDRANT_URL,
|
|
2684
|
-
qdrantApiKey: env.IRANTI_QDRANT_API_KEY,
|
|
2685
|
-
qdrantCollection: env.IRANTI_QDRANT_COLLECTION,
|
|
2686
|
-
chromaUrl: env.IRANTI_CHROMA_URL,
|
|
2687
|
-
chromaCollection: env.IRANTI_CHROMA_COLLECTION,
|
|
2688
|
-
chromaTenant: env.IRANTI_CHROMA_TENANT,
|
|
2689
|
-
chromaDatabase: env.IRANTI_CHROMA_DATABASE,
|
|
2690
|
-
chromaToken: env.IRANTI_CHROMA_TOKEN,
|
|
2691
|
-
});
|
|
2692
|
-
const reachable = await backend.ping();
|
|
2693
|
-
const url = vectorBackendUrl(backendName, env);
|
|
2694
|
-
checks.push({
|
|
2695
|
-
name: 'vector backend',
|
|
2696
|
-
status: reachable ? 'pass' : 'warn',
|
|
2697
|
-
detail: url
|
|
2698
|
-
? `${backendName} (${url}) is ${reachable ? 'reachable' : 'unreachable'}`
|
|
2699
|
-
: `${backendName} is ${reachable ? 'reachable' : 'unreachable'}`,
|
|
2700
|
-
});
|
|
2701
|
-
}
|
|
2702
|
-
catch (error) {
|
|
2703
|
-
checks.push({
|
|
2704
|
-
name: 'vector backend',
|
|
2705
|
-
status: 'fail',
|
|
2706
|
-
detail: error instanceof Error ? error.message : String(error),
|
|
2707
|
-
});
|
|
2708
|
-
}
|
|
2709
2811
|
}
|
|
2710
2812
|
const result = {
|
|
2711
2813
|
version,
|
|
@@ -3102,19 +3204,21 @@ async function showInstanceCommand(args) {
|
|
|
3102
3204
|
}
|
|
3103
3205
|
async function runInstanceCommand(args) {
|
|
3104
3206
|
const name = getFlag(args, 'instance') ?? args.positionals[0] ?? args.subcommand;
|
|
3105
|
-
if (!name)
|
|
3106
|
-
throw
|
|
3207
|
+
if (!name) {
|
|
3208
|
+
throw cliError('IRANTI_INSTANCE_NAME_REQUIRED', 'Missing instance name. Usage: iranti run --instance <name>', ['Run `iranti instance list` to see configured instances.']);
|
|
3209
|
+
}
|
|
3107
3210
|
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
3108
3211
|
const root = resolveInstallRoot(args, scope);
|
|
3109
3212
|
const envFile = path_1.default.join(root, 'instances', name, '.env');
|
|
3110
|
-
if (!fs_1.default.existsSync(envFile))
|
|
3111
|
-
throw
|
|
3213
|
+
if (!fs_1.default.existsSync(envFile)) {
|
|
3214
|
+
throw cliError('IRANTI_INSTANCE_NOT_FOUND', `Instance '${name}' not found. Create it first.`, [`Run \`iranti setup\` or \`iranti instance create ${name}\` first.`], { instance: name, envFile });
|
|
3215
|
+
}
|
|
3112
3216
|
const env = await readEnvFile(envFile);
|
|
3113
3217
|
for (const [k, v] of Object.entries(env)) {
|
|
3114
3218
|
process.env[k] = v;
|
|
3115
3219
|
}
|
|
3116
3220
|
if (!process.env.DATABASE_URL || process.env.DATABASE_URL.includes('yourpassword')) {
|
|
3117
|
-
throw
|
|
3221
|
+
throw cliError('IRANTI_INSTANCE_DATABASE_PLACEHOLDER', `Instance '${name}' has placeholder DATABASE_URL. Edit ${envFile} first.`, ['Run `iranti configure instance <name> --interactive` or rerun `iranti setup`.'], { instance: name, envFile });
|
|
3118
3222
|
}
|
|
3119
3223
|
console.log(`${infoLabel()} Starting Iranti instance '${name}' on port ${process.env.IRANTI_PORT ?? '3001'}...`);
|
|
3120
3224
|
const serverEntry = path_1.default.resolve(__dirname, '..', 'src', 'api', 'server');
|
|
@@ -3125,7 +3229,7 @@ async function projectInitCommand(args) {
|
|
|
3125
3229
|
const projectPath = path_1.default.resolve(args.positionals[0] ?? process.cwd());
|
|
3126
3230
|
const instanceName = getFlag(args, 'instance');
|
|
3127
3231
|
if (!instanceName) {
|
|
3128
|
-
throw
|
|
3232
|
+
throw cliError('IRANTI_INSTANCE_NAME_REQUIRED', 'Missing --instance <name>. Usage: iranti project init [path] --instance <name>', ['Run `iranti instance list` to see available instances.']);
|
|
3129
3233
|
}
|
|
3130
3234
|
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
3131
3235
|
const root = resolveInstallRoot(args, scope);
|
|
@@ -3136,7 +3240,7 @@ async function projectInitCommand(args) {
|
|
|
3136
3240
|
const projectMode = normalizeProjectMode(getFlag(args, 'mode'), 'isolated');
|
|
3137
3241
|
const outFile = path_1.default.join(projectPath, '.env.iranti');
|
|
3138
3242
|
if (fs_1.default.existsSync(outFile) && !hasFlag(args, 'force')) {
|
|
3139
|
-
throw
|
|
3243
|
+
throw cliError('IRANTI_PROJECT_BINDING_EXISTS', `${outFile} already exists. Use --force to overwrite.`, ['Use `iranti configure project` if you want to refresh the existing binding instead.'], { outFile });
|
|
3140
3244
|
}
|
|
3141
3245
|
await writeProjectBinding(projectPath, {
|
|
3142
3246
|
IRANTI_URL: `http://localhost:${port}`,
|
|
@@ -3173,13 +3277,13 @@ async function configureInstanceCommand(args) {
|
|
|
3173
3277
|
let clearProviderKey = hasFlag(args, 'clear-provider-key');
|
|
3174
3278
|
if (hasFlag(args, 'interactive')) {
|
|
3175
3279
|
await withPromptSession(async (prompt) => {
|
|
3176
|
-
portRaw = await prompt.line('
|
|
3177
|
-
dbUrl = await prompt.line('
|
|
3178
|
-
providerInput = await prompt.line('
|
|
3280
|
+
portRaw = await prompt.line('API port', portRaw ?? env.IRANTI_PORT);
|
|
3281
|
+
dbUrl = await prompt.line('DATABASE_URL', dbUrl ?? env.DATABASE_URL);
|
|
3282
|
+
providerInput = await prompt.line('LLM provider', providerInput ?? env.LLM_PROVIDER ?? 'mock');
|
|
3179
3283
|
const interactiveProvider = normalizeProvider(providerInput ?? env.LLM_PROVIDER ?? 'mock');
|
|
3180
3284
|
const interactiveProviderEnvKey = providerKeyEnv(interactiveProvider);
|
|
3181
3285
|
if (interactiveProvider && interactiveProviderEnvKey) {
|
|
3182
|
-
providerKey = await prompt.secret(
|
|
3286
|
+
providerKey = await prompt.secret(`${providerDisplayName(interactiveProvider)} API key`, providerKey ?? env[interactiveProviderEnvKey]);
|
|
3183
3287
|
}
|
|
3184
3288
|
apiKey = await prompt.secret('Iranti API key', apiKey ?? env.IRANTI_API_KEY);
|
|
3185
3289
|
});
|
|
@@ -3256,12 +3360,12 @@ async function configureProjectCommand(args) {
|
|
|
3256
3360
|
let explicitProjectMode = getFlag(args, 'mode');
|
|
3257
3361
|
if (hasFlag(args, 'interactive')) {
|
|
3258
3362
|
await withPromptSession(async (prompt) => {
|
|
3259
|
-
instanceName = await prompt.line('
|
|
3260
|
-
explicitUrl = await prompt.line('
|
|
3261
|
-
explicitApiKey = await prompt.secret('
|
|
3262
|
-
explicitAgentId = await prompt.line('
|
|
3263
|
-
explicitMemoryEntity = await prompt.line('
|
|
3264
|
-
explicitProjectMode = await prompt.line('
|
|
3363
|
+
instanceName = await prompt.line('Instance name', instanceName);
|
|
3364
|
+
explicitUrl = await prompt.line('Iranti URL', explicitUrl ?? existing.IRANTI_URL);
|
|
3365
|
+
explicitApiKey = await prompt.secret('Project API key', explicitApiKey ?? existing.IRANTI_API_KEY);
|
|
3366
|
+
explicitAgentId = await prompt.line('Project agent ID', explicitAgentId ?? existing.IRANTI_AGENT_ID ?? projectAgentDefault(projectPath));
|
|
3367
|
+
explicitMemoryEntity = await prompt.line('Project memory entity', explicitMemoryEntity ?? existing.IRANTI_MEMORY_ENTITY ?? 'user/main');
|
|
3368
|
+
explicitProjectMode = await prompt.line('Project mode (isolated or shared)', explicitProjectMode ?? existing.IRANTI_PROJECT_MODE ?? inferProjectMode(projectPath, existing.IRANTI_INSTANCE_ENV));
|
|
3265
3369
|
});
|
|
3266
3370
|
}
|
|
3267
3371
|
let instanceEnvFile = existing.IRANTI_INSTANCE_ENV;
|
|
@@ -3591,7 +3695,7 @@ async function claudeSetupCommand(args) {
|
|
|
3591
3695
|
?? (args.command === 'claude-setup' ? args.subcommand ?? undefined : undefined);
|
|
3592
3696
|
const scanDir = path_1.default.resolve(dirArg ?? process.cwd());
|
|
3593
3697
|
if (!fs_1.default.existsSync(scanDir)) {
|
|
3594
|
-
throw
|
|
3698
|
+
throw cliError('IRANTI_SCAN_PATH_NOT_FOUND', `Scan directory not found: ${scanDir}`);
|
|
3595
3699
|
}
|
|
3596
3700
|
const candidates = findClaudeProjects(scanDir, recursive);
|
|
3597
3701
|
if (candidates.length === 0) {
|
|
@@ -3638,10 +3742,10 @@ async function claudeSetupCommand(args) {
|
|
|
3638
3742
|
? path_1.default.resolve(explicitProjectEnv)
|
|
3639
3743
|
: path_1.default.join(projectPath, '.env.iranti');
|
|
3640
3744
|
if (!fs_1.default.existsSync(projectPath)) {
|
|
3641
|
-
throw
|
|
3745
|
+
throw cliError('IRANTI_PROJECT_PATH_NOT_FOUND', `Project path not found: ${projectPath}`);
|
|
3642
3746
|
}
|
|
3643
3747
|
if (!fs_1.default.existsSync(projectEnvPath)) {
|
|
3644
|
-
throw
|
|
3748
|
+
throw cliError('IRANTI_PROJECT_BINDING_MISSING', `Project binding not found at ${projectEnvPath}. Run \`iranti project init\` or \`iranti configure project\` first.`, ['Run `iranti project init . --instance <name>` before `iranti claude-setup`.'], { projectEnvPath });
|
|
3645
3749
|
}
|
|
3646
3750
|
const result = await writeClaudeCodeProjectFiles(projectPath, projectEnvPath, force);
|
|
3647
3751
|
console.log(`${okLabel()} Claude Code integration scaffolded`);
|
|
@@ -3675,24 +3779,27 @@ function printHelp() {
|
|
|
3675
3779
|
const printRows = (title, entries) => {
|
|
3676
3780
|
console.log(sectionTitle(title));
|
|
3677
3781
|
for (const [command, description] of entries) {
|
|
3678
|
-
console.log(` ${commandText(command
|
|
3782
|
+
console.log(` ${commandText(command)}`);
|
|
3783
|
+
console.log(` ${description}`);
|
|
3679
3784
|
}
|
|
3680
3785
|
console.log('');
|
|
3681
3786
|
};
|
|
3682
3787
|
console.log(sectionTitle('Iranti CLI'));
|
|
3683
3788
|
console.log('Memory infrastructure for multi-agent systems.');
|
|
3789
|
+
console.log('Most instance-aware commands also accept --root <path> in addition to --scope.');
|
|
3790
|
+
console.log('Global debugging flags: --debug for extra diagnostics, --verbose for subprocess trace output.');
|
|
3684
3791
|
console.log('');
|
|
3685
3792
|
printRows('Start Here', rows);
|
|
3686
3793
|
printRows('Setup And Runtime', [
|
|
3687
3794
|
['iranti install [--scope user|system] [--root <path>]', 'Initialize the machine-level runtime folders.'],
|
|
3688
|
-
['iranti setup [--scope user|system] [--root <path>] [--mode isolated|shared] [--config <file> | --defaults] [--db-mode local|managed|docker] [--db-url <url>] [--bootstrap-db]', 'Guided setup for runtime, database, instance, keys, and project binding.'],
|
|
3795
|
+
['iranti setup [--scope user|system] [--root <path>] [--mode isolated|shared] [--instance <name>] [--port <n>] [--config <file> | --defaults] [--db-mode local|managed|docker] [--db-url <url>] [--provider <name>] [--api-key <token>] [--projects <path1,path2>] [--claude-code] [--bootstrap-db]', 'Guided setup for runtime, database, instance, keys, and project binding. Run iranti setup --help for the non-interactive flow.'],
|
|
3689
3796
|
['iranti instance create <name> [--port 3001] [--db-url <url>] [--api-key <token>] [--provider <name>] [--provider-key <token>] [--scope user|system]', 'Create an instance directly if you want low-level control.'],
|
|
3690
3797
|
['iranti instance list [--scope user|system]', 'List configured instances.'],
|
|
3691
3798
|
['iranti instance show <name> [--scope user|system]', 'Show one instance env, port, and database target.'],
|
|
3692
3799
|
['iranti run --instance <name> [--scope user|system]', 'Start an instance.'],
|
|
3693
3800
|
]);
|
|
3694
3801
|
printRows('Configuration', [
|
|
3695
|
-
['iranti configure instance <name> [--interactive] [--db-url <url>] [--port <n>] [--api-key <token>] [--provider <name>] [--provider-key <token>] [--clear-provider-key]', 'Update an instance without editing env files manually.'],
|
|
3802
|
+
['iranti configure instance <name> [--interactive] [--db-url <url>] [--port <n>] [--api-key <token>] [--provider <name>] [--provider-key <token>] [--clear-provider-key] [--json]', 'Update an instance without editing env files manually.'],
|
|
3696
3803
|
['iranti project init [path] --instance <name> [--api-key <token>] [--agent-id <id>] [--mode isolated|shared] [--force]', 'Create a new .env.iranti binding for one project.'],
|
|
3697
3804
|
['iranti configure project [path] [--interactive] [--instance <name>] [--url <http://host:port>] [--api-key <token>] [--agent-id <id>] [--memory-entity <entity>] [--mode isolated|shared] [--json]', 'Refresh or retarget an existing project binding.'],
|
|
3698
3805
|
]);
|
|
@@ -3706,7 +3813,7 @@ function printHelp() {
|
|
|
3706
3813
|
['iranti remove api-key [provider] [--instance <name>] [--project <path>] [--json]', 'Remove a stored provider key.'],
|
|
3707
3814
|
]);
|
|
3708
3815
|
printRows('Diagnostics And Operator Tools', [
|
|
3709
|
-
['iranti doctor [--instance <name>] [--scope user|system] [--env <file>] [--json]', 'Run environment and runtime diagnostics.'],
|
|
3816
|
+
['iranti doctor [--instance <name>] [--scope user|system] [--env <file>] [--json] [--debug]', 'Run environment and runtime diagnostics.'],
|
|
3710
3817
|
['iranti status [--scope user|system] [--json]', 'Show runtime roots, bindings, and known instances.'],
|
|
3711
3818
|
['iranti upgrade [--check] [--dry-run] [--yes] [--all] [--target auto|npm-global|npm-repo|python[,python]] [--json]', 'Check or run CLI/runtime/package upgrades.'],
|
|
3712
3819
|
['iranti handshake [--instance <name> | --project-env <file>] [--agent <id>] [--task <text>] [--recent <msg1||msg2>] [--recent-file <path>] [--json]', 'Manually inspect Attendant handshake output.'],
|
|
@@ -3738,22 +3845,31 @@ function printHelp() {
|
|
|
3738
3845
|
console.log(` ${commandText('iranti auth create-key --instance local --key-id app_main --owner \"App Main\" --scopes \"kb:read,kb:write,memory:read,memory:write\"')}`);
|
|
3739
3846
|
console.log(` ${commandText('iranti add api-key openai --instance local --set-default')}`);
|
|
3740
3847
|
}
|
|
3848
|
+
function printSetupHelp() {
|
|
3849
|
+
console.log(sectionTitle('Setup Command'));
|
|
3850
|
+
console.log(` ${commandText('iranti setup [--scope user|system] [--root <path>] [--mode isolated|shared] [--instance <name>] [--port <n>] [--config <file> | --defaults] [--db-mode local|managed|docker] [--db-url <url>] [--provider <name>] [--api-key <token>] [--projects <path1,path2>] [--claude-code] [--bootstrap-db]')}`);
|
|
3851
|
+
console.log('');
|
|
3852
|
+
console.log(' Interactive mode walks through runtime, database, provider keys, API keys, and project binding.');
|
|
3853
|
+
console.log(' Use `--defaults` to build a plan from flags and environment variables without prompts.');
|
|
3854
|
+
console.log(' Use `--config <file>` to execute a saved setup plan.');
|
|
3855
|
+
console.log(' `--projects` and `--claude-code` apply to the non-interactive defaults flow.');
|
|
3856
|
+
}
|
|
3741
3857
|
function printInstanceHelp() {
|
|
3742
3858
|
console.log(sectionTitle('Instance Commands'));
|
|
3743
|
-
console.log(` ${commandText('iranti instance create <name> [--port 3001] [--db-url <url>] [--api-key <token>] [--provider <name>] [--provider-key <token>] [--scope user|system]')}`);
|
|
3744
|
-
console.log(` ${commandText('iranti instance list [--scope user|system]')}`);
|
|
3745
|
-
console.log(` ${commandText('iranti instance show <name> [--scope user|system]')}`);
|
|
3859
|
+
console.log(` ${commandText('iranti instance create <name> [--port 3001] [--db-url <url>] [--api-key <token>] [--provider <name>] [--provider-key <token>] [--scope user|system] [--root <path>]')}`);
|
|
3860
|
+
console.log(` ${commandText('iranti instance list [--scope user|system] [--root <path>]')}`);
|
|
3861
|
+
console.log(` ${commandText('iranti instance show <name> [--scope user|system] [--root <path>]')}`);
|
|
3746
3862
|
}
|
|
3747
3863
|
function printConfigureHelp() {
|
|
3748
3864
|
console.log(sectionTitle('Configure Commands'));
|
|
3749
|
-
console.log(` ${commandText('iranti configure instance <name> [--interactive] [--db-url <url>] [--port <n>] [--api-key <token>] [--provider <name>] [--provider-key <token>] [--clear-provider-key]')}`);
|
|
3750
|
-
console.log(` ${commandText('iranti configure project [path] [--interactive] [--instance <name>] [--url <http://host:port>] [--api-key <token>] [--agent-id <id>] [--memory-entity <entity>] [--mode isolated|shared] [--json]')}`);
|
|
3865
|
+
console.log(` ${commandText('iranti configure instance <name> [--interactive] [--db-url <url>] [--port <n>] [--api-key <token>] [--provider <name>] [--provider-key <token>] [--clear-provider-key] [--scope user|system] [--root <path>] [--json]')}`);
|
|
3866
|
+
console.log(` ${commandText('iranti configure project [path] [--interactive] [--instance <name>] [--url <http://host:port>] [--api-key <token>] [--agent-id <id>] [--memory-entity <entity>] [--mode isolated|shared] [--scope user|system] [--root <path>] [--json]')}`);
|
|
3751
3867
|
}
|
|
3752
3868
|
function printAuthHelp() {
|
|
3753
3869
|
console.log(sectionTitle('Auth Commands'));
|
|
3754
|
-
console.log(` ${commandText('iranti auth create-key --instance <name> --key-id <id> --owner <owner> [--scopes ...] [--description <text>] [--write-instance] [--project <path>] [--agent-id <id>] [--json]')}`);
|
|
3755
|
-
console.log(` ${commandText('iranti auth list-keys --instance <name> [--json]')}`);
|
|
3756
|
-
console.log(` ${commandText('iranti auth revoke-key --instance <name> --key-id <id> [--json]')}`);
|
|
3870
|
+
console.log(` ${commandText('iranti auth create-key --instance <name> --key-id <id> --owner <owner> [--scopes ...] [--description <text>] [--write-instance] [--project <path>] [--agent-id <id>] [--scope user|system] [--root <path>] [--json]')}`);
|
|
3871
|
+
console.log(` ${commandText('iranti auth list-keys --instance <name> [--scope user|system] [--root <path>] [--json]')}`);
|
|
3872
|
+
console.log(` ${commandText('iranti auth revoke-key --instance <name> --key-id <id> [--scope user|system] [--root <path>] [--json]')}`);
|
|
3757
3873
|
}
|
|
3758
3874
|
function printIntegrateHelp() {
|
|
3759
3875
|
console.log(sectionTitle('Integrations'));
|
|
@@ -3761,8 +3877,23 @@ function printIntegrateHelp() {
|
|
|
3761
3877
|
console.log(` ${commandText('iranti integrate claude --scan <dir> [--recursive] [--force]')}`);
|
|
3762
3878
|
console.log(` ${commandText('iranti integrate codex [--name iranti] [--agent codex_code] [--source Codex] [--provider openai] [--project-env <path>] [--local-script]')}`);
|
|
3763
3879
|
}
|
|
3880
|
+
function printProviderKeyHelp() {
|
|
3881
|
+
console.log(sectionTitle('Provider Key Commands'));
|
|
3882
|
+
console.log(` ${commandText('iranti list api-keys [--instance <name>] [--project <path>] [--json]')}`);
|
|
3883
|
+
console.log(` ${commandText('iranti add api-key [provider] [--instance <name>] [--project <path>] [--key <token>] [--set-default] [--json]')}`);
|
|
3884
|
+
console.log(` ${commandText('iranti update api-key [provider] [--instance <name>] [--project <path>] [--key <token>] [--set-default] [--json]')}`);
|
|
3885
|
+
console.log(` ${commandText('iranti remove api-key [provider] [--instance <name>] [--project <path>] [--json]')}`);
|
|
3886
|
+
console.log('');
|
|
3887
|
+
console.log(' Target either an instance env or a project binding. If neither is supplied, the CLI will try the current project first.');
|
|
3888
|
+
}
|
|
3764
3889
|
async function main() {
|
|
3765
3890
|
const args = parseArgs(process.argv.slice(2));
|
|
3891
|
+
setCliDebugFlags(args);
|
|
3892
|
+
debugLog('CLI invocation started.', {
|
|
3893
|
+
command: args.command,
|
|
3894
|
+
subcommand: args.subcommand,
|
|
3895
|
+
cwd: process.cwd(),
|
|
3896
|
+
});
|
|
3766
3897
|
if (!args.command || args.command === 'help' || args.command === '--help') {
|
|
3767
3898
|
printHelp();
|
|
3768
3899
|
return;
|
|
@@ -3772,6 +3903,10 @@ async function main() {
|
|
|
3772
3903
|
return;
|
|
3773
3904
|
}
|
|
3774
3905
|
if (args.command === 'setup') {
|
|
3906
|
+
if (hasFlag(args, 'help')) {
|
|
3907
|
+
printSetupHelp();
|
|
3908
|
+
return;
|
|
3909
|
+
}
|
|
3775
3910
|
await setupCommand(args);
|
|
3776
3911
|
return;
|
|
3777
3912
|
}
|
|
@@ -3833,18 +3968,34 @@ async function main() {
|
|
|
3833
3968
|
throw new Error(`Unknown auth subcommand '${args.subcommand ?? ''}'.`);
|
|
3834
3969
|
}
|
|
3835
3970
|
if (args.command === 'list' && args.subcommand === 'api-keys') {
|
|
3971
|
+
if (hasFlag(args, 'help')) {
|
|
3972
|
+
printProviderKeyHelp();
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3836
3975
|
await listProviderKeysCommand(args);
|
|
3837
3976
|
return;
|
|
3838
3977
|
}
|
|
3839
3978
|
if (args.command === 'add' && args.subcommand === 'api-key') {
|
|
3979
|
+
if (hasFlag(args, 'help')) {
|
|
3980
|
+
printProviderKeyHelp();
|
|
3981
|
+
return;
|
|
3982
|
+
}
|
|
3840
3983
|
await upsertProviderKeyCommand(args, 'add');
|
|
3841
3984
|
return;
|
|
3842
3985
|
}
|
|
3843
3986
|
if (args.command === 'update' && args.subcommand === 'api-key') {
|
|
3987
|
+
if (hasFlag(args, 'help')) {
|
|
3988
|
+
printProviderKeyHelp();
|
|
3989
|
+
return;
|
|
3990
|
+
}
|
|
3844
3991
|
await upsertProviderKeyCommand(args, 'update');
|
|
3845
3992
|
return;
|
|
3846
3993
|
}
|
|
3847
3994
|
if (args.command === 'remove' && args.subcommand === 'api-key') {
|
|
3995
|
+
if (hasFlag(args, 'help')) {
|
|
3996
|
+
printProviderKeyHelp();
|
|
3997
|
+
return;
|
|
3998
|
+
}
|
|
3848
3999
|
await removeProviderKeyCommand(args);
|
|
3849
4000
|
return;
|
|
3850
4001
|
}
|
|
@@ -3911,11 +4062,27 @@ async function main() {
|
|
|
3911
4062
|
}
|
|
3912
4063
|
throw new Error(`Unknown integrate target '${args.subcommand ?? ''}'. Use 'claude' or 'codex'.`);
|
|
3913
4064
|
}
|
|
3914
|
-
throw
|
|
4065
|
+
throw cliError('IRANTI_UNKNOWN_COMMAND', `Unknown command '${args.command}'. Run: iranti help`, ['Use `iranti help` to see the current command surface.'], { command: args.command });
|
|
3915
4066
|
}
|
|
3916
4067
|
main().catch((err) => {
|
|
3917
4068
|
const message = err instanceof Error ? err.message : String(err);
|
|
3918
|
-
|
|
4069
|
+
const code = err instanceof CliError ? err.code : null;
|
|
4070
|
+
console.error(`${failLabel('ERROR')}${code ? ` [${code}]` : ''} ${message}`);
|
|
4071
|
+
if (err instanceof CliError && err.hints.length > 0) {
|
|
4072
|
+
console.error('');
|
|
4073
|
+
console.error('Possible fixes:');
|
|
4074
|
+
for (const hint of err.hints) {
|
|
4075
|
+
console.error(` - ${hint}`);
|
|
4076
|
+
}
|
|
4077
|
+
}
|
|
4078
|
+
if (CLI_DEBUG && err instanceof CliError && err.details && Object.keys(err.details).length > 0) {
|
|
4079
|
+
console.error('');
|
|
4080
|
+
console.error(`${paint('[DEBUG]', 'gray')} ${JSON.stringify(err.details, null, 2)}`);
|
|
4081
|
+
}
|
|
4082
|
+
if (CLI_DEBUG && err instanceof Error && err.stack) {
|
|
4083
|
+
console.error('');
|
|
4084
|
+
console.error(err.stack);
|
|
4085
|
+
}
|
|
3919
4086
|
process.exit(1);
|
|
3920
4087
|
});
|
|
3921
4088
|
//# sourceMappingURL=iranti-cli.js.map
|
|
@@ -144,7 +144,7 @@ async function main() {
|
|
|
144
144
|
await ensureDefaultAgent(iranti);
|
|
145
145
|
const server = new mcp_js_1.McpServer({
|
|
146
146
|
name: 'iranti-mcp',
|
|
147
|
-
version: '0.2.
|
|
147
|
+
version: '0.2.12',
|
|
148
148
|
});
|
|
149
149
|
server.registerTool('iranti_handshake', {
|
|
150
150
|
description: `Initialize or refresh an agent's working-memory brief for the current task.
|
package/dist/scripts/seed.js
CHANGED
|
@@ -15,7 +15,7 @@ const STAFF_ENTRIES = [
|
|
|
15
15
|
entityId: 'librarian',
|
|
16
16
|
key: 'operating_rules',
|
|
17
17
|
valueRaw: {
|
|
18
|
-
version: '0.2.
|
|
18
|
+
version: '0.2.12',
|
|
19
19
|
rules: [
|
|
20
20
|
'All writes from external agents go through the Librarian — never directly to the database',
|
|
21
21
|
'Check for existing entries before every write',
|
|
@@ -39,7 +39,7 @@ const STAFF_ENTRIES = [
|
|
|
39
39
|
entityId: 'attendant',
|
|
40
40
|
key: 'operating_rules',
|
|
41
41
|
valueRaw: {
|
|
42
|
-
version: '0.2.
|
|
42
|
+
version: '0.2.12',
|
|
43
43
|
rules: [
|
|
44
44
|
'Assigned one-per-external-agent — serve the agent, not the user',
|
|
45
45
|
'On handshake: read AGENTS.md and MCP config, query Librarian for relevant rules and task context',
|
|
@@ -61,7 +61,7 @@ const STAFF_ENTRIES = [
|
|
|
61
61
|
entityId: 'archivist',
|
|
62
62
|
key: 'operating_rules',
|
|
63
63
|
valueRaw: {
|
|
64
|
-
version: '0.2.
|
|
64
|
+
version: '0.2.12',
|
|
65
65
|
rules: [
|
|
66
66
|
'Run on schedule or when conflict flags exceed threshold — not on every write',
|
|
67
67
|
'Scan for expired, low-confidence, flagged, and duplicate entries',
|
|
@@ -82,7 +82,7 @@ const STAFF_ENTRIES = [
|
|
|
82
82
|
entityType: 'system',
|
|
83
83
|
entityId: 'library',
|
|
84
84
|
key: 'schema_version',
|
|
85
|
-
valueRaw: { version: '0.2.
|
|
85
|
+
valueRaw: { version: '0.2.12' },
|
|
86
86
|
valueSummary: 'Current Library schema version.',
|
|
87
87
|
confidence: 100,
|
|
88
88
|
source: 'seed',
|
|
@@ -95,7 +95,7 @@ const STAFF_ENTRIES = [
|
|
|
95
95
|
key: 'initialization_log',
|
|
96
96
|
valueRaw: {
|
|
97
97
|
initializedAt: new Date().toISOString(),
|
|
98
|
-
seedVersion: '0.2.
|
|
98
|
+
seedVersion: '0.2.12',
|
|
99
99
|
},
|
|
100
100
|
valueSummary: 'Record of when and how this Library was initialized.',
|
|
101
101
|
confidence: 100,
|
|
@@ -108,7 +108,7 @@ const STAFF_ENTRIES = [
|
|
|
108
108
|
entityId: 'ontology',
|
|
109
109
|
key: 'core_schema',
|
|
110
110
|
valueRaw: {
|
|
111
|
-
version: '0.2.
|
|
111
|
+
version: '0.2.12',
|
|
112
112
|
states: ['candidate', 'provisional', 'canonical'],
|
|
113
113
|
coreEntityTypes: [
|
|
114
114
|
'person',
|
|
@@ -156,7 +156,7 @@ const STAFF_ENTRIES = [
|
|
|
156
156
|
entityId: 'ontology',
|
|
157
157
|
key: 'extension_registry',
|
|
158
158
|
valueRaw: {
|
|
159
|
-
version: '0.2.
|
|
159
|
+
version: '0.2.12',
|
|
160
160
|
namespaces: {
|
|
161
161
|
education: {
|
|
162
162
|
status: 'provisional',
|
|
@@ -187,7 +187,7 @@ const STAFF_ENTRIES = [
|
|
|
187
187
|
entityId: 'ontology',
|
|
188
188
|
key: 'candidate_terms',
|
|
189
189
|
valueRaw: {
|
|
190
|
-
version: '0.2.
|
|
190
|
+
version: '0.2.12',
|
|
191
191
|
terms: [],
|
|
192
192
|
},
|
|
193
193
|
valueSummary: 'Staging area for ontology terms detected repeatedly but not yet promoted.',
|
|
@@ -201,7 +201,7 @@ const STAFF_ENTRIES = [
|
|
|
201
201
|
entityId: 'ontology',
|
|
202
202
|
key: 'promotion_policy',
|
|
203
203
|
valueRaw: {
|
|
204
|
-
version: '0.2.
|
|
204
|
+
version: '0.2.12',
|
|
205
205
|
candidateToProvisional: {
|
|
206
206
|
minSeenCount: 3,
|
|
207
207
|
minDistinctAgents: 2,
|
|
@@ -237,7 +237,7 @@ const STAFF_ENTRIES = [
|
|
|
237
237
|
entityId: 'ontology',
|
|
238
238
|
key: 'change_log',
|
|
239
239
|
valueRaw: {
|
|
240
|
-
version: '0.2.
|
|
240
|
+
version: '0.2.12',
|
|
241
241
|
events: [
|
|
242
242
|
{
|
|
243
243
|
at: new Date().toISOString(),
|
package/dist/src/api/server.js
CHANGED
|
@@ -69,7 +69,7 @@ app.use(express_1.default.json({ limit: process.env.IRANTI_MAX_BODY_BYTES ?? '25
|
|
|
69
69
|
app.get(ROUTES.health, (_req, res) => {
|
|
70
70
|
res.json({
|
|
71
71
|
status: 'ok',
|
|
72
|
-
version: '0.2.
|
|
72
|
+
version: '0.2.12',
|
|
73
73
|
provider: process.env.LLM_PROVIDER ?? 'mock',
|
|
74
74
|
});
|
|
75
75
|
});
|