iranti 0.2.7 → 0.2.9
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 +14 -0
- package/dist/scripts/iranti-cli.js +402 -22
- package/dist/scripts/iranti-mcp.js +1 -1
- package/dist/scripts/seed.js +10 -10
- package/dist/src/api/server.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -354,6 +354,19 @@ Use `--agent`, `--provider`, and `--model` to pin the session identity and model
|
|
|
354
354
|
The chat surface now includes slash commands for fact history, relationships, conflict-resolution handoff, and confidence updates in addition to memory search/write operations.
|
|
355
355
|
Guide: [`docs/guides/chat.md`](docs/guides/chat.md)
|
|
356
356
|
|
|
357
|
+
### Manual Attendant Inspection
|
|
358
|
+
|
|
359
|
+
For debugging and operator visibility, Iranti also exposes manual Attendant commands:
|
|
360
|
+
|
|
361
|
+
```bash
|
|
362
|
+
iranti handshake --task "Working on ProofScript repo"
|
|
363
|
+
iranti attend "What did we decide about the parser?" --context-file transcript.txt
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Both commands accept `--json`.
|
|
367
|
+
They are useful for verifying what the Attendant would load or inject for a given agent and project binding.
|
|
368
|
+
They are not a replacement for Claude Code hooks or MCP tools in normal use.
|
|
369
|
+
|
|
357
370
|
---
|
|
358
371
|
|
|
359
372
|
## Install Strategy (Double Layer)
|
|
@@ -482,6 +495,7 @@ iranti upgrade --yes
|
|
|
482
495
|
This validates the active env file, database URL, API key presence, provider selection, and provider-specific credentials.
|
|
483
496
|
`iranti status` shows the current runtime root, known instances, and local binding files.
|
|
484
497
|
`iranti upgrade` detects repo/global/Python install paths, compares current vs latest published versions, prints the exact plan, and executes the selected upgrade path when you pass `--yes`.
|
|
498
|
+
On Windows, if the currently running CLI is itself the global npm install being upgraded, Iranti now hands that npm-global step off to a detached updater process instead of trying to replace the live binary in place.
|
|
485
499
|
`iranti configure ...` updates instance/project credentials without manual env editing.
|
|
486
500
|
`iranti auth ...` manages registry-backed API keys and can sync them into instance or project bindings.
|
|
487
501
|
|
|
@@ -16,9 +16,11 @@ const net_1 = __importDefault(require("net"));
|
|
|
16
16
|
const client_1 = require("../src/library/client");
|
|
17
17
|
const apiKeys_1 = require("../src/security/apiKeys");
|
|
18
18
|
const escalationPaths_1 = require("../src/lib/escalationPaths");
|
|
19
|
+
const runtimeEnv_1 = require("../src/lib/runtimeEnv");
|
|
19
20
|
const resolutionist_1 = require("../src/resolutionist");
|
|
20
21
|
const chat_1 = require("../src/chat");
|
|
21
22
|
const backends_1 = require("../src/library/backends");
|
|
23
|
+
const sdk_1 = require("../src/sdk");
|
|
22
24
|
const PROVIDER_ENV_KEYS = {
|
|
23
25
|
mock: null,
|
|
24
26
|
ollama: null,
|
|
@@ -650,28 +652,216 @@ function makeIrantiMcpServerConfig() {
|
|
|
650
652
|
args: ['mcp'],
|
|
651
653
|
};
|
|
652
654
|
}
|
|
653
|
-
function
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
655
|
+
function applyEnvMap(vars) {
|
|
656
|
+
for (const [key, value] of Object.entries(vars)) {
|
|
657
|
+
process.env[key] = value;
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
async function resolveAttendantCliTarget(args) {
|
|
661
|
+
const explicitAgent = getFlag(args, 'agent')?.trim();
|
|
662
|
+
const explicitProjectEnv = getFlag(args, 'project-env');
|
|
663
|
+
const instanceName = getFlag(args, 'instance');
|
|
664
|
+
let envSource = 'project';
|
|
665
|
+
let envFile = null;
|
|
666
|
+
let projectEnvFile;
|
|
667
|
+
let instanceEnvFile;
|
|
668
|
+
if (instanceName) {
|
|
669
|
+
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
670
|
+
const root = resolveInstallRoot(args, scope);
|
|
671
|
+
const loaded = await loadInstanceEnv(root, instanceName);
|
|
672
|
+
applyEnvMap(loaded.env);
|
|
673
|
+
envSource = `instance:${instanceName}`;
|
|
674
|
+
envFile = loaded.envFile;
|
|
675
|
+
instanceEnvFile = loaded.envFile;
|
|
676
|
+
}
|
|
677
|
+
else {
|
|
678
|
+
const cwd = path_1.default.resolve(getFlag(args, 'project') ?? process.cwd());
|
|
679
|
+
const loaded = (0, runtimeEnv_1.loadRuntimeEnv)({
|
|
680
|
+
cwd,
|
|
681
|
+
projectEnvFile: explicitProjectEnv ? path_1.default.resolve(explicitProjectEnv) : undefined,
|
|
682
|
+
});
|
|
683
|
+
envSource = loaded.projectEnvFile ? 'project-binding' : 'environment';
|
|
684
|
+
envFile = loaded.projectEnvFile ?? loaded.instanceEnvFile ?? null;
|
|
685
|
+
projectEnvFile = loaded.projectEnvFile;
|
|
686
|
+
instanceEnvFile = loaded.instanceEnvFile;
|
|
687
|
+
}
|
|
688
|
+
const connectionString = process.env.DATABASE_URL?.trim();
|
|
689
|
+
if (!connectionString) {
|
|
690
|
+
throw new Error('DATABASE_URL is required. Run from a bound project, pass --instance <name>, or set DATABASE_URL first.');
|
|
691
|
+
}
|
|
692
|
+
const agentId = explicitAgent
|
|
693
|
+
|| process.env.IRANTI_AGENT_ID?.trim()
|
|
694
|
+
|| process.env.IRANTI_CLAUDE_AGENT_ID?.trim()
|
|
695
|
+
|| 'iranti_cli';
|
|
696
|
+
return {
|
|
697
|
+
envSource,
|
|
698
|
+
envFile,
|
|
699
|
+
projectEnvFile,
|
|
700
|
+
instanceEnvFile,
|
|
701
|
+
agentId,
|
|
702
|
+
iranti: new sdk_1.Iranti({ connectionString }),
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
function truncateText(value, limit) {
|
|
706
|
+
return value.length <= limit ? value : `${value.slice(0, limit - 3)}...`;
|
|
707
|
+
}
|
|
708
|
+
function resolveRecentMessages(args) {
|
|
709
|
+
const inline = getFlag(args, 'recent');
|
|
710
|
+
const file = getFlag(args, 'recent-file');
|
|
711
|
+
if (inline) {
|
|
712
|
+
return inline
|
|
713
|
+
.split('||')
|
|
714
|
+
.map((item) => item.trim())
|
|
715
|
+
.filter(Boolean);
|
|
716
|
+
}
|
|
717
|
+
if (file) {
|
|
718
|
+
const content = fs_1.default.readFileSync(path_1.default.resolve(file), 'utf-8');
|
|
719
|
+
return content
|
|
720
|
+
.split(/\r?\n/)
|
|
721
|
+
.map((line) => line.trim())
|
|
722
|
+
.filter(Boolean);
|
|
723
|
+
}
|
|
724
|
+
return [];
|
|
725
|
+
}
|
|
726
|
+
function parsePositiveInteger(raw, label) {
|
|
727
|
+
if (!raw)
|
|
728
|
+
return undefined;
|
|
729
|
+
const parsed = Number.parseInt(raw, 10);
|
|
730
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
731
|
+
throw new Error(`${label} must be a positive integer.`);
|
|
732
|
+
}
|
|
733
|
+
return parsed;
|
|
734
|
+
}
|
|
735
|
+
function resolveContextText(args) {
|
|
736
|
+
const inline = getFlag(args, 'context');
|
|
737
|
+
if (inline)
|
|
738
|
+
return inline;
|
|
739
|
+
const file = getFlag(args, 'context-file');
|
|
740
|
+
if (file) {
|
|
741
|
+
return fs_1.default.readFileSync(path_1.default.resolve(file), 'utf-8');
|
|
742
|
+
}
|
|
743
|
+
return '';
|
|
744
|
+
}
|
|
745
|
+
function resolveAttendMessage(args) {
|
|
746
|
+
const fromFlag = getFlag(args, 'message');
|
|
747
|
+
if (fromFlag?.trim())
|
|
748
|
+
return fromFlag.trim();
|
|
749
|
+
const fromPositionals = args.positionals.join(' ').trim();
|
|
750
|
+
if (fromPositionals)
|
|
751
|
+
return fromPositionals;
|
|
752
|
+
throw new Error('Missing latest message. Usage: iranti attend [message] [--message <text>] [--context <text>] [--json]');
|
|
753
|
+
}
|
|
754
|
+
function printHandshakeResult(target, task, result) {
|
|
755
|
+
console.log(bold('Iranti handshake'));
|
|
756
|
+
console.log(` agent ${target.agentId}`);
|
|
757
|
+
console.log(` env source ${target.envSource}`);
|
|
758
|
+
if (target.envFile)
|
|
759
|
+
console.log(` env file ${target.envFile}`);
|
|
760
|
+
console.log(` task ${task}`);
|
|
761
|
+
console.log(` inferred task ${result.inferredTaskType}`);
|
|
762
|
+
console.log(` memory facts ${result.workingMemory.length}`);
|
|
763
|
+
console.log(` generated ${result.briefGeneratedAt}`);
|
|
764
|
+
console.log('');
|
|
765
|
+
console.log(`Rules: ${truncateText(result.operatingRules, 160)}`);
|
|
766
|
+
if (result.workingMemory.length === 0) {
|
|
767
|
+
console.log('');
|
|
768
|
+
console.log('No working memory entries loaded.');
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
console.log('');
|
|
772
|
+
for (const entry of result.workingMemory) {
|
|
773
|
+
console.log(`- ${entry.entityKey} | ${entry.summary} | ${entry.confidence} | ${entry.source}`);
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
function printAttendResult(target, latestMessage, result) {
|
|
777
|
+
console.log(bold('Iranti attend'));
|
|
778
|
+
console.log(` agent ${target.agentId}`);
|
|
779
|
+
console.log(` env source ${target.envSource}`);
|
|
780
|
+
if (target.envFile)
|
|
781
|
+
console.log(` env file ${target.envFile}`);
|
|
782
|
+
console.log(` message ${truncateText(latestMessage, 120)}`);
|
|
783
|
+
console.log(` inject ${result.shouldInject ? 'yes' : 'no'}`);
|
|
784
|
+
console.log(` reason ${result.reason}`);
|
|
785
|
+
console.log(` method ${result.decision.method}`);
|
|
786
|
+
console.log(` confidence ${result.decision.confidence}`);
|
|
787
|
+
console.log(` explanation ${result.decision.explanation}`);
|
|
788
|
+
console.log(` facts ${result.facts.length}`);
|
|
789
|
+
if (result.facts.length === 0) {
|
|
790
|
+
console.log('');
|
|
791
|
+
console.log('No facts selected for injection.');
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
console.log('');
|
|
795
|
+
for (const fact of result.facts) {
|
|
796
|
+
console.log(`- ${fact.entityKey} | ${fact.summary} | ${fact.confidence} | ${fact.source}`);
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
function quoteClaudeHookArg(value) {
|
|
800
|
+
if (/^[A-Za-z0-9_./:-]+$/.test(value)) {
|
|
801
|
+
return value;
|
|
802
|
+
}
|
|
803
|
+
return `"${value.replace(/(["\\])/g, '\\$1')}"`;
|
|
804
|
+
}
|
|
805
|
+
function makeClaudeHookCommand(event, projectEnvPath) {
|
|
806
|
+
const parts = ['iranti', 'claude-hook', '--event', event];
|
|
807
|
+
if (projectEnvPath) {
|
|
808
|
+
parts.push('--project-env', quoteClaudeHookArg(projectEnvPath));
|
|
809
|
+
}
|
|
810
|
+
return parts.join(' ');
|
|
811
|
+
}
|
|
812
|
+
function makeClaudeHookEntry(event, projectEnvPath) {
|
|
813
|
+
return {
|
|
814
|
+
matcher: '',
|
|
815
|
+
hooks: [
|
|
816
|
+
{
|
|
817
|
+
type: 'command',
|
|
818
|
+
command: makeClaudeHookCommand(event, projectEnvPath),
|
|
819
|
+
},
|
|
820
|
+
],
|
|
660
821
|
};
|
|
822
|
+
}
|
|
823
|
+
function isClaudeHooksObject(value) {
|
|
824
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
825
|
+
}
|
|
826
|
+
function isLegacyIrantiClaudeHookEntry(value) {
|
|
827
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
828
|
+
return false;
|
|
829
|
+
}
|
|
830
|
+
const entry = value;
|
|
831
|
+
return entry.command === 'iranti'
|
|
832
|
+
&& Array.isArray(entry.args)
|
|
833
|
+
&& entry.args[0] === 'claude-hook';
|
|
834
|
+
}
|
|
835
|
+
function needsClaudeHookSettingsUpgrade(value) {
|
|
836
|
+
if (!value || typeof value !== 'object' || Array.isArray(value)) {
|
|
837
|
+
return false;
|
|
838
|
+
}
|
|
839
|
+
const settings = value;
|
|
840
|
+
const hooks = isClaudeHooksObject(settings.hooks) ? settings.hooks : null;
|
|
841
|
+
if (!hooks) {
|
|
842
|
+
return false;
|
|
843
|
+
}
|
|
844
|
+
for (const event of ['SessionStart', 'UserPromptSubmit']) {
|
|
845
|
+
const entries = hooks[event];
|
|
846
|
+
if (!Array.isArray(entries) || entries.length === 0) {
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
if (entries.some((entry) => isLegacyIrantiClaudeHookEntry(entry))) {
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
return false;
|
|
854
|
+
}
|
|
855
|
+
function makeClaudeHookSettings(projectEnvPath, existing) {
|
|
856
|
+
const existingHooks = existing && isClaudeHooksObject(existing.hooks)
|
|
857
|
+
? existing.hooks
|
|
858
|
+
: {};
|
|
661
859
|
return {
|
|
860
|
+
...(existing ?? {}),
|
|
662
861
|
hooks: {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
args: hookArgs('SessionStart'),
|
|
667
|
-
},
|
|
668
|
-
],
|
|
669
|
-
UserPromptSubmit: [
|
|
670
|
-
{
|
|
671
|
-
command: 'iranti',
|
|
672
|
-
args: hookArgs('UserPromptSubmit'),
|
|
673
|
-
},
|
|
674
|
-
],
|
|
862
|
+
...existingHooks,
|
|
863
|
+
SessionStart: [makeClaudeHookEntry('SessionStart', projectEnvPath)],
|
|
864
|
+
UserPromptSubmit: [makeClaudeHookEntry('UserPromptSubmit', projectEnvPath)],
|
|
675
865
|
},
|
|
676
866
|
};
|
|
677
867
|
}
|
|
@@ -725,9 +915,18 @@ async function writeClaudeCodeProjectFiles(projectPath, projectEnvPath, force =
|
|
|
725
915
|
await writeText(settingsFile, `${JSON.stringify(makeClaudeHookSettings(projectEnvPath), null, 2)}\n`);
|
|
726
916
|
settingsStatus = 'created';
|
|
727
917
|
}
|
|
728
|
-
else
|
|
729
|
-
|
|
730
|
-
|
|
918
|
+
else {
|
|
919
|
+
const existingSettings = readJsonFile(settingsFile);
|
|
920
|
+
if (existingSettings && typeof existingSettings === 'object' && !Array.isArray(existingSettings)) {
|
|
921
|
+
if (force || needsClaudeHookSettingsUpgrade(existingSettings)) {
|
|
922
|
+
await writeText(settingsFile, `${JSON.stringify(makeClaudeHookSettings(projectEnvPath, existingSettings), null, 2)}\n`);
|
|
923
|
+
settingsStatus = 'updated';
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
else if (force) {
|
|
927
|
+
await writeText(settingsFile, `${JSON.stringify(makeClaudeHookSettings(projectEnvPath), null, 2)}\n`);
|
|
928
|
+
settingsStatus = 'updated';
|
|
929
|
+
}
|
|
731
930
|
}
|
|
732
931
|
return {
|
|
733
932
|
mcp: mcpStatus,
|
|
@@ -1189,6 +1388,73 @@ function isPathInside(parentDir, childDir) {
|
|
|
1189
1388
|
const child = normalizePathForCompare(childDir);
|
|
1190
1389
|
return child === parent || child.startsWith(`${parent}/`);
|
|
1191
1390
|
}
|
|
1391
|
+
function hasCommandInstalled(command) {
|
|
1392
|
+
try {
|
|
1393
|
+
const proc = process.platform === 'win32'
|
|
1394
|
+
? (0, child_process_1.spawnSync)(process.env.ComSpec ?? 'cmd.exe', ['/d', '/c', `${command} --version`], { stdio: 'ignore' })
|
|
1395
|
+
: (0, child_process_1.spawnSync)(command, ['--version'], { stdio: 'ignore' });
|
|
1396
|
+
return proc.status === 0;
|
|
1397
|
+
}
|
|
1398
|
+
catch {
|
|
1399
|
+
return false;
|
|
1400
|
+
}
|
|
1401
|
+
}
|
|
1402
|
+
async function canConnectTcp(port, host = '127.0.0.1', timeoutMs = 800) {
|
|
1403
|
+
return await new Promise((resolve) => {
|
|
1404
|
+
const socket = net_1.default.createConnection({ port, host });
|
|
1405
|
+
let settled = false;
|
|
1406
|
+
const finish = (value) => {
|
|
1407
|
+
if (settled)
|
|
1408
|
+
return;
|
|
1409
|
+
settled = true;
|
|
1410
|
+
socket.destroy();
|
|
1411
|
+
resolve(value);
|
|
1412
|
+
};
|
|
1413
|
+
socket.setTimeout(timeoutMs);
|
|
1414
|
+
socket.on('connect', () => finish(true));
|
|
1415
|
+
socket.on('timeout', () => finish(false));
|
|
1416
|
+
socket.on('error', () => finish(false));
|
|
1417
|
+
});
|
|
1418
|
+
}
|
|
1419
|
+
async function collectDependencyChecks() {
|
|
1420
|
+
const docker = hasDockerInstalled();
|
|
1421
|
+
const psql = hasCommandInstalled('psql');
|
|
1422
|
+
const pgIsReady = hasCommandInstalled('pg_isready');
|
|
1423
|
+
const postgresPort = await canConnectTcp(5432);
|
|
1424
|
+
const checks = [
|
|
1425
|
+
{
|
|
1426
|
+
name: 'docker',
|
|
1427
|
+
status: docker ? 'pass' : 'warn',
|
|
1428
|
+
detail: docker ? 'Docker is installed.' : 'Docker is not installed or not on PATH.',
|
|
1429
|
+
},
|
|
1430
|
+
{
|
|
1431
|
+
name: 'psql',
|
|
1432
|
+
status: psql ? 'pass' : 'warn',
|
|
1433
|
+
detail: psql ? 'psql is installed.' : 'psql is not installed or not on PATH.',
|
|
1434
|
+
},
|
|
1435
|
+
{
|
|
1436
|
+
name: 'pg_isready',
|
|
1437
|
+
status: pgIsReady ? 'pass' : 'warn',
|
|
1438
|
+
detail: pgIsReady ? 'pg_isready is installed.' : 'pg_isready is not installed or not on PATH.',
|
|
1439
|
+
},
|
|
1440
|
+
{
|
|
1441
|
+
name: 'localhost:5432',
|
|
1442
|
+
status: postgresPort ? 'pass' : 'warn',
|
|
1443
|
+
detail: postgresPort ? 'A PostgreSQL listener appears reachable on localhost:5432.' : 'Nothing is reachable on localhost:5432 right now.',
|
|
1444
|
+
},
|
|
1445
|
+
];
|
|
1446
|
+
return checks;
|
|
1447
|
+
}
|
|
1448
|
+
function printDependencyChecks(checks) {
|
|
1449
|
+
console.log(bold('Dependency check'));
|
|
1450
|
+
for (const check of checks) {
|
|
1451
|
+
const marker = check.status === 'pass' ? okLabel('PASS') : warnLabel('WARN');
|
|
1452
|
+
console.log(`${marker} ${check.name} — ${check.detail}`);
|
|
1453
|
+
}
|
|
1454
|
+
if (checks.every((check) => check.status === 'warn')) {
|
|
1455
|
+
console.log(`${warnLabel()} No PostgreSQL tooling or reachable local Postgres was detected. You can still continue if you plan to use managed Postgres, but local setup will be rough until you install PostgreSQL tooling or Docker.`);
|
|
1456
|
+
}
|
|
1457
|
+
}
|
|
1192
1458
|
function quoteForCmd(arg) {
|
|
1193
1459
|
if (arg.length === 0)
|
|
1194
1460
|
return '""';
|
|
@@ -1369,6 +1635,7 @@ function detectUpgradeContext(args) {
|
|
|
1369
1635
|
const globalNpmRoot = detectGlobalNpmRoot();
|
|
1370
1636
|
const globalNpmVersion = detectGlobalNpmInstalledVersion();
|
|
1371
1637
|
const globalNpmInstall = globalNpmVersion !== null;
|
|
1638
|
+
const runningFromGlobalNpmInstall = Boolean(globalNpmRoot && isPathInside(globalNpmRoot, packageRootPath));
|
|
1372
1639
|
const python = detectPythonLauncher();
|
|
1373
1640
|
const pythonVersion = detectPythonInstalledVersion(python);
|
|
1374
1641
|
const availableTargets = [];
|
|
@@ -1388,6 +1655,7 @@ function detectUpgradeContext(args) {
|
|
|
1388
1655
|
globalNpmInstall,
|
|
1389
1656
|
globalNpmRoot,
|
|
1390
1657
|
globalNpmVersion,
|
|
1658
|
+
runningFromGlobalNpmInstall,
|
|
1391
1659
|
python,
|
|
1392
1660
|
pythonVersion,
|
|
1393
1661
|
availableTargets,
|
|
@@ -1554,6 +1822,21 @@ function verifyGlobalNpmInstall() {
|
|
|
1554
1822
|
};
|
|
1555
1823
|
}
|
|
1556
1824
|
}
|
|
1825
|
+
function canScheduleWindowsGlobalNpmSelfUpgrade(context) {
|
|
1826
|
+
return process.platform === 'win32' && context.runningFromGlobalNpmInstall;
|
|
1827
|
+
}
|
|
1828
|
+
function scheduleDetachedWindowsGlobalNpmUpgrade(command) {
|
|
1829
|
+
const shell = process.env.ComSpec ?? 'cmd.exe';
|
|
1830
|
+
const commandLine = `ping 127.0.0.1 -n 3 >nul & ${command.display}`;
|
|
1831
|
+
const child = (0, child_process_1.spawn)(shell, ['/d', '/c', commandLine], {
|
|
1832
|
+
detached: true,
|
|
1833
|
+
stdio: 'ignore',
|
|
1834
|
+
windowsHide: true,
|
|
1835
|
+
cwd: command.cwd,
|
|
1836
|
+
env: process.env,
|
|
1837
|
+
});
|
|
1838
|
+
child.unref();
|
|
1839
|
+
}
|
|
1557
1840
|
function verifyPythonInstall(command) {
|
|
1558
1841
|
const version = detectPythonInstalledVersion(command);
|
|
1559
1842
|
return version
|
|
@@ -1566,6 +1849,20 @@ async function executeUpgradeTarget(target, context) {
|
|
|
1566
1849
|
}
|
|
1567
1850
|
const commands = commandListForTarget(target, context);
|
|
1568
1851
|
const steps = [];
|
|
1852
|
+
if (target === 'npm-global' && canScheduleWindowsGlobalNpmSelfUpgrade(context)) {
|
|
1853
|
+
const command = commands[0];
|
|
1854
|
+
console.log(`${infoLabel()} ${command.display} (scheduled in a detached updater because the current Windows CLI cannot replace its own live global install)`);
|
|
1855
|
+
scheduleDetachedWindowsGlobalNpmUpgrade(command);
|
|
1856
|
+
steps.push({ label: `${command.label} (detached)`, command: command.display });
|
|
1857
|
+
return {
|
|
1858
|
+
target,
|
|
1859
|
+
steps,
|
|
1860
|
+
verification: {
|
|
1861
|
+
status: 'warn',
|
|
1862
|
+
detail: 'Scheduled detached npm global upgrade. Wait a few seconds, then open a new shell or rerun `iranti upgrade --check` to confirm the new global CLI is active.',
|
|
1863
|
+
},
|
|
1864
|
+
};
|
|
1865
|
+
}
|
|
1569
1866
|
for (const command of commands) {
|
|
1570
1867
|
console.log(`${infoLabel()} ${command.display}`);
|
|
1571
1868
|
const status = runCommandInteractive(command);
|
|
@@ -1732,6 +2029,9 @@ async function setupCommand(args) {
|
|
|
1732
2029
|
throw new Error('Use either --config <file> or --defaults, not both.');
|
|
1733
2030
|
}
|
|
1734
2031
|
if (configPath || useDefaults) {
|
|
2032
|
+
const dependencyChecks = await collectDependencyChecks();
|
|
2033
|
+
printDependencyChecks(dependencyChecks);
|
|
2034
|
+
console.log('');
|
|
1735
2035
|
const plan = configPath ? parseSetupConfig(configPath) : defaultsSetupPlan(args);
|
|
1736
2036
|
const result = await executeSetupPlan(plan);
|
|
1737
2037
|
console.log(bold('Setup complete'));
|
|
@@ -1763,6 +2063,8 @@ async function setupCommand(args) {
|
|
|
1763
2063
|
console.log(bold('Iranti setup'));
|
|
1764
2064
|
console.log('This wizard will get Iranti set up: install a runtime, create or update an instance, connect provider keys, create a usable Iranti API key, and optionally bind one or more project folders.');
|
|
1765
2065
|
console.log('');
|
|
2066
|
+
printDependencyChecks(await collectDependencyChecks());
|
|
2067
|
+
console.log('');
|
|
1766
2068
|
let result = null;
|
|
1767
2069
|
await withPromptSession(async (prompt) => {
|
|
1768
2070
|
let setupMode = 'shared';
|
|
@@ -2299,6 +2601,7 @@ async function upgradeCommand(args) {
|
|
|
2299
2601
|
globalNpmInstall: context.globalNpmInstall,
|
|
2300
2602
|
globalNpmRoot: context.globalNpmRoot,
|
|
2301
2603
|
globalNpmVersion: context.globalNpmVersion,
|
|
2604
|
+
runningFromGlobalNpmInstall: context.runningFromGlobalNpmInstall,
|
|
2302
2605
|
pythonLauncher: context.python?.executable ?? null,
|
|
2303
2606
|
pythonVersion: context.pythonVersion,
|
|
2304
2607
|
},
|
|
@@ -2323,6 +2626,9 @@ async function upgradeCommand(args) {
|
|
|
2323
2626
|
console.log(` runtime_root ${context.runtimeRoot}`);
|
|
2324
2627
|
console.log(` repo_checkout ${context.repoCheckout ? paint('yes', 'green') : paint('no', 'gray')}${context.repoDirty ? paint(' (dirty)', 'yellow') : ''}`);
|
|
2325
2628
|
console.log(` npm_global ${context.globalNpmInstall ? paint('yes', 'green') : paint('no', 'gray')}${context.globalNpmVersion ? ` (${context.globalNpmVersion})` : ''}`);
|
|
2629
|
+
if (context.runningFromGlobalNpmInstall) {
|
|
2630
|
+
console.log(` npm_global_mode ${paint('self-update requires detached handoff on Windows', 'yellow')}`);
|
|
2631
|
+
}
|
|
2326
2632
|
console.log(` python ${context.python?.executable ?? paint('not found', 'yellow')}${context.pythonVersion ? ` (${context.pythonVersion})` : ''}`);
|
|
2327
2633
|
console.log('');
|
|
2328
2634
|
if (selectedTargets.length > 0) {
|
|
@@ -2368,6 +2674,7 @@ async function upgradeCommand(args) {
|
|
|
2368
2674
|
async function installCommand(args) {
|
|
2369
2675
|
const scope = normalizeScope(getFlag(args, 'scope'));
|
|
2370
2676
|
const root = resolveInstallRoot(args, scope);
|
|
2677
|
+
const dependencyChecks = await collectDependencyChecks();
|
|
2371
2678
|
await ensureDir(root);
|
|
2372
2679
|
await ensureDir(path_1.default.join(root, 'instances'));
|
|
2373
2680
|
await ensureDir(path_1.default.join(root, 'logs'));
|
|
@@ -2382,6 +2689,8 @@ async function installCommand(args) {
|
|
|
2382
2689
|
console.log(`${okLabel()} Iranti runtime initialized`);
|
|
2383
2690
|
console.log(` scope: ${scope}`);
|
|
2384
2691
|
console.log(` root : ${root}`);
|
|
2692
|
+
console.log('');
|
|
2693
|
+
printDependencyChecks(dependencyChecks);
|
|
2385
2694
|
console.log(`Next: iranti instance create local --port 3001`);
|
|
2386
2695
|
}
|
|
2387
2696
|
async function createInstanceCommand(args) {
|
|
@@ -2809,6 +3118,67 @@ async function resolveCommand(args) {
|
|
|
2809
3118
|
const escalationDir = explicitDir ? path_1.default.resolve(explicitDir) : (0, escalationPaths_1.getEscalationPaths)().root;
|
|
2810
3119
|
await (0, resolutionist_1.resolveInteractive)(escalationDir);
|
|
2811
3120
|
}
|
|
3121
|
+
async function handshakeCommand(args) {
|
|
3122
|
+
const json = hasFlag(args, 'json');
|
|
3123
|
+
const target = await resolveAttendantCliTarget(args);
|
|
3124
|
+
const task = getFlag(args, 'task')?.trim() || 'CLI handshake';
|
|
3125
|
+
const recentMessages = resolveRecentMessages(args);
|
|
3126
|
+
const result = await target.iranti.handshake({
|
|
3127
|
+
agent: target.agentId,
|
|
3128
|
+
task,
|
|
3129
|
+
recentMessages,
|
|
3130
|
+
});
|
|
3131
|
+
if (json) {
|
|
3132
|
+
console.log(JSON.stringify({
|
|
3133
|
+
agent: target.agentId,
|
|
3134
|
+
envSource: target.envSource,
|
|
3135
|
+
envFile: target.envFile,
|
|
3136
|
+
task,
|
|
3137
|
+
recentMessages,
|
|
3138
|
+
result,
|
|
3139
|
+
}, null, 2));
|
|
3140
|
+
return;
|
|
3141
|
+
}
|
|
3142
|
+
printHandshakeResult(target, task, result);
|
|
3143
|
+
console.log('');
|
|
3144
|
+
console.log(`${infoLabel()} This is a manual Attendant inspection tool. Claude Code should still use hooks + MCP in normal operation.`);
|
|
3145
|
+
}
|
|
3146
|
+
async function attendCommand(args) {
|
|
3147
|
+
const json = hasFlag(args, 'json');
|
|
3148
|
+
const target = await resolveAttendantCliTarget(args);
|
|
3149
|
+
const latestMessage = resolveAttendMessage(args);
|
|
3150
|
+
const currentContext = resolveContextText(args);
|
|
3151
|
+
const maxFacts = parsePositiveInteger(getFlag(args, 'max-facts'), 'max-facts');
|
|
3152
|
+
const entityHint = getFlag(args, 'entity-hint')?.trim();
|
|
3153
|
+
if (entityHint && !entityHint.includes('/')) {
|
|
3154
|
+
throw new Error('entity-hint must use entityType/entityId format.');
|
|
3155
|
+
}
|
|
3156
|
+
const result = await target.iranti.attend({
|
|
3157
|
+
agent: target.agentId,
|
|
3158
|
+
currentContext,
|
|
3159
|
+
latestMessage,
|
|
3160
|
+
forceInject: hasFlag(args, 'force'),
|
|
3161
|
+
maxFacts,
|
|
3162
|
+
entityHints: entityHint ? [entityHint] : undefined,
|
|
3163
|
+
});
|
|
3164
|
+
if (json) {
|
|
3165
|
+
console.log(JSON.stringify({
|
|
3166
|
+
agent: target.agentId,
|
|
3167
|
+
envSource: target.envSource,
|
|
3168
|
+
envFile: target.envFile,
|
|
3169
|
+
latestMessage,
|
|
3170
|
+
currentContext,
|
|
3171
|
+
maxFacts: maxFacts ?? null,
|
|
3172
|
+
entityHints: entityHint ? [entityHint] : [],
|
|
3173
|
+
forceInject: hasFlag(args, 'force'),
|
|
3174
|
+
result,
|
|
3175
|
+
}, null, 2));
|
|
3176
|
+
return;
|
|
3177
|
+
}
|
|
3178
|
+
printAttendResult(target, latestMessage, result);
|
|
3179
|
+
console.log('');
|
|
3180
|
+
console.log(`${infoLabel()} This is a manual Attendant inspection tool. Claude Code should still use hooks + MCP in normal operation.`);
|
|
3181
|
+
}
|
|
2812
3182
|
function printClaudeSetupHelp() {
|
|
2813
3183
|
console.log([
|
|
2814
3184
|
'Scaffold Claude Code MCP and hook files for the current project.',
|
|
@@ -3001,6 +3371,8 @@ Project-level:
|
|
|
3001
3371
|
iranti doctor [--instance <name>] [--scope user|system] [--env <file>] [--json]
|
|
3002
3372
|
iranti status [--scope user|system] [--json]
|
|
3003
3373
|
iranti upgrade [--check] [--dry-run] [--yes] [--all] [--target auto|npm-global|npm-repo|python[,python]] [--json]
|
|
3374
|
+
iranti handshake [--instance <name> | --project-env <file>] [--agent <id>] [--task <text>] [--recent <msg1||msg2>] [--recent-file <path>] [--json]
|
|
3375
|
+
iranti attend [message] [--instance <name> | --project-env <file>] [--agent <id>] [--context <text> | --context-file <path>] [--entity-hint <entity>] [--force] [--max-facts <n>] [--json]
|
|
3004
3376
|
iranti chat [--agent <agent-id>] [--provider <provider>] [--model <model>]
|
|
3005
3377
|
iranti resolve [--dir <escalation-dir>]
|
|
3006
3378
|
|
|
@@ -3106,6 +3478,14 @@ async function main() {
|
|
|
3106
3478
|
await upgradeCommand(args);
|
|
3107
3479
|
return;
|
|
3108
3480
|
}
|
|
3481
|
+
if (args.command === 'handshake') {
|
|
3482
|
+
await handshakeCommand(args);
|
|
3483
|
+
return;
|
|
3484
|
+
}
|
|
3485
|
+
if (args.command === 'attend') {
|
|
3486
|
+
await attendCommand(args);
|
|
3487
|
+
return;
|
|
3488
|
+
}
|
|
3109
3489
|
if (args.command === 'chat') {
|
|
3110
3490
|
await chatCommand(args);
|
|
3111
3491
|
return;
|
|
@@ -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.9',
|
|
148
148
|
});
|
|
149
149
|
server.registerTool('iranti_handshake', {
|
|
150
150
|
description: `Initialize or refresh an agent's working-memory brief for the current task.
|
package/dist/scripts/seed.js
CHANGED
|
@@ -15,7 +15,7 @@ const STAFF_ENTRIES = [
|
|
|
15
15
|
entityId: 'librarian',
|
|
16
16
|
key: 'operating_rules',
|
|
17
17
|
valueRaw: {
|
|
18
|
-
version: '0.2.
|
|
18
|
+
version: '0.2.9',
|
|
19
19
|
rules: [
|
|
20
20
|
'All writes from external agents go through the Librarian — never directly to the database',
|
|
21
21
|
'Check for existing entries before every write',
|
|
@@ -39,7 +39,7 @@ const STAFF_ENTRIES = [
|
|
|
39
39
|
entityId: 'attendant',
|
|
40
40
|
key: 'operating_rules',
|
|
41
41
|
valueRaw: {
|
|
42
|
-
version: '0.2.
|
|
42
|
+
version: '0.2.9',
|
|
43
43
|
rules: [
|
|
44
44
|
'Assigned one-per-external-agent — serve the agent, not the user',
|
|
45
45
|
'On handshake: read AGENTS.md and MCP config, query Librarian for relevant rules and task context',
|
|
@@ -61,7 +61,7 @@ const STAFF_ENTRIES = [
|
|
|
61
61
|
entityId: 'archivist',
|
|
62
62
|
key: 'operating_rules',
|
|
63
63
|
valueRaw: {
|
|
64
|
-
version: '0.2.
|
|
64
|
+
version: '0.2.9',
|
|
65
65
|
rules: [
|
|
66
66
|
'Run on schedule or when conflict flags exceed threshold — not on every write',
|
|
67
67
|
'Scan for expired, low-confidence, flagged, and duplicate entries',
|
|
@@ -82,7 +82,7 @@ const STAFF_ENTRIES = [
|
|
|
82
82
|
entityType: 'system',
|
|
83
83
|
entityId: 'library',
|
|
84
84
|
key: 'schema_version',
|
|
85
|
-
valueRaw: { version: '0.2.
|
|
85
|
+
valueRaw: { version: '0.2.9' },
|
|
86
86
|
valueSummary: 'Current Library schema version.',
|
|
87
87
|
confidence: 100,
|
|
88
88
|
source: 'seed',
|
|
@@ -95,7 +95,7 @@ const STAFF_ENTRIES = [
|
|
|
95
95
|
key: 'initialization_log',
|
|
96
96
|
valueRaw: {
|
|
97
97
|
initializedAt: new Date().toISOString(),
|
|
98
|
-
seedVersion: '0.2.
|
|
98
|
+
seedVersion: '0.2.9',
|
|
99
99
|
},
|
|
100
100
|
valueSummary: 'Record of when and how this Library was initialized.',
|
|
101
101
|
confidence: 100,
|
|
@@ -108,7 +108,7 @@ const STAFF_ENTRIES = [
|
|
|
108
108
|
entityId: 'ontology',
|
|
109
109
|
key: 'core_schema',
|
|
110
110
|
valueRaw: {
|
|
111
|
-
version: '0.2.
|
|
111
|
+
version: '0.2.9',
|
|
112
112
|
states: ['candidate', 'provisional', 'canonical'],
|
|
113
113
|
coreEntityTypes: [
|
|
114
114
|
'person',
|
|
@@ -156,7 +156,7 @@ const STAFF_ENTRIES = [
|
|
|
156
156
|
entityId: 'ontology',
|
|
157
157
|
key: 'extension_registry',
|
|
158
158
|
valueRaw: {
|
|
159
|
-
version: '0.2.
|
|
159
|
+
version: '0.2.9',
|
|
160
160
|
namespaces: {
|
|
161
161
|
education: {
|
|
162
162
|
status: 'provisional',
|
|
@@ -187,7 +187,7 @@ const STAFF_ENTRIES = [
|
|
|
187
187
|
entityId: 'ontology',
|
|
188
188
|
key: 'candidate_terms',
|
|
189
189
|
valueRaw: {
|
|
190
|
-
version: '0.2.
|
|
190
|
+
version: '0.2.9',
|
|
191
191
|
terms: [],
|
|
192
192
|
},
|
|
193
193
|
valueSummary: 'Staging area for ontology terms detected repeatedly but not yet promoted.',
|
|
@@ -201,7 +201,7 @@ const STAFF_ENTRIES = [
|
|
|
201
201
|
entityId: 'ontology',
|
|
202
202
|
key: 'promotion_policy',
|
|
203
203
|
valueRaw: {
|
|
204
|
-
version: '0.2.
|
|
204
|
+
version: '0.2.9',
|
|
205
205
|
candidateToProvisional: {
|
|
206
206
|
minSeenCount: 3,
|
|
207
207
|
minDistinctAgents: 2,
|
|
@@ -237,7 +237,7 @@ const STAFF_ENTRIES = [
|
|
|
237
237
|
entityId: 'ontology',
|
|
238
238
|
key: 'change_log',
|
|
239
239
|
valueRaw: {
|
|
240
|
-
version: '0.2.
|
|
240
|
+
version: '0.2.9',
|
|
241
241
|
events: [
|
|
242
242
|
{
|
|
243
243
|
at: new Date().toISOString(),
|
package/dist/src/api/server.js
CHANGED