@ouro.bot/cli 0.1.0-alpha.364 → 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 (41) hide show
  1. package/README.md +15 -6
  2. package/changelog.json +9 -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 +246 -89
  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/repertoire/bitwarden-store.js +63 -17
  33. package/dist/repertoire/bundle-templates.js +2 -2
  34. package/dist/repertoire/credential-access.js +47 -467
  35. package/dist/repertoire/tools-attachments.js +5 -4
  36. package/dist/repertoire/tools-vault.js +10 -80
  37. package/dist/repertoire/vault-unlock.js +359 -0
  38. package/dist/senses/bluebubbles/client.js +39 -4
  39. package/dist/senses/pipeline.js +0 -1
  40. package/package.json +1 -1
  41. 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);
@@ -546,7 +547,7 @@ async function resolveHatchInput(command, deps) {
546
547
  }
547
548
  // ── Provider state CLI helpers ──
548
549
  function providerCliHomeDir(deps) {
549
- return (0, provider_credential_pool_1.providerCredentialHomeDirFromSecretsRoot)(deps.secretsRoot);
550
+ return (0, provider_credentials_1.providerCredentialMachineHomeDir)(deps.homeDir);
550
551
  }
551
552
  function providerCliAgentRoot(command, deps) {
552
553
  return path.join(deps.bundlesRoot ?? (0, identity_1.getAgentBundlesRoot)(), `${command.agent}.ouro`);
@@ -554,6 +555,144 @@ function providerCliAgentRoot(command, deps) {
554
555
  function providerCliNow(deps) {
555
556
  return new Date((deps.now ?? Date.now)());
556
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
+ }
557
696
  function readOrBootstrapProviderState(agentName, deps) {
558
697
  const agentRoot = providerCliAgentRoot({ agent: agentName }, deps);
559
698
  const readResult = (0, provider_state_1.readProviderState)(agentRoot);
@@ -604,56 +743,15 @@ function pingAttemptCount(result) {
604
743
  return result.attempts.length;
605
744
  return undefined;
606
745
  }
607
- function providerCliLegacyRecord(agent, provider, deps) {
608
- try {
609
- const legacyCandidates = (0, provider_credential_pool_1.readLegacyAgentProviderCredentials)({
610
- homeDir: providerCliHomeDir(deps),
611
- agentName: agent,
612
- });
613
- const candidate = legacyCandidates.find((entry) => entry.provider === provider);
614
- if (candidate)
615
- return { credentials: candidate.credentials, config: candidate.config };
616
- }
617
- catch {
618
- // Fall through to the injected/secretsRoot-aware legacy reader below.
619
- }
620
- try {
621
- const { secrets } = (0, auth_flow_1.loadAgentSecrets)(agent, { secretsRoot: deps.secretsRoot });
622
- const providerSecrets = secrets.providers[provider];
623
- const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(provider, providerSecrets);
624
- if (Object.keys(split.credentials).length === 0 && Object.keys(split.config).length === 0)
625
- return null;
626
- return split;
627
- }
628
- catch {
629
- return null;
630
- }
631
- }
632
- function readProviderCredentialRecord(agent, provider, deps) {
633
- const homeDir = providerCliHomeDir(deps);
634
- 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);
635
748
  if (poolResult.ok) {
636
749
  const existing = poolResult.pool.providers[provider];
637
750
  if (existing)
638
751
  return { ok: true, record: existing };
639
752
  }
640
- else if (poolResult.reason === "invalid") {
641
- return { ok: false, reason: "invalid", poolPath: poolResult.poolPath, error: poolResult.error };
642
- }
643
- const legacy = providerCliLegacyRecord(agent, provider, deps);
644
- if (legacy) {
645
- const record = (0, provider_credential_pool_1.upsertProviderCredential)({
646
- homeDir,
647
- provider,
648
- credentials: legacy.credentials,
649
- config: legacy.config,
650
- provenance: {
651
- source: "legacy-agent-secrets",
652
- contributedByAgent: agent,
653
- },
654
- now: providerCliNow(deps),
655
- });
656
- 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 };
657
755
  }
658
756
  return {
659
757
  ok: false,
@@ -703,7 +801,7 @@ async function executeProviderUse(command, deps, options = {}) {
703
801
  return message;
704
802
  };
705
803
  const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
706
- const credential = readProviderCredentialRecord(command.agent, command.provider, deps);
804
+ const credential = await readProviderCredentialRecord(command.agent, command.provider, deps);
707
805
  if (!credential.ok) {
708
806
  if (!command.force) {
709
807
  const message = [
@@ -763,7 +861,7 @@ async function executeProviderUse(command, deps, options = {}) {
763
861
  async function executeProviderCheck(command, deps) {
764
862
  const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
765
863
  const binding = state.lanes[command.lane];
766
- const credential = readProviderCredentialRecord(command.agent, binding.provider, deps);
864
+ const credential = await readProviderCredentialRecord(command.agent, binding.provider, deps);
767
865
  if (!credential.ok) {
768
866
  const message = [
769
867
  `${command.agent} ${command.lane} ${binding.provider} / ${binding.model}: unknown (${credential.error})`,
@@ -803,18 +901,18 @@ async function executeProviderCheck(command, deps) {
803
901
  }
804
902
  function renderProviderCredentialLine(credential) {
805
903
  if (credential.status === "present") {
806
- const contributor = credential.contributedByAgent ? ` by ${credential.contributedByAgent}` : "";
807
904
  const credentialFields = credential.credentialFields.length > 0 ? ` credentials: ${credential.credentialFields.join(", ")}` : " credentials: none";
808
905
  const configFields = credential.configFields.length > 0 ? ` config: ${credential.configFields.join(", ")}` : " config: none";
809
- return `credentials: present (${credential.source}${contributor}; ${credential.revision};${credentialFields};${configFields})`;
906
+ return `credentials: present in vault (${credential.source}; ${credential.revision};${credentialFields};${configFields})`;
810
907
  }
811
908
  if (credential.status === "invalid-pool") {
812
- return `credentials: invalid pool (${credential.error}); repair: ${credential.repair.command}`;
909
+ return `credentials: vault unavailable (${credential.error}); repair: ${credential.repair.command}`;
813
910
  }
814
911
  return `credentials: missing; repair: ${credential.repair.command}`;
815
912
  }
816
- function executeProviderStatus(command, deps) {
913
+ async function executeProviderStatus(command, deps) {
817
914
  const agentRoot = providerCliAgentRoot(command, deps);
915
+ await (0, provider_credentials_1.refreshProviderCredentialPool)(command.agent);
818
916
  const homeDir = providerCliHomeDir(deps);
819
917
  const lines = [`provider status: ${command.agent}`];
820
918
  for (const lane of ["outward", "inner"]) {
@@ -841,6 +939,45 @@ function executeProviderStatus(command, deps) {
841
939
  deps.writeStdout(message);
842
940
  return message;
843
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
+ }
844
981
  async function executeLegacyAuthSwitch(command, deps) {
845
982
  const { state } = readOrBootstrapProviderState(command.agent, deps);
846
983
  const lanes = command.facing
@@ -855,6 +992,7 @@ async function executeLegacyAuthSwitch(command, deps) {
855
992
  lane,
856
993
  provider: command.provider,
857
994
  model,
995
+ force: true,
858
996
  legacyFacing: command.facing,
859
997
  }, deps, { writeStdout: false }));
860
998
  }
@@ -871,7 +1009,7 @@ async function executeLegacyConfigModel(command, deps) {
871
1009
  const { agentRoot, state } = readOrBootstrapProviderState(command.agent, deps);
872
1010
  const binding = state.lanes[lane];
873
1011
  if (binding.provider === "github-copilot") {
874
- const credential = readProviderCredentialRecord(command.agent, "github-copilot", deps);
1012
+ const credential = await readProviderCredentialRecord(command.agent, "github-copilot", deps);
875
1013
  if (credential.ok) {
876
1014
  const ghConfig = {
877
1015
  ...credential.record.config,
@@ -1641,15 +1779,12 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
1641
1779
  else {
1642
1780
  await (0, agentic_repair_1.runAgenticRepair)(daemonResult.stability.degraded, {
1643
1781
  /* v8 ignore start -- production provider discovery wiring @preserve */
1644
- discoverWorkingProvider: async () => {
1782
+ discoverWorkingProvider: async (agentName) => {
1645
1783
  const { discoverWorkingProvider: discover } = await Promise.resolve().then(() => __importStar(require("./provider-discovery")));
1646
- const { discoverExistingCredentials } = await Promise.resolve().then(() => __importStar(require("./cli-defaults")));
1647
1784
  const { pingProvider } = await Promise.resolve().then(() => __importStar(require("../provider-ping")));
1648
1785
  return discover({
1649
- discoverExistingCredentials,
1786
+ agentName,
1650
1787
  pingProvider: pingProvider,
1651
- env: process.env,
1652
- secretsRoot: deps.secretsRoot ?? `${process.env["HOME"]}/.agentsecrets`,
1653
1788
  });
1654
1789
  },
1655
1790
  createProviderRuntime: agentic_repair_1.createAgenticDiagnosisProviderRuntime,
@@ -2269,6 +2404,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2269
2404
  if (command.kind === "provider.status") {
2270
2405
  return executeProviderStatus(command, deps);
2271
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
+ }
2272
2419
  // ── auth (local, no daemon socket needed) ──
2273
2420
  if (command.kind === "auth.run") {
2274
2421
  const provider = command.provider ?? (0, auth_flow_1.readAgentConfigForAgent)(command.agent, deps.bundlesRoot).config.humanFacing.provider;
@@ -2279,29 +2426,18 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2279
2426
  provider,
2280
2427
  promptInput: deps.promptInput,
2281
2428
  });
2282
- const credentials = (result.credentials ?? {});
2283
- const split = (0, provider_credential_pool_1.splitProviderCredentialFields)(provider, credentials);
2284
- if (Object.keys(split.credentials).length > 0 || Object.keys(split.config).length > 0) {
2285
- (0, provider_credential_pool_1.upsertProviderCredential)({
2286
- homeDir: providerCliHomeDir(deps),
2287
- provider,
2288
- credentials: split.credentials,
2289
- config: split.config,
2290
- provenance: {
2291
- source: "auth-flow",
2292
- contributedByAgent: command.agent,
2293
- },
2294
- now: providerCliNow(deps),
2295
- });
2296
- }
2297
2429
  // Behavior: ouro auth stores credentials only — does NOT switch provider.
2298
2430
  // Use `ouro auth switch` to change the active provider.
2299
2431
  deps.writeStdout(result.message);
2300
2432
  // Verify the credentials actually work by pinging the provider
2301
2433
  /* v8 ignore start -- integration: real API ping after auth @preserve */
2302
2434
  try {
2303
- const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
2304
- 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})`;
2305
2441
  deps.writeStdout(`${provider}: ${status}`);
2306
2442
  }
2307
2443
  catch {
@@ -2313,19 +2449,35 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2313
2449
  // ── auth verify (local, no daemon socket needed) ──
2314
2450
  /* v8 ignore start -- auth verify/switch: tested in daemon-cli.test.ts but v8 traces differ in CI @preserve */
2315
2451
  if (command.kind === "auth.verify") {
2316
- const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
2317
- 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
+ }
2318
2458
  if (command.provider) {
2319
- 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
+ });
2320
2468
  const message = `${command.provider}: ${status}`;
2321
2469
  deps.writeStdout(message);
2322
2470
  return message;
2323
2471
  }
2324
2472
  const lines = [];
2325
- for (const p of Object.keys(providers)) {
2326
- 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
+ });
2327
2477
  lines.push(`${p}: ${status}`);
2328
2478
  }
2479
+ if (lines.length === 0)
2480
+ lines.push(`no provider credentials in ${command.agent}'s vault`);
2329
2481
  const message = lines.join("\n");
2330
2482
  deps.writeStdout(message);
2331
2483
  return message;
@@ -2345,9 +2497,11 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2345
2497
  deps.writeStdout(message);
2346
2498
  return message;
2347
2499
  }
2348
- const { secrets } = (0, auth_flow_1.loadAgentSecrets)(command.agent, { secretsRoot: deps.secretsRoot });
2349
- const ghConfig = secrets.providers["github-copilot"];
2350
- 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") {
2351
2505
  throw new Error(`github-copilot credentials not configured. Run \`ouro auth --agent ${command.agent} --provider github-copilot\` first.`);
2352
2506
  }
2353
2507
  const fetchFn = deps.fetchImpl ?? fetch;
@@ -2728,7 +2882,10 @@ async function runOuroCli(args, deps = (0, cli_defaults_1.createDefaultOuroCliDe
2728
2882
  await deps.startChat(hatchInput.agentName);
2729
2883
  return "";
2730
2884
  }
2731
- 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}`;
2732
2889
  deps.writeStdout(message);
2733
2890
  return message;
2734
2891
  }
@@ -65,6 +65,7 @@ function usage() {
65
65
  " ouro status --agent <name>",
66
66
  " ouro use --agent <name> --lane outward|inner --provider <provider> --model <model> [--force]",
67
67
  " ouro check --agent <name> --lane outward|inner",
68
+ " ouro provider refresh --agent <name>",
68
69
  " ouro outlook [--json]",
69
70
  " ouro -v|--version",
70
71
  " ouro config model --agent <name> <model-name>",
@@ -72,6 +73,9 @@ function usage() {
72
73
  " ouro auth --agent <name> [--provider <provider>]",
73
74
  " ouro auth verify --agent <name> [--provider <provider>]",
74
75
  " ouro auth switch --agent <name> --provider <provider>",
76
+ " ouro vault create --agent <name> --email <email> [--server <url>] [--store <store>] [--generate-unlock-secret]",
77
+ " ouro vault unlock --agent <name> [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
78
+ " ouro vault status --agent <name> [--store auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file]",
75
79
  " ouro chat <agent>",
76
80
  " ouro msg --to <agent> [--session <id>] [--task <ref>] <message>",
77
81
  " ouro poke <agent> --task <task-id>",
@@ -426,6 +430,61 @@ function parseAuthCommand(args) {
426
430
  }
427
431
  return provider ? { kind: "auth.run", agent, provider } : { kind: "auth.run", agent };
428
432
  }
433
+ function isVaultUnlockStoreKind(value) {
434
+ return value === "auto" || value === "macos-keychain" || value === "windows-dpapi" || value === "linux-secret-service" || value === "plaintext-file";
435
+ }
436
+ function parseVaultCommand(args) {
437
+ const sub = args[0];
438
+ const { agent, rest } = extractAgentFlag(args.slice(1));
439
+ let email;
440
+ let serverUrl;
441
+ let store;
442
+ let generateUnlockSecret = false;
443
+ for (let i = 0; i < rest.length; i += 1) {
444
+ const token = rest[i];
445
+ if (token === "--email") {
446
+ email = rest[i + 1];
447
+ i += 1;
448
+ continue;
449
+ }
450
+ if (token === "--server") {
451
+ serverUrl = rest[i + 1];
452
+ i += 1;
453
+ continue;
454
+ }
455
+ if (token === "--store") {
456
+ const value = rest[i + 1];
457
+ if (!isVaultUnlockStoreKind(value)) {
458
+ throw new Error("vault --store must be auto|macos-keychain|windows-dpapi|linux-secret-service|plaintext-file");
459
+ }
460
+ store = value;
461
+ i += 1;
462
+ continue;
463
+ }
464
+ if (token === "--generate-unlock-secret") {
465
+ generateUnlockSecret = true;
466
+ continue;
467
+ }
468
+ throw new Error("Usage: ouro vault create|unlock|status --agent <name>");
469
+ }
470
+ if (!agent || (sub !== "create" && sub !== "unlock" && sub !== "status")) {
471
+ throw new Error("Usage: ouro vault create|unlock|status --agent <name>");
472
+ }
473
+ if (sub === "create") {
474
+ return {
475
+ kind: "vault.create",
476
+ agent,
477
+ ...(email ? { email } : {}),
478
+ ...(serverUrl ? { serverUrl } : {}),
479
+ ...(store ? { store } : {}),
480
+ ...(generateUnlockSecret ? { generateUnlockSecret: true } : {}),
481
+ };
482
+ }
483
+ if (sub === "unlock") {
484
+ return { kind: "vault.unlock", agent, ...(store ? { store } : {}) };
485
+ }
486
+ return { kind: "vault.status", agent, ...(store ? { store } : {}) };
487
+ }
429
488
  function parseProviderUseCommand(args) {
430
489
  const { agent, rest: afterAgent } = extractAgentFlag(args);
431
490
  const { facing, rest: afterFacing } = extractFacingFlag(afterAgent);
@@ -483,6 +542,14 @@ function parseProviderCheckCommand(args) {
483
542
  ...(facing ? { legacyFacing: facing } : {}),
484
543
  };
485
544
  }
545
+ function parseProviderCommand(args) {
546
+ const sub = args[0];
547
+ const { agent, rest } = extractAgentFlag(args.slice(1));
548
+ if (sub === "refresh" && agent && rest.length === 0) {
549
+ return { kind: "provider.refresh", agent };
550
+ }
551
+ throw new Error("Usage: ouro provider refresh --agent <name>");
552
+ }
486
553
  function parseReminderCommand(args) {
487
554
  const { agent, rest: cleaned } = extractAgentFlag(args);
488
555
  const [sub, ...rest] = cleaned;
@@ -878,6 +945,8 @@ function parseOuroCommand(args) {
878
945
  return parseProviderUseCommand(args.slice(1));
879
946
  if (head === "check")
880
947
  return parseProviderCheckCommand(args.slice(1));
948
+ if (head === "provider")
949
+ return parseProviderCommand(args.slice(1));
881
950
  if (head === "logs") {
882
951
  if (second === "prune")
883
952
  return { kind: "daemon.logs.prune" };
@@ -889,6 +958,8 @@ function parseOuroCommand(args) {
889
958
  return parseHatchCommand(args.slice(1));
890
959
  if (head === "auth")
891
960
  return parseAuthCommand(args.slice(1));
961
+ if (head === "vault")
962
+ return parseVaultCommand(args.slice(1));
892
963
  if (head === "task")
893
964
  return parseTaskCommand(args.slice(1));
894
965
  if (head === "reminder")
@@ -14,7 +14,7 @@
14
14
  * ouro-entry.ts, ouro-bot-wrapper.ts) continue to work unchanged.
15
15
  */
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.discoverExistingCredentials = exports.readFirstBundleMetaVersion = exports.createDefaultOuroCliDeps = exports.pingGithubCopilotModel = exports.listGithubCopilotModels = exports.ensureDaemonRunning = exports.runOuroCli = exports.parseOuroCommand = void 0;
17
+ exports.readFirstBundleMetaVersion = exports.createDefaultOuroCliDeps = exports.pingGithubCopilotModel = exports.listGithubCopilotModels = exports.ensureDaemonRunning = exports.runOuroCli = exports.parseOuroCommand = void 0;
18
18
  // ── Parsing ──
19
19
  var cli_parse_1 = require("./cli-parse");
20
20
  Object.defineProperty(exports, "parseOuroCommand", { enumerable: true, get: function () { return cli_parse_1.parseOuroCommand; } });
@@ -29,4 +29,3 @@ Object.defineProperty(exports, "pingGithubCopilotModel", { enumerable: true, get
29
29
  var cli_defaults_1 = require("./cli-defaults");
30
30
  Object.defineProperty(exports, "createDefaultOuroCliDeps", { enumerable: true, get: function () { return cli_defaults_1.createDefaultOuroCliDeps; } });
31
31
  Object.defineProperty(exports, "readFirstBundleMetaVersion", { enumerable: true, get: function () { return cli_defaults_1.readFirstBundleMetaVersion; } });
32
- Object.defineProperty(exports, "discoverExistingCredentials", { enumerable: true, get: function () { return cli_defaults_1.discoverExistingCredentials; } });
@@ -54,7 +54,6 @@ const habit_migration_1 = require("../habits/habit-migration");
54
54
  const os_cron_deps_1 = require("./os-cron-deps");
55
55
  const os_cron_1 = require("./os-cron");
56
56
  const daemon_tombstone_1 = require("./daemon-tombstone");
57
- const os = __importStar(require("os"));
58
57
  const agent_config_check_1 = require("./agent-config-check");
59
58
  const pulse_1 = require("./pulse");
60
59
  const socket_client_1 = require("./socket-client");
@@ -102,8 +101,7 @@ const processManager = new process_manager_1.DaemonProcessManager({
102
101
  /* v8 ignore next 4 -- wiring: delegates to checkAgentConfigWithProviderHealth which has full unit tests @preserve */
103
102
  configCheck: async (agent) => {
104
103
  const bundlesRoot = (0, identity_1.getAgentBundlesRoot)();
105
- const secretsRoot = path.join(os.homedir(), ".agentsecrets");
106
- return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot, secretsRoot);
104
+ return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agent, bundlesRoot);
107
105
  },
108
106
  /* v8 ignore start -- pulse flush wiring: integration code; flushPulse itself has full unit tests @preserve */
109
107
  onSnapshotChange: () => {