iranti 0.2.4 → 0.2.5
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/dist/scripts/iranti-cli.js +281 -64
- 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
|
@@ -1015,6 +1015,53 @@ function summarizeStatus(checks) {
|
|
|
1015
1015
|
return 'warn';
|
|
1016
1016
|
return 'pass';
|
|
1017
1017
|
}
|
|
1018
|
+
function collectDoctorRemediations(checks, envSource, envFile) {
|
|
1019
|
+
const hints = [];
|
|
1020
|
+
const add = (hint) => {
|
|
1021
|
+
if (!hints.includes(hint))
|
|
1022
|
+
hints.push(hint);
|
|
1023
|
+
};
|
|
1024
|
+
for (const check of checks) {
|
|
1025
|
+
if (check.name === 'node version' && check.status === 'fail') {
|
|
1026
|
+
add('Upgrade Node.js to version 18 or newer, then rerun `iranti doctor`.');
|
|
1027
|
+
}
|
|
1028
|
+
if (check.name === 'cli build artifact' && check.status !== 'pass') {
|
|
1029
|
+
add('If this is a repo checkout, run `npm run build`. If this is an installed CLI, reinstall it with `npm install -g iranti@latest`.');
|
|
1030
|
+
}
|
|
1031
|
+
if (check.name === 'environment file' && check.status === 'fail') {
|
|
1032
|
+
if (envFile) {
|
|
1033
|
+
add(`Fix or recreate the target env file at ${envFile}, or rerun \`iranti setup\`.`);
|
|
1034
|
+
}
|
|
1035
|
+
else {
|
|
1036
|
+
add('Run `iranti setup`, or rerun `iranti doctor` with `--instance <name>` or `--env <file>`.');
|
|
1037
|
+
}
|
|
1038
|
+
}
|
|
1039
|
+
if (check.name === 'database configuration' && check.status === 'fail') {
|
|
1040
|
+
add(`Set a real DATABASE_URL in ${envFile ?? 'the target env file'}, or rerun \`iranti setup\` to configure the database again.`);
|
|
1041
|
+
}
|
|
1042
|
+
if (check.name === 'project binding url' && check.status === 'fail') {
|
|
1043
|
+
add('Run `iranti configure project` to refresh the project binding, or set IRANTI_URL in `.env.iranti`.');
|
|
1044
|
+
}
|
|
1045
|
+
if (check.name === 'project api key' && check.status === 'fail') {
|
|
1046
|
+
add('Run `iranti configure project` or set IRANTI_API_KEY in `.env.iranti`.');
|
|
1047
|
+
}
|
|
1048
|
+
if (check.name === 'api key' && check.status !== 'pass') {
|
|
1049
|
+
add(envSource === 'project-binding'
|
|
1050
|
+
? 'Set IRANTI_API_KEY in the project binding, or rerun `iranti configure project`.'
|
|
1051
|
+
: 'Create or rotate an Iranti key with `iranti auth create-key`, then store it in the target env.');
|
|
1052
|
+
}
|
|
1053
|
+
if (check.name === 'provider credentials' && check.status === 'fail') {
|
|
1054
|
+
add('Store or refresh the upstream provider key with `iranti add api-key` or `iranti update api-key`.');
|
|
1055
|
+
}
|
|
1056
|
+
if (check.name === 'vector backend' && check.status === 'fail') {
|
|
1057
|
+
add('Check the vector backend env vars, or switch back to `IRANTI_VECTOR_BACKEND=pgvector` if the external backend is not ready.');
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
if (checks.some((check) => check.status !== 'pass')) {
|
|
1061
|
+
add('Use `iranti upgrade --all --dry-run` to see whether this machine has stale CLI or Python installs.');
|
|
1062
|
+
}
|
|
1063
|
+
return hints;
|
|
1064
|
+
}
|
|
1018
1065
|
function resolveDoctorEnvTarget(args) {
|
|
1019
1066
|
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
1020
1067
|
const instanceName = getFlag(args, 'instance');
|
|
@@ -1139,6 +1186,32 @@ function detectGlobalNpmRoot() {
|
|
|
1139
1186
|
const value = proc.stdout.trim();
|
|
1140
1187
|
return value ? path_1.default.resolve(value) : null;
|
|
1141
1188
|
}
|
|
1189
|
+
function detectGlobalNpmInstalledVersion() {
|
|
1190
|
+
const proc = runCommandCapture('npm', ['list', '-g', 'iranti', '--depth=0', '--json']);
|
|
1191
|
+
if (proc.status !== 0)
|
|
1192
|
+
return null;
|
|
1193
|
+
try {
|
|
1194
|
+
const payload = JSON.parse(proc.stdout);
|
|
1195
|
+
return typeof payload?.dependencies?.iranti?.version === 'string'
|
|
1196
|
+
? payload.dependencies.iranti.version
|
|
1197
|
+
: null;
|
|
1198
|
+
}
|
|
1199
|
+
catch {
|
|
1200
|
+
return null;
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
function detectPythonInstalledVersion(command) {
|
|
1204
|
+
if (!command)
|
|
1205
|
+
return null;
|
|
1206
|
+
const args = command.executable === 'py' ? ['-3', '-m', 'pip', 'show', 'iranti'] : ['-m', 'pip', 'show', 'iranti'];
|
|
1207
|
+
const proc = runCommandCapture(command.executable, args);
|
|
1208
|
+
if (proc.status !== 0)
|
|
1209
|
+
return null;
|
|
1210
|
+
const versionLine = proc.stdout.split(/\r?\n/).find((line) => line.toLowerCase().startsWith('version:'));
|
|
1211
|
+
if (!versionLine)
|
|
1212
|
+
return null;
|
|
1213
|
+
return versionLine.split(':').slice(1).join(':').trim() || null;
|
|
1214
|
+
}
|
|
1142
1215
|
function readJsonFile(filePath) {
|
|
1143
1216
|
if (!fs_1.default.existsSync(filePath))
|
|
1144
1217
|
return null;
|
|
@@ -1219,9 +1292,12 @@ function detectUpgradeContext(args) {
|
|
|
1219
1292
|
const runtimeRoot = resolveInstallRoot(args, scope);
|
|
1220
1293
|
const runtimeInstalled = fs_1.default.existsSync(path_1.default.join(runtimeRoot, 'install.json'));
|
|
1221
1294
|
const repoCheckout = fs_1.default.existsSync(path_1.default.join(packageRootPath, '.git'));
|
|
1295
|
+
const repoDirty = repoCheckout ? repoIsDirty(packageRootPath) : false;
|
|
1222
1296
|
const globalNpmRoot = detectGlobalNpmRoot();
|
|
1223
1297
|
const globalNpmInstall = globalNpmRoot !== null && isPathInside(globalNpmRoot, packageRootPath);
|
|
1298
|
+
const globalNpmVersion = globalNpmInstall ? detectGlobalNpmInstalledVersion() : null;
|
|
1224
1299
|
const python = detectPythonLauncher();
|
|
1300
|
+
const pythonVersion = detectPythonInstalledVersion(python);
|
|
1225
1301
|
const availableTargets = [];
|
|
1226
1302
|
if (globalNpmInstall)
|
|
1227
1303
|
availableTargets.push('npm-global');
|
|
@@ -1235,9 +1311,12 @@ function detectUpgradeContext(args) {
|
|
|
1235
1311
|
runtimeRoot,
|
|
1236
1312
|
runtimeInstalled,
|
|
1237
1313
|
repoCheckout,
|
|
1314
|
+
repoDirty,
|
|
1238
1315
|
globalNpmInstall,
|
|
1239
1316
|
globalNpmRoot,
|
|
1317
|
+
globalNpmVersion,
|
|
1240
1318
|
python,
|
|
1319
|
+
pythonVersion,
|
|
1241
1320
|
availableTargets,
|
|
1242
1321
|
};
|
|
1243
1322
|
}
|
|
@@ -1256,6 +1335,101 @@ function chooseUpgradeTarget(requested, context) {
|
|
|
1256
1335
|
return 'python';
|
|
1257
1336
|
return null;
|
|
1258
1337
|
}
|
|
1338
|
+
function resolveRequestedUpgradeTargets(raw, all) {
|
|
1339
|
+
if (all) {
|
|
1340
|
+
return ['npm-global', 'npm-repo', 'python'];
|
|
1341
|
+
}
|
|
1342
|
+
if (!raw) {
|
|
1343
|
+
return ['auto'];
|
|
1344
|
+
}
|
|
1345
|
+
return raw
|
|
1346
|
+
.split(',')
|
|
1347
|
+
.map((value) => resolveUpgradeTarget(value))
|
|
1348
|
+
.filter((value, index, array) => array.indexOf(value) === index);
|
|
1349
|
+
}
|
|
1350
|
+
function buildUpgradeTargetStatuses(context, latestNpm, latestPython) {
|
|
1351
|
+
return [
|
|
1352
|
+
{
|
|
1353
|
+
target: 'npm-global',
|
|
1354
|
+
available: context.globalNpmInstall,
|
|
1355
|
+
currentVersion: context.globalNpmVersion,
|
|
1356
|
+
latestVersion: latestNpm,
|
|
1357
|
+
upToDate: context.globalNpmVersion && latestNpm ? compareVersions(context.globalNpmVersion, latestNpm) >= 0 : null,
|
|
1358
|
+
blockedReason: context.globalNpmInstall ? undefined : 'No global npm install detected on PATH.',
|
|
1359
|
+
},
|
|
1360
|
+
{
|
|
1361
|
+
target: 'npm-repo',
|
|
1362
|
+
available: context.repoCheckout,
|
|
1363
|
+
currentVersion: context.currentVersion,
|
|
1364
|
+
latestVersion: latestNpm,
|
|
1365
|
+
upToDate: null,
|
|
1366
|
+
blockedReason: !context.repoCheckout
|
|
1367
|
+
? 'Current package root is not a git checkout.'
|
|
1368
|
+
: context.repoDirty
|
|
1369
|
+
? 'Repository worktree is dirty.'
|
|
1370
|
+
: undefined,
|
|
1371
|
+
},
|
|
1372
|
+
{
|
|
1373
|
+
target: 'python',
|
|
1374
|
+
available: context.python !== null,
|
|
1375
|
+
currentVersion: context.pythonVersion,
|
|
1376
|
+
latestVersion: latestPython,
|
|
1377
|
+
upToDate: context.pythonVersion && latestPython ? compareVersions(context.pythonVersion, latestPython) >= 0 : null,
|
|
1378
|
+
blockedReason: context.python ? undefined : 'Python launcher not found.',
|
|
1379
|
+
},
|
|
1380
|
+
];
|
|
1381
|
+
}
|
|
1382
|
+
function describeUpgradeTarget(target) {
|
|
1383
|
+
const current = target.currentVersion ?? 'not installed';
|
|
1384
|
+
const latest = target.latestVersion ?? 'unknown';
|
|
1385
|
+
if (target.target === 'npm-repo') {
|
|
1386
|
+
return target.blockedReason
|
|
1387
|
+
? `repo checkout (${current}) — ${target.blockedReason}`
|
|
1388
|
+
: `repo checkout (${current}) — refresh local checkout and rebuild`;
|
|
1389
|
+
}
|
|
1390
|
+
if (target.upToDate === true) {
|
|
1391
|
+
return `${target.target} (${current}) — already at latest ${latest}`;
|
|
1392
|
+
}
|
|
1393
|
+
if (target.blockedReason) {
|
|
1394
|
+
return `${target.target} (${current}) — ${target.blockedReason}`;
|
|
1395
|
+
}
|
|
1396
|
+
return `${target.target} (${current}) — latest ${latest}`;
|
|
1397
|
+
}
|
|
1398
|
+
async function chooseInteractiveUpgradeTargets(statuses) {
|
|
1399
|
+
const selected = [];
|
|
1400
|
+
await withPromptSession(async (prompt) => {
|
|
1401
|
+
for (const status of statuses) {
|
|
1402
|
+
if (!status.available) {
|
|
1403
|
+
console.log(`${warnLabel()} ${describeUpgradeTarget(status)}`);
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (status.target === 'npm-repo' && status.blockedReason) {
|
|
1407
|
+
console.log(`${warnLabel()} ${describeUpgradeTarget(status)}`);
|
|
1408
|
+
continue;
|
|
1409
|
+
}
|
|
1410
|
+
const defaultChoice = status.target === 'npm-repo'
|
|
1411
|
+
? false
|
|
1412
|
+
: status.upToDate === false;
|
|
1413
|
+
const question = status.target === 'npm-global'
|
|
1414
|
+
? `Upgrade global npm install now? (${describeUpgradeTarget(status)})`
|
|
1415
|
+
: status.target === 'python'
|
|
1416
|
+
? `Upgrade Python client now? (${describeUpgradeTarget(status)})`
|
|
1417
|
+
: `Refresh local repo checkout now? (${describeUpgradeTarget(status)})`;
|
|
1418
|
+
if (await promptYesNo(prompt, question, defaultChoice)) {
|
|
1419
|
+
selected.push(status.target);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
});
|
|
1423
|
+
return selected;
|
|
1424
|
+
}
|
|
1425
|
+
async function executeUpgradeTargets(targets, context) {
|
|
1426
|
+
const results = [];
|
|
1427
|
+
for (const target of targets) {
|
|
1428
|
+
const result = await executeUpgradeTarget(target, context);
|
|
1429
|
+
results.push(result);
|
|
1430
|
+
}
|
|
1431
|
+
return results;
|
|
1432
|
+
}
|
|
1259
1433
|
function commandListForTarget(target, context) {
|
|
1260
1434
|
if (target === 'npm-repo') {
|
|
1261
1435
|
return repoUpgradeCommands(context.packageRootPath);
|
|
@@ -1308,17 +1482,9 @@ function verifyGlobalNpmInstall() {
|
|
|
1308
1482
|
}
|
|
1309
1483
|
}
|
|
1310
1484
|
function verifyPythonInstall(command) {
|
|
1311
|
-
const
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
return {
|
|
1315
|
-
status: 'warn',
|
|
1316
|
-
detail: 'Python upgrade finished, but `pip show iranti` did not confirm the installed version.',
|
|
1317
|
-
};
|
|
1318
|
-
}
|
|
1319
|
-
const versionLine = proc.stdout.split(/\r?\n/).find((line) => line.toLowerCase().startsWith('version:'));
|
|
1320
|
-
return versionLine
|
|
1321
|
-
? { status: 'pass', detail: `Python client ${versionLine.trim()}.` }
|
|
1485
|
+
const version = detectPythonInstalledVersion(command);
|
|
1486
|
+
return version
|
|
1487
|
+
? { status: 'pass', detail: `Python client Version: ${version}.` }
|
|
1322
1488
|
: { status: 'warn', detail: 'Python upgrade finished, but installed version could not be confirmed.' };
|
|
1323
1489
|
}
|
|
1324
1490
|
async function executeUpgradeTarget(target, context) {
|
|
@@ -1538,7 +1704,7 @@ async function setupCommand(args) {
|
|
|
1538
1704
|
let finalScope = 'user';
|
|
1539
1705
|
let finalRoot = '';
|
|
1540
1706
|
if (setupMode === 'isolated') {
|
|
1541
|
-
finalRoot = path_1.default.resolve(await promptNonEmpty(prompt, '
|
|
1707
|
+
finalRoot = path_1.default.resolve(await promptNonEmpty(prompt, 'Where should the isolated runtime live', explicitRoot ?? path_1.default.join(process.cwd(), '.iranti-runtime')));
|
|
1542
1708
|
finalScope = 'user';
|
|
1543
1709
|
}
|
|
1544
1710
|
else {
|
|
@@ -1574,7 +1740,7 @@ async function setupCommand(args) {
|
|
|
1574
1740
|
const dbMode = (await prompt.line('How should we set up the database: existing, managed, or docker', defaultMode) ?? defaultMode).trim().toLowerCase();
|
|
1575
1741
|
if (dbMode === 'existing' || dbMode === 'managed') {
|
|
1576
1742
|
while (true) {
|
|
1577
|
-
dbUrl = await promptNonEmpty(prompt, 'DATABASE_URL', existingInstance?.env.DATABASE_URL ?? `postgresql://postgres:yourpassword@localhost:5432/iranti_${instanceName}`);
|
|
1743
|
+
dbUrl = await promptNonEmpty(prompt, 'Database connection string (DATABASE_URL)', existingInstance?.env.DATABASE_URL ?? `postgresql://postgres:yourpassword@localhost:5432/iranti_${instanceName}`);
|
|
1578
1744
|
if (!detectPlaceholder(dbUrl))
|
|
1579
1745
|
break;
|
|
1580
1746
|
console.log(`${warnLabel()} DATABASE_URL still looks like a placeholder. Enter a real connection string before finishing setup.`);
|
|
@@ -1613,7 +1779,7 @@ async function setupCommand(args) {
|
|
|
1613
1779
|
let provider = normalizeProvider(existingInstance?.env.LLM_PROVIDER ?? 'openai') ?? 'openai';
|
|
1614
1780
|
while (true) {
|
|
1615
1781
|
listProviderChoices(provider, existingInstance?.env ?? {});
|
|
1616
|
-
const chosen = normalizeProvider(await promptNonEmpty(prompt, '
|
|
1782
|
+
const chosen = normalizeProvider(await promptNonEmpty(prompt, 'Which LLM provider should Iranti use by default', provider));
|
|
1617
1783
|
if (chosen && isSupportedProvider(chosen)) {
|
|
1618
1784
|
provider = chosen;
|
|
1619
1785
|
break;
|
|
@@ -1636,7 +1802,7 @@ async function setupCommand(args) {
|
|
|
1636
1802
|
let extraProvider = provider;
|
|
1637
1803
|
while (true) {
|
|
1638
1804
|
listProviderChoices(provider, { ...seedEnv, ...providerKeys });
|
|
1639
|
-
const chosen = normalizeProvider(await promptNonEmpty(prompt, '
|
|
1805
|
+
const chosen = normalizeProvider(await promptNonEmpty(prompt, 'Which extra provider would you like to add', 'claude'));
|
|
1640
1806
|
if (!chosen) {
|
|
1641
1807
|
console.log(`${warnLabel()} Provider is required.`);
|
|
1642
1808
|
continue;
|
|
@@ -1671,10 +1837,10 @@ async function setupCommand(args) {
|
|
|
1671
1837
|
const defaultProjectPath = process.cwd();
|
|
1672
1838
|
let shouldBindProject = await promptYesNo(prompt, 'Bind a project folder to this instance now?', true);
|
|
1673
1839
|
while (shouldBindProject) {
|
|
1674
|
-
const projectPath = path_1.default.resolve(await promptNonEmpty(prompt, '
|
|
1675
|
-
const agentId = sanitizeIdentifier(await promptNonEmpty(prompt, '
|
|
1676
|
-
const memoryEntity = await promptNonEmpty(prompt, '
|
|
1677
|
-
const claudeCode = await promptYesNo(prompt, 'Create Claude Code project files here?', true);
|
|
1840
|
+
const projectPath = path_1.default.resolve(await promptNonEmpty(prompt, 'Which project folder should we bind', projects.length === 0 ? defaultProjectPath : process.cwd()));
|
|
1841
|
+
const agentId = sanitizeIdentifier(await promptNonEmpty(prompt, 'What agent id should this project use', projectAgentDefault(projectPath)), 'project_main');
|
|
1842
|
+
const memoryEntity = await promptNonEmpty(prompt, 'What memory entity should this project use', 'user/main');
|
|
1843
|
+
const claudeCode = await promptYesNo(prompt, 'Create Claude Code project files here now?', true);
|
|
1678
1844
|
projects.push({
|
|
1679
1845
|
path: projectPath,
|
|
1680
1846
|
agentId,
|
|
@@ -1726,6 +1892,10 @@ async function setupCommand(args) {
|
|
|
1726
1892
|
console.log(`${infoLabel()} Next steps:`);
|
|
1727
1893
|
console.log(` 1. iranti run --instance ${finalResult.instanceName} --root "${finalResult.root}"`);
|
|
1728
1894
|
console.log(` 2. iranti doctor --instance ${finalResult.instanceName} --root "${finalResult.root}"`);
|
|
1895
|
+
if (finalResult.bindings.length > 0) {
|
|
1896
|
+
console.log(` 3. cd "${finalResult.bindings[0].projectPath}"`);
|
|
1897
|
+
console.log(' 4. iranti chat');
|
|
1898
|
+
}
|
|
1729
1899
|
}
|
|
1730
1900
|
async function doctorCommand(args) {
|
|
1731
1901
|
const json = hasFlag(args, 'json');
|
|
@@ -1871,6 +2041,7 @@ async function doctorCommand(args) {
|
|
|
1871
2041
|
envFile,
|
|
1872
2042
|
status: summarizeStatus(checks),
|
|
1873
2043
|
checks,
|
|
2044
|
+
remediations: collectDoctorRemediations(checks, envSource, envFile),
|
|
1874
2045
|
};
|
|
1875
2046
|
if (json) {
|
|
1876
2047
|
console.log(JSON.stringify(result, null, 2));
|
|
@@ -1894,6 +2065,13 @@ async function doctorCommand(args) {
|
|
|
1894
2065
|
: failLabel('FAIL');
|
|
1895
2066
|
console.log(`${marker} ${check.name} — ${check.detail}`);
|
|
1896
2067
|
}
|
|
2068
|
+
if (result.remediations.length > 0) {
|
|
2069
|
+
console.log('');
|
|
2070
|
+
console.log('Suggested fixes:');
|
|
2071
|
+
for (const remediation of result.remediations) {
|
|
2072
|
+
console.log(` - ${remediation}`);
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
1897
2075
|
if (result.status !== 'pass') {
|
|
1898
2076
|
process.exitCode = 1;
|
|
1899
2077
|
}
|
|
@@ -1965,46 +2143,72 @@ async function statusCommand(args) {
|
|
|
1965
2143
|
}
|
|
1966
2144
|
}
|
|
1967
2145
|
async function upgradeCommand(args) {
|
|
2146
|
+
const runAll = hasFlag(args, 'all');
|
|
1968
2147
|
const checkOnly = hasFlag(args, 'check');
|
|
1969
2148
|
const dryRun = hasFlag(args, 'dry-run');
|
|
1970
2149
|
const execute = hasFlag(args, 'yes');
|
|
1971
2150
|
const json = hasFlag(args, 'json');
|
|
1972
|
-
const
|
|
2151
|
+
const requestedTargets = resolveRequestedUpgradeTargets(getFlag(args, 'target'), runAll);
|
|
1973
2152
|
const context = detectUpgradeContext(args);
|
|
1974
2153
|
const latestNpm = await fetchLatestNpmVersion();
|
|
1975
2154
|
const latestPython = await fetchLatestPypiVersion();
|
|
1976
|
-
const
|
|
2155
|
+
const statuses = buildUpgradeTargetStatuses(context, latestNpm, latestPython);
|
|
2156
|
+
const statusByTarget = new Map(statuses.map((status) => [status.target, status]));
|
|
2157
|
+
const autoSelected = requestedTargets.includes('auto')
|
|
2158
|
+
? chooseUpgradeTarget('auto', context)
|
|
2159
|
+
: null;
|
|
2160
|
+
const explicitTargets = requestedTargets
|
|
2161
|
+
.filter((target) => target !== 'auto');
|
|
2162
|
+
for (const target of explicitTargets) {
|
|
2163
|
+
const status = statusByTarget.get(target);
|
|
2164
|
+
if (!runAll && !status?.available) {
|
|
2165
|
+
throw new Error(`Requested target '${target}' is not available in this environment.`);
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
const selectedTargets = requestedTargets.includes('auto')
|
|
2169
|
+
? (autoSelected ? [autoSelected] : [])
|
|
2170
|
+
: explicitTargets.filter((target) => {
|
|
2171
|
+
const status = statusByTarget.get(target);
|
|
2172
|
+
if (!status?.available)
|
|
2173
|
+
return false;
|
|
2174
|
+
if (runAll && status.blockedReason)
|
|
2175
|
+
return false;
|
|
2176
|
+
return true;
|
|
2177
|
+
});
|
|
1977
2178
|
const commands = {
|
|
1978
2179
|
npmGlobal: 'npm install -g iranti@latest',
|
|
1979
2180
|
npmRepo: 'git pull --ff-only && npm install && npm run build',
|
|
1980
2181
|
python: context.python?.display ?? 'python -m pip install --upgrade iranti',
|
|
1981
2182
|
};
|
|
1982
2183
|
const updateAvailable = {
|
|
1983
|
-
npm: latestNpm ? compareVersions(latestNpm, context.
|
|
1984
|
-
python: latestPython ? compareVersions(latestPython, context.
|
|
2184
|
+
npm: context.globalNpmVersion && latestNpm ? compareVersions(latestNpm, context.globalNpmVersion) > 0 : null,
|
|
2185
|
+
python: context.pythonVersion && latestPython ? compareVersions(latestPython, context.pythonVersion) > 0 : null,
|
|
1985
2186
|
};
|
|
1986
|
-
const plan =
|
|
1987
|
-
let execution =
|
|
2187
|
+
const plan = selectedTargets.flatMap((target) => commandListForTarget(target, context).map((step) => step.display));
|
|
2188
|
+
let execution = [];
|
|
1988
2189
|
let note = null;
|
|
1989
2190
|
if (execute) {
|
|
1990
|
-
if (
|
|
1991
|
-
throw new Error('No executable upgrade path was detected. Use --target npm-global, --target npm-repo,
|
|
2191
|
+
if (selectedTargets.length === 0) {
|
|
2192
|
+
throw new Error('No executable upgrade path was detected. Use --target npm-global, --target npm-repo, --target python, or --all.');
|
|
1992
2193
|
}
|
|
1993
2194
|
if (dryRun || checkOnly) {
|
|
1994
2195
|
note = 'Execution skipped because --dry-run or --check was provided.';
|
|
1995
2196
|
}
|
|
1996
|
-
else
|
|
1997
|
-
|
|
2197
|
+
else {
|
|
2198
|
+
execution = await executeUpgradeTargets(selectedTargets, context);
|
|
1998
2199
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2200
|
+
}
|
|
2201
|
+
else if (!checkOnly && !dryRun && !json && process.stdin.isTTY && process.stdout.isTTY) {
|
|
2202
|
+
const interactiveTargets = await chooseInteractiveUpgradeTargets(statuses);
|
|
2203
|
+
if (interactiveTargets.length === 0) {
|
|
2204
|
+
note = 'No upgrade targets selected.';
|
|
2001
2205
|
}
|
|
2002
2206
|
else {
|
|
2003
|
-
execution = await
|
|
2207
|
+
execution = await executeUpgradeTargets(interactiveTargets, context);
|
|
2004
2208
|
}
|
|
2005
2209
|
}
|
|
2006
2210
|
else if (!checkOnly && !dryRun) {
|
|
2007
|
-
note = 'Run with --yes to execute the selected upgrade path
|
|
2211
|
+
note = 'Run with --yes to execute the selected upgrade path, or run plain `iranti upgrade` in a TTY to choose interactively.';
|
|
2008
2212
|
}
|
|
2009
2213
|
if (json) {
|
|
2010
2214
|
console.log(JSON.stringify({
|
|
@@ -2018,17 +2222,21 @@ async function upgradeCommand(args) {
|
|
|
2018
2222
|
runtimeRoot: context.runtimeRoot,
|
|
2019
2223
|
runtimeInstalled: context.runtimeInstalled,
|
|
2020
2224
|
repoCheckout: context.repoCheckout,
|
|
2225
|
+
repoDirty: context.repoDirty,
|
|
2021
2226
|
globalNpmInstall: context.globalNpmInstall,
|
|
2022
2227
|
globalNpmRoot: context.globalNpmRoot,
|
|
2228
|
+
globalNpmVersion: context.globalNpmVersion,
|
|
2023
2229
|
pythonLauncher: context.python?.executable ?? null,
|
|
2230
|
+
pythonVersion: context.pythonVersion,
|
|
2024
2231
|
},
|
|
2025
|
-
|
|
2026
|
-
|
|
2232
|
+
requestedTargets,
|
|
2233
|
+
selectedTargets,
|
|
2027
2234
|
availableTargets: context.availableTargets,
|
|
2235
|
+
targets: statuses,
|
|
2028
2236
|
updateAvailable,
|
|
2029
2237
|
commands,
|
|
2030
|
-
plan
|
|
2031
|
-
action:
|
|
2238
|
+
plan,
|
|
2239
|
+
action: execution.length > 0 ? 'upgrade' : checkOnly ? 'check' : dryRun ? 'dry-run' : 'inspect',
|
|
2032
2240
|
execution,
|
|
2033
2241
|
note,
|
|
2034
2242
|
}, null, 2));
|
|
@@ -2040,38 +2248,43 @@ async function upgradeCommand(args) {
|
|
|
2040
2248
|
console.log(` latest_python ${latestPython ?? '(unavailable)'}`);
|
|
2041
2249
|
console.log(` package_root ${context.packageRootPath}`);
|
|
2042
2250
|
console.log(` runtime_root ${context.runtimeRoot}`);
|
|
2043
|
-
console.log(` repo_checkout ${context.repoCheckout ? paint('yes', 'green') : paint('no', 'gray')}`);
|
|
2044
|
-
console.log(` npm_global ${context.globalNpmInstall ? paint('yes', 'green') : paint('no', 'gray')}`);
|
|
2045
|
-
console.log(` python ${context.python?.executable ?? paint('not found', 'yellow')}`);
|
|
2251
|
+
console.log(` repo_checkout ${context.repoCheckout ? paint('yes', 'green') : paint('no', 'gray')}${context.repoDirty ? paint(' (dirty)', 'yellow') : ''}`);
|
|
2252
|
+
console.log(` npm_global ${context.globalNpmInstall ? paint('yes', 'green') : paint('no', 'gray')}${context.globalNpmVersion ? ` (${context.globalNpmVersion})` : ''}`);
|
|
2253
|
+
console.log(` python ${context.python?.executable ?? paint('not found', 'yellow')}${context.pythonVersion ? ` (${context.pythonVersion})` : ''}`);
|
|
2046
2254
|
console.log('');
|
|
2047
|
-
if (
|
|
2048
|
-
console.log(` selected_target
|
|
2255
|
+
if (selectedTargets.length > 0) {
|
|
2256
|
+
console.log(` selected_target${selectedTargets.length > 1 ? 's' : ''} ${paint(selectedTargets.join(', '), 'cyan')}${requestedTargets.includes('auto') ? paint(' (auto)', 'gray') : ''}`);
|
|
2049
2257
|
console.log(' plan');
|
|
2050
2258
|
for (const step of plan) {
|
|
2051
|
-
console.log(` - ${step
|
|
2259
|
+
console.log(` - ${step}`);
|
|
2052
2260
|
}
|
|
2053
2261
|
}
|
|
2054
2262
|
else {
|
|
2055
|
-
console.log(`
|
|
2263
|
+
console.log(` selected_targets ${paint('none', 'yellow')}`);
|
|
2056
2264
|
console.log(' plan No executable upgrade path detected automatically.');
|
|
2057
2265
|
}
|
|
2058
2266
|
console.log('');
|
|
2059
2267
|
console.log(` npm global ${commands.npmGlobal}`);
|
|
2060
2268
|
console.log(` npm repo ${commands.npmRepo}`);
|
|
2061
2269
|
console.log(` python client ${commands.python}`);
|
|
2062
|
-
if (execution) {
|
|
2063
|
-
const marker = execution.verification.status === 'pass'
|
|
2064
|
-
? okLabel('PASS')
|
|
2065
|
-
: execution.verification.status === 'warn'
|
|
2066
|
-
? warnLabel('WARN')
|
|
2067
|
-
: failLabel('FAIL');
|
|
2270
|
+
if (execution.length > 0) {
|
|
2068
2271
|
console.log('');
|
|
2069
|
-
|
|
2070
|
-
|
|
2272
|
+
for (const result of execution) {
|
|
2273
|
+
const marker = result.verification.status === 'pass'
|
|
2274
|
+
? okLabel('PASS')
|
|
2275
|
+
: result.verification.status === 'warn'
|
|
2276
|
+
? warnLabel('WARN')
|
|
2277
|
+
: failLabel('FAIL');
|
|
2278
|
+
console.log(`${okLabel()} Upgrade completed for ${result.target}.`);
|
|
2279
|
+
console.log(`${marker} ${result.verification.detail}`);
|
|
2280
|
+
}
|
|
2071
2281
|
const { envFile } = resolveDoctorEnvTarget(args);
|
|
2072
2282
|
if (envFile) {
|
|
2073
2283
|
console.log(`${infoLabel()} Run \`iranti doctor\` to verify the active environment after the package upgrade.`);
|
|
2074
2284
|
}
|
|
2285
|
+
if (execution.some((result) => result.target === 'npm-global')) {
|
|
2286
|
+
console.log(`${infoLabel()} If this shell started on an older global CLI, open a new terminal or rerun \`iranti upgrade --check\` to confirm the new binary is active.`);
|
|
2287
|
+
}
|
|
2075
2288
|
return;
|
|
2076
2289
|
}
|
|
2077
2290
|
if (note) {
|
|
@@ -2266,14 +2479,15 @@ async function configureInstanceCommand(args) {
|
|
|
2266
2479
|
let clearProviderKey = hasFlag(args, 'clear-provider-key');
|
|
2267
2480
|
if (hasFlag(args, 'interactive')) {
|
|
2268
2481
|
await withPromptSession(async (prompt) => {
|
|
2269
|
-
portRaw = await prompt.line('
|
|
2270
|
-
dbUrl = await prompt.line('DATABASE_URL', dbUrl ?? env.DATABASE_URL);
|
|
2271
|
-
providerInput = await prompt.line('
|
|
2482
|
+
portRaw = await prompt.line('Which API port should this instance use', portRaw ?? env.IRANTI_PORT);
|
|
2483
|
+
dbUrl = await prompt.line('Database connection string (DATABASE_URL)', dbUrl ?? env.DATABASE_URL);
|
|
2484
|
+
providerInput = await prompt.line('Which LLM provider should this instance use', providerInput ?? env.LLM_PROVIDER ?? 'mock');
|
|
2272
2485
|
const interactiveProvider = normalizeProvider(providerInput ?? env.LLM_PROVIDER ?? 'mock');
|
|
2273
|
-
|
|
2274
|
-
|
|
2486
|
+
const interactiveProviderEnvKey = providerKeyEnv(interactiveProvider);
|
|
2487
|
+
if (interactiveProvider && interactiveProviderEnvKey) {
|
|
2488
|
+
providerKey = await prompt.secret(`Enter the ${providerDisplayName(interactiveProvider)} API key`, providerKey ?? env[interactiveProviderEnvKey]);
|
|
2275
2489
|
}
|
|
2276
|
-
apiKey = await prompt.secret('
|
|
2490
|
+
apiKey = await prompt.secret('Iranti API key', apiKey ?? env.IRANTI_API_KEY);
|
|
2277
2491
|
});
|
|
2278
2492
|
clearProviderKey = false;
|
|
2279
2493
|
}
|
|
@@ -2330,6 +2544,7 @@ async function configureInstanceCommand(args) {
|
|
|
2330
2544
|
if (providerKey) {
|
|
2331
2545
|
console.log(` provider ${result.provider}`);
|
|
2332
2546
|
}
|
|
2547
|
+
console.log(`${infoLabel()} Next: iranti doctor --instance ${name}${scope ? ` --scope ${scope}` : ''}`);
|
|
2333
2548
|
}
|
|
2334
2549
|
async function configureProjectCommand(args) {
|
|
2335
2550
|
const projectPath = path_1.default.resolve(args.positionals[0] ?? process.cwd());
|
|
@@ -2343,11 +2558,11 @@ async function configureProjectCommand(args) {
|
|
|
2343
2558
|
let explicitMemoryEntity = getFlag(args, 'memory-entity');
|
|
2344
2559
|
if (hasFlag(args, 'interactive')) {
|
|
2345
2560
|
await withPromptSession(async (prompt) => {
|
|
2346
|
-
instanceName = await prompt.line('
|
|
2347
|
-
explicitUrl = await prompt.line('
|
|
2348
|
-
explicitApiKey = await prompt.secret('
|
|
2349
|
-
explicitAgentId = await prompt.line('
|
|
2350
|
-
explicitMemoryEntity = await prompt.line('
|
|
2561
|
+
instanceName = await prompt.line('Which instance should this project use', instanceName);
|
|
2562
|
+
explicitUrl = await prompt.line('What Iranti URL should this project talk to', explicitUrl ?? existing.IRANTI_URL);
|
|
2563
|
+
explicitApiKey = await prompt.secret('What API key should this project use', explicitApiKey ?? existing.IRANTI_API_KEY);
|
|
2564
|
+
explicitAgentId = await prompt.line('What agent id should this project use', explicitAgentId ?? existing.IRANTI_AGENT_ID ?? 'my_agent');
|
|
2565
|
+
explicitMemoryEntity = await prompt.line('What memory entity should this project use', explicitMemoryEntity ?? existing.IRANTI_MEMORY_ENTITY ?? 'user/main');
|
|
2351
2566
|
});
|
|
2352
2567
|
}
|
|
2353
2568
|
let instanceEnvFile = existing.IRANTI_INSTANCE_ENV;
|
|
@@ -2395,6 +2610,7 @@ async function configureProjectCommand(args) {
|
|
|
2395
2610
|
if (updates.IRANTI_INSTANCE) {
|
|
2396
2611
|
console.log(` instance ${updates.IRANTI_INSTANCE}`);
|
|
2397
2612
|
}
|
|
2613
|
+
console.log(`${infoLabel()} Next: iranti doctor${updates.IRANTI_INSTANCE ? ` --instance ${updates.IRANTI_INSTANCE}` : ''}`);
|
|
2398
2614
|
}
|
|
2399
2615
|
async function authCreateKeyCommand(args) {
|
|
2400
2616
|
const instanceName = getFlag(args, 'instance');
|
|
@@ -2462,6 +2678,7 @@ async function authCreateKeyCommand(args) {
|
|
|
2462
2678
|
if (projectPath) {
|
|
2463
2679
|
console.log(` project ${path_1.default.resolve(projectPath)}`);
|
|
2464
2680
|
}
|
|
2681
|
+
console.log(`${infoLabel()} Next: iranti doctor --instance ${instanceName}`);
|
|
2465
2682
|
process.exit(0);
|
|
2466
2683
|
}
|
|
2467
2684
|
async function authListKeysCommand(args) {
|
|
@@ -2565,7 +2782,7 @@ Project-level:
|
|
|
2565
2782
|
Diagnostics:
|
|
2566
2783
|
iranti doctor [--instance <name>] [--scope user|system] [--env <file>] [--json]
|
|
2567
2784
|
iranti status [--scope user|system] [--json]
|
|
2568
|
-
iranti upgrade [--check] [--dry-run] [--yes] [--target auto|npm-global|npm-repo|python] [--json]
|
|
2785
|
+
iranti upgrade [--check] [--dry-run] [--yes] [--all] [--target auto|npm-global|npm-repo|python[,python]] [--json]
|
|
2569
2786
|
iranti chat [--agent <agent-id>] [--provider <provider>] [--model <model>]
|
|
2570
2787
|
iranti resolve [--dir <escalation-dir>]
|
|
2571
2788
|
|
|
@@ -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.5',
|
|
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.5',
|
|
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.5',
|
|
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.5',
|
|
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.5' },
|
|
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.5',
|
|
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.5',
|
|
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.5',
|
|
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.5',
|
|
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.5',
|
|
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.5',
|
|
241
241
|
events: [
|
|
242
242
|
{
|
|
243
243
|
at: new Date().toISOString(),
|
package/dist/src/api/server.js
CHANGED