iranti 0.2.19 → 0.2.20

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 CHANGED
@@ -9,10 +9,10 @@
9
9
 
10
10
  Iranti gives agents persistent, identity-based memory. Facts written by one agent are retrievable by any other agent through exact entity+key lookup. Iranti also supports hybrid search (lexical + vector) when exact keys are unknown. Memory persists across sessions and survives context window limits.
11
11
 
12
- **Latest release:** [`v0.2.19`](https://github.com/nfemmanuel/iranti/releases/tag/v0.2.19)
12
+ **Latest release:** [`v0.2.20`](https://github.com/nfemmanuel/iranti/releases/tag/v0.2.20)
13
13
  Published packages:
14
- - `iranti@0.2.19`
15
- - `@iranti/sdk@0.2.19`
14
+ - `iranti@0.2.20`
15
+ - `@iranti/sdk@0.2.20`
16
16
 
17
17
  ---
18
18
 
@@ -173,7 +173,7 @@ The next leverage is still product simplicity: setup, operations, and day-to-day
173
173
 
174
174
  ## Quickstart
175
175
 
176
- **Requirements**: Node.js 18+, PostgreSQL, Python 3.8+
176
+ **Requirements**: Node.js 18+, PostgreSQL with pgvector support, Python 3.8+
177
177
 
178
178
  Docker is optional. It is one local way to run PostgreSQL if you do not already have a database. Iranti still requires PostgreSQL; the setup improvement is smarter bootstrap and clearer guidance, not a second storage backend.
179
179
 
@@ -190,7 +190,7 @@ iranti run --instance local
190
190
 
191
191
  `iranti setup` now defaults to an isolated per-project runtime. Shared machine-level instances are still supported, but they are now an explicit choice rather than the default.
192
192
 
193
- 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.
193
+ If local PostgreSQL is available and pgvector-capable, setup can bootstrap a localhost database for you. If Docker is available, setup now prefers the Docker path over a plain local listener because it guarantees pgvector. If local PostgreSQL is reachable but does not provide pgvector, setup now fails early with a direct action path instead of a late Prisma migration error.
194
194
 
195
195
  Long-running agents can now checkpoint and recover interrupted work. Programmatic session lifecycle methods are available through the SDK and REST API:
196
196
  - `checkpoint()`
@@ -221,6 +221,17 @@ function builtScriptPath(scriptName) {
221
221
  }
222
222
  function formatSetupBootstrapFailure(error) {
223
223
  const reason = error instanceof Error ? error.message : String(error);
224
+ if (/Could not find Prisma Schema/i.test(reason)) {
225
+ return new Error(`Database bootstrap failed because Prisma could not locate prisma/schema.prisma from the active package. ` +
226
+ `This usually means the installed CLI bundle or working-directory handoff is wrong. ` +
227
+ `Underlying error: ${reason}`);
228
+ }
229
+ if (/extension "vector" is not available|pgvector extension is not installed|does not have the pgvector extension installed/i.test(reason)) {
230
+ return new Error(`Database bootstrap failed because the target PostgreSQL server does not provide pgvector. ` +
231
+ `Iranti currently requires pgvector-capable PostgreSQL for schema bootstrap. ` +
232
+ `Install pgvector on that server, or rerun setup with --db-mode docker or a managed pgvector-capable database. ` +
233
+ `Underlying error: ${reason}`);
234
+ }
224
235
  return new Error(`Database bootstrap failed after instance configuration. ` +
225
236
  `Common causes are a non-empty database that Prisma has not baselined yet, or a PostgreSQL server without the pgvector extension installed. ` +
226
237
  `Re-run setup without --bootstrap-db, or point Iranti at a fresh pgvector-capable database. ` +
@@ -761,29 +772,31 @@ function inferProjectMode(projectPath, instanceEnvFile) {
761
772
  }
762
773
  function recommendDatabaseMode(checks) {
763
774
  const reachableLocal = checks.find((check) => check.name === 'localhost:5432')?.status === 'pass';
764
- const docker = checks.find((check) => check.name === 'docker')?.status === 'pass';
765
- if (reachableLocal)
775
+ const docker = checks.find((check) => check.name === 'docker daemon')?.status === 'pass';
776
+ if (reachableLocal && !docker)
766
777
  return 'local';
767
778
  if (docker)
768
779
  return 'docker';
780
+ if (reachableLocal)
781
+ return 'local';
769
782
  return 'managed';
770
783
  }
771
784
  function quickInstallGuidanceLines() {
772
785
  if (process.platform === 'win32') {
773
786
  return [
774
787
  'Docker: winget install Docker.DockerDesktop',
775
- 'PostgreSQL: winget install PostgreSQL.PostgreSQL.17',
788
+ 'PostgreSQL: install PostgreSQL 17 and a pgvector-capable build or extension package.',
776
789
  ];
777
790
  }
778
791
  if (process.platform === 'darwin') {
779
792
  return [
780
793
  'Docker: brew install --cask docker',
781
- 'PostgreSQL: brew install postgresql@17',
794
+ 'PostgreSQL: brew install postgresql@17 plus pgvector for that server.',
782
795
  ];
783
796
  }
784
797
  return [
785
798
  'Docker: install Docker Engine using your distro package manager or the official Docker instructions.',
786
- 'PostgreSQL: install PostgreSQL 16+ using your distro package manager.',
799
+ 'PostgreSQL: install PostgreSQL 16+ and the pgvector extension using your distro package manager.',
787
800
  ];
788
801
  }
789
802
  function printQuickInstallGuidance() {
@@ -1181,6 +1194,32 @@ function hasDockerInstalled() {
1181
1194
  return false;
1182
1195
  }
1183
1196
  }
1197
+ function inspectDockerAvailability() {
1198
+ if (!hasDockerInstalled()) {
1199
+ return {
1200
+ installed: false,
1201
+ daemonReachable: false,
1202
+ detail: 'Docker is not installed or not on PATH.',
1203
+ };
1204
+ }
1205
+ const proc = runCommandCapture('docker', ['info', '--format', '{{.ServerVersion}}']);
1206
+ if (proc.status === 0) {
1207
+ const version = proc.stdout.trim();
1208
+ return {
1209
+ installed: true,
1210
+ daemonReachable: true,
1211
+ detail: version
1212
+ ? `Docker daemon is reachable (server ${version}).`
1213
+ : 'Docker daemon is reachable.',
1214
+ };
1215
+ }
1216
+ const reason = (proc.stderr || proc.stdout).trim() || 'Docker daemon did not respond.';
1217
+ return {
1218
+ installed: true,
1219
+ daemonReachable: false,
1220
+ detail: `Docker CLI is installed, but the daemon is not reachable. ${reason}`,
1221
+ };
1222
+ }
1184
1223
  async function isPortAvailable(port, host = '127.0.0.1') {
1185
1224
  return await new Promise((resolve) => {
1186
1225
  const server = net_1.default.createServer();
@@ -1244,9 +1283,19 @@ async function waitForTcpPort(host, port, timeoutMs) {
1244
1283
  throw new Error(`Timed out waiting for ${host}:${port} to accept TCP connections.`);
1245
1284
  }
1246
1285
  async function runDockerPostgresContainer(options) {
1286
+ const docker = inspectDockerAvailability();
1287
+ if (!docker.installed) {
1288
+ throw new Error('Docker CLI is not installed or not on PATH. Install Docker Desktop or Docker Engine before using --db-mode docker.');
1289
+ }
1290
+ if (!docker.daemonReachable) {
1291
+ throw new Error(`Docker daemon is not reachable. Start Docker Desktop or Docker Engine, then retry. ${docker.detail}`);
1292
+ }
1247
1293
  const inspect = process.platform === 'win32'
1248
1294
  ? (0, child_process_1.spawnSync)(process.env.ComSpec ?? 'cmd.exe', ['/d', '/c', `docker ps -a --format "{{.Names}}"`], { encoding: 'utf8' })
1249
1295
  : (0, child_process_1.spawnSync)('docker', ['ps', '-a', '--format', '{{.Names}}'], { encoding: 'utf8' });
1296
+ if (inspect.status !== 0) {
1297
+ throw new Error(`Failed to inspect Docker containers. ${(inspect.stderr ?? inspect.stdout ?? '').trim() || 'docker ps returned a non-zero exit code.'}`);
1298
+ }
1250
1299
  const names = (inspect.stdout ?? '').split(/\r?\n/).map((value) => value.trim()).filter(Boolean);
1251
1300
  if (names.includes(options.containerName)) {
1252
1301
  const start = process.platform === 'win32'
@@ -1306,6 +1355,7 @@ async function executeSetupPlan(plan) {
1306
1355
  }
1307
1356
  if (plan.databaseMode === 'local') {
1308
1357
  await ensurePostgresDatabaseExists(plan.databaseUrl);
1358
+ await ensureLocalPostgresPgvectorAvailable(plan.databaseUrl);
1309
1359
  }
1310
1360
  await runBundledScript('setup', [], {
1311
1361
  DATABASE_URL: plan.databaseUrl,
@@ -1313,6 +1363,9 @@ async function executeSetupPlan(plan) {
1313
1363
  });
1314
1364
  }
1315
1365
  catch (error) {
1366
+ if (plan.databaseMode === 'docker' && error instanceof Error && /Docker daemon is not reachable|Docker CLI is not installed/i.test(error.message)) {
1367
+ throw error;
1368
+ }
1316
1369
  throw formatSetupBootstrapFailure(error);
1317
1370
  }
1318
1371
  }
@@ -1437,7 +1490,7 @@ function defaultsSetupPlan(args) {
1437
1490
  if (!explicit) {
1438
1491
  if (hasCommandInstalled('psql'))
1439
1492
  return 'local';
1440
- if (hasDockerInstalled())
1493
+ if (inspectDockerAvailability().daemonReachable)
1441
1494
  return 'docker';
1442
1495
  return 'managed';
1443
1496
  }
@@ -1699,15 +1752,20 @@ async function canConnectTcp(port, host = '127.0.0.1', timeoutMs = 800) {
1699
1752
  });
1700
1753
  }
1701
1754
  async function collectDependencyChecks() {
1702
- const docker = hasDockerInstalled();
1755
+ const docker = inspectDockerAvailability();
1703
1756
  const psql = hasCommandInstalled('psql');
1704
1757
  const pgIsReady = hasCommandInstalled('pg_isready');
1705
1758
  const postgresPort = await canConnectTcp(5432);
1706
1759
  const checks = [
1707
1760
  {
1708
1761
  name: 'docker',
1709
- status: docker ? 'pass' : 'warn',
1710
- detail: docker ? 'Docker is installed.' : 'Docker is not installed or not on PATH.',
1762
+ status: docker.installed ? 'pass' : 'warn',
1763
+ detail: docker.installed ? 'Docker CLI is installed.' : docker.detail,
1764
+ },
1765
+ {
1766
+ name: 'docker daemon',
1767
+ status: docker.daemonReachable ? 'pass' : 'warn',
1768
+ detail: docker.detail,
1711
1769
  },
1712
1770
  {
1713
1771
  name: 'psql',
@@ -1942,6 +2000,46 @@ async function ensurePostgresDatabaseExists(databaseUrl) {
1942
2000
  throw new Error(`Failed to create local PostgreSQL database '${databaseName}'. ${create.stderr?.trim() || create.stdout?.trim() || 'psql returned a non-zero exit code.'}`);
1943
2001
  }
1944
2002
  }
2003
+ async function ensureLocalPostgresPgvectorAvailable(databaseUrl) {
2004
+ const parsed = parsePostgresConnectionString(databaseUrl);
2005
+ if (!isLocalPostgresHost(parsed.hostname)) {
2006
+ return;
2007
+ }
2008
+ if (!hasCommandInstalled('psql')) {
2009
+ return;
2010
+ }
2011
+ const adminDatabase = parsed.searchParams.get('admin_db')?.trim() || 'postgres';
2012
+ const host = parsed.hostname;
2013
+ const port = parsed.port || '5432';
2014
+ const user = decodeURIComponent(parsed.username || 'postgres');
2015
+ const password = decodeURIComponent(parsed.password || '');
2016
+ const extraEnv = password ? { PGPASSWORD: password } : undefined;
2017
+ const probe = (0, child_process_1.spawnSync)('psql', [
2018
+ '-h',
2019
+ host,
2020
+ '-p',
2021
+ port,
2022
+ '-U',
2023
+ user,
2024
+ '-d',
2025
+ adminDatabase,
2026
+ '-tAc',
2027
+ "SELECT 1 FROM pg_available_extensions WHERE name = 'vector';",
2028
+ ], {
2029
+ encoding: 'utf8',
2030
+ stdio: ['ignore', 'pipe', 'pipe'],
2031
+ env: {
2032
+ ...process.env,
2033
+ ...extraEnv,
2034
+ },
2035
+ });
2036
+ if (probe.status !== 0) {
2037
+ throw new Error(`Failed to inspect local PostgreSQL for pgvector availability. ${probe.stderr?.trim() || probe.stdout?.trim() || 'psql returned a non-zero exit code.'}`);
2038
+ }
2039
+ if ((probe.stdout ?? '').trim() !== '1') {
2040
+ throw new Error('Local PostgreSQL server does not have the pgvector extension installed.');
2041
+ }
2042
+ }
1945
2043
  function detectPythonLauncher() {
1946
2044
  const candidates = process.platform === 'win32'
1947
2045
  ? [
@@ -2270,6 +2368,32 @@ function canScheduleWindowsGlobalNpmSelfUpgrade(context) {
2270
2368
  function escapeForSingleQuotedPowerShell(value) {
2271
2369
  return value.replace(/'/g, "''");
2272
2370
  }
2371
+ function resolveWindowsDetachedExecutable(executable) {
2372
+ if (path_1.default.isAbsolute(executable)) {
2373
+ return executable;
2374
+ }
2375
+ const candidates = executable.toLowerCase().endsWith('.cmd') || executable.toLowerCase().endsWith('.exe')
2376
+ ? [executable]
2377
+ : [`${executable}.cmd`, `${executable}.exe`, executable];
2378
+ for (const candidate of candidates) {
2379
+ const probe = (0, child_process_1.spawnSync)('where', [candidate], { encoding: 'utf8' });
2380
+ if (probe.status === 0) {
2381
+ const resolved = (probe.stdout ?? '').split(/\r?\n/).map((line) => line.trim()).find(Boolean);
2382
+ if (resolved) {
2383
+ if (!path_1.default.extname(resolved)) {
2384
+ const cmdVariant = `${resolved}.cmd`;
2385
+ const exeVariant = `${resolved}.exe`;
2386
+ if (fs_1.default.existsSync(cmdVariant))
2387
+ return cmdVariant;
2388
+ if (fs_1.default.existsSync(exeVariant))
2389
+ return exeVariant;
2390
+ }
2391
+ return resolved;
2392
+ }
2393
+ }
2394
+ }
2395
+ return executable;
2396
+ }
2273
2397
  function resolveDetachedUpgradeCwd(command) {
2274
2398
  const desired = command.cwd?.trim();
2275
2399
  if (!desired) {
@@ -2283,12 +2407,30 @@ function resolveDetachedUpgradeCwd(command) {
2283
2407
  }
2284
2408
  return normalized;
2285
2409
  }
2410
+ function launchDetachedWindowsPowerShellFile(scriptPath, cwd) {
2411
+ const command = [
2412
+ 'Start-Process',
2413
+ '-WindowStyle Hidden',
2414
+ `-WorkingDirectory '${escapeForSingleQuotedPowerShell(cwd)}'`,
2415
+ "-FilePath 'powershell.exe'",
2416
+ `-ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','${escapeForSingleQuotedPowerShell(scriptPath)}')`,
2417
+ ].join(' ');
2418
+ const proc = (0, child_process_1.spawnSync)('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command], {
2419
+ encoding: 'utf8',
2420
+ cwd,
2421
+ env: process.env,
2422
+ windowsHide: true,
2423
+ });
2424
+ if (proc.status !== 0) {
2425
+ throw new Error(`Failed to schedule detached PowerShell handoff. ${(proc.stderr || proc.stdout).trim() || 'powershell returned a non-zero exit code.'}`);
2426
+ }
2427
+ }
2286
2428
  function scheduleDetachedWindowsGlobalNpmUpgrade(command, postCommand) {
2287
2429
  const neutralCwd = resolveDetachedUpgradeCwd(command);
2288
2430
  const parentPid = process.pid;
2289
- const powershell = 'powershell.exe';
2290
2431
  const escapedCwd = escapeForSingleQuotedPowerShell(neutralCwd);
2291
- const escapedExecutable = escapeForSingleQuotedPowerShell(command.executable);
2432
+ const detachedExecutable = resolveWindowsDetachedExecutable(command.executable);
2433
+ const escapedExecutable = escapeForSingleQuotedPowerShell(detachedExecutable);
2292
2434
  const escapedArgs = command.args.map((arg) => `'${escapeForSingleQuotedPowerShell(arg)}'`).join(', ');
2293
2435
  const script = [
2294
2436
  `$parentPid = ${parentPid}`,
@@ -2297,15 +2439,9 @@ function scheduleDetachedWindowsGlobalNpmUpgrade(command, postCommand) {
2297
2439
  `& '${escapedExecutable}' @(${escapedArgs})`,
2298
2440
  ...(postCommand ? [`if ($LASTEXITCODE -eq 0) { ${postCommand} }`] : []),
2299
2441
  'exit $LASTEXITCODE',
2300
- ].join('; ');
2301
- const child = (0, child_process_1.spawn)(powershell, ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', script], {
2302
- detached: true,
2303
- stdio: 'ignore',
2304
- windowsHide: true,
2305
- cwd: neutralCwd,
2306
- env: process.env,
2307
- });
2308
- child.unref();
2442
+ ].join(';\r\n');
2443
+ const scriptPath = writeDetachedWindowsScript('iranti-upgrade', `${script};\r\n`);
2444
+ launchDetachedWindowsPowerShellFile(scriptPath, neutralCwd);
2309
2445
  }
2310
2446
  function verifyPythonInstall(command) {
2311
2447
  const version = detectPythonInstalledVersion(command);
@@ -2684,6 +2820,7 @@ async function stopUninstallProcesses(processes) {
2684
2820
  }
2685
2821
  function buildDetachedWindowsUninstallScript(options) {
2686
2822
  const lines = [
2823
+ "$ErrorActionPreference = 'SilentlyContinue'",
2687
2824
  `$parentPid = ${options.parentPid}`,
2688
2825
  'while (Get-Process -Id $parentPid -ErrorAction SilentlyContinue) { Start-Sleep -Milliseconds 500 }',
2689
2826
  ];
@@ -2691,15 +2828,20 @@ function buildDetachedWindowsUninstallScript(options) {
2691
2828
  lines.push(`taskkill /PID ${pid} /T /F > $null 2>&1`);
2692
2829
  }
2693
2830
  if (options.removeCodex) {
2694
- lines.push("$codexGet = Get-Command codex -ErrorAction SilentlyContinue");
2695
- lines.push("if ($codexGet) { codex mcp get iranti --json > $null 2>&1; if ($LASTEXITCODE -eq 0) { codex mcp remove iranti > $null 2>&1 } }");
2831
+ const codexExecutable = escapeForSingleQuotedPowerShell(options.codexExecutable ?? resolveWindowsDetachedExecutable('codex'));
2832
+ lines.push(`$codexExe = '${codexExecutable}'`);
2833
+ lines.push("if (Test-Path -LiteralPath $codexExe) { & $codexExe mcp get iranti --json > $null 2>&1; if ($LASTEXITCODE -eq 0) { & $codexExe mcp remove iranti > $null 2>&1 } }");
2696
2834
  }
2697
2835
  if (options.removeGlobalNpm) {
2698
- lines.push('& npm uninstall -g iranti');
2836
+ const npmExecutable = escapeForSingleQuotedPowerShell(options.npmExecutable ?? resolveWindowsDetachedExecutable('npm'));
2837
+ lines.push(`$npmExe = '${npmExecutable}'`);
2838
+ lines.push("if (Test-Path -LiteralPath $npmExe) { & $npmExe uninstall -g iranti > $null 2>&1 }");
2699
2839
  }
2700
2840
  if (options.python) {
2701
2841
  const args = options.python.args.map((arg) => `'${escapeForSingleQuotedPowerShell(arg)}'`).join(', ');
2702
- lines.push(`& '${escapeForSingleQuotedPowerShell(options.python.executable)}' @(${args})`);
2842
+ const pythonExecutable = escapeForSingleQuotedPowerShell(resolveWindowsDetachedExecutable(options.python.executable));
2843
+ lines.push(`$pythonExe = '${pythonExecutable}'`);
2844
+ lines.push(`if (Test-Path -LiteralPath $pythonExe) { & $pythonExe @(${args}) > $null 2>&1 }`);
2703
2845
  }
2704
2846
  for (const filePath of options.artifactFiles) {
2705
2847
  lines.push(`if (Test-Path -LiteralPath '${escapeForSingleQuotedPowerShell(filePath)}') { Remove-Item -LiteralPath '${escapeForSingleQuotedPowerShell(filePath)}' -Force }`);
@@ -2708,7 +2850,13 @@ function buildDetachedWindowsUninstallScript(options) {
2708
2850
  lines.push(`if (Test-Path -LiteralPath '${escapeForSingleQuotedPowerShell(dirPath)}') { Remove-Item -LiteralPath '${escapeForSingleQuotedPowerShell(dirPath)}' -Recurse -Force }`);
2709
2851
  }
2710
2852
  lines.push('exit 0');
2711
- return lines.join('; ');
2853
+ return `${lines.join(';\r\n')};\r\n`;
2854
+ }
2855
+ function writeDetachedWindowsScript(prefix, scriptContents) {
2856
+ const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `${prefix}-`));
2857
+ const scriptPath = path_1.default.join(tempDir, `${prefix}.ps1`);
2858
+ fs_1.default.writeFileSync(scriptPath, scriptContents, 'utf8');
2859
+ return scriptPath;
2712
2860
  }
2713
2861
  async function uninstallCommand(args) {
2714
2862
  const scope = normalizeScope(getFlag(args, 'scope'));
@@ -2786,17 +2934,13 @@ async function uninstallCommand(args) {
2786
2934
  removeCodex: actions.removeCodexRegistration,
2787
2935
  python: actions.removePython ? pythonCommand : null,
2788
2936
  removeGlobalNpm: actions.removeGlobalNpm,
2937
+ npmExecutable: resolveWindowsDetachedExecutable('npm'),
2938
+ codexExecutable: resolveWindowsDetachedExecutable('codex'),
2789
2939
  runtimeRoots: actions.removeRuntimeRoots ? runtimeRoots.map((entry) => entry.path) : [],
2790
2940
  artifactFiles,
2791
2941
  });
2792
- const child = (0, child_process_1.spawn)('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', script], {
2793
- detached: true,
2794
- stdio: 'ignore',
2795
- windowsHide: true,
2796
- cwd: os_1.default.homedir(),
2797
- env: process.env,
2798
- });
2799
- child.unref();
2942
+ const scriptPath = writeDetachedWindowsScript('iranti-uninstall', script);
2943
+ launchDetachedWindowsPowerShellFile(scriptPath, os_1.default.homedir());
2800
2944
  execution.push({
2801
2945
  label: 'detached-uninstall',
2802
2946
  status: 'warn',
@@ -3130,7 +3274,7 @@ async function setupCommand(args) {
3130
3274
  const dependencyChecks = await collectDependencyChecks();
3131
3275
  printDependencyChecks(dependencyChecks);
3132
3276
  const recommendedDatabaseMode = recommendDatabaseMode(dependencyChecks);
3133
- console.log(`${infoLabel()} Recommended database path: ${recommendedDatabaseMode === 'local' ? 'local PostgreSQL' : recommendedDatabaseMode === 'docker' ? 'Docker-hosted PostgreSQL' : 'managed PostgreSQL'}`);
3277
+ console.log(`${infoLabel()} Recommended database path: ${recommendedDatabaseMode === 'local' ? 'local PostgreSQL (pgvector required)' : recommendedDatabaseMode === 'docker' ? 'Docker-hosted PostgreSQL' : 'managed PostgreSQL'}`);
3134
3278
  if (recommendedDatabaseMode === 'managed' && dependencyChecks.every((check) => check.status === 'warn')) {
3135
3279
  printQuickInstallGuidance();
3136
3280
  }
@@ -3177,7 +3321,8 @@ async function setupCommand(args) {
3177
3321
  }
3178
3322
  const existingPort = Number.parseInt(existingInstance?.env.IRANTI_PORT ?? '3001', 10);
3179
3323
  const port = await chooseAvailablePort(prompt, 'API port', existingPort, Boolean(existingInstance));
3180
- const dockerAvailable = hasDockerInstalled();
3324
+ const dockerStatus = inspectDockerAvailability();
3325
+ const dockerAvailable = dockerStatus.daemonReachable;
3181
3326
  const psqlAvailable = hasCommandInstalled('psql');
3182
3327
  let dbUrl = '';
3183
3328
  let bootstrapDatabase = false;
@@ -3210,7 +3355,7 @@ async function setupCommand(args) {
3210
3355
  if (dbMode === 'docker') {
3211
3356
  databaseMode = 'docker';
3212
3357
  if (!dockerAvailable) {
3213
- console.log(`${warnLabel()} Docker is not installed or not on PATH. Choose local or managed instead.`);
3358
+ console.log(`${warnLabel()} ${dockerStatus.detail}`);
3214
3359
  continue;
3215
3360
  }
3216
3361
  const dbHostPort = await chooseAvailablePort(prompt, 'Docker PostgreSQL host port', 5432, false);
@@ -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.19',
147
+ version: '0.2.20',
148
148
  });
149
149
  server.registerTool('iranti_handshake', {
150
150
  description: `Initialize or refresh an agent's working-memory brief for the current task.
@@ -9,15 +9,35 @@ const fs_1 = __importDefault(require("fs"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const client_1 = require("../src/library/client");
11
11
  const escalationPaths_1 = require("../src/lib/escalationPaths");
12
- const PACKAGE_ROOT = path_1.default.resolve(__dirname, '..');
12
+ function resolvePackageRoot() {
13
+ let dir = __dirname;
14
+ for (let i = 0; i < 8; i += 1) {
15
+ const pkgPath = path_1.default.join(dir, 'package.json');
16
+ const prismaSchema = path_1.default.join(dir, 'prisma', 'schema.prisma');
17
+ if (fs_1.default.existsSync(pkgPath) && fs_1.default.existsSync(prismaSchema)) {
18
+ return dir;
19
+ }
20
+ const parent = path_1.default.dirname(dir);
21
+ if (parent === dir)
22
+ break;
23
+ dir = parent;
24
+ }
25
+ throw new Error(`Could not resolve Iranti package root from ${__dirname}`);
26
+ }
27
+ const PACKAGE_ROOT = resolvePackageRoot();
28
+ const PRISMA_SCHEMA = path_1.default.join(PACKAGE_ROOT, 'prisma', 'schema.prisma');
29
+ const BASE_ENV = { ...process.env };
13
30
  // ─── Helpers ─────────────────────────────────────────────────────────────────
14
- function run(command, label) {
31
+ function run(command, label, extraEnv) {
15
32
  console.log(` Running: ${label}...`);
16
33
  try {
17
34
  (0, child_process_1.execSync)(command, {
18
35
  stdio: 'inherit',
19
36
  cwd: PACKAGE_ROOT,
20
- env: process.env,
37
+ env: {
38
+ ...BASE_ENV,
39
+ ...extraEnv,
40
+ },
21
41
  });
22
42
  }
23
43
  catch {
@@ -26,7 +46,7 @@ function run(command, label) {
26
46
  }
27
47
  }
28
48
  function scriptCommand(distScriptName, sourceScriptPath) {
29
- const distScriptPath = path_1.default.resolve(__dirname, '..', 'dist', 'scripts', distScriptName);
49
+ const distScriptPath = path_1.default.join(PACKAGE_ROOT, 'dist', 'scripts', distScriptName);
30
50
  if (fs_1.default.existsSync(distScriptPath)) {
31
51
  return `node ${JSON.stringify(distScriptPath)}`;
32
52
  }
@@ -57,13 +77,18 @@ async function setup() {
57
77
  process.env.DATABASE_URL = dbUrl;
58
78
  (0, client_1.initDb)(dbUrl);
59
79
  }
80
+ const runtimeEnv = dbUrl
81
+ ? {
82
+ DATABASE_URL: dbUrl,
83
+ }
84
+ : undefined;
60
85
  // 1. Run migrations
61
86
  console.log('Step 1 — Running database migrations...');
62
- run('npx prisma migrate deploy', 'prisma migrate deploy');
87
+ run(`npx prisma migrate deploy --schema ${JSON.stringify(PRISMA_SCHEMA)}`, 'prisma migrate deploy', runtimeEnv);
63
88
  console.log(' ✓ Migrations complete\n');
64
89
  // 2. Generate Prisma client
65
90
  console.log('Step 2 — Generating Prisma client...');
66
- run('npx prisma generate', 'prisma generate');
91
+ run(`npx prisma generate --schema ${JSON.stringify(PRISMA_SCHEMA)}`, 'prisma generate', runtimeEnv);
67
92
  console.log(' ✓ Client generated\n');
68
93
  // 3. Seed Staff Namespace if not already seeded
69
94
  console.log('Step 3 — Seeding Staff Namespace...');
@@ -39,7 +39,7 @@ const INSTANCE_DIR = process.env.IRANTI_INSTANCE_DIR?.trim()
39
39
  const INSTANCE_RUNTIME_FILE = process.env.IRANTI_INSTANCE_RUNTIME_FILE?.trim()
40
40
  || (INSTANCE_DIR ? (0, runtimeLifecycle_1.runtimeFileForInstance)(INSTANCE_DIR) : null);
41
41
  const INSTANCE_NAME = process.env.IRANTI_INSTANCE_NAME?.trim() || (INSTANCE_DIR ? path_1.default.basename(INSTANCE_DIR) : 'adhoc');
42
- const VERSION = '0.2.19';
42
+ const VERSION = '0.2.20';
43
43
  try {
44
44
  fs_1.default.mkdirSync(path_1.default.dirname(REQUEST_LOG_FILE), { recursive: true });
45
45
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iranti",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "description": "Memory infrastructure for multi-agent AI systems",
5
5
  "main": "dist/src/sdk/index.js",
6
6
  "files": [
@@ -16,6 +16,7 @@
16
16
  "dist/scripts/api-key-list.js",
17
17
  "dist/scripts/api-key-revoke.js",
18
18
  "bin/iranti.js",
19
+ "prisma.config.ts",
19
20
  "prisma/schema.prisma",
20
21
  "prisma/migrations/**/*",
21
22
  ".env.example",
@@ -0,0 +1,14 @@
1
+ // This file was generated by Prisma, and assumes you have installed the following:
2
+ // npm install --save-dev prisma dotenv
3
+ import "dotenv/config";
4
+ import { defineConfig } from "prisma/config";
5
+
6
+ export default defineConfig({
7
+ schema: "prisma/schema.prisma",
8
+ migrations: {
9
+ path: "prisma/migrations",
10
+ },
11
+ datasource: {
12
+ url: process.env["DATABASE_URL"],
13
+ },
14
+ });