iranti 0.2.10 → 0.2.11

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.
@@ -707,7 +707,7 @@ function isSupportedProvider(provider) {
707
707
  async function promptYesNo(session, prompt, defaultValue) {
708
708
  const defaultToken = defaultValue ? 'Y/n' : 'y/N';
709
709
  while (true) {
710
- const answer = (await session.line(`${prompt} (${defaultToken})`, '') ?? '').trim().toLowerCase();
710
+ const answer = (await session.line(`${prompt} (${defaultToken})`) ?? '').trim().toLowerCase();
711
711
  if (!answer)
712
712
  return defaultValue;
713
713
  if (['y', 'yes'].includes(answer))
@@ -730,7 +730,7 @@ async function promptRequiredSecret(session, prompt, currentValue) {
730
730
  const value = (await session.secretRequired(prompt, currentValue) ?? '').trim();
731
731
  if (value.length > 0 && !detectPlaceholder(value))
732
732
  return value;
733
- console.log(`${warnLabel()} A real secret is required.`);
733
+ console.log(`${warnLabel()} ${prompt} is required.`);
734
734
  }
735
735
  }
736
736
  function makeLegacyInstanceApiKey(instanceName) {
@@ -1466,8 +1466,13 @@ function collectDoctorRemediations(checks, envSource, envFile) {
1466
1466
  add('Run `iranti setup`, or rerun `iranti doctor` with `--instance <name>` or `--env <file>`.');
1467
1467
  }
1468
1468
  }
1469
- if (check.name === 'database configuration' && check.status === 'fail') {
1470
- add(`Set a real DATABASE_URL in ${envFile ?? 'the target env file'}, or rerun \`iranti setup\` to configure the database again.`);
1469
+ if ((check.name === 'database configuration' || check.name === 'bound instance database configuration') && check.status === 'fail') {
1470
+ if (check.name === 'bound instance database configuration') {
1471
+ add('Fix the linked instance env, or rerun `iranti setup` / `iranti configure instance` for the bound instance.');
1472
+ }
1473
+ else {
1474
+ add(`Set a real DATABASE_URL in ${envFile ?? 'the target env file'}, or rerun \`iranti setup\` to configure the database again.`);
1475
+ }
1471
1476
  }
1472
1477
  if (check.name === 'project binding url' && check.status === 'fail') {
1473
1478
  add('Run `iranti configure project` to refresh the project binding, or set IRANTI_URL in `.env.iranti`.');
@@ -1475,15 +1480,18 @@ function collectDoctorRemediations(checks, envSource, envFile) {
1475
1480
  if (check.name === 'project api key' && check.status === 'fail') {
1476
1481
  add('Run `iranti configure project` or set IRANTI_API_KEY in `.env.iranti`.');
1477
1482
  }
1483
+ if (check.name === 'bound instance env' && check.status !== 'pass') {
1484
+ 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.');
1485
+ }
1478
1486
  if (check.name === 'api key' && check.status !== 'pass') {
1479
1487
  add(envSource === 'project-binding'
1480
1488
  ? 'Set IRANTI_API_KEY in the project binding, or rerun `iranti configure project`.'
1481
1489
  : 'Create or rotate an Iranti key with `iranti auth create-key`, then store it in the target env.');
1482
1490
  }
1483
- if (check.name === 'provider credentials' && check.status === 'fail') {
1491
+ if ((check.name === 'provider credentials' || check.name === 'bound instance provider credentials') && check.status === 'fail') {
1484
1492
  add('Store or refresh the upstream provider key with `iranti add api-key` or `iranti update api-key`.');
1485
1493
  }
1486
- if (check.name === 'vector backend' && check.status === 'fail') {
1494
+ if ((check.name === 'vector backend' || check.name === 'bound instance vector backend') && check.status === 'fail') {
1487
1495
  add('Check the vector backend env vars, or switch back to `IRANTI_VECTOR_BACKEND=pgvector` if the external backend is not ready.');
1488
1496
  }
1489
1497
  }
@@ -2341,7 +2349,7 @@ async function setupCommand(args) {
2341
2349
  await withPromptSession(async (prompt) => {
2342
2350
  let setupMode = 'isolated';
2343
2351
  while (true) {
2344
- const chosen = (await prompt.line('How should Iranti install the runtime: isolated per-project or shared machine runtime', 'isolated') ?? 'isolated').trim().toLowerCase();
2352
+ const chosen = (await prompt.line('Runtime mode (isolated or shared)', 'isolated') ?? 'isolated').trim().toLowerCase();
2345
2353
  if (chosen === 'shared' || chosen === 'isolated') {
2346
2354
  setupMode = chosen;
2347
2355
  break;
@@ -2351,12 +2359,12 @@ async function setupCommand(args) {
2351
2359
  let finalScope = 'user';
2352
2360
  let finalRoot = '';
2353
2361
  if (setupMode === 'isolated') {
2354
- finalRoot = path_1.default.resolve(await promptNonEmpty(prompt, 'Where should the isolated runtime live', explicitRoot ?? path_1.default.join(process.cwd(), '.iranti-runtime')));
2362
+ finalRoot = path_1.default.resolve(await promptNonEmpty(prompt, 'Isolated runtime path', explicitRoot ?? path_1.default.join(process.cwd(), '.iranti-runtime')));
2355
2363
  finalScope = 'user';
2356
2364
  }
2357
2365
  else {
2358
2366
  while (true) {
2359
- const chosenScope = (await prompt.line('Install scope: user or system', explicitScope ?? 'user') ?? 'user').trim().toLowerCase();
2367
+ const chosenScope = (await prompt.line('Install scope (user or system)', explicitScope ?? 'user') ?? 'user').trim().toLowerCase();
2360
2368
  if (chosenScope === 'user' || chosenScope === 'system') {
2361
2369
  finalScope = chosenScope;
2362
2370
  break;
@@ -2367,7 +2375,7 @@ async function setupCommand(args) {
2367
2375
  }
2368
2376
  await ensureRuntimeInstalled(finalRoot, finalScope);
2369
2377
  console.log(`${okLabel()} Runtime ready at ${finalRoot}`);
2370
- const instanceName = sanitizeIdentifier(await promptNonEmpty(prompt, 'What should this instance be called', setupMode === 'isolated' ? sanitizeIdentifier(path_1.default.basename(process.cwd()), 'local') : 'local'), 'local');
2378
+ const instanceName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Instance name', setupMode === 'isolated' ? sanitizeIdentifier(path_1.default.basename(process.cwd()), 'local') : 'local'), 'local');
2371
2379
  const existingInstance = fs_1.default.existsSync(instancePaths(finalRoot, instanceName).envFile)
2372
2380
  ? await loadInstanceEnv(finalRoot, instanceName)
2373
2381
  : null;
@@ -2378,7 +2386,7 @@ async function setupCommand(args) {
2378
2386
  console.log(`${infoLabel()} Creating new instance '${instanceName}'.`);
2379
2387
  }
2380
2388
  const existingPort = Number.parseInt(existingInstance?.env.IRANTI_PORT ?? '3001', 10);
2381
- const port = await chooseAvailablePort(prompt, 'Which port should the Iranti API use', existingPort, Boolean(existingInstance));
2389
+ const port = await chooseAvailablePort(prompt, 'API port', existingPort, Boolean(existingInstance));
2382
2390
  const dockerAvailable = hasDockerInstalled();
2383
2391
  const psqlAvailable = hasCommandInstalled('psql');
2384
2392
  let dbUrl = '';
@@ -2386,14 +2394,14 @@ async function setupCommand(args) {
2386
2394
  let databaseMode = recommendedDatabaseMode;
2387
2395
  while (true) {
2388
2396
  const defaultMode = recommendedDatabaseMode;
2389
- const dbMode = (await prompt.line('How should we set up the database: local, managed, or docker', defaultMode) ?? defaultMode).trim().toLowerCase();
2397
+ const dbMode = (await prompt.line('Database mode (local, managed, or docker)', defaultMode) ?? defaultMode).trim().toLowerCase();
2390
2398
  if (dbMode === 'existing' || dbMode === 'local' || dbMode === 'managed') {
2391
2399
  databaseMode = dbMode === 'existing' ? 'local' : dbMode;
2392
2400
  const defaultDatabaseUrl = databaseMode === 'local'
2393
2401
  ? existingInstance?.env.DATABASE_URL ?? deriveDatabaseUrlForMode('local', instanceName)
2394
2402
  : existingInstance?.env.DATABASE_URL ?? '';
2395
2403
  while (true) {
2396
- dbUrl = await promptNonEmpty(prompt, 'Database connection string (DATABASE_URL)', defaultDatabaseUrl);
2404
+ dbUrl = await promptNonEmpty(prompt, 'DATABASE_URL', defaultDatabaseUrl);
2397
2405
  if (!detectPlaceholder(dbUrl))
2398
2406
  break;
2399
2407
  console.log(`${warnLabel()} DATABASE_URL still looks like a placeholder. Enter a real connection string before finishing setup.`);
@@ -2415,9 +2423,9 @@ async function setupCommand(args) {
2415
2423
  console.log(`${warnLabel()} Docker is not installed or not on PATH. Choose local or managed instead.`);
2416
2424
  continue;
2417
2425
  }
2418
- const dbHostPort = await chooseAvailablePort(prompt, 'Which host port should Docker PostgreSQL use', 5432, false);
2419
- const dbName = sanitizeIdentifier(await promptNonEmpty(prompt, 'What should the Docker PostgreSQL database be called', `iranti_${instanceName}`), `iranti_${instanceName}`);
2420
- const dbPassword = await promptRequiredSecret(prompt, 'Set a password for Docker PostgreSQL');
2426
+ const dbHostPort = await chooseAvailablePort(prompt, 'Docker PostgreSQL host port', 5432, false);
2427
+ const dbName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Docker PostgreSQL database name', `iranti_${instanceName}`), `iranti_${instanceName}`);
2428
+ const dbPassword = await promptRequiredSecret(prompt, 'Docker PostgreSQL password');
2421
2429
  const containerName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Docker container name', `iranti_${instanceName}_db`), `iranti_${instanceName}_db`);
2422
2430
  dbUrl = `postgresql://postgres:${dbPassword}@localhost:${dbHostPort}/${dbName}`;
2423
2431
  console.log(`${infoLabel()} Docker will be used only for PostgreSQL. Iranti itself does not require Docker once a PostgreSQL database is available.`);
@@ -2441,7 +2449,7 @@ async function setupCommand(args) {
2441
2449
  let provider = normalizeProvider(existingInstance?.env.LLM_PROVIDER ?? 'openai') ?? 'openai';
2442
2450
  while (true) {
2443
2451
  listProviderChoices(provider, existingInstance?.env ?? {});
2444
- const chosen = normalizeProvider(await promptNonEmpty(prompt, 'Which LLM provider should Iranti use by default', provider));
2452
+ const chosen = normalizeProvider(await promptNonEmpty(prompt, 'Default LLM provider', provider));
2445
2453
  if (chosen && isSupportedProvider(chosen)) {
2446
2454
  provider = chosen;
2447
2455
  break;
@@ -2464,7 +2472,7 @@ async function setupCommand(args) {
2464
2472
  let extraProvider = provider;
2465
2473
  while (true) {
2466
2474
  listProviderChoices(provider, { ...seedEnv, ...providerKeys });
2467
- const chosen = normalizeProvider(await promptNonEmpty(prompt, 'Which extra provider would you like to add', 'claude'));
2475
+ const chosen = normalizeProvider(await promptNonEmpty(prompt, 'Additional provider', 'claude'));
2468
2476
  if (!chosen) {
2469
2477
  console.log(`${warnLabel()} Provider is required.`);
2470
2478
  continue;
@@ -2499,9 +2507,9 @@ async function setupCommand(args) {
2499
2507
  const defaultProjectPath = process.cwd();
2500
2508
  let shouldBindProject = await promptYesNo(prompt, 'Bind a project folder to this instance now?', true);
2501
2509
  while (shouldBindProject) {
2502
- const projectPath = path_1.default.resolve(await promptNonEmpty(prompt, 'Which project folder should we bind', projects.length === 0 ? defaultProjectPath : process.cwd()));
2503
- const agentId = sanitizeIdentifier(await promptNonEmpty(prompt, 'What agent id should this project use', projectAgentDefault(projectPath)), 'project_main');
2504
- const memoryEntity = await promptNonEmpty(prompt, 'What memory entity should this project use', 'user/main');
2510
+ const projectPath = path_1.default.resolve(await promptNonEmpty(prompt, 'Project path to bind', projects.length === 0 ? defaultProjectPath : process.cwd()));
2511
+ const agentId = sanitizeIdentifier(await promptNonEmpty(prompt, 'Project agent ID', projectAgentDefault(projectPath)), 'project_main');
2512
+ const memoryEntity = await promptNonEmpty(prompt, 'Project memory entity', 'user/main');
2505
2513
  const claudeCode = await promptYesNo(prompt, 'Create Claude Code project files here now?', true);
2506
2514
  projects.push({
2507
2515
  path: projectPath,
@@ -2574,6 +2582,71 @@ async function doctorCommand(args) {
2574
2582
  const { envFile, envSource } = resolveDoctorEnvTarget(args);
2575
2583
  const checks = [];
2576
2584
  const version = getPackageVersion();
2585
+ const pushEnvironmentChecks = async (env, prefix = '') => {
2586
+ const databaseUrl = env.DATABASE_URL;
2587
+ checks.push(detectPlaceholder(databaseUrl)
2588
+ ? {
2589
+ name: `${prefix}database configuration`,
2590
+ status: 'fail',
2591
+ detail: 'DATABASE_URL is missing or still uses a placeholder value.',
2592
+ }
2593
+ : {
2594
+ name: `${prefix}database configuration`,
2595
+ status: 'pass',
2596
+ detail: 'DATABASE_URL is present and non-placeholder.',
2597
+ });
2598
+ const provider = env.LLM_PROVIDER ?? 'mock';
2599
+ checks.push({
2600
+ name: `${prefix}llm provider`,
2601
+ status: 'pass',
2602
+ detail: `LLM_PROVIDER=${provider}`,
2603
+ });
2604
+ const providerKeyCheck = detectProviderKey(provider, env);
2605
+ checks.push({
2606
+ ...providerKeyCheck,
2607
+ name: `${prefix}${providerKeyCheck.name}`,
2608
+ });
2609
+ try {
2610
+ const backendName = (0, backends_1.resolveVectorBackendName)({
2611
+ vectorBackend: env.IRANTI_VECTOR_BACKEND,
2612
+ qdrantUrl: env.IRANTI_QDRANT_URL,
2613
+ qdrantApiKey: env.IRANTI_QDRANT_API_KEY,
2614
+ qdrantCollection: env.IRANTI_QDRANT_COLLECTION,
2615
+ chromaUrl: env.IRANTI_CHROMA_URL,
2616
+ chromaCollection: env.IRANTI_CHROMA_COLLECTION,
2617
+ chromaTenant: env.IRANTI_CHROMA_TENANT,
2618
+ chromaDatabase: env.IRANTI_CHROMA_DATABASE,
2619
+ chromaToken: env.IRANTI_CHROMA_TOKEN,
2620
+ });
2621
+ const backend = (0, backends_1.createVectorBackend)({
2622
+ vectorBackend: backendName,
2623
+ qdrantUrl: env.IRANTI_QDRANT_URL,
2624
+ qdrantApiKey: env.IRANTI_QDRANT_API_KEY,
2625
+ qdrantCollection: env.IRANTI_QDRANT_COLLECTION,
2626
+ chromaUrl: env.IRANTI_CHROMA_URL,
2627
+ chromaCollection: env.IRANTI_CHROMA_COLLECTION,
2628
+ chromaTenant: env.IRANTI_CHROMA_TENANT,
2629
+ chromaDatabase: env.IRANTI_CHROMA_DATABASE,
2630
+ chromaToken: env.IRANTI_CHROMA_TOKEN,
2631
+ });
2632
+ const reachable = await backend.ping();
2633
+ const url = vectorBackendUrl(backendName, env);
2634
+ checks.push({
2635
+ name: `${prefix}vector backend`,
2636
+ status: reachable ? 'pass' : 'warn',
2637
+ detail: url
2638
+ ? `${backendName} (${url}) is ${reachable ? 'reachable' : 'unreachable'}`
2639
+ : `${backendName} is ${reachable ? 'reachable' : 'unreachable'}`,
2640
+ });
2641
+ }
2642
+ catch (error) {
2643
+ checks.push({
2644
+ name: `${prefix}vector backend`,
2645
+ status: 'fail',
2646
+ detail: error instanceof Error ? error.message : String(error),
2647
+ });
2648
+ }
2649
+ };
2577
2650
  checks.push({
2578
2651
  name: 'node version',
2579
2652
  status: Number.parseInt(process.versions.node.split('.')[0] ?? '0', 10) >= 18 ? 'pass' : 'fail',
@@ -2603,24 +2676,15 @@ async function doctorCommand(args) {
2603
2676
  }
2604
2677
  else {
2605
2678
  const env = await readEnvFile(envFile);
2679
+ const treatAsProjectBinding = envSource === 'project-binding'
2680
+ || path_1.default.basename(envFile).toLowerCase() === '.env.iranti'
2681
+ || (Boolean(env.IRANTI_URL?.trim()) && detectPlaceholder(env.DATABASE_URL));
2606
2682
  checks.push({
2607
2683
  name: 'environment file',
2608
2684
  status: 'pass',
2609
2685
  detail: `${envSource} env loaded from ${envFile}`,
2610
2686
  });
2611
- const databaseUrl = env.DATABASE_URL;
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') {
2687
+ if (treatAsProjectBinding) {
2624
2688
  checks.push(detectPlaceholder(env.IRANTI_URL)
2625
2689
  ? {
2626
2690
  name: 'project binding url',
@@ -2633,7 +2697,7 @@ async function doctorCommand(args) {
2633
2697
  detail: `IRANTI_URL=${env.IRANTI_URL}`,
2634
2698
  });
2635
2699
  }
2636
- if (envSource === 'project-binding') {
2700
+ if (treatAsProjectBinding) {
2637
2701
  checks.push(detectPlaceholder(env.IRANTI_API_KEY)
2638
2702
  ? {
2639
2703
  name: 'project api key',
@@ -2645,8 +2709,33 @@ async function doctorCommand(args) {
2645
2709
  status: 'pass',
2646
2710
  detail: 'IRANTI_API_KEY is present in .env.iranti.',
2647
2711
  });
2712
+ const linkedInstanceEnv = env.IRANTI_INSTANCE_ENV?.trim();
2713
+ if (!linkedInstanceEnv) {
2714
+ checks.push({
2715
+ name: 'bound instance env',
2716
+ status: 'warn',
2717
+ detail: 'IRANTI_INSTANCE_ENV is not set in .env.iranti. Skipping database and provider checks for the bound instance.',
2718
+ });
2719
+ }
2720
+ else if (!fs_1.default.existsSync(linkedInstanceEnv)) {
2721
+ checks.push({
2722
+ name: 'bound instance env',
2723
+ status: 'warn',
2724
+ detail: `Linked instance env not found: ${linkedInstanceEnv}. Skipping database and provider checks for the bound instance.`,
2725
+ });
2726
+ }
2727
+ else {
2728
+ checks.push({
2729
+ name: 'bound instance env',
2730
+ status: 'pass',
2731
+ detail: `Using ${linkedInstanceEnv} for bound instance diagnostics.`,
2732
+ });
2733
+ const linkedEnv = await readEnvFile(linkedInstanceEnv);
2734
+ await pushEnvironmentChecks(linkedEnv, 'bound instance ');
2735
+ }
2648
2736
  }
2649
2737
  else {
2738
+ await pushEnvironmentChecks(env);
2650
2739
  checks.push(detectPlaceholder(env.IRANTI_API_KEY)
2651
2740
  ? {
2652
2741
  name: 'api key',
@@ -2659,53 +2748,6 @@ async function doctorCommand(args) {
2659
2748
  detail: 'IRANTI_API_KEY is present.',
2660
2749
  });
2661
2750
  }
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
2751
  }
2710
2752
  const result = {
2711
2753
  version,
@@ -3173,13 +3215,13 @@ async function configureInstanceCommand(args) {
3173
3215
  let clearProviderKey = hasFlag(args, 'clear-provider-key');
3174
3216
  if (hasFlag(args, 'interactive')) {
3175
3217
  await withPromptSession(async (prompt) => {
3176
- portRaw = await prompt.line('Which API port should this instance use', portRaw ?? env.IRANTI_PORT);
3177
- dbUrl = await prompt.line('Database connection string (DATABASE_URL)', dbUrl ?? env.DATABASE_URL);
3178
- providerInput = await prompt.line('Which LLM provider should this instance use', providerInput ?? env.LLM_PROVIDER ?? 'mock');
3218
+ portRaw = await prompt.line('API port', portRaw ?? env.IRANTI_PORT);
3219
+ dbUrl = await prompt.line('DATABASE_URL', dbUrl ?? env.DATABASE_URL);
3220
+ providerInput = await prompt.line('LLM provider', providerInput ?? env.LLM_PROVIDER ?? 'mock');
3179
3221
  const interactiveProvider = normalizeProvider(providerInput ?? env.LLM_PROVIDER ?? 'mock');
3180
3222
  const interactiveProviderEnvKey = providerKeyEnv(interactiveProvider);
3181
3223
  if (interactiveProvider && interactiveProviderEnvKey) {
3182
- providerKey = await prompt.secret(`Enter the ${providerDisplayName(interactiveProvider)} API key`, providerKey ?? env[interactiveProviderEnvKey]);
3224
+ providerKey = await prompt.secret(`${providerDisplayName(interactiveProvider)} API key`, providerKey ?? env[interactiveProviderEnvKey]);
3183
3225
  }
3184
3226
  apiKey = await prompt.secret('Iranti API key', apiKey ?? env.IRANTI_API_KEY);
3185
3227
  });
@@ -3256,12 +3298,12 @@ async function configureProjectCommand(args) {
3256
3298
  let explicitProjectMode = getFlag(args, 'mode');
3257
3299
  if (hasFlag(args, 'interactive')) {
3258
3300
  await withPromptSession(async (prompt) => {
3259
- instanceName = await prompt.line('Which instance should this project use', instanceName);
3260
- explicitUrl = await prompt.line('What Iranti URL should this project talk to', explicitUrl ?? existing.IRANTI_URL);
3261
- explicitApiKey = await prompt.secret('What API key should this project use', explicitApiKey ?? existing.IRANTI_API_KEY);
3262
- explicitAgentId = await prompt.line('What agent id should this project use', explicitAgentId ?? existing.IRANTI_AGENT_ID ?? projectAgentDefault(projectPath));
3263
- explicitMemoryEntity = await prompt.line('What memory entity should this project use', explicitMemoryEntity ?? existing.IRANTI_MEMORY_ENTITY ?? 'user/main');
3264
- explicitProjectMode = await prompt.line('Should this project be isolated or shared', explicitProjectMode ?? existing.IRANTI_PROJECT_MODE ?? inferProjectMode(projectPath, existing.IRANTI_INSTANCE_ENV));
3301
+ instanceName = await prompt.line('Instance name', instanceName);
3302
+ explicitUrl = await prompt.line('Iranti URL', explicitUrl ?? existing.IRANTI_URL);
3303
+ explicitApiKey = await prompt.secret('Project API key', explicitApiKey ?? existing.IRANTI_API_KEY);
3304
+ explicitAgentId = await prompt.line('Project agent ID', explicitAgentId ?? existing.IRANTI_AGENT_ID ?? projectAgentDefault(projectPath));
3305
+ explicitMemoryEntity = await prompt.line('Project memory entity', explicitMemoryEntity ?? existing.IRANTI_MEMORY_ENTITY ?? 'user/main');
3306
+ explicitProjectMode = await prompt.line('Project mode (isolated or shared)', explicitProjectMode ?? existing.IRANTI_PROJECT_MODE ?? inferProjectMode(projectPath, existing.IRANTI_INSTANCE_ENV));
3265
3307
  });
3266
3308
  }
3267
3309
  let instanceEnvFile = existing.IRANTI_INSTANCE_ENV;
@@ -3675,24 +3717,26 @@ function printHelp() {
3675
3717
  const printRows = (title, entries) => {
3676
3718
  console.log(sectionTitle(title));
3677
3719
  for (const [command, description] of entries) {
3678
- console.log(` ${commandText(command.padEnd(72))} ${description}`);
3720
+ console.log(` ${commandText(command)}`);
3721
+ console.log(` ${description}`);
3679
3722
  }
3680
3723
  console.log('');
3681
3724
  };
3682
3725
  console.log(sectionTitle('Iranti CLI'));
3683
3726
  console.log('Memory infrastructure for multi-agent systems.');
3727
+ console.log('Most instance-aware commands also accept --root <path> in addition to --scope.');
3684
3728
  console.log('');
3685
3729
  printRows('Start Here', rows);
3686
3730
  printRows('Setup And Runtime', [
3687
3731
  ['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.'],
3732
+ ['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
3733
  ['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
3734
  ['iranti instance list [--scope user|system]', 'List configured instances.'],
3691
3735
  ['iranti instance show <name> [--scope user|system]', 'Show one instance env, port, and database target.'],
3692
3736
  ['iranti run --instance <name> [--scope user|system]', 'Start an instance.'],
3693
3737
  ]);
3694
3738
  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.'],
3739
+ ['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
3740
  ['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
3741
  ['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
3742
  ]);
@@ -3738,22 +3782,31 @@ function printHelp() {
3738
3782
  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
3783
  console.log(` ${commandText('iranti add api-key openai --instance local --set-default')}`);
3740
3784
  }
3785
+ function printSetupHelp() {
3786
+ console.log(sectionTitle('Setup Command'));
3787
+ 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]')}`);
3788
+ console.log('');
3789
+ console.log(' Interactive mode walks through runtime, database, provider keys, API keys, and project binding.');
3790
+ console.log(' Use `--defaults` to build a plan from flags and environment variables without prompts.');
3791
+ console.log(' Use `--config <file>` to execute a saved setup plan.');
3792
+ console.log(' `--projects` and `--claude-code` apply to the non-interactive defaults flow.');
3793
+ }
3741
3794
  function printInstanceHelp() {
3742
3795
  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]')}`);
3796
+ 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>]')}`);
3797
+ console.log(` ${commandText('iranti instance list [--scope user|system] [--root <path>]')}`);
3798
+ console.log(` ${commandText('iranti instance show <name> [--scope user|system] [--root <path>]')}`);
3746
3799
  }
3747
3800
  function printConfigureHelp() {
3748
3801
  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]')}`);
3802
+ 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]')}`);
3803
+ 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
3804
  }
3752
3805
  function printAuthHelp() {
3753
3806
  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]')}`);
3807
+ 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]')}`);
3808
+ console.log(` ${commandText('iranti auth list-keys --instance <name> [--scope user|system] [--root <path>] [--json]')}`);
3809
+ console.log(` ${commandText('iranti auth revoke-key --instance <name> --key-id <id> [--scope user|system] [--root <path>] [--json]')}`);
3757
3810
  }
3758
3811
  function printIntegrateHelp() {
3759
3812
  console.log(sectionTitle('Integrations'));
@@ -3761,6 +3814,15 @@ function printIntegrateHelp() {
3761
3814
  console.log(` ${commandText('iranti integrate claude --scan <dir> [--recursive] [--force]')}`);
3762
3815
  console.log(` ${commandText('iranti integrate codex [--name iranti] [--agent codex_code] [--source Codex] [--provider openai] [--project-env <path>] [--local-script]')}`);
3763
3816
  }
3817
+ function printProviderKeyHelp() {
3818
+ console.log(sectionTitle('Provider Key Commands'));
3819
+ console.log(` ${commandText('iranti list api-keys [--instance <name>] [--project <path>] [--json]')}`);
3820
+ console.log(` ${commandText('iranti add api-key [provider] [--instance <name>] [--project <path>] [--key <token>] [--set-default] [--json]')}`);
3821
+ console.log(` ${commandText('iranti update api-key [provider] [--instance <name>] [--project <path>] [--key <token>] [--set-default] [--json]')}`);
3822
+ console.log(` ${commandText('iranti remove api-key [provider] [--instance <name>] [--project <path>] [--json]')}`);
3823
+ console.log('');
3824
+ console.log(' Target either an instance env or a project binding. If neither is supplied, the CLI will try the current project first.');
3825
+ }
3764
3826
  async function main() {
3765
3827
  const args = parseArgs(process.argv.slice(2));
3766
3828
  if (!args.command || args.command === 'help' || args.command === '--help') {
@@ -3772,6 +3834,10 @@ async function main() {
3772
3834
  return;
3773
3835
  }
3774
3836
  if (args.command === 'setup') {
3837
+ if (hasFlag(args, 'help')) {
3838
+ printSetupHelp();
3839
+ return;
3840
+ }
3775
3841
  await setupCommand(args);
3776
3842
  return;
3777
3843
  }
@@ -3833,18 +3899,34 @@ async function main() {
3833
3899
  throw new Error(`Unknown auth subcommand '${args.subcommand ?? ''}'.`);
3834
3900
  }
3835
3901
  if (args.command === 'list' && args.subcommand === 'api-keys') {
3902
+ if (hasFlag(args, 'help')) {
3903
+ printProviderKeyHelp();
3904
+ return;
3905
+ }
3836
3906
  await listProviderKeysCommand(args);
3837
3907
  return;
3838
3908
  }
3839
3909
  if (args.command === 'add' && args.subcommand === 'api-key') {
3910
+ if (hasFlag(args, 'help')) {
3911
+ printProviderKeyHelp();
3912
+ return;
3913
+ }
3840
3914
  await upsertProviderKeyCommand(args, 'add');
3841
3915
  return;
3842
3916
  }
3843
3917
  if (args.command === 'update' && args.subcommand === 'api-key') {
3918
+ if (hasFlag(args, 'help')) {
3919
+ printProviderKeyHelp();
3920
+ return;
3921
+ }
3844
3922
  await upsertProviderKeyCommand(args, 'update');
3845
3923
  return;
3846
3924
  }
3847
3925
  if (args.command === 'remove' && args.subcommand === 'api-key') {
3926
+ if (hasFlag(args, 'help')) {
3927
+ printProviderKeyHelp();
3928
+ return;
3929
+ }
3848
3930
  await removeProviderKeyCommand(args);
3849
3931
  return;
3850
3932
  }
@@ -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.10',
147
+ version: '0.2.11',
148
148
  });
149
149
  server.registerTool('iranti_handshake', {
150
150
  description: `Initialize or refresh an agent's working-memory brief for the current task.
@@ -15,7 +15,7 @@ const STAFF_ENTRIES = [
15
15
  entityId: 'librarian',
16
16
  key: 'operating_rules',
17
17
  valueRaw: {
18
- version: '0.2.10',
18
+ version: '0.2.11',
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.10',
42
+ version: '0.2.11',
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.10',
64
+ version: '0.2.11',
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.10' },
85
+ valueRaw: { version: '0.2.11' },
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.10',
98
+ seedVersion: '0.2.11',
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.10',
111
+ version: '0.2.11',
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.10',
159
+ version: '0.2.11',
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.10',
190
+ version: '0.2.11',
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.10',
204
+ version: '0.2.11',
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.10',
240
+ version: '0.2.11',
241
241
  events: [
242
242
  {
243
243
  at: new Date().toISOString(),
@@ -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.10',
72
+ version: '0.2.11',
73
73
  provider: process.env.LLM_PROVIDER ?? 'mock',
74
74
  });
75
75
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iranti",
3
- "version": "0.2.10",
3
+ "version": "0.2.11",
4
4
  "description": "Memory infrastructure for multi-agent AI systems",
5
5
  "main": "dist/src/sdk/index.js",
6
6
  "files": [