@ouro.bot/cli 0.1.0-alpha.447 → 0.1.0-alpha.449

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 CHANGED
@@ -99,7 +99,7 @@ Task docs do not live in this repo anymore. Planning and doing docs live in the
99
99
  - Vault unlock material is local machine state. Prefer macOS Keychain, Windows DPAPI, or Linux Secret Service; plaintext fallback is allowed only by explicit human choice.
100
100
  - New vault unlock secrets are confirmed before use and rejected if they do not meet the minimum strength requirements.
101
101
  - Provider and runtime credentials are loaded into process memory at startup/auth/unlock/refresh and reused. The remote vault is not queried for every model or sense request.
102
- - Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up` uses the boot checklist, `ouro connect`/`ouro auth verify`/`ouro repair` reuse the same readiness truth, and `ouro help`/`ouro whoami`/`ouro versions`/`ouro hatch` render through the same Ouro-branded wizard/guide language instead of raw transcript walls.
102
+ - Human TTY commands share one CLI surface family: bare `ouro` opens the home deck, `ouro up` uses the boot checklist, `ouro connect`/`ouro auth verify`/`ouro repair` agree on provider and vault truth, and `ouro help`/`ouro whoami`/`ouro versions`/`ouro hatch` render through the same Ouro-branded wizard/guide language instead of raw transcript walls. Orientation commands such as root `ouro connect` may use shorter live probes, while startup and verification commands own durable readiness updates.
103
103
  - Human-facing CLI commands that can wait on browser auth, vault IO, daemon startup, daemon restart, provider checks, or connector setup use a shared progress checklist. If a cursor may blink for more than a few seconds, the command should print or animate the current step instead of going quiet.
104
104
  - CLI commands that mutate bundle config, such as vault setup or `ouro connect bluebubbles`, run bundle sync after the change when `sync.enabled` is true and report a compact `bundle sync:` line.
105
105
  - The daemon discovers bundles dynamically from `~/AgentBundles`.
package/changelog.json CHANGED
@@ -1,6 +1,22 @@
1
1
  {
2
2
  "_note": "This changelog is maintained as part of the PR/version-bump workflow. Agent-curated, not auto-generated. Agents read this file directly via read_file to understand what changed between versions.",
3
3
  "versions": [
4
+ {
5
+ "version": "0.1.0-alpha.449",
6
+ "changes": [
7
+ "`ouro connect` now uses a bounded live provider probe before opening the connection menu: it still checks the real selected providers, but it makes one orientation attempt with a 5-second hard timeout instead of spending the full startup retry budget before the human can choose a setup path.",
8
+ "Provider ping timeouts are now hard timeouts even when an SDK ignores the abort signal. The failing attempt is classified through the same shared retry machinery and reports a clear `provider ping timed out after <ms>ms` message instead of leaving the CLI stuck behind a blinking cursor.",
9
+ "Root connect provider checks no longer overwrite durable provider readiness. A quick menu probe can show `needs attention` for the current screen, while `ouro up`, `ouro check`, auth verification, and chat startup remain the flows that record lasting ready/failed provider state."
10
+ ]
11
+ },
12
+ {
13
+ "version": "0.1.0-alpha.448",
14
+ "changes": [
15
+ "`ouro status` and agent prompt provider visibility now distinguish \"the daemon has not loaded provider credentials in this process\" from \"the agent vault is missing credentials.\" A saved live check that passed stays ready instead of being downgraded to stale/missing just because status rendering is running in a fresh daemon process.",
16
+ "Provider visibility now carries a safe `not-loaded` credential state and renders it as `checked previously`, preserving the last live-check result without reading or printing secrets during ordinary status/prompt rendering.",
17
+ "Regression coverage locks the installed-product failure shape: an empty in-process provider cache plus ready provider state no longer produces misleading auth repair guidance."
18
+ ]
19
+ },
4
20
  {
5
21
  "version": "0.1.0-alpha.447",
6
22
  "changes": [
@@ -389,6 +389,7 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
389
389
  return { ok: true };
390
390
  deps.onProgress?.(selectedProviderPlan(agentName, stateResult.state));
391
391
  const ping = deps.pingProvider ?? (await Promise.resolve().then(() => __importStar(require("../provider-ping")))).pingProvider;
392
+ const shouldRecordReadiness = deps.recordReadiness ?? true;
392
393
  const providers = selectedProvidersForState(stateResult.state);
393
394
  const poolResult = await (0, provider_credentials_1.refreshProviderCredentialPool)(agentName, {
394
395
  ...(deps.onProgress ? { onProgress: mapVaultRefreshProgress(agentName, deps.onProgress) } : {}),
@@ -430,6 +431,7 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
430
431
  const pingResults = await Promise.all(groups.map(async (group) => {
431
432
  const result = await ping(group.provider, providerCredentialConfig(group.record), {
432
433
  model: group.model,
434
+ ...(deps.providerPingOptions ?? {}),
433
435
  ...(deps.onProgress
434
436
  ? (0, provider_ping_progress_1.createProviderPingProgressReporter)({
435
437
  provider: group.provider,
@@ -443,29 +445,33 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
443
445
  let firstFailure = null;
444
446
  for (const { group, result } of pingResults) {
445
447
  if (!result.ok) {
448
+ if (shouldRecordReadiness) {
449
+ for (const lane of group.lanes) {
450
+ writeLaneReadiness({
451
+ agentRoot: stateResult.agentRoot,
452
+ state: stateResult.state,
453
+ lane,
454
+ status: "failed",
455
+ credentialRevision: group.record.revision,
456
+ error: result.message,
457
+ attempts: pingAttemptCount(result),
458
+ });
459
+ }
460
+ }
461
+ firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
462
+ continue;
463
+ }
464
+ if (shouldRecordReadiness) {
446
465
  for (const lane of group.lanes) {
447
466
  writeLaneReadiness({
448
467
  agentRoot: stateResult.agentRoot,
449
468
  state: stateResult.state,
450
469
  lane,
451
- status: "failed",
470
+ status: "ready",
452
471
  credentialRevision: group.record.revision,
453
- error: result.message,
454
472
  attempts: pingAttemptCount(result),
455
473
  });
456
474
  }
457
- firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
458
- continue;
459
- }
460
- for (const lane of group.lanes) {
461
- writeLaneReadiness({
462
- agentRoot: stateResult.agentRoot,
463
- state: stateResult.state,
464
- lane,
465
- status: "ready",
466
- credentialRevision: group.record.revision,
467
- attempts: pingAttemptCount(result),
468
- });
469
475
  }
470
476
  }
471
477
  if (firstFailure)
@@ -106,6 +106,10 @@ const DEFAULT_DAEMON_STARTUP_POLL_INTERVAL_MS = 500;
106
106
  const DEFAULT_DAEMON_STARTUP_STABILITY_WINDOW_MS = 1_500;
107
107
  const DEFAULT_DAEMON_STARTUP_RETRY_LIMIT = 1;
108
108
  const DEFAULT_DAEMON_STARTUP_LOG_LINES = 10;
109
+ const CONNECT_PROVIDER_ORIENTATION_PING_OPTIONS = {
110
+ attemptPolicy: { maxAttempts: 1, baseDelayMs: 0, backoffMultiplier: 2 },
111
+ timeoutMs: 5_000,
112
+ };
109
113
  function summarizeCliUpdateCheckStatus(error, timedOut = false) {
110
114
  const normalized = error.trim().toLowerCase();
111
115
  if (timedOut || normalized.includes("timed out") || normalized.includes("abort")) {
@@ -218,13 +222,20 @@ async function checkAgentProviders(deps, agentsOverride, onProgress) {
218
222
  }
219
223
  return degraded;
220
224
  }
221
- async function checkAgentProviderHealth(agentName, bundlesRoot, deps, onProgress) {
225
+ async function checkAgentProviderHealth(agentName, bundlesRoot, deps, onProgress, options = {}) {
222
226
  const liveDeps = {};
223
227
  if (deps.homeDir)
224
228
  liveDeps.homeDir = deps.homeDir;
225
229
  if (onProgress)
226
230
  liveDeps.onProgress = onProgress;
227
- if (liveDeps.homeDir || liveDeps.onProgress) {
231
+ if (options.providerPingOptions)
232
+ liveDeps.providerPingOptions = options.providerPingOptions;
233
+ if (options.recordReadiness !== undefined)
234
+ liveDeps.recordReadiness = options.recordReadiness;
235
+ if (liveDeps.homeDir ||
236
+ liveDeps.onProgress ||
237
+ liveDeps.providerPingOptions ||
238
+ liveDeps.recordReadiness !== undefined) {
228
239
  return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot, liveDeps);
229
240
  }
230
241
  return (0, agent_config_check_1.checkAgentConfigWithProviderHealth)(agentName, bundlesRoot);
@@ -2040,7 +2051,10 @@ async function buildConnectMenu(agent, deps, onProgress) {
2040
2051
  let providerHealth;
2041
2052
  try {
2042
2053
  onProgress?.("checking selected providers");
2043
- providerHealth = await checkAgentProviderHealth(agent, bundlesRoot, deps, onProgress);
2054
+ providerHealth = await checkAgentProviderHealth(agent, bundlesRoot, deps, onProgress, {
2055
+ providerPingOptions: CONNECT_PROVIDER_ORIENTATION_PING_OPTIONS,
2056
+ recordReadiness: false,
2057
+ });
2044
2058
  }
2045
2059
  catch (error) {
2046
2060
  providerHealth = {
@@ -2966,7 +2980,7 @@ async function executeProviderCheck(command, deps) {
2966
2980
  throw error;
2967
2981
  }
2968
2982
  }
2969
- function renderProviderCredentialLine(credential) {
2983
+ function renderProviderCredentialLine(agentName, credential) {
2970
2984
  if (credential.status === "present") {
2971
2985
  const credentialFields = credential.credentialFields.length > 0 ? ` credentials: ${credential.credentialFields.join(", ")}` : " credentials: none";
2972
2986
  const configFields = credential.configFields.length > 0 ? ` config: ${credential.configFields.join(", ")}` : " config: none";
@@ -2975,6 +2989,9 @@ function renderProviderCredentialLine(credential) {
2975
2989
  if (credential.status === "invalid-pool") {
2976
2990
  return `credentials: vault unavailable (${credential.error}); repair: ${credential.repair.command}`;
2977
2991
  }
2992
+ if (credential.status === "not-loaded") {
2993
+ return `credentials: not loaded in this process; run \`ouro provider refresh --agent ${agentName}\` to read the vault now`;
2994
+ }
2978
2995
  return `credentials: missing; repair: ${credential.repair.command}`;
2979
2996
  }
2980
2997
  async function executeProviderStatus(command, deps) {
@@ -3010,7 +3027,7 @@ async function executeProviderStatus(command, deps) {
3010
3027
  const binding = resolved.binding;
3011
3028
  lines.push(` ${lane}: ${binding.provider} / ${binding.model} (${binding.source})`);
3012
3029
  lines.push(` readiness: ${binding.readiness.status}${binding.readiness.error ? ` (${binding.readiness.error})` : ""}`);
3013
- lines.push(` ${renderProviderCredentialLine(binding.credential)}`);
3030
+ lines.push(` ${renderProviderCredentialLine(command.agent, binding.credential)}`);
3014
3031
  for (const warning of binding.warnings) {
3015
3032
  lines.push(` warning: ${warning.message}`);
3016
3033
  }
@@ -34,6 +34,8 @@ function providerRetryTiming(delayMs) {
34
34
  }
35
35
  function formatProviderAttemptProgress(context, attempt, maxAttempts) {
36
36
  const prefix = context.subject ? `${context.subject}: ` : "";
37
+ if (maxAttempts <= 1)
38
+ return `${prefix}checking ${formatProviderPingLabel(context)}...`;
37
39
  return `${prefix}checking ${formatProviderPingLabel(context)} (attempt ${attempt} of ${maxAttempts})...`;
38
40
  }
39
41
  function formatProviderRetryProgress(context, record, maxAttempts) {
@@ -89,6 +89,16 @@ function resolveCredential(poolResult, provider, agentName) {
89
89
  warnings: [missingCredentialWarning(provider)],
90
90
  };
91
91
  }
92
+ if ((0, provider_credentials_1.isProviderCredentialPoolNotLoaded)(poolResult)) {
93
+ return {
94
+ credential: {
95
+ status: "not-loaded",
96
+ provider,
97
+ poolPath: poolResult.poolPath,
98
+ },
99
+ warnings: [],
100
+ };
101
+ }
92
102
  if (poolResult.reason === "invalid" || poolResult.reason === "unavailable") {
93
103
  return {
94
104
  credential: {
@@ -130,6 +140,9 @@ function staleReadiness(readiness, reason) {
130
140
  }
131
141
  function resolveReadiness(input) {
132
142
  if (!input.readiness) {
143
+ if (input.credential.status === "not-loaded") {
144
+ return { readiness: { status: "unknown" }, warnings: [] };
145
+ }
133
146
  if (input.credential.status === "missing") {
134
147
  return { readiness: { status: "unknown", reason: "credential-missing" }, warnings: [] };
135
148
  }
@@ -170,6 +183,9 @@ function resolveReadiness(input) {
170
183
  warnings: [],
171
184
  };
172
185
  }
186
+ if (input.credential.status === "not-loaded") {
187
+ return { readiness: readinessFromState(input.readiness), warnings: [] };
188
+ }
173
189
  return { readiness: readinessFromState(input.readiness), warnings: [] };
174
190
  }
175
191
  function resolveEffectiveProviderBinding(input) {
@@ -33,10 +33,12 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PROVIDER_CREDENTIAL_POOL_NOT_LOADED_ERROR = void 0;
36
37
  exports.splitProviderCredentialFields = splitProviderCredentialFields;
37
38
  exports.providerCredentialsVaultPath = providerCredentialsVaultPath;
38
39
  exports.providerCredentialItemName = providerCredentialItemName;
39
40
  exports.providerCredentialMachineHomeDir = providerCredentialMachineHomeDir;
41
+ exports.isProviderCredentialPoolNotLoaded = isProviderCredentialPoolNotLoaded;
40
42
  exports.readProviderCredentialPool = readProviderCredentialPool;
41
43
  exports.refreshProviderCredentialPool = refreshProviderCredentialPool;
42
44
  exports.createProviderCredentialRecord = createProviderCredentialRecord;
@@ -54,6 +56,7 @@ const credential_access_1 = require("../repertoire/credential-access");
54
56
  const VALID_PROVIDERS = ["azure", "minimax", "anthropic", "openai-codex", "github-copilot"];
55
57
  const VALID_PROVENANCE_SOURCES = ["auth-flow", "manual"];
56
58
  const VAULT_ITEM_PREFIX = "providers/";
59
+ exports.PROVIDER_CREDENTIAL_POOL_NOT_LOADED_ERROR = "provider credentials have not been loaded from vault";
57
60
  const PROVIDER_FIELD_SPLITS = {
58
61
  anthropic: {
59
62
  credentials: ["setupToken", "refreshToken", "expiresAt"],
@@ -197,7 +200,7 @@ function recordFromPayload(payload) {
197
200
  provenance: { ...payload.provenance },
198
201
  };
199
202
  }
200
- function missingPool(agentName, error = "provider credentials have not been loaded from vault") {
203
+ function missingPool(agentName, error = exports.PROVIDER_CREDENTIAL_POOL_NOT_LOADED_ERROR) {
201
204
  return {
202
205
  ok: false,
203
206
  reason: "missing",
@@ -205,6 +208,11 @@ function missingPool(agentName, error = "provider credentials have not been load
205
208
  error,
206
209
  };
207
210
  }
211
+ function isProviderCredentialPoolNotLoaded(result) {
212
+ return !result.ok
213
+ && result.reason === "missing"
214
+ && result.error === exports.PROVIDER_CREDENTIAL_POOL_NOT_LOADED_ERROR;
215
+ }
208
216
  function cacheResult(agentName, result) {
209
217
  cachedPools.set(agentName, result);
210
218
  return result;
@@ -83,6 +83,37 @@ async function readGithubCopilotModelPingError(response) {
83
83
  function createStatusError(message, status) {
84
84
  return Object.assign(new Error(message), { status });
85
85
  }
86
+ function normalizePingTimeoutMs(timeoutMs) {
87
+ if (timeoutMs === undefined)
88
+ return PING_TIMEOUT_MS;
89
+ if (!Number.isFinite(timeoutMs))
90
+ return PING_TIMEOUT_MS;
91
+ return Math.max(1, Math.floor(timeoutMs));
92
+ }
93
+ function createPingTimeoutError(timeoutMs) {
94
+ return Object.assign(new Error(`provider ping timed out after ${timeoutMs}ms`), {
95
+ code: "ETIMEDOUT",
96
+ });
97
+ }
98
+ async function runPingWithHardTimeout(runtime, timeoutMs) {
99
+ const controller = new AbortController();
100
+ let timeout;
101
+ const timeoutPromise = new Promise((_, reject) => {
102
+ timeout = setTimeout(() => {
103
+ controller.abort();
104
+ reject(createPingTimeoutError(timeoutMs));
105
+ }, timeoutMs);
106
+ });
107
+ try {
108
+ await Promise.race([
109
+ Promise.resolve().then(() => runtime.ping(controller.signal)),
110
+ timeoutPromise,
111
+ ]);
112
+ }
113
+ finally {
114
+ clearTimeout(timeout);
115
+ }
116
+ }
86
117
  async function pingGithubCopilotModel(baseUrl, token, model, fetchImpl = fetch) {
87
118
  const base = baseUrl.replace(/\/+$/, "");
88
119
  const isClaude = model.startsWith("claude");
@@ -183,15 +214,7 @@ async function pingProvider(provider, config, options = {}) {
183
214
  onAttemptStart: options.onAttemptStart,
184
215
  onRetry: options.onRetry,
185
216
  run: async () => {
186
- const controller = new AbortController();
187
- /* v8 ignore next -- timeout callback: only fires after 10s, tests resolve faster @preserve */
188
- const timeout = setTimeout(() => controller.abort(), PING_TIMEOUT_MS);
189
- try {
190
- await runtime.ping(controller.signal);
191
- }
192
- finally {
193
- clearTimeout(timeout);
194
- }
217
+ await runPingWithHardTimeout(runtime, normalizePingTimeoutMs(options.timeoutMs));
195
218
  },
196
219
  });
197
220
  if (attempt.ok) {
@@ -21,7 +21,7 @@ function credentialVisibility(binding) {
21
21
  }
22
22
  return {
23
23
  status: credential.status,
24
- repairCommand: credential.repair.command,
24
+ ...("repair" in credential ? { repairCommand: credential.repair.command } : {}),
25
25
  };
26
26
  }
27
27
  function readinessVisibility(binding) {
@@ -87,6 +87,8 @@ function credentialLabel(credential) {
87
87
  return credential.source ?? "vault";
88
88
  if (credential.status === "invalid-pool")
89
89
  return "vault unavailable";
90
+ if (credential.status === "not-loaded")
91
+ return "checked previously";
90
92
  return "missing";
91
93
  }
92
94
  function readinessLabel(readiness) {
@@ -102,7 +104,7 @@ function readinessLabel(readiness) {
102
104
  return readiness.status;
103
105
  }
104
106
  function providerStatusDetail(lane) {
105
- if (lane.credential.status !== "present")
107
+ if (lane.credential.status === "missing" || lane.credential.status === "invalid-pool")
106
108
  return undefined;
107
109
  return lane.readiness.error;
108
110
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.447",
3
+ "version": "0.1.0-alpha.449",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",