@ouro.bot/cli 0.1.0-alpha.363 → 0.1.0-alpha.365
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 +16 -7
- package/changelog.json +17 -0
- package/dist/heart/auth/auth-flow.js +25 -110
- package/dist/heart/config.js +69 -55
- package/dist/heart/core.js +83 -33
- package/dist/heart/daemon/agent-config-check.js +41 -238
- package/dist/heart/daemon/agentic-repair.js +1 -1
- package/dist/heart/daemon/cli-defaults.js +15 -68
- package/dist/heart/daemon/cli-exec.js +334 -102
- package/dist/heart/daemon/cli-parse.js +71 -0
- package/dist/heart/daemon/daemon-cli.js +1 -2
- package/dist/heart/daemon/daemon-entry.js +1 -3
- package/dist/heart/daemon/doctor.js +9 -29
- package/dist/heart/daemon/provider-discovery.js +32 -59
- package/dist/heart/hatch/hatch-flow.js +9 -12
- package/dist/heart/hatch/specialist-prompt.js +1 -1
- package/dist/heart/hatch/specialist-tools.js +21 -1
- package/dist/heart/migrate-config.js +15 -42
- package/dist/heart/provider-binding-resolver.js +6 -7
- package/dist/heart/provider-credentials.js +379 -0
- package/dist/heart/provider-failover.js +3 -11
- package/dist/heart/provider-ping.js +13 -3
- package/dist/heart/provider-state.js +8 -0
- package/dist/heart/provider-visibility.js +3 -6
- package/dist/heart/providers/anthropic-token.js +15 -47
- package/dist/heart/providers/anthropic.js +4 -9
- package/dist/heart/providers/azure.js +3 -3
- package/dist/heart/providers/github-copilot.js +2 -2
- package/dist/heart/providers/minimax-vlm.js +2 -2
- package/dist/heart/providers/minimax.js +1 -1
- package/dist/heart/providers/openai-codex.js +4 -9
- package/dist/heart/versioning/ouro-path-installer.js +10 -5
- package/dist/mind/prompt.js +1 -1
- package/dist/repertoire/bitwarden-store.js +63 -17
- package/dist/repertoire/bundle-templates.js +2 -2
- package/dist/repertoire/credential-access.js +47 -467
- package/dist/repertoire/tools-attachments.js +5 -4
- package/dist/repertoire/tools-vault.js +10 -80
- package/dist/repertoire/vault-unlock.js +359 -0
- package/dist/senses/bluebubbles/client.js +39 -4
- package/dist/senses/pipeline.js +0 -1
- package/package.json +1 -1
- package/skills/configure-dev-tools.md +10 -0
- package/dist/heart/provider-credential-pool.js +0 -395
|
@@ -62,10 +62,13 @@ const bundle_meta_1 = require("./hooks/bundle-meta");
|
|
|
62
62
|
const agent_config_v2_1 = require("./hooks/agent-config-v2");
|
|
63
63
|
const bundle_manifest_1 = require("../../mind/bundle-manifest");
|
|
64
64
|
const tasks_1 = require("../../repertoire/tasks");
|
|
65
|
+
const credential_access_1 = require("../../repertoire/credential-access");
|
|
66
|
+
const vault_setup_1 = require("../../repertoire/vault-setup");
|
|
67
|
+
const vault_unlock_1 = require("../../repertoire/vault-unlock");
|
|
65
68
|
const thoughts_1 = require("./thoughts");
|
|
66
69
|
const launchd_1 = require("./launchd");
|
|
67
70
|
const auth_flow_1 = require("../auth/auth-flow");
|
|
68
|
-
const
|
|
71
|
+
const provider_credentials_1 = require("../provider-credentials");
|
|
69
72
|
const provider_binding_resolver_1 = require("../provider-binding-resolver");
|
|
70
73
|
const provider_state_1 = require("../provider-state");
|
|
71
74
|
const machine_identity_1 = require("../machine-identity");
|
|
@@ -94,11 +97,10 @@ const DEFAULT_DAEMON_STARTUP_LOG_LINES = 10;
|
|
|
94
97
|
async function checkAlreadyRunningAgentProviders(deps) {
|
|
95
98
|
const agents = await Promise.resolve(deps.listDiscoveredAgents ? deps.listDiscoveredAgents() : (0, cli_defaults_1.defaultListDiscoveredAgents)());
|
|
96
99
|
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
97
|
-
const secretsRoot = deps.secretsRoot ?? path.join(os.homedir(), ".agentsecrets");
|
|
98
100
|
const degraded = [];
|
|
99
101
|
for (const agent of agents) {
|
|
100
102
|
try {
|
|
101
|
-
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot
|
|
103
|
+
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);
|
|
102
104
|
if (result.ok)
|
|
103
105
|
continue;
|
|
104
106
|
const errorReason = result.error ?? "agent provider health check failed";
|
|
@@ -132,8 +134,7 @@ async function checkAlreadyRunningAgentProviders(deps) {
|
|
|
132
134
|
}
|
|
133
135
|
async function checkProviderHealthBeforeChat(agentName, deps) {
|
|
134
136
|
const bundlesRoot = deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)();
|
|
135
|
-
const
|
|
136
|
-
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot, secretsRoot);
|
|
137
|
+
const result = await (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot);
|
|
137
138
|
if (!result.ok) {
|
|
138
139
|
const output = `${result.error}\n${result.fix ? ` fix: ${result.fix}` : ""}`;
|
|
139
140
|
deps.writeStdout(output);
|
|
@@ -492,8 +493,9 @@ async function checkManualCloneBundles(deps) {
|
|
|
492
493
|
message: "bundle appears to be a manually cloned git repo",
|
|
493
494
|
meta: { agent: agentDir, remote: remoteName },
|
|
494
495
|
});
|
|
495
|
-
|
|
496
|
-
|
|
496
|
+
/* v8 ignore next -- ?? fallback: promptInput always returns string in practice @preserve */
|
|
497
|
+
const answer = (await deps.promptInput(`Bundle ${agentDir} appears to be a git clone with a remote. Enable sync? (y/n): `)) ?? "";
|
|
498
|
+
if (answer.trim().toLowerCase() === "y" && fs.existsSync(agentJsonPath)) {
|
|
497
499
|
const raw = fs.readFileSync(agentJsonPath, "utf-8");
|
|
498
500
|
const config = JSON.parse(raw);
|
|
499
501
|
config.sync = { enabled: true, remote: remoteName };
|
|
@@ -545,7 +547,7 @@ async function resolveHatchInput(command, deps) {
|
|
|
545
547
|
}
|
|
546
548
|
// ── Provider state CLI helpers ──
|
|
547
549
|
function providerCliHomeDir(deps) {
|
|
548
|
-
return (0,
|
|
550
|
+
return (0, provider_credentials_1.providerCredentialMachineHomeDir)(deps.homeDir);
|
|
549
551
|
}
|
|
550
552
|
function providerCliAgentRoot(command, deps) {
|
|
551
553
|
return path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
|
|
@@ -553,6 +555,144 @@ function providerCliAgentRoot(command, deps) {
|
|
|
553
555
|
function providerCliNow(deps) {
|
|
554
556
|
return new Date((deps.now ?? Date.now)());
|
|
555
557
|
}
|
|
558
|
+
function writeAgentVaultConfig(agentName, configPath, config, vault) {
|
|
559
|
+
const nextConfig = {
|
|
560
|
+
...config,
|
|
561
|
+
vault: {
|
|
562
|
+
email: vault.email,
|
|
563
|
+
serverUrl: vault.serverUrl,
|
|
564
|
+
},
|
|
565
|
+
};
|
|
566
|
+
fs.writeFileSync(configPath, `${JSON.stringify(nextConfig, null, 2)}\n`, "utf8");
|
|
567
|
+
(0, runtime_1.emitNervesEvent)({
|
|
568
|
+
component: "daemon",
|
|
569
|
+
event: "daemon.vault_config_written",
|
|
570
|
+
message: "wrote credential vault locator to agent config",
|
|
571
|
+
meta: { agentName, configPath, email: vault.email, serverUrl: vault.serverUrl },
|
|
572
|
+
});
|
|
573
|
+
}
|
|
574
|
+
async function executeVaultUnlock(command, deps) {
|
|
575
|
+
if (command.agent === "SerpentGuide") {
|
|
576
|
+
throw new Error("SerpentGuide does not have a persistent credential vault. Hatch bootstrap uses selected provider credentials in memory only.");
|
|
577
|
+
}
|
|
578
|
+
const prompt = deps.promptInput;
|
|
579
|
+
if (!prompt)
|
|
580
|
+
throw new Error("vault unlock requires an interactive prompt to capture the vault unlock secret");
|
|
581
|
+
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
582
|
+
const vault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
583
|
+
const unlockSecret = await prompt(`Ouro vault unlock secret for ${vault.email}: `);
|
|
584
|
+
const store = (0, vault_unlock_1.storeVaultUnlockSecret)({
|
|
585
|
+
agentName: command.agent,
|
|
586
|
+
email: vault.email,
|
|
587
|
+
serverUrl: vault.serverUrl,
|
|
588
|
+
}, unlockSecret, { homeDir: deps.homeDir, store: command.store });
|
|
589
|
+
(0, credential_access_1.resetCredentialStore)();
|
|
590
|
+
await (0, credential_access_1.getCredentialStore)(command.agent).get("__ouro_vault_probe__");
|
|
591
|
+
const message = [
|
|
592
|
+
`vault unlocked for ${command.agent} on this machine`,
|
|
593
|
+
`vault: ${vault.email} at ${vault.serverUrl}`,
|
|
594
|
+
`local unlock store: ${store.kind}${store.secure ? "" : " (explicit plaintext fallback)"}`,
|
|
595
|
+
].join("\n");
|
|
596
|
+
deps.writeStdout(message);
|
|
597
|
+
return message;
|
|
598
|
+
}
|
|
599
|
+
async function executeVaultCreate(command, deps) {
|
|
600
|
+
if (command.agent === "SerpentGuide") {
|
|
601
|
+
throw new Error("SerpentGuide does not have a persistent credential vault. Create a vault for the hatchling agent, not SerpentGuide.");
|
|
602
|
+
}
|
|
603
|
+
const prompt = deps.promptInput;
|
|
604
|
+
const { configPath, config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
605
|
+
const configuredVault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
606
|
+
const email = command.email ?? config.vault?.email ?? (prompt ? (await prompt("Ouro credential vault email: ")).trim() : "");
|
|
607
|
+
if (!email) {
|
|
608
|
+
throw new Error("vault create requires --email <email> when the agent bundle has no vault.email");
|
|
609
|
+
}
|
|
610
|
+
const serverUrl = command.serverUrl ?? config.vault?.serverUrl ?? configuredVault.serverUrl;
|
|
611
|
+
const requestedUnlockSecret = command.generateUnlockSecret
|
|
612
|
+
? ""
|
|
613
|
+
: prompt
|
|
614
|
+
? (await prompt(`Choose Ouro vault unlock secret for ${email}: `)).trim()
|
|
615
|
+
: "";
|
|
616
|
+
if (!requestedUnlockSecret && !command.generateUnlockSecret) {
|
|
617
|
+
throw new Error("vault create requires an unlock secret. Re-run with an interactive terminal or pass --generate-unlock-secret.");
|
|
618
|
+
}
|
|
619
|
+
const unlockSecret = requestedUnlockSecret || (0, crypto_1.randomBytes)(32).toString("base64");
|
|
620
|
+
const result = await (0, vault_setup_1.createVaultAccount)("Ouro credential vault", serverUrl, email, unlockSecret);
|
|
621
|
+
if (!result.success) {
|
|
622
|
+
const message = [
|
|
623
|
+
`vault create failed for ${command.agent}: ${result.error}`,
|
|
624
|
+
"",
|
|
625
|
+
"If this vault account already exists, run:",
|
|
626
|
+
` ouro vault unlock --agent ${command.agent}`,
|
|
627
|
+
].join("\n");
|
|
628
|
+
deps.writeStdout(message);
|
|
629
|
+
return message;
|
|
630
|
+
}
|
|
631
|
+
writeAgentVaultConfig(command.agent, configPath, config, { email, serverUrl });
|
|
632
|
+
const store = (0, vault_unlock_1.storeVaultUnlockSecret)({
|
|
633
|
+
agentName: command.agent,
|
|
634
|
+
email,
|
|
635
|
+
serverUrl,
|
|
636
|
+
}, unlockSecret, { homeDir: deps.homeDir, store: command.store });
|
|
637
|
+
(0, credential_access_1.resetCredentialStore)();
|
|
638
|
+
await (0, credential_access_1.getCredentialStore)(command.agent).get("__ouro_vault_probe__");
|
|
639
|
+
const message = [
|
|
640
|
+
`vault created for ${command.agent}`,
|
|
641
|
+
`vault: ${email} at ${serverUrl}`,
|
|
642
|
+
`local unlock store: ${store.kind}${store.secure ? "" : " (explicit plaintext fallback)"}`,
|
|
643
|
+
"Provider credentials will be stored in this agent's Ouro credential vault.",
|
|
644
|
+
...(command.generateUnlockSecret
|
|
645
|
+
? [
|
|
646
|
+
"",
|
|
647
|
+
`vault unlock secret: ${unlockSecret}`,
|
|
648
|
+
"",
|
|
649
|
+
"Store this in the operator password manager now. Another machine cannot unlock the vault without it.",
|
|
650
|
+
]
|
|
651
|
+
: ["Store the vault unlock secret in the operator password manager. Another machine will need it once."]),
|
|
652
|
+
].join("\n");
|
|
653
|
+
deps.writeStdout(message);
|
|
654
|
+
return message;
|
|
655
|
+
}
|
|
656
|
+
async function executeVaultStatus(command, deps) {
|
|
657
|
+
if (command.agent === "SerpentGuide") {
|
|
658
|
+
const message = "SerpentGuide has no persistent credential vault. Hatch bootstrap uses selected provider credentials in memory only.";
|
|
659
|
+
deps.writeStdout(message);
|
|
660
|
+
return message;
|
|
661
|
+
}
|
|
662
|
+
const { config } = (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot);
|
|
663
|
+
const vault = (0, identity_1.resolveVaultConfig)(command.agent, config.vault);
|
|
664
|
+
const status = (0, vault_unlock_1.getVaultUnlockStatus)({
|
|
665
|
+
agentName: command.agent,
|
|
666
|
+
email: vault.email,
|
|
667
|
+
serverUrl: vault.serverUrl,
|
|
668
|
+
}, { homeDir: deps.homeDir, store: command.store });
|
|
669
|
+
const lines = [
|
|
670
|
+
`agent: ${command.agent}`,
|
|
671
|
+
`vault: ${vault.email} at ${vault.serverUrl}`,
|
|
672
|
+
`local unlock store: ${status.store ? `${status.store.kind}${status.store.secure ? "" : " (explicit plaintext fallback)"}` : "unavailable"}`,
|
|
673
|
+
`local unlock: ${status.stored ? "available" : "missing"}`,
|
|
674
|
+
];
|
|
675
|
+
if (status.stored) {
|
|
676
|
+
const pool = await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
|
|
677
|
+
if (pool.ok) {
|
|
678
|
+
const summary = (0, provider_credentials_1.summarizeProviderCredentialPool)(pool.pool);
|
|
679
|
+
lines.push(`provider credentials: ${summary.providers.length === 0 ? "none stored" : ""}`);
|
|
680
|
+
for (const provider of summary.providers) {
|
|
681
|
+
lines.push(` ${provider.provider}: credential fields ${provider.credentialFields.join(", ") || "none"}, config fields ${provider.configFields.join(", ") || "none"}`);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
lines.push(`provider credentials: unavailable (${pool.error})`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
lines.push("");
|
|
690
|
+
lines.push(status.fix);
|
|
691
|
+
}
|
|
692
|
+
const message = lines.join("\n");
|
|
693
|
+
deps.writeStdout(message);
|
|
694
|
+
return message;
|
|
695
|
+
}
|
|
556
696
|
function readOrBootstrapProviderState(agentName, deps) {
|
|
557
697
|
const agentRoot = providerCliAgentRoot({ agent: agentName }, deps);
|
|
558
698
|
const readResult = (0, provider_state_1.readProviderState)(agentRoot);
|
|
@@ -603,56 +743,15 @@ function pingAttemptCount(result) {
|
|
|
603
743
|
return result.attempts.length;
|
|
604
744
|
return undefined;
|
|
605
745
|
}
|
|
606
|
-
function
|
|
607
|
-
|
|
608
|
-
const legacyCandidates = (0, provider_credential_pool_1.readLegacyAgentProviderCredentials)({
|
|
609
|
-
homeDir: providerCliHomeDir(deps),
|
|
610
|
-
agentName: agent,
|
|
611
|
-
});
|
|
612
|
-
const candidate = legacyCandidates.find((entry) => entry.provider === provider);
|
|
613
|
-
if (candidate)
|
|
614
|
-
return { credentials: candidate.credentials, config: candidate.config };
|
|
615
|
-
}
|
|
616
|
-
catch {
|
|
617
|
-
// Fall through to the injected/secretsRoot-aware legacy reader below.
|
|
618
|
-
}
|
|
619
|
-
try {
|
|
620
|
-
const { secrets } = (0, auth_flow_1.loadAgentSecrets)(agent, { secretsRoot: deps.secretsRoot });
|
|
621
|
-
const providerSecrets = secrets.providers[provider];
|
|
622
|
-
const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(provider, providerSecrets);
|
|
623
|
-
if (Object.keys(split.credentials).length === 0 && Object.keys(split.config).length === 0)
|
|
624
|
-
return null;
|
|
625
|
-
return split;
|
|
626
|
-
}
|
|
627
|
-
catch {
|
|
628
|
-
return null;
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
function readProviderCredentialRecord(agent, provider, deps) {
|
|
632
|
-
const homeDir = providerCliHomeDir(deps);
|
|
633
|
-
const poolResult = (0, provider_credential_pool_1.readProviderCredentialPool)(homeDir);
|
|
746
|
+
async function readProviderCredentialRecord(agent, provider, _deps) {
|
|
747
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agent);
|
|
634
748
|
if (poolResult.ok) {
|
|
635
749
|
const existing = poolResult.pool.providers[provider];
|
|
636
750
|
if (existing)
|
|
637
751
|
return { ok: true, record: existing };
|
|
638
752
|
}
|
|
639
|
-
else if (poolResult.reason === "invalid") {
|
|
640
|
-
return { ok: false, reason:
|
|
641
|
-
}
|
|
642
|
-
const legacy = providerCliLegacyRecord(agent, provider, deps);
|
|
643
|
-
if (legacy) {
|
|
644
|
-
const record = (0, provider_credential_pool_1.upsertProviderCredential)({
|
|
645
|
-
homeDir,
|
|
646
|
-
provider,
|
|
647
|
-
credentials: legacy.credentials,
|
|
648
|
-
config: legacy.config,
|
|
649
|
-
provenance: {
|
|
650
|
-
source: "legacy-agent-secrets",
|
|
651
|
-
contributedByAgent: agent,
|
|
652
|
-
},
|
|
653
|
-
now: providerCliNow(deps),
|
|
654
|
-
});
|
|
655
|
-
return { ok: true, record };
|
|
753
|
+
else if (poolResult.reason === "invalid" || poolResult.reason === "unavailable") {
|
|
754
|
+
return { ok: false, reason: poolResult.reason, poolPath: poolResult.poolPath, error: poolResult.error };
|
|
656
755
|
}
|
|
657
756
|
return {
|
|
658
757
|
ok: false,
|
|
@@ -702,7 +801,7 @@ async function executeProviderUse(command, deps, options = {}) {
|
|
|
702
801
|
return message;
|
|
703
802
|
};
|
|
704
803
|
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
705
|
-
const credential = readProviderCredentialRecord(command.agent, command.provider, deps);
|
|
804
|
+
const credential = await readProviderCredentialRecord(command.agent, command.provider, deps);
|
|
706
805
|
if (!credential.ok) {
|
|
707
806
|
if (!command.force) {
|
|
708
807
|
const message = [
|
|
@@ -762,7 +861,7 @@ async function executeProviderUse(command, deps, options = {}) {
|
|
|
762
861
|
async function executeProviderCheck(command, deps) {
|
|
763
862
|
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
764
863
|
const binding = state.lanes[command.lane];
|
|
765
|
-
const credential = readProviderCredentialRecord(command.agent, binding.provider, deps);
|
|
864
|
+
const credential = await readProviderCredentialRecord(command.agent, binding.provider, deps);
|
|
766
865
|
if (!credential.ok) {
|
|
767
866
|
const message = [
|
|
768
867
|
`${command.agent} ${command.lane} ${binding.provider} / ${binding.model}: unknown (${credential.error})`,
|
|
@@ -802,18 +901,18 @@ async function executeProviderCheck(command, deps) {
|
|
|
802
901
|
}
|
|
803
902
|
function renderProviderCredentialLine(credential) {
|
|
804
903
|
if (credential.status === "present") {
|
|
805
|
-
const contributor = credential.contributedByAgent ? ` by ${credential.contributedByAgent}` : "";
|
|
806
904
|
const credentialFields = credential.credentialFields.length > 0 ? ` credentials: ${credential.credentialFields.join(", ")}` : " credentials: none";
|
|
807
905
|
const configFields = credential.configFields.length > 0 ? ` config: ${credential.configFields.join(", ")}` : " config: none";
|
|
808
|
-
return `credentials: present (${credential.source}
|
|
906
|
+
return `credentials: present in vault (${credential.source}; ${credential.revision};${credentialFields};${configFields})`;
|
|
809
907
|
}
|
|
810
908
|
if (credential.status === "invalid-pool") {
|
|
811
|
-
return `credentials:
|
|
909
|
+
return `credentials: vault unavailable (${credential.error}); repair: ${credential.repair.command}`;
|
|
812
910
|
}
|
|
813
911
|
return `credentials: missing; repair: ${credential.repair.command}`;
|
|
814
912
|
}
|
|
815
|
-
function executeProviderStatus(command, deps) {
|
|
913
|
+
async function executeProviderStatus(command, deps) {
|
|
816
914
|
const agentRoot = providerCliAgentRoot(command, deps);
|
|
915
|
+
await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
|
|
817
916
|
const homeDir = providerCliHomeDir(deps);
|
|
818
917
|
const lines = [`provider status: ${command.agent}`];
|
|
819
918
|
for (const lane of ["outward", "inner"]) {
|
|
@@ -840,6 +939,45 @@ function executeProviderStatus(command, deps) {
|
|
|
840
939
|
deps.writeStdout(message);
|
|
841
940
|
return message;
|
|
842
941
|
}
|
|
942
|
+
async function executeProviderRefresh(command, deps) {
|
|
943
|
+
if (command.agent === "SerpentGuide") {
|
|
944
|
+
const message = "SerpentGuide has no persistent provider credentials to refresh. Hatch bootstrap uses selected credentials in memory only.";
|
|
945
|
+
deps.writeStdout(message);
|
|
946
|
+
return message;
|
|
947
|
+
}
|
|
948
|
+
const pool = await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
|
|
949
|
+
const lines = [];
|
|
950
|
+
if (pool.ok) {
|
|
951
|
+
const summary = (0, provider_credentials_1.summarizeProviderCredentialPool)(pool.pool);
|
|
952
|
+
lines.push(`refreshed provider credential snapshot for ${command.agent}`);
|
|
953
|
+
lines.push(`providers: ${summary.providers.map((provider) => provider.provider).join(", ") || "none"}`);
|
|
954
|
+
}
|
|
955
|
+
else {
|
|
956
|
+
lines.push(`provider credential refresh failed for ${command.agent}: ${pool.error}`);
|
|
957
|
+
lines.push(`Run \`ouro vault unlock --agent ${command.agent}\`, then retry.`);
|
|
958
|
+
}
|
|
959
|
+
try {
|
|
960
|
+
const alive = await deps.checkSocketAlive(deps.socketPath);
|
|
961
|
+
if (alive) {
|
|
962
|
+
const response = await deps.sendCommand(deps.socketPath, { kind: "agent.restart", agent: command.agent });
|
|
963
|
+
if (response.ok) {
|
|
964
|
+
lines.push(`restarted ${command.agent} so the running agent reloads credentials`);
|
|
965
|
+
}
|
|
966
|
+
else {
|
|
967
|
+
lines.push(`daemon restart skipped: ${response.error ?? response.message ?? "unknown daemon error"}`);
|
|
968
|
+
}
|
|
969
|
+
}
|
|
970
|
+
else {
|
|
971
|
+
lines.push("daemon is not running; the next start will load the refreshed snapshot");
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
catch (error) {
|
|
975
|
+
lines.push(`daemon restart skipped: ${error instanceof Error ? error.message : String(error)}`);
|
|
976
|
+
}
|
|
977
|
+
const message = lines.join("\n");
|
|
978
|
+
deps.writeStdout(message);
|
|
979
|
+
return message;
|
|
980
|
+
}
|
|
843
981
|
async function executeLegacyAuthSwitch(command, deps) {
|
|
844
982
|
const { state } = readOrBootstrapProviderState(command.agent, deps);
|
|
845
983
|
const lanes = command.facing
|
|
@@ -854,6 +992,7 @@ async function executeLegacyAuthSwitch(command, deps) {
|
|
|
854
992
|
lane,
|
|
855
993
|
provider: command.provider,
|
|
856
994
|
model,
|
|
995
|
+
force: true,
|
|
857
996
|
legacyFacing: command.facing,
|
|
858
997
|
}, deps, { writeStdout: false }));
|
|
859
998
|
}
|
|
@@ -870,7 +1009,7 @@ async function executeLegacyConfigModel(command, deps) {
|
|
|
870
1009
|
const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
|
|
871
1010
|
const binding = state.lanes[lane];
|
|
872
1011
|
if (binding.provider === "github-copilot") {
|
|
873
|
-
const credential = readProviderCredentialRecord(command.agent, "github-copilot", deps);
|
|
1012
|
+
const credential = await readProviderCredentialRecord(command.agent, "github-copilot", deps);
|
|
874
1013
|
if (credential.ok) {
|
|
875
1014
|
const ghConfig = {
|
|
876
1015
|
...credential.record.config,
|
|
@@ -1353,10 +1492,15 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1353
1492
|
message: "user chose clone in first-run flow",
|
|
1354
1493
|
meta: {},
|
|
1355
1494
|
});
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1495
|
+
/* v8 ignore next -- ?? fallback: promptInput always returns string in practice @preserve */
|
|
1496
|
+
const remote = (await deps.promptInput("Enter the git remote URL for the agent bundle: "))?.trim() ?? "";
|
|
1497
|
+
if (!remote) {
|
|
1498
|
+
deps.writeStdout("no remote URL provided — skipping clone");
|
|
1499
|
+
// Fall through to hatch flow
|
|
1500
|
+
}
|
|
1501
|
+
else {
|
|
1502
|
+
return await runOuroCli(["clone", remote], deps);
|
|
1503
|
+
}
|
|
1360
1504
|
}
|
|
1361
1505
|
(0, runtime_1.emitNervesEvent)({
|
|
1362
1506
|
component: "daemon",
|
|
@@ -1635,15 +1779,12 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
1635
1779
|
else {
|
|
1636
1780
|
await (0, agentic_repair_1.runAgenticRepair)(daemonResult.stability.degraded, {
|
|
1637
1781
|
/* v8 ignore start -- production provider discovery wiring @preserve */
|
|
1638
|
-
discoverWorkingProvider: async () => {
|
|
1782
|
+
discoverWorkingProvider: async (agentName) => {
|
|
1639
1783
|
const { discoverWorkingProvider: discover } = await Promise.resolve().then(() => __importStar(require("./provider-discovery")));
|
|
1640
|
-
const { discoverExistingCredentials } = await Promise.resolve().then(() => __importStar(require("./cli-defaults")));
|
|
1641
1784
|
const { pingProvider } = await Promise.resolve().then(() => __importStar(require("../provider-ping")));
|
|
1642
1785
|
return discover({
|
|
1643
|
-
|
|
1786
|
+
agentName,
|
|
1644
1787
|
pingProvider: pingProvider,
|
|
1645
|
-
env: process.env,
|
|
1646
|
-
secretsRoot: deps.secretsRoot ?? `${process.env["HOME"]}/.agentsecrets`,
|
|
1647
1788
|
});
|
|
1648
1789
|
},
|
|
1649
1790
|
createProviderRuntime: agentic_repair_1.createAgenticDiagnosisProviderRuntime,
|
|
@@ -2263,6 +2404,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2263
2404
|
if (command.kind === "provider.status") {
|
|
2264
2405
|
return executeProviderStatus(command, deps);
|
|
2265
2406
|
}
|
|
2407
|
+
if (command.kind === "provider.refresh") {
|
|
2408
|
+
return executeProviderRefresh(command, deps);
|
|
2409
|
+
}
|
|
2410
|
+
if (command.kind === "vault.unlock") {
|
|
2411
|
+
return executeVaultUnlock(command, deps);
|
|
2412
|
+
}
|
|
2413
|
+
if (command.kind === "vault.create") {
|
|
2414
|
+
return executeVaultCreate(command, deps);
|
|
2415
|
+
}
|
|
2416
|
+
if (command.kind === "vault.status") {
|
|
2417
|
+
return executeVaultStatus(command, deps);
|
|
2418
|
+
}
|
|
2266
2419
|
// ── auth (local, no daemon socket needed) ──
|
|
2267
2420
|
if (command.kind === "auth.run") {
|
|
2268
2421
|
const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot).config.humanFacing.provider;
|
|
@@ -2273,29 +2426,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2273
2426
|
provider,
|
|
2274
2427
|
promptInput: deps.promptInput,
|
|
2275
2428
|
});
|
|
2276
|
-
const credentials = (result.credentials ?? {});
|
|
2277
|
-
const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(provider, credentials);
|
|
2278
|
-
if (Object.keys(split.credentials).length > 0 || Object.keys(split.config).length > 0) {
|
|
2279
|
-
(0, provider_credential_pool_1.upsertProviderCredential)({
|
|
2280
|
-
homeDir: providerCliHomeDir(deps),
|
|
2281
|
-
provider,
|
|
2282
|
-
credentials: split.credentials,
|
|
2283
|
-
config: split.config,
|
|
2284
|
-
provenance: {
|
|
2285
|
-
source: "auth-flow",
|
|
2286
|
-
contributedByAgent: command.agent,
|
|
2287
|
-
},
|
|
2288
|
-
now: providerCliNow(deps),
|
|
2289
|
-
});
|
|
2290
|
-
}
|
|
2291
2429
|
// Behavior: ouro auth stores credentials only — does NOT switch provider.
|
|
2292
2430
|
// Use `ouro auth switch` to change the active provider.
|
|
2293
2431
|
deps.writeStdout(result.message);
|
|
2294
2432
|
// Verify the credentials actually work by pinging the provider
|
|
2295
2433
|
/* v8 ignore start -- integration: real API ping after auth @preserve */
|
|
2296
2434
|
try {
|
|
2297
|
-
const
|
|
2298
|
-
const status =
|
|
2435
|
+
const credential = await readProviderCredentialRecord(command.agent, provider, deps);
|
|
2436
|
+
const status = credential.ok
|
|
2437
|
+
? await verifyProviderCredentials(provider, {
|
|
2438
|
+
[provider]: { ...credential.record.config, ...credential.record.credentials },
|
|
2439
|
+
})
|
|
2440
|
+
: `stored but could not be re-read from vault (${credential.error})`;
|
|
2299
2441
|
deps.writeStdout(`${provider}: ${status}`);
|
|
2300
2442
|
}
|
|
2301
2443
|
catch {
|
|
@@ -2307,19 +2449,35 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2307
2449
|
// ── auth verify (local, no daemon socket needed) ──
|
|
2308
2450
|
/* v8 ignore start -- auth verify/switch: tested in daemon-cli.test.ts but v8 traces differ in CI @preserve */
|
|
2309
2451
|
if (command.kind === "auth.verify") {
|
|
2310
|
-
const
|
|
2311
|
-
|
|
2452
|
+
const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
|
|
2453
|
+
if (!poolResult.ok) {
|
|
2454
|
+
const message = `vault unavailable: ${poolResult.error}\nRun \`ouro vault unlock --agent ${command.agent}\`, then retry.`;
|
|
2455
|
+
deps.writeStdout(message);
|
|
2456
|
+
return message;
|
|
2457
|
+
}
|
|
2312
2458
|
if (command.provider) {
|
|
2313
|
-
const
|
|
2459
|
+
const record = poolResult.pool.providers[command.provider];
|
|
2460
|
+
if (!record) {
|
|
2461
|
+
const message = `${command.provider}: missing. Run \`ouro auth --agent ${command.agent} --provider ${command.provider}\`.`;
|
|
2462
|
+
deps.writeStdout(message);
|
|
2463
|
+
return message;
|
|
2464
|
+
}
|
|
2465
|
+
const status = await verifyProviderCredentials(command.provider, {
|
|
2466
|
+
[command.provider]: { ...record.config, ...record.credentials },
|
|
2467
|
+
});
|
|
2314
2468
|
const message = `${command.provider}: ${status}`;
|
|
2315
2469
|
deps.writeStdout(message);
|
|
2316
2470
|
return message;
|
|
2317
2471
|
}
|
|
2318
2472
|
const lines = [];
|
|
2319
|
-
for (const p of Object.
|
|
2320
|
-
const status = await verifyProviderCredentials(p,
|
|
2473
|
+
for (const [p, record] of Object.entries(poolResult.pool.providers)) {
|
|
2474
|
+
const status = await verifyProviderCredentials(p, {
|
|
2475
|
+
[p]: { ...record.config, ...record.credentials },
|
|
2476
|
+
});
|
|
2321
2477
|
lines.push(`${p}: ${status}`);
|
|
2322
2478
|
}
|
|
2479
|
+
if (lines.length === 0)
|
|
2480
|
+
lines.push(`no provider credentials in ${command.agent}'s vault`);
|
|
2323
2481
|
const message = lines.join("\n");
|
|
2324
2482
|
deps.writeStdout(message);
|
|
2325
2483
|
return message;
|
|
@@ -2339,9 +2497,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2339
2497
|
deps.writeStdout(message);
|
|
2340
2498
|
return message;
|
|
2341
2499
|
}
|
|
2342
|
-
const
|
|
2343
|
-
const ghConfig =
|
|
2344
|
-
|
|
2500
|
+
const credential = await readProviderCredentialRecord(command.agent, "github-copilot", deps);
|
|
2501
|
+
const ghConfig = credential.ok
|
|
2502
|
+
? { ...credential.record.config, ...credential.record.credentials }
|
|
2503
|
+
: {};
|
|
2504
|
+
if (typeof ghConfig.githubToken !== "string" || typeof ghConfig.baseUrl !== "string") {
|
|
2345
2505
|
throw new Error(`github-copilot credentials not configured. Run \`ouro auth --agent ${command.agent} --provider github-copilot\` first.`);
|
|
2346
2506
|
}
|
|
2347
2507
|
const fetchFn = deps.fetchImpl ?? fetch;
|
|
@@ -2722,7 +2882,10 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2722
2882
|
await deps.startChat(hatchInput.agentName);
|
|
2723
2883
|
return "";
|
|
2724
2884
|
}
|
|
2725
|
-
const
|
|
2885
|
+
const vaultLine = result.vaultUnlockSecret
|
|
2886
|
+
? `\nvault unlock secret for ${hatchInput.agentName}: ${result.vaultUnlockSecret}\nUse this with \`ouro vault unlock --agent ${hatchInput.agentName}\` on another machine.`
|
|
2887
|
+
: "";
|
|
2888
|
+
const message = `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${daemonResult.message}${vaultLine}`;
|
|
2726
2889
|
deps.writeStdout(message);
|
|
2727
2890
|
return message;
|
|
2728
2891
|
}
|
|
@@ -2787,10 +2950,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2787
2950
|
try {
|
|
2788
2951
|
(0, child_process_1.execFileSync)("git", ["ls-remote", "--exit-code", command.remote], { stdio: "pipe", timeout: 15000 });
|
|
2789
2952
|
}
|
|
2790
|
-
catch {
|
|
2791
|
-
const
|
|
2792
|
-
|
|
2793
|
-
|
|
2953
|
+
catch (lsErr) {
|
|
2954
|
+
const stderr = lsErr?.stderr?.toString() ?? "";
|
|
2955
|
+
const isAuth = stderr.includes("Authentication failed")
|
|
2956
|
+
|| stderr.includes("could not read Username")
|
|
2957
|
+
|| stderr.includes("terminal prompts disabled")
|
|
2958
|
+
|| stderr.includes("403")
|
|
2959
|
+
|| stderr.includes("401");
|
|
2960
|
+
const hint = isAuth
|
|
2961
|
+
? `authentication failed for: ${command.remote}\nSet up credentials first:\n gh auth login (GitHub repos)\n git config credential.helper store (other hosts)`
|
|
2962
|
+
: `could not reach remote: ${command.remote}\nCheck the URL and your network connection.`;
|
|
2963
|
+
deps.writeStdout(hint);
|
|
2964
|
+
return hint;
|
|
2794
2965
|
}
|
|
2795
2966
|
// 5. Clone
|
|
2796
2967
|
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_git_clone", message: "cloning agent bundle", meta: { remote: command.remote, targetPath } });
|
|
@@ -2800,18 +2971,79 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
|
|
|
2800
2971
|
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_identity_created", message: "machine identity created", meta: {} });
|
|
2801
2972
|
// 7. Enable sync in agent.json
|
|
2802
2973
|
const agentJsonPath = path.join(targetPath, "agent.json");
|
|
2974
|
+
let syncEnabled = false;
|
|
2803
2975
|
if (fs.existsSync(agentJsonPath)) {
|
|
2804
2976
|
const raw = fs.readFileSync(agentJsonPath, "utf-8");
|
|
2805
2977
|
const config = JSON.parse(raw);
|
|
2806
2978
|
config.sync = { enabled: true, remote: "origin" };
|
|
2807
2979
|
fs.writeFileSync(agentJsonPath, JSON.stringify(config, null, 2) + "\n");
|
|
2980
|
+
syncEnabled = true;
|
|
2808
2981
|
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_sync_enabled", message: "sync enabled in agent.json", meta: { agentName } });
|
|
2809
2982
|
}
|
|
2983
|
+
else {
|
|
2984
|
+
(0, runtime_1.emitNervesEvent)({ level: "warn", component: "daemon", event: "daemon.clone_no_agent_json", message: "cloned repo has no agent.json — may not be a valid bundle", meta: { agentName, targetPath } });
|
|
2985
|
+
}
|
|
2810
2986
|
// 8. Output success message
|
|
2811
2987
|
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_complete", message: "clone complete", meta: { agentName, targetPath } });
|
|
2812
|
-
const
|
|
2813
|
-
deps.writeStdout(
|
|
2814
|
-
|
|
2988
|
+
const syncMsg = syncEnabled ? "\nsync enabled (remote: origin)" : "\nwarning: no agent.json found — this may not be a valid agent bundle";
|
|
2989
|
+
deps.writeStdout(`cloned ${agentName} to ${targetPath}${syncMsg}`);
|
|
2990
|
+
// 9. Guided post-clone flow (when interactive)
|
|
2991
|
+
if (deps.promptInput) {
|
|
2992
|
+
// Auth
|
|
2993
|
+
const authAnswer = await deps.promptInput(`\nSet up provider auth now? (y/n): `) ?? "";
|
|
2994
|
+
if (authAnswer.trim().toLowerCase() === "y") {
|
|
2995
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_chain_auth", message: "chaining auth from clone flow", meta: { agentName } });
|
|
2996
|
+
try {
|
|
2997
|
+
await runOuroCli(["auth", "--agent", agentName], deps);
|
|
2998
|
+
/* v8 ignore start -- chained command failures: tested via interactive clone test, catch branches are defensive @preserve */
|
|
2999
|
+
}
|
|
3000
|
+
catch (e) {
|
|
3001
|
+
deps.writeStdout(`auth setup failed: ${e instanceof Error ? e.message : String(e)}\nYou can retry later with: ouro auth run --agent ${agentName}`);
|
|
3002
|
+
}
|
|
3003
|
+
/* v8 ignore stop */
|
|
3004
|
+
}
|
|
3005
|
+
// Daemon
|
|
3006
|
+
const upAnswer = await deps.promptInput(`\nStart the daemon now? (y/n): `) ?? "";
|
|
3007
|
+
if (upAnswer.trim().toLowerCase() === "y") {
|
|
3008
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_chain_up", message: "chaining daemon start from clone flow", meta: { agentName } });
|
|
3009
|
+
try {
|
|
3010
|
+
await runOuroCli(["up"], deps);
|
|
3011
|
+
/* v8 ignore start -- chained command failures: defensive catch @preserve */
|
|
3012
|
+
}
|
|
3013
|
+
catch (e) {
|
|
3014
|
+
deps.writeStdout(`daemon start failed: ${e instanceof Error ? e.message : String(e)}\nYou can retry later with: ouro up`);
|
|
3015
|
+
}
|
|
3016
|
+
/* v8 ignore stop */
|
|
3017
|
+
}
|
|
3018
|
+
// Dev tool setup
|
|
3019
|
+
const setupAnswer = await deps.promptInput(`\nSet up Claude Code integration? (y/n): `) ?? "";
|
|
3020
|
+
if (setupAnswer.trim().toLowerCase() === "y") {
|
|
3021
|
+
(0, runtime_1.emitNervesEvent)({ component: "daemon", event: "daemon.clone_chain_setup", message: "chaining dev tool setup from clone flow", meta: { agentName } });
|
|
3022
|
+
try {
|
|
3023
|
+
await runOuroCli(["setup", "--tool", "claude-code", "--agent", agentName], deps);
|
|
3024
|
+
/* v8 ignore start -- chained command failures: defensive catch @preserve */
|
|
3025
|
+
}
|
|
3026
|
+
catch (e) {
|
|
3027
|
+
deps.writeStdout(`dev tool setup failed: ${e instanceof Error ? e.message : String(e)}\nYou can retry later with: ouro setup --tool claude-code --agent ${agentName}`);
|
|
3028
|
+
}
|
|
3029
|
+
/* v8 ignore stop */
|
|
3030
|
+
}
|
|
3031
|
+
}
|
|
3032
|
+
else {
|
|
3033
|
+
deps.writeStdout(`\nnext steps:\n ouro auth run --agent ${agentName}\n ouro up\n ouro setup --tool claude-code --agent ${agentName}`);
|
|
3034
|
+
}
|
|
3035
|
+
/* v8 ignore start -- PATH hint: only fires inside npx, not testable in vitest @preserve */
|
|
3036
|
+
if (process.env.npm_execpath) {
|
|
3037
|
+
const shell = process.env.SHELL ? path.basename(process.env.SHELL) : "";
|
|
3038
|
+
const bashProfile = process.platform === "darwin" ? "~/.bash_profile" : "~/.bashrc";
|
|
3039
|
+
const sourceCmd = shell === "zsh" ? "source ~/.zshrc"
|
|
3040
|
+
: shell === "bash" ? `source ${bashProfile}`
|
|
3041
|
+
: shell === "fish" ? "source ~/.config/fish/config.fish"
|
|
3042
|
+
: "restart your shell";
|
|
3043
|
+
deps.writeStdout(`\ntip: if 'ouro' is not found, run: ${sourceCmd}`);
|
|
3044
|
+
}
|
|
3045
|
+
/* v8 ignore stop */
|
|
3046
|
+
return `clone complete: ${agentName}`;
|
|
2815
3047
|
}
|
|
2816
3048
|
const daemonCommand = toDaemonCommand(command);
|
|
2817
3049
|
let response;
|