iranti 0.2.19 → 0.2.21
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 +5 -5
- package/dist/scripts/iranti-cli.js +202 -42
- package/dist/scripts/iranti-mcp.js +1 -1
- package/dist/scripts/setup.js +31 -6
- package/dist/src/api/server.js +1 -1
- package/package.json +2 -1
- package/prisma.config.ts +14 -0
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.
|
|
12
|
+
**Latest release:** [`v0.2.20`](https://github.com/nfemmanuel/iranti/releases/tag/v0.2.20)
|
|
13
13
|
Published packages:
|
|
14
|
-
- `iranti@0.2.
|
|
15
|
-
- `@iranti/sdk@0.2.
|
|
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
|
|
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:
|
|
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'
|
|
@@ -1295,10 +1344,12 @@ async function executeSetupPlan(plan) {
|
|
|
1295
1344
|
});
|
|
1296
1345
|
if (plan.bootstrapDatabase) {
|
|
1297
1346
|
try {
|
|
1298
|
-
if (plan.databaseMode === 'docker') {
|
|
1347
|
+
if (plan.databaseMode === 'docker' && !plan.databaseProvisioned) {
|
|
1299
1348
|
const parsed = parsePostgresConnectionString(plan.databaseUrl);
|
|
1300
1349
|
await runDockerPostgresContainer({
|
|
1301
|
-
containerName:
|
|
1350
|
+
containerName: plan.dockerContainerName
|
|
1351
|
+
? sanitizeIdentifier(plan.dockerContainerName, `iranti_${plan.instanceName}_db`)
|
|
1352
|
+
: sanitizeIdentifier(`iranti_${plan.instanceName}_db`, `iranti_${plan.instanceName}_db`),
|
|
1302
1353
|
hostPort: Number.parseInt(parsed.port || '5432', 10),
|
|
1303
1354
|
password: decodeURIComponent(parsed.password || 'postgres'),
|
|
1304
1355
|
database: postgresDatabaseName(plan.databaseUrl),
|
|
@@ -1306,6 +1357,7 @@ async function executeSetupPlan(plan) {
|
|
|
1306
1357
|
}
|
|
1307
1358
|
if (plan.databaseMode === 'local') {
|
|
1308
1359
|
await ensurePostgresDatabaseExists(plan.databaseUrl);
|
|
1360
|
+
await ensureLocalPostgresPgvectorAvailable(plan.databaseUrl);
|
|
1309
1361
|
}
|
|
1310
1362
|
await runBundledScript('setup', [], {
|
|
1311
1363
|
DATABASE_URL: plan.databaseUrl,
|
|
@@ -1313,6 +1365,9 @@ async function executeSetupPlan(plan) {
|
|
|
1313
1365
|
});
|
|
1314
1366
|
}
|
|
1315
1367
|
catch (error) {
|
|
1368
|
+
if (plan.databaseMode === 'docker' && error instanceof Error && /Docker daemon is not reachable|Docker CLI is not installed/i.test(error.message)) {
|
|
1369
|
+
throw error;
|
|
1370
|
+
}
|
|
1316
1371
|
throw formatSetupBootstrapFailure(error);
|
|
1317
1372
|
}
|
|
1318
1373
|
}
|
|
@@ -1437,7 +1492,7 @@ function defaultsSetupPlan(args) {
|
|
|
1437
1492
|
if (!explicit) {
|
|
1438
1493
|
if (hasCommandInstalled('psql'))
|
|
1439
1494
|
return 'local';
|
|
1440
|
-
if (
|
|
1495
|
+
if (inspectDockerAvailability().daemonReachable)
|
|
1441
1496
|
return 'docker';
|
|
1442
1497
|
return 'managed';
|
|
1443
1498
|
}
|
|
@@ -1699,15 +1754,20 @@ async function canConnectTcp(port, host = '127.0.0.1', timeoutMs = 800) {
|
|
|
1699
1754
|
});
|
|
1700
1755
|
}
|
|
1701
1756
|
async function collectDependencyChecks() {
|
|
1702
|
-
const docker =
|
|
1757
|
+
const docker = inspectDockerAvailability();
|
|
1703
1758
|
const psql = hasCommandInstalled('psql');
|
|
1704
1759
|
const pgIsReady = hasCommandInstalled('pg_isready');
|
|
1705
1760
|
const postgresPort = await canConnectTcp(5432);
|
|
1706
1761
|
const checks = [
|
|
1707
1762
|
{
|
|
1708
1763
|
name: 'docker',
|
|
1709
|
-
status: docker ? 'pass' : 'warn',
|
|
1710
|
-
detail: docker ? 'Docker is installed.' :
|
|
1764
|
+
status: docker.installed ? 'pass' : 'warn',
|
|
1765
|
+
detail: docker.installed ? 'Docker CLI is installed.' : docker.detail,
|
|
1766
|
+
},
|
|
1767
|
+
{
|
|
1768
|
+
name: 'docker daemon',
|
|
1769
|
+
status: docker.daemonReachable ? 'pass' : 'warn',
|
|
1770
|
+
detail: docker.detail,
|
|
1711
1771
|
},
|
|
1712
1772
|
{
|
|
1713
1773
|
name: 'psql',
|
|
@@ -1942,6 +2002,46 @@ async function ensurePostgresDatabaseExists(databaseUrl) {
|
|
|
1942
2002
|
throw new Error(`Failed to create local PostgreSQL database '${databaseName}'. ${create.stderr?.trim() || create.stdout?.trim() || 'psql returned a non-zero exit code.'}`);
|
|
1943
2003
|
}
|
|
1944
2004
|
}
|
|
2005
|
+
async function ensureLocalPostgresPgvectorAvailable(databaseUrl) {
|
|
2006
|
+
const parsed = parsePostgresConnectionString(databaseUrl);
|
|
2007
|
+
if (!isLocalPostgresHost(parsed.hostname)) {
|
|
2008
|
+
return;
|
|
2009
|
+
}
|
|
2010
|
+
if (!hasCommandInstalled('psql')) {
|
|
2011
|
+
return;
|
|
2012
|
+
}
|
|
2013
|
+
const adminDatabase = parsed.searchParams.get('admin_db')?.trim() || 'postgres';
|
|
2014
|
+
const host = parsed.hostname;
|
|
2015
|
+
const port = parsed.port || '5432';
|
|
2016
|
+
const user = decodeURIComponent(parsed.username || 'postgres');
|
|
2017
|
+
const password = decodeURIComponent(parsed.password || '');
|
|
2018
|
+
const extraEnv = password ? { PGPASSWORD: password } : undefined;
|
|
2019
|
+
const probe = (0, child_process_1.spawnSync)('psql', [
|
|
2020
|
+
'-h',
|
|
2021
|
+
host,
|
|
2022
|
+
'-p',
|
|
2023
|
+
port,
|
|
2024
|
+
'-U',
|
|
2025
|
+
user,
|
|
2026
|
+
'-d',
|
|
2027
|
+
adminDatabase,
|
|
2028
|
+
'-tAc',
|
|
2029
|
+
"SELECT 1 FROM pg_available_extensions WHERE name = 'vector';",
|
|
2030
|
+
], {
|
|
2031
|
+
encoding: 'utf8',
|
|
2032
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
2033
|
+
env: {
|
|
2034
|
+
...process.env,
|
|
2035
|
+
...extraEnv,
|
|
2036
|
+
},
|
|
2037
|
+
});
|
|
2038
|
+
if (probe.status !== 0) {
|
|
2039
|
+
throw new Error(`Failed to inspect local PostgreSQL for pgvector availability. ${probe.stderr?.trim() || probe.stdout?.trim() || 'psql returned a non-zero exit code.'}`);
|
|
2040
|
+
}
|
|
2041
|
+
if ((probe.stdout ?? '').trim() !== '1') {
|
|
2042
|
+
throw new Error('Local PostgreSQL server does not have the pgvector extension installed.');
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
1945
2045
|
function detectPythonLauncher() {
|
|
1946
2046
|
const candidates = process.platform === 'win32'
|
|
1947
2047
|
? [
|
|
@@ -2270,6 +2370,32 @@ function canScheduleWindowsGlobalNpmSelfUpgrade(context) {
|
|
|
2270
2370
|
function escapeForSingleQuotedPowerShell(value) {
|
|
2271
2371
|
return value.replace(/'/g, "''");
|
|
2272
2372
|
}
|
|
2373
|
+
function resolveWindowsDetachedExecutable(executable) {
|
|
2374
|
+
if (path_1.default.isAbsolute(executable)) {
|
|
2375
|
+
return executable;
|
|
2376
|
+
}
|
|
2377
|
+
const candidates = executable.toLowerCase().endsWith('.cmd') || executable.toLowerCase().endsWith('.exe')
|
|
2378
|
+
? [executable]
|
|
2379
|
+
: [`${executable}.cmd`, `${executable}.exe`, executable];
|
|
2380
|
+
for (const candidate of candidates) {
|
|
2381
|
+
const probe = (0, child_process_1.spawnSync)('where', [candidate], { encoding: 'utf8' });
|
|
2382
|
+
if (probe.status === 0) {
|
|
2383
|
+
const resolved = (probe.stdout ?? '').split(/\r?\n/).map((line) => line.trim()).find(Boolean);
|
|
2384
|
+
if (resolved) {
|
|
2385
|
+
if (!path_1.default.extname(resolved)) {
|
|
2386
|
+
const cmdVariant = `${resolved}.cmd`;
|
|
2387
|
+
const exeVariant = `${resolved}.exe`;
|
|
2388
|
+
if (fs_1.default.existsSync(cmdVariant))
|
|
2389
|
+
return cmdVariant;
|
|
2390
|
+
if (fs_1.default.existsSync(exeVariant))
|
|
2391
|
+
return exeVariant;
|
|
2392
|
+
}
|
|
2393
|
+
return resolved;
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
}
|
|
2397
|
+
return executable;
|
|
2398
|
+
}
|
|
2273
2399
|
function resolveDetachedUpgradeCwd(command) {
|
|
2274
2400
|
const desired = command.cwd?.trim();
|
|
2275
2401
|
if (!desired) {
|
|
@@ -2283,12 +2409,30 @@ function resolveDetachedUpgradeCwd(command) {
|
|
|
2283
2409
|
}
|
|
2284
2410
|
return normalized;
|
|
2285
2411
|
}
|
|
2412
|
+
function launchDetachedWindowsPowerShellFile(scriptPath, cwd) {
|
|
2413
|
+
const command = [
|
|
2414
|
+
'Start-Process',
|
|
2415
|
+
'-WindowStyle Hidden',
|
|
2416
|
+
`-WorkingDirectory '${escapeForSingleQuotedPowerShell(cwd)}'`,
|
|
2417
|
+
"-FilePath 'powershell.exe'",
|
|
2418
|
+
`-ArgumentList @('-NoProfile','-ExecutionPolicy','Bypass','-File','${escapeForSingleQuotedPowerShell(scriptPath)}')`,
|
|
2419
|
+
].join(' ');
|
|
2420
|
+
const proc = (0, child_process_1.spawnSync)('powershell.exe', ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-Command', command], {
|
|
2421
|
+
encoding: 'utf8',
|
|
2422
|
+
cwd,
|
|
2423
|
+
env: process.env,
|
|
2424
|
+
windowsHide: true,
|
|
2425
|
+
});
|
|
2426
|
+
if (proc.status !== 0) {
|
|
2427
|
+
throw new Error(`Failed to schedule detached PowerShell handoff. ${(proc.stderr || proc.stdout).trim() || 'powershell returned a non-zero exit code.'}`);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2286
2430
|
function scheduleDetachedWindowsGlobalNpmUpgrade(command, postCommand) {
|
|
2287
2431
|
const neutralCwd = resolveDetachedUpgradeCwd(command);
|
|
2288
2432
|
const parentPid = process.pid;
|
|
2289
|
-
const powershell = 'powershell.exe';
|
|
2290
2433
|
const escapedCwd = escapeForSingleQuotedPowerShell(neutralCwd);
|
|
2291
|
-
const
|
|
2434
|
+
const detachedExecutable = resolveWindowsDetachedExecutable(command.executable);
|
|
2435
|
+
const escapedExecutable = escapeForSingleQuotedPowerShell(detachedExecutable);
|
|
2292
2436
|
const escapedArgs = command.args.map((arg) => `'${escapeForSingleQuotedPowerShell(arg)}'`).join(', ');
|
|
2293
2437
|
const script = [
|
|
2294
2438
|
`$parentPid = ${parentPid}`,
|
|
@@ -2297,15 +2441,9 @@ function scheduleDetachedWindowsGlobalNpmUpgrade(command, postCommand) {
|
|
|
2297
2441
|
`& '${escapedExecutable}' @(${escapedArgs})`,
|
|
2298
2442
|
...(postCommand ? [`if ($LASTEXITCODE -eq 0) { ${postCommand} }`] : []),
|
|
2299
2443
|
'exit $LASTEXITCODE',
|
|
2300
|
-
].join('
|
|
2301
|
-
const
|
|
2302
|
-
|
|
2303
|
-
stdio: 'ignore',
|
|
2304
|
-
windowsHide: true,
|
|
2305
|
-
cwd: neutralCwd,
|
|
2306
|
-
env: process.env,
|
|
2307
|
-
});
|
|
2308
|
-
child.unref();
|
|
2444
|
+
].join(';\r\n');
|
|
2445
|
+
const scriptPath = writeDetachedWindowsScript('iranti-upgrade', `${script};\r\n`);
|
|
2446
|
+
launchDetachedWindowsPowerShellFile(scriptPath, neutralCwd);
|
|
2309
2447
|
}
|
|
2310
2448
|
function verifyPythonInstall(command) {
|
|
2311
2449
|
const version = detectPythonInstalledVersion(command);
|
|
@@ -2684,6 +2822,7 @@ async function stopUninstallProcesses(processes) {
|
|
|
2684
2822
|
}
|
|
2685
2823
|
function buildDetachedWindowsUninstallScript(options) {
|
|
2686
2824
|
const lines = [
|
|
2825
|
+
"$ErrorActionPreference = 'SilentlyContinue'",
|
|
2687
2826
|
`$parentPid = ${options.parentPid}`,
|
|
2688
2827
|
'while (Get-Process -Id $parentPid -ErrorAction SilentlyContinue) { Start-Sleep -Milliseconds 500 }',
|
|
2689
2828
|
];
|
|
@@ -2691,15 +2830,20 @@ function buildDetachedWindowsUninstallScript(options) {
|
|
|
2691
2830
|
lines.push(`taskkill /PID ${pid} /T /F > $null 2>&1`);
|
|
2692
2831
|
}
|
|
2693
2832
|
if (options.removeCodex) {
|
|
2694
|
-
|
|
2695
|
-
lines.push(
|
|
2833
|
+
const codexExecutable = escapeForSingleQuotedPowerShell(options.codexExecutable ?? resolveWindowsDetachedExecutable('codex'));
|
|
2834
|
+
lines.push(`$codexExe = '${codexExecutable}'`);
|
|
2835
|
+
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
2836
|
}
|
|
2697
2837
|
if (options.removeGlobalNpm) {
|
|
2698
|
-
|
|
2838
|
+
const npmExecutable = escapeForSingleQuotedPowerShell(options.npmExecutable ?? resolveWindowsDetachedExecutable('npm'));
|
|
2839
|
+
lines.push(`$npmExe = '${npmExecutable}'`);
|
|
2840
|
+
lines.push("if (Test-Path -LiteralPath $npmExe) { & $npmExe uninstall -g iranti > $null 2>&1 }");
|
|
2699
2841
|
}
|
|
2700
2842
|
if (options.python) {
|
|
2701
2843
|
const args = options.python.args.map((arg) => `'${escapeForSingleQuotedPowerShell(arg)}'`).join(', ');
|
|
2702
|
-
|
|
2844
|
+
const pythonExecutable = escapeForSingleQuotedPowerShell(resolveWindowsDetachedExecutable(options.python.executable));
|
|
2845
|
+
lines.push(`$pythonExe = '${pythonExecutable}'`);
|
|
2846
|
+
lines.push(`if (Test-Path -LiteralPath $pythonExe) { & $pythonExe @(${args}) > $null 2>&1 }`);
|
|
2703
2847
|
}
|
|
2704
2848
|
for (const filePath of options.artifactFiles) {
|
|
2705
2849
|
lines.push(`if (Test-Path -LiteralPath '${escapeForSingleQuotedPowerShell(filePath)}') { Remove-Item -LiteralPath '${escapeForSingleQuotedPowerShell(filePath)}' -Force }`);
|
|
@@ -2708,7 +2852,13 @@ function buildDetachedWindowsUninstallScript(options) {
|
|
|
2708
2852
|
lines.push(`if (Test-Path -LiteralPath '${escapeForSingleQuotedPowerShell(dirPath)}') { Remove-Item -LiteralPath '${escapeForSingleQuotedPowerShell(dirPath)}' -Recurse -Force }`);
|
|
2709
2853
|
}
|
|
2710
2854
|
lines.push('exit 0');
|
|
2711
|
-
return lines.join('
|
|
2855
|
+
return `${lines.join(';\r\n')};\r\n`;
|
|
2856
|
+
}
|
|
2857
|
+
function writeDetachedWindowsScript(prefix, scriptContents) {
|
|
2858
|
+
const tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), `${prefix}-`));
|
|
2859
|
+
const scriptPath = path_1.default.join(tempDir, `${prefix}.ps1`);
|
|
2860
|
+
fs_1.default.writeFileSync(scriptPath, scriptContents, 'utf8');
|
|
2861
|
+
return scriptPath;
|
|
2712
2862
|
}
|
|
2713
2863
|
async function uninstallCommand(args) {
|
|
2714
2864
|
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
@@ -2786,17 +2936,13 @@ async function uninstallCommand(args) {
|
|
|
2786
2936
|
removeCodex: actions.removeCodexRegistration,
|
|
2787
2937
|
python: actions.removePython ? pythonCommand : null,
|
|
2788
2938
|
removeGlobalNpm: actions.removeGlobalNpm,
|
|
2939
|
+
npmExecutable: resolveWindowsDetachedExecutable('npm'),
|
|
2940
|
+
codexExecutable: resolveWindowsDetachedExecutable('codex'),
|
|
2789
2941
|
runtimeRoots: actions.removeRuntimeRoots ? runtimeRoots.map((entry) => entry.path) : [],
|
|
2790
2942
|
artifactFiles,
|
|
2791
2943
|
});
|
|
2792
|
-
const
|
|
2793
|
-
|
|
2794
|
-
stdio: 'ignore',
|
|
2795
|
-
windowsHide: true,
|
|
2796
|
-
cwd: os_1.default.homedir(),
|
|
2797
|
-
env: process.env,
|
|
2798
|
-
});
|
|
2799
|
-
child.unref();
|
|
2944
|
+
const scriptPath = writeDetachedWindowsScript('iranti-uninstall', script);
|
|
2945
|
+
launchDetachedWindowsPowerShellFile(scriptPath, os_1.default.homedir());
|
|
2800
2946
|
execution.push({
|
|
2801
2947
|
label: 'detached-uninstall',
|
|
2802
2948
|
status: 'warn',
|
|
@@ -3130,7 +3276,7 @@ async function setupCommand(args) {
|
|
|
3130
3276
|
const dependencyChecks = await collectDependencyChecks();
|
|
3131
3277
|
printDependencyChecks(dependencyChecks);
|
|
3132
3278
|
const recommendedDatabaseMode = recommendDatabaseMode(dependencyChecks);
|
|
3133
|
-
console.log(`${infoLabel()} Recommended database path: ${recommendedDatabaseMode === 'local' ? 'local PostgreSQL' : recommendedDatabaseMode === 'docker' ? 'Docker-hosted PostgreSQL' : 'managed PostgreSQL'}`);
|
|
3279
|
+
console.log(`${infoLabel()} Recommended database path: ${recommendedDatabaseMode === 'local' ? 'local PostgreSQL (pgvector required)' : recommendedDatabaseMode === 'docker' ? 'Docker-hosted PostgreSQL' : 'managed PostgreSQL'}`);
|
|
3134
3280
|
if (recommendedDatabaseMode === 'managed' && dependencyChecks.every((check) => check.status === 'warn')) {
|
|
3135
3281
|
printQuickInstallGuidance();
|
|
3136
3282
|
}
|
|
@@ -3177,10 +3323,13 @@ async function setupCommand(args) {
|
|
|
3177
3323
|
}
|
|
3178
3324
|
const existingPort = Number.parseInt(existingInstance?.env.IRANTI_PORT ?? '3001', 10);
|
|
3179
3325
|
const port = await chooseAvailablePort(prompt, 'API port', existingPort, Boolean(existingInstance));
|
|
3180
|
-
const
|
|
3326
|
+
const dockerStatus = inspectDockerAvailability();
|
|
3327
|
+
const dockerAvailable = dockerStatus.daemonReachable;
|
|
3181
3328
|
const psqlAvailable = hasCommandInstalled('psql');
|
|
3182
3329
|
let dbUrl = '';
|
|
3183
3330
|
let bootstrapDatabase = false;
|
|
3331
|
+
let databaseProvisioned = false;
|
|
3332
|
+
let dockerContainerName;
|
|
3184
3333
|
let databaseMode = recommendedDatabaseMode;
|
|
3185
3334
|
while (true) {
|
|
3186
3335
|
const defaultMode = recommendedDatabaseMode;
|
|
@@ -3210,13 +3359,14 @@ async function setupCommand(args) {
|
|
|
3210
3359
|
if (dbMode === 'docker') {
|
|
3211
3360
|
databaseMode = 'docker';
|
|
3212
3361
|
if (!dockerAvailable) {
|
|
3213
|
-
console.log(`${warnLabel()}
|
|
3362
|
+
console.log(`${warnLabel()} ${dockerStatus.detail}`);
|
|
3214
3363
|
continue;
|
|
3215
3364
|
}
|
|
3216
3365
|
const dbHostPort = await chooseAvailablePort(prompt, 'Docker PostgreSQL host port', 5432, false);
|
|
3217
3366
|
const dbName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Docker PostgreSQL database name', `iranti_${instanceName}`), `iranti_${instanceName}`);
|
|
3218
3367
|
const dbPassword = await promptRequiredSecret(prompt, 'Docker PostgreSQL password');
|
|
3219
3368
|
const containerName = sanitizeIdentifier(await promptNonEmpty(prompt, 'Docker container name', `iranti_${instanceName}_db`), `iranti_${instanceName}_db`);
|
|
3369
|
+
dockerContainerName = containerName;
|
|
3220
3370
|
dbUrl = `postgresql://postgres:${dbPassword}@localhost:${dbHostPort}/${dbName}`;
|
|
3221
3371
|
console.log(`${infoLabel()} Docker will be used only for PostgreSQL. Iranti itself does not require Docker once a PostgreSQL database is available.`);
|
|
3222
3372
|
if (await promptYesNo(prompt, `Start or reuse Docker container '${containerName}' now?`, true)) {
|
|
@@ -3227,11 +3377,9 @@ async function setupCommand(args) {
|
|
|
3227
3377
|
database: dbName,
|
|
3228
3378
|
});
|
|
3229
3379
|
console.log(`${okLabel()} Docker PostgreSQL ready at localhost:${dbHostPort}`);
|
|
3230
|
-
|
|
3231
|
-
}
|
|
3232
|
-
else {
|
|
3233
|
-
bootstrapDatabase = await promptYesNo(prompt, 'Will you start PostgreSQL separately before first run?', false);
|
|
3380
|
+
databaseProvisioned = true;
|
|
3234
3381
|
}
|
|
3382
|
+
bootstrapDatabase = await promptYesNo(prompt, 'Run migrations and seed the database now?', true);
|
|
3235
3383
|
break;
|
|
3236
3384
|
}
|
|
3237
3385
|
console.log(`${warnLabel()} Choose one of: local, managed, docker.`);
|
|
@@ -3333,6 +3481,8 @@ async function setupCommand(args) {
|
|
|
3333
3481
|
codex,
|
|
3334
3482
|
codexAgent: projects[0]?.agentId,
|
|
3335
3483
|
bootstrapDatabase,
|
|
3484
|
+
dockerContainerName,
|
|
3485
|
+
databaseProvisioned,
|
|
3336
3486
|
});
|
|
3337
3487
|
});
|
|
3338
3488
|
if (!result) {
|
|
@@ -3374,6 +3524,7 @@ async function doctorCommand(args) {
|
|
|
3374
3524
|
const version = getPackageVersion();
|
|
3375
3525
|
const pushEnvironmentChecks = async (env, prefix = '') => {
|
|
3376
3526
|
const databaseUrl = env.DATABASE_URL;
|
|
3527
|
+
let databaseInitializedForDoctor = false;
|
|
3377
3528
|
checks.push(detectPlaceholder(databaseUrl)
|
|
3378
3529
|
? {
|
|
3379
3530
|
name: `${prefix}database configuration`,
|
|
@@ -3397,6 +3548,10 @@ async function doctorCommand(args) {
|
|
|
3397
3548
|
name: `${prefix}${providerKeyCheck.name}`,
|
|
3398
3549
|
});
|
|
3399
3550
|
try {
|
|
3551
|
+
if (!detectPlaceholder(databaseUrl)) {
|
|
3552
|
+
(0, client_1.initDb)(databaseUrl);
|
|
3553
|
+
databaseInitializedForDoctor = true;
|
|
3554
|
+
}
|
|
3400
3555
|
const backendName = (0, backends_1.resolveVectorBackendName)({
|
|
3401
3556
|
vectorBackend: env.IRANTI_VECTOR_BACKEND,
|
|
3402
3557
|
qdrantUrl: env.IRANTI_QDRANT_URL,
|
|
@@ -3436,6 +3591,11 @@ async function doctorCommand(args) {
|
|
|
3436
3591
|
detail: error instanceof Error ? error.message : String(error),
|
|
3437
3592
|
});
|
|
3438
3593
|
}
|
|
3594
|
+
finally {
|
|
3595
|
+
if (databaseInitializedForDoctor) {
|
|
3596
|
+
await (0, client_1.disconnectDb)();
|
|
3597
|
+
}
|
|
3598
|
+
}
|
|
3439
3599
|
};
|
|
3440
3600
|
checks.push({
|
|
3441
3601
|
name: 'node version',
|
|
@@ -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.21',
|
|
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/setup.js
CHANGED
|
@@ -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
|
-
|
|
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:
|
|
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.
|
|
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(
|
|
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(
|
|
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...');
|
package/dist/src/api/server.js
CHANGED
|
@@ -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.
|
|
42
|
+
const VERSION = '0.2.21';
|
|
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.
|
|
3
|
+
"version": "0.2.21",
|
|
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",
|
package/prisma.config.ts
ADDED
|
@@ -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
|
+
});
|