@ouro.bot/cli 0.1.0-alpha.426 → 0.1.0-alpha.427

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/changelog.json CHANGED
@@ -1,6 +1,15 @@
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.427",
6
+ "changes": [
7
+ "Root `ouro connect` now runs the same shared live provider verification path as `ouro up` and `ouro auth verify` before it renders the connect bay, so freshly authed providers and failed live checks show their real current state.",
8
+ "The root connect bay now groups capabilities into a clearer `Next best move`, `Provider core`, `Portable`, and `This machine` layout, with truthful lane-specific status for outward and inner providers.",
9
+ "Shared provider health checks now refresh readiness for every selected provider/model pair instead of stopping after the first failure, and `ouro connect` keeps working when provider selection is missing so repair guidance can still render.",
10
+ "Auth/provider/testing docs now describe the live-verifying connect bay behavior, and `@ouro.bot/cli` plus the `ouro.bot` wrapper are version-synced for the truthful connect release."
11
+ ]
12
+ },
4
13
  {
5
14
  "version": "0.1.0-alpha.426",
6
15
  "changes": [
@@ -378,7 +378,9 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
378
378
  });
379
379
  }
380
380
  }
381
+ let firstFailure = null;
381
382
  for (const group of pingGroups.values()) {
383
+ deps.onProgress?.(`checking ${group.provider} / ${group.model}...`);
382
384
  const result = await ping(group.provider, providerCredentialConfig(group.record), { model: group.model });
383
385
  if (!result.ok) {
384
386
  for (const lane of group.lanes) {
@@ -392,7 +394,8 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
392
394
  attempts: pingAttemptCount(result),
393
395
  });
394
396
  }
395
- return failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
397
+ firstFailure ??= failedPingResult(agentName, group.lanes[0], group.provider, group.model, result);
398
+ continue;
396
399
  }
397
400
  for (const lane of group.lanes) {
398
401
  writeLaneReadiness({
@@ -405,6 +408,8 @@ async function checkAgentConfigWithProviderHealth(agentName, bundlesRoot, deps =
405
408
  });
406
409
  }
407
410
  }
411
+ if (firstFailure)
412
+ return firstFailure;
408
413
  (0, runtime_1.emitNervesEvent)({
409
414
  component: "daemon",
410
415
  event: "daemon.agent_config_valid",
@@ -1625,29 +1625,218 @@ function enableAgentSense(agent, sense, deps) {
1625
1625
  };
1626
1626
  fs.writeFileSync(configPath, `${JSON.stringify(raw, null, 2)}\n`, "utf-8");
1627
1627
  }
1628
+ const CONNECT_MENU_PROMPT = "Choose [1-6] or type a name: ";
1629
+ const CONNECT_TITLE_WIDTH = 34;
1630
+ const CONNECT_STATUS_PRIORITY = {
1631
+ "needs attention": 0,
1632
+ locked: 1,
1633
+ "needs credentials": 2,
1634
+ "needs setup": 3,
1635
+ missing: 4,
1636
+ "not attached": 5,
1637
+ ready: 6,
1638
+ attached: 6,
1639
+ };
1640
+ const RESET = "\x1b[0m";
1641
+ const BOLD = "\x1b[1m";
1642
+ const DIM = "\x1b[2m";
1643
+ const TEAL = "\x1b[38;2;78;201;176m";
1644
+ const GREEN = "\x1b[38;2;46;204;64m";
1645
+ const YELLOW = "\x1b[38;2;230;190;50m";
1646
+ /* v8 ignore start -- cosmetic ANSI wrappers @preserve */
1647
+ function connectBold(text) { return `${BOLD}${text}${RESET}`; }
1648
+ function connectDim(text) { return `${DIM}${text}${RESET}`; }
1649
+ function connectTeal(text) { return `${TEAL}${text}${RESET}`; }
1650
+ function connectGreen(text) { return `${GREEN}${text}${RESET}`; }
1651
+ function connectYellow(text) { return `${YELLOW}${text}${RESET}`; }
1652
+ /* v8 ignore stop */
1653
+ function connectMenuIsTTY(deps) {
1654
+ return deps.isTTY ?? process.stdout.isTTY === true;
1655
+ }
1656
+ function connectStatusText(status, isTTY) {
1657
+ if (!isTTY)
1658
+ return status;
1659
+ if (status === "ready" || status === "attached")
1660
+ return connectGreen(status);
1661
+ if (status === "not attached")
1662
+ return connectDim(status);
1663
+ return connectYellow(status);
1664
+ }
1665
+ function connectSectionHeader(label, isTTY) {
1666
+ const rule = "─".repeat(Math.max(6, CONNECT_TITLE_WIDTH - label.length));
1667
+ if (!isTTY)
1668
+ return `${label}\n${rule}`;
1669
+ return ` ${connectTeal("──")} ${connectBold(label)} ${connectTeal(rule)}`;
1670
+ }
1671
+ function summarizeProviderLane(agent, lane, providerHealth) {
1672
+ if (lane.status === "unconfigured") {
1673
+ return {
1674
+ lane: lane.lane,
1675
+ status: "needs setup",
1676
+ title: "choose provider and model",
1677
+ detail: "needs setup",
1678
+ action: lane.repairCommand,
1679
+ };
1680
+ }
1681
+ const fallbackAction = lane.credential.repairCommand ?? providerHealth?.fix;
1682
+ if (lane.credential.status === "missing") {
1683
+ return {
1684
+ lane: lane.lane,
1685
+ status: "needs credentials",
1686
+ title: `${lane.provider} / ${lane.model}`,
1687
+ detail: "credentials missing",
1688
+ action: fallbackAction,
1689
+ };
1690
+ }
1691
+ if (lane.credential.status === "invalid-pool") {
1692
+ return {
1693
+ lane: lane.lane,
1694
+ status: "needs attention",
1695
+ title: `${lane.provider} / ${lane.model}`,
1696
+ detail: "vault unavailable",
1697
+ action: fallbackAction,
1698
+ };
1699
+ }
1700
+ if (lane.readiness.status === "failed") {
1701
+ return {
1702
+ lane: lane.lane,
1703
+ status: "needs attention",
1704
+ title: `${lane.provider} / ${lane.model}`,
1705
+ detail: `failed live check: ${lane.readiness.error ?? "unknown error"}`,
1706
+ action: providerHealth?.fix ?? `ouro auth --agent ${agent} --provider ${lane.provider}`,
1707
+ };
1708
+ }
1709
+ if (lane.readiness.status === "stale") {
1710
+ return {
1711
+ lane: lane.lane,
1712
+ status: "needs attention",
1713
+ title: `${lane.provider} / ${lane.model}`,
1714
+ detail: ["live check is stale", lane.readiness.reason].filter(Boolean).join(": "),
1715
+ action: providerHealth?.fix,
1716
+ };
1717
+ }
1718
+ if (lane.readiness.status === "ready") {
1719
+ return {
1720
+ lane: lane.lane,
1721
+ status: "ready",
1722
+ title: `${lane.provider} / ${lane.model}`,
1723
+ detail: "ready",
1724
+ };
1725
+ }
1726
+ return {
1727
+ lane: lane.lane,
1728
+ status: "needs attention",
1729
+ title: `${lane.provider} / ${lane.model}`,
1730
+ detail: "live check did not complete yet",
1731
+ action: providerHealth?.fix,
1732
+ };
1733
+ }
1734
+ function summarizeProvidersForConnect(agent, visibility, providerHealth) {
1735
+ const laneSummaries = visibility.lanes.map((lane) => summarizeProviderLane(agent, lane, providerHealth));
1736
+ const worstLaneStatus = laneSummaries.reduce((worst, lane) => CONNECT_STATUS_PRIORITY[lane.status] < CONNECT_STATUS_PRIORITY[worst] ? lane.status : worst, "ready");
1737
+ const providerHealthStatus = !providerHealth || providerHealth.ok
1738
+ ? undefined
1739
+ : (() => {
1740
+ const error = String(providerHealth.error).toLowerCase();
1741
+ const fix = String(providerHealth.fix).toLowerCase();
1742
+ if (error.includes("failed live check"))
1743
+ return "needs attention";
1744
+ if (error.includes("has no credentials"))
1745
+ return "needs credentials";
1746
+ if (error.includes("missing") && error.includes("provider"))
1747
+ return "needs setup";
1748
+ if (error.includes("vault is locked") || error.includes("vault locked"))
1749
+ return "locked";
1750
+ if (fix.includes("ouro auth"))
1751
+ return "needs credentials";
1752
+ if (fix.includes("ouro use"))
1753
+ return "needs setup";
1754
+ if (fix.includes("vault unlock"))
1755
+ return "locked";
1756
+ return "needs attention";
1757
+ })();
1758
+ const nextLane = laneSummaries.find((lane) => lane.status !== "ready");
1759
+ return {
1760
+ status: providerHealthStatus ?? worstLaneStatus,
1761
+ detailLines: laneSummaries.flatMap((lane) => [
1762
+ `${lane.lane.padEnd(8)} ${lane.title}`,
1763
+ ` ${lane.detail}`,
1764
+ ]),
1765
+ nextAction: nextLane?.action ?? providerHealth?.fix,
1766
+ nextNote: nextLane ? `${nextLane.lane} ${nextLane.detail}` : undefined,
1767
+ };
1768
+ }
1769
+ function connectEntryNeedsAttention(entry) {
1770
+ return entry.status !== "ready" && entry.status !== "attached";
1771
+ }
1772
+ function connectNextEntry(entries) {
1773
+ return entries.find((entry) => connectEntryNeedsAttention(entry));
1774
+ }
1775
+ function appendConnectOptionalLine(lines, prefix, value) {
1776
+ if (typeof value === "string" && value.length > 0) {
1777
+ lines.push(` ${prefix}${value}`);
1778
+ }
1779
+ }
1780
+ function connectNextMoveLines(entries, isTTY) {
1781
+ const next = connectNextEntry(entries);
1782
+ if (!next) {
1783
+ return [
1784
+ connectSectionHeader("Next best move", isTTY),
1785
+ " Everything here is ready. Pick what you want to review or refresh.",
1786
+ ];
1787
+ }
1788
+ const lines = [
1789
+ connectSectionHeader("Next best move", isTTY),
1790
+ ` ${next.name} - ${next.status}`,
1791
+ ];
1792
+ appendConnectOptionalLine(lines, "", next.nextNote);
1793
+ appendConnectOptionalLine(lines, "run: ", next.nextAction);
1794
+ return lines;
1795
+ }
1796
+ function renderConnectEntry(entry, isTTY) {
1797
+ const label = `${entry.option}. ${entry.name}`.padEnd(25);
1798
+ const lines = [` ${label} ${connectStatusText(entry.status, isTTY)}`];
1799
+ for (const detail of entry.detailLines ?? []) {
1800
+ lines.push(` ${detail}`);
1801
+ }
1802
+ if (entry.description) {
1803
+ lines.push(isTTY ? ` ${connectDim(entry.description)}` : ` ${entry.description}`);
1804
+ }
1805
+ return lines;
1806
+ }
1807
+ function readConnectBaySenseFlags(agent, deps) {
1808
+ const configPath = path.join(providerCliAgentRoot({ agent }, deps), "agent.json");
1809
+ const parsed = JSON.parse(fs.readFileSync(configPath, "utf-8"));
1810
+ return {
1811
+ teamsEnabled: parsed.senses?.teams?.enabled === true,
1812
+ blueBubblesEnabled: parsed.senses?.bluebubbles?.enabled === true,
1813
+ };
1814
+ }
1628
1815
  async function buildConnectMenu(agent, deps, onProgress) {
1816
+ const bundlesRoot = path.dirname(providerCliAgentRoot({ agent }, deps));
1817
+ let providerHealth;
1818
+ try {
1819
+ onProgress?.("checking selected providers");
1820
+ providerHealth = await checkAgentProviderHealth(agent, bundlesRoot, deps, onProgress);
1821
+ }
1822
+ catch (error) {
1823
+ providerHealth = {
1824
+ ok: false,
1825
+ error: error instanceof Error ? error.message : String(error),
1826
+ fix: `Run 'ouro auth verify --agent ${agent}' to inspect provider health.`,
1827
+ };
1828
+ }
1629
1829
  const providerVisibility = (0, provider_visibility_1.buildAgentProviderVisibility)({
1630
1830
  agentName: agent,
1631
1831
  agentRoot: providerCliAgentRoot({ agent }, deps),
1632
1832
  homeDir: providerCliHomeDir(deps),
1633
1833
  });
1634
- const providerStatus = providerVisibility.lanes.some((lane) => lane.status === "unconfigured")
1635
- ? "needs setup"
1636
- : providerVisibility.lanes.some((lane) => lane.status === "configured" && lane.credential.status !== "present")
1637
- ? "needs auth"
1638
- : providerVisibility.lanes.some((lane) => lane.status === "configured" && (lane.readiness.status === "failed" || lane.readiness.status === "stale"))
1639
- ? "needs attention"
1640
- : "ready";
1641
- const providerDetail = providerVisibility.lanes.map((lane) => lane.status === "configured"
1642
- ? `${lane.lane}: ${lane.provider} / ${lane.model}`
1643
- : `${lane.lane}: choose provider/model`).join(" | ");
1834
+ const providerSummary = summarizeProvidersForConnect(agent, providerVisibility, providerHealth);
1644
1835
  onProgress?.("loading portable settings");
1645
1836
  const runtimeConfig = await (0, runtime_credentials_1.refreshRuntimeCredentialConfig)(agent, { preserveCachedOnFailure: true });
1646
1837
  onProgress?.("loading this machine's settings");
1647
1838
  const machineRuntime = await (0, runtime_credentials_1.refreshMachineRuntimeCredentialConfig)(agent, currentMachineId(deps), { preserveCachedOnFailure: true });
1648
- const agentConfig = (0, auth_flow_1.readAgentConfigForAgent)(agent, deps.bundlesRoot).config;
1649
- const teamsEnabled = agentConfig.senses?.teams?.enabled === true;
1650
- const blueBubblesEnabled = agentConfig.senses?.bluebubbles?.enabled === true;
1839
+ const { teamsEnabled, blueBubblesEnabled } = readConnectBaySenseFlags(agent, deps);
1651
1840
  const perplexityStatus = runtimeConfig.ok
1652
1841
  ? hasRuntimeConfigValue(runtimeConfig.config, "integrations.perplexityApiKey") ? "ready" : "missing"
1653
1842
  : runtimeConfigReadStatus(runtimeConfig);
@@ -1669,29 +1858,90 @@ async function buildConnectMenu(agent, deps, onProgress) {
1669
1858
  ? "attached"
1670
1859
  : "not attached"
1671
1860
  : machineRuntimeReadStatus(machineRuntime);
1672
- return [
1673
- `${agent} // connect bay`,
1674
- "Bring one capability online at a time.",
1675
- "",
1676
- ` 1. [${providerStatus}] Providers`,
1677
- ` ${providerDetail}`,
1678
- "",
1679
- ` 2. [${perplexityStatus}] Perplexity search`,
1680
- " Portable. Web search via Perplexity.",
1681
- "",
1682
- ` 3. [${embeddingsStatus}] Memory embeddings`,
1683
- " Portable. Memory retrieval and note search.",
1684
- "",
1685
- ` 4. [${teamsStatus}] Teams`,
1686
- " Portable. Microsoft Teams sense credentials.",
1687
- "",
1688
- ` 5. [${blueBubblesStatus}] BlueBubbles iMessage`,
1689
- " This machine only. Local Mac Messages bridge.",
1861
+ const entries = [
1862
+ {
1863
+ option: "1",
1864
+ name: "Providers",
1865
+ section: "Provider core",
1866
+ status: providerSummary.status,
1867
+ detailLines: providerSummary.detailLines,
1868
+ nextAction: providerSummary.nextAction,
1869
+ nextNote: providerSummary.nextNote,
1870
+ },
1871
+ {
1872
+ option: "2",
1873
+ name: "Perplexity search",
1874
+ section: "Portable",
1875
+ status: perplexityStatus,
1876
+ description: "Web search via Perplexity.",
1877
+ nextAction: connectEntryNeedsAttention({
1878
+ option: "2",
1879
+ name: "Perplexity search",
1880
+ section: "Portable",
1881
+ status: perplexityStatus,
1882
+ }) ? `ouro connect perplexity --agent ${agent}` : undefined,
1883
+ },
1884
+ {
1885
+ option: "3",
1886
+ name: "Memory embeddings",
1887
+ section: "Portable",
1888
+ status: embeddingsStatus,
1889
+ description: "Memory retrieval and note search.",
1890
+ nextAction: connectEntryNeedsAttention({
1891
+ option: "3",
1892
+ name: "Memory embeddings",
1893
+ section: "Portable",
1894
+ status: embeddingsStatus,
1895
+ }) ? `ouro connect embeddings --agent ${agent}` : undefined,
1896
+ },
1897
+ {
1898
+ option: "4",
1899
+ name: "Teams",
1900
+ section: "Portable",
1901
+ status: teamsStatus,
1902
+ description: "Microsoft Teams sense credentials.",
1903
+ nextAction: connectEntryNeedsAttention({
1904
+ option: "4",
1905
+ name: "Teams",
1906
+ section: "Portable",
1907
+ status: teamsStatus,
1908
+ }) ? `ouro connect teams --agent ${agent}` : undefined,
1909
+ },
1910
+ {
1911
+ option: "5",
1912
+ name: "BlueBubbles iMessage",
1913
+ section: "This machine",
1914
+ status: blueBubblesStatus,
1915
+ description: "Local Mac Messages bridge.",
1916
+ nextAction: connectEntryNeedsAttention({
1917
+ option: "5",
1918
+ name: "BlueBubbles iMessage",
1919
+ section: "This machine",
1920
+ status: blueBubblesStatus,
1921
+ }) ? `ouro connect bluebubbles --agent ${agent}` : undefined,
1922
+ },
1923
+ ];
1924
+ const isTTY = connectMenuIsTTY(deps);
1925
+ const lines = [
1926
+ isTTY ? connectBold(`${agent} connect bay`) : `${agent} connect bay`,
1927
+ isTTY
1928
+ ? connectDim("Bring one capability online. Provider status is checked live.")
1929
+ : "Bring one capability online. Provider status is checked live.",
1690
1930
  "",
1691
- " 6. Cancel",
1931
+ ...connectNextMoveLines(entries, isTTY),
1692
1932
  "",
1693
- "Choose [1-6] or type a name: ",
1694
- ].join("\n");
1933
+ ];
1934
+ for (const section of ["Provider core", "Portable", "This machine"]) {
1935
+ lines.push(connectSectionHeader(section, isTTY));
1936
+ for (const entry of entries.filter((candidate) => candidate.section === section)) {
1937
+ lines.push(...renderConnectEntry(entry, isTTY));
1938
+ lines.push("");
1939
+ }
1940
+ }
1941
+ lines.push(isTTY ? ` ${connectDim("6. Not now")}` : " 6. Not now");
1942
+ lines.push("");
1943
+ lines.push(CONNECT_MENU_PROMPT);
1944
+ return lines.join("\n");
1695
1945
  }
1696
1946
  async function executeConnectPerplexity(agent, deps) {
1697
1947
  if (agent === "SerpentGuide") {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.426",
3
+ "version": "0.1.0-alpha.427",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",