@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.
Files changed (44) hide show
  1. package/README.md +16 -7
  2. package/changelog.json +17 -0
  3. package/dist/heart/auth/auth-flow.js +25 -110
  4. package/dist/heart/config.js +69 -55
  5. package/dist/heart/core.js +83 -33
  6. package/dist/heart/daemon/agent-config-check.js +41 -238
  7. package/dist/heart/daemon/agentic-repair.js +1 -1
  8. package/dist/heart/daemon/cli-defaults.js +15 -68
  9. package/dist/heart/daemon/cli-exec.js +334 -102
  10. package/dist/heart/daemon/cli-parse.js +71 -0
  11. package/dist/heart/daemon/daemon-cli.js +1 -2
  12. package/dist/heart/daemon/daemon-entry.js +1 -3
  13. package/dist/heart/daemon/doctor.js +9 -29
  14. package/dist/heart/daemon/provider-discovery.js +32 -59
  15. package/dist/heart/hatch/hatch-flow.js +9 -12
  16. package/dist/heart/hatch/specialist-prompt.js +1 -1
  17. package/dist/heart/hatch/specialist-tools.js +21 -1
  18. package/dist/heart/migrate-config.js +15 -42
  19. package/dist/heart/provider-binding-resolver.js +6 -7
  20. package/dist/heart/provider-credentials.js +379 -0
  21. package/dist/heart/provider-failover.js +3 -11
  22. package/dist/heart/provider-ping.js +13 -3
  23. package/dist/heart/provider-state.js +8 -0
  24. package/dist/heart/provider-visibility.js +3 -6
  25. package/dist/heart/providers/anthropic-token.js +15 -47
  26. package/dist/heart/providers/anthropic.js +4 -9
  27. package/dist/heart/providers/azure.js +3 -3
  28. package/dist/heart/providers/github-copilot.js +2 -2
  29. package/dist/heart/providers/minimax-vlm.js +2 -2
  30. package/dist/heart/providers/minimax.js +1 -1
  31. package/dist/heart/providers/openai-codex.js +4 -9
  32. package/dist/heart/versioning/ouro-path-installer.js +10 -5
  33. package/dist/mind/prompt.js +1 -1
  34. package/dist/repertoire/bitwarden-store.js +63 -17
  35. package/dist/repertoire/bundle-templates.js +2 -2
  36. package/dist/repertoire/credential-access.js +47 -467
  37. package/dist/repertoire/tools-attachments.js +5 -4
  38. package/dist/repertoire/tools-vault.js +10 -80
  39. package/dist/repertoire/vault-unlock.js +359 -0
  40. package/dist/senses/bluebubbles/client.js +39 -4
  41. package/dist/senses/pipeline.js +0 -1
  42. package/package.json +1 -1
  43. package/skills/configure-dev-tools.md +10 -0
  44. 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 provider_credential_pool_1 = require("../provider-credential-pool");
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, secretsRoot);
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 secretsRoot = deps.secretsRoot ?? path.join(os.homedir(), ".agentsecrets");
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
- const answer = await deps.promptInput(`Bundle ${agentDir} appears to be a git clone with a remote. Enable sync? (y/n): `);
496
- if (answer.trim().toLowerCase() === "y") {
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, provider_credential_pool_1.providerCredentialHomeDirFromSecretsRoot)(deps.secretsRoot);
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 providerCliLegacyRecord(agent, provider, deps) {
607
- try {
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: "invalid", poolPath: poolResult.poolPath, error: poolResult.error };
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}${contributor}; ${credential.revision};${credentialFields};${configFields})`;
906
+ return `credentials: present in vault (${credential.source}; ${credential.revision};${credentialFields};${configFields})`;
809
907
  }
810
908
  if (credential.status === "invalid-pool") {
811
- return `credentials: invalid pool (${credential.error}); repair: ${credential.repair.command}`;
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
- const remote = await deps.promptInput("Enter the git remote URL for the agent bundle: ");
1357
- // Run clone execution path
1358
- const cloneCommand = { kind: "clone", remote: remote.trim() };
1359
- return await runOuroCli(["clone", cloneCommand.remote], deps);
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
- discoverExistingCredentials,
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 { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
2298
- const status = await verifyProviderCredentials(provider, secrets.providers);
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 { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
2311
- const providers = secrets.providers;
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 status = await verifyProviderCredentials(command.provider, providers);
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.keys(providers)) {
2320
- const status = await verifyProviderCredentials(p, providers);
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 { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
2343
- const ghConfig = secrets.providers["github-copilot"];
2344
- if (!ghConfig.githubToken || !ghConfig.baseUrl) {
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 message = `hatched ${hatchInput.agentName} at ${result.bundleRoot} using specialist identity ${result.selectedIdentity}; ${daemonResult.message}`;
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 message = `could not reach remote: ${command.remote}\nCheck the URL and your network connection.`;
2792
- deps.writeStdout(message);
2793
- return message;
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 message = `cloned ${agentName} to ${targetPath}\nsync enabled (remote: origin)\nnext steps:\n ouro auth run --agent ${agentName}`;
2813
- deps.writeStdout(message);
2814
- return message;
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;