@ouro.bot/cli 0.1.0-alpha.78 → 0.1.0-alpha.79

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
@@ -2,8 +2,14 @@
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
4
  {
5
- "version": "0.1.0-alpha.78",
6
- "changes": [
5
+ "version": "0.1.0-alpha.79",
6
+ "changes": [
7
+ "New: Versioned CLI directory layout (~/.ouro-cli/) replaces npx-based ouro wrapper. Explicit version management, rollback support, and deterministic updates.",
8
+ "New: `ouro up` now checks the registry for newer CLI versions, installs them into ~/.ouro-cli/versions/, activates via symlink flip, and re-execs — no more silent npx downloads.",
9
+ "New: `ouro rollback [<version>]` swaps CurrentVersion/previous symlinks, stops the daemon. With a version arg, installs if needed then activates.",
10
+ "New: `ouro versions` lists cached CLI versions with * current and (previous) markers.",
11
+ "Migration: On first run, old ~/.local/bin/ouro wrapper is removed, old PATH entry cleaned from shell profile, new ~/.ouro-cli/bin added to PATH.",
12
+ "Trust manifest: rollback requires family trust, versions requires acquaintance.",
7
13
  "Fix: BlueBubbles group chats no longer start typing just because the model turn began. Group chats stay visually quiet until the agent has committed to replying.",
8
14
  "Tool/progress updates now count as reply commitment in BlueBubbles group chats, so read/typing can begin before final user-facing text without regressing back to model-start typing.",
9
15
  "Silent group-chat turns remain fully quiet: when the agent chooses no_response, the inbound message still reaches the model but the chat is not marked read, never types, and never sends a message."
@@ -298,6 +298,8 @@ function usage() {
298
298
  " ouro session list [--agent <name>]",
299
299
  " ouro mcp list",
300
300
  " ouro mcp call <server> <tool> [--args '{...}']",
301
+ " ouro rollback [<version>]",
302
+ " ouro versions",
301
303
  ].join("\n");
302
304
  }
303
305
  function formatVersionOutput() {
@@ -908,6 +910,10 @@ function parseOuroCommand(args) {
908
910
  }
909
911
  if (head === "up")
910
912
  return { kind: "daemon.up" };
913
+ if (head === "rollback")
914
+ return { kind: "rollback", ...(second ? { version: second } : {}) };
915
+ if (head === "versions")
916
+ return { kind: "versions" };
911
917
  if (head === "stop" || head === "down")
912
918
  return { kind: "daemon.stop" };
913
919
  if (head === "status")
@@ -1439,7 +1445,11 @@ async function performSystemSetup(deps) {
1439
1445
  // Install ouro command to PATH (non-blocking)
1440
1446
  if (deps.installOuroCommand) {
1441
1447
  try {
1442
- deps.installOuroCommand();
1448
+ const installResult = deps.installOuroCommand();
1449
+ /* v8 ignore next -- migration hint: only fires once during old→new layout migration @preserve */
1450
+ if (installResult.migratedFromOldPath) {
1451
+ deps.writeStdout("migrated ouro to ~/.ouro-cli/ — open a new terminal or run: source ~/.zshrc");
1452
+ }
1443
1453
  }
1444
1454
  catch (error) {
1445
1455
  (0, runtime_1.emitNervesEvent)({
@@ -1752,6 +1762,35 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
1752
1762
  meta: { kind: command.kind },
1753
1763
  });
1754
1764
  if (command.kind === "daemon.up") {
1765
+ // ── versioned CLI update check ──
1766
+ if (deps.checkForCliUpdate) {
1767
+ let pendingReExec = false;
1768
+ try {
1769
+ const updateResult = await deps.checkForCliUpdate();
1770
+ if (updateResult.available && updateResult.latestVersion) {
1771
+ /* v8 ignore next -- fallback: getCurrentCliVersion always injected in tests @preserve */
1772
+ const currentVersion = deps.getCurrentCliVersion?.() ?? "unknown";
1773
+ await deps.installCliVersion(updateResult.latestVersion);
1774
+ deps.activateCliVersion(updateResult.latestVersion);
1775
+ deps.writeStdout(`ouro updated to ${updateResult.latestVersion} (was ${currentVersion})`);
1776
+ pendingReExec = true;
1777
+ }
1778
+ /* v8 ignore start -- update check error: tested via daemon-cli-update-flow.test.ts @preserve */
1779
+ }
1780
+ catch (error) {
1781
+ (0, runtime_1.emitNervesEvent)({
1782
+ level: "warn",
1783
+ component: "daemon",
1784
+ event: "daemon.cli_update_check_error",
1785
+ message: "CLI update check failed",
1786
+ meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
1787
+ });
1788
+ }
1789
+ /* v8 ignore stop */
1790
+ if (pendingReExec) {
1791
+ deps.reExecFromNewVersion(args);
1792
+ }
1793
+ }
1755
1794
  await performSystemSetup(deps);
1756
1795
  if (deps.ensureDaemonBootPersistence) {
1757
1796
  try {
@@ -1793,6 +1832,70 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
1793
1832
  deps.writeStdout(daemonResult.message);
1794
1833
  return daemonResult.message;
1795
1834
  }
1835
+ // ── rollback command (local, no daemon socket needed for symlinks) ──
1836
+ /* v8 ignore start -- rollback/versions: tested via daemon-cli-rollback/versions tests @preserve */
1837
+ if (command.kind === "rollback") {
1838
+ const currentVersion = deps.getCurrentCliVersion?.() ?? "unknown";
1839
+ if (command.version) {
1840
+ // Rollback to a specific version
1841
+ const installed = deps.listCliVersions?.() ?? [];
1842
+ if (!installed.includes(command.version)) {
1843
+ try {
1844
+ await deps.installCliVersion(command.version);
1845
+ }
1846
+ catch (error) {
1847
+ const message = `failed to install version ${command.version}: ${error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error)}`;
1848
+ deps.writeStdout(message);
1849
+ return message;
1850
+ }
1851
+ }
1852
+ deps.activateCliVersion(command.version);
1853
+ }
1854
+ else {
1855
+ // Rollback to previous version
1856
+ const previousVersion = deps.getPreviousCliVersion?.();
1857
+ if (!previousVersion) {
1858
+ const message = "no previous version to roll back to";
1859
+ deps.writeStdout(message);
1860
+ return message;
1861
+ }
1862
+ deps.activateCliVersion(previousVersion);
1863
+ command = { ...command, version: previousVersion };
1864
+ }
1865
+ // Stop daemon (non-fatal if not running)
1866
+ try {
1867
+ await deps.sendCommand(deps.socketPath, { kind: "daemon.stop" });
1868
+ }
1869
+ catch {
1870
+ // Daemon may not be running — that's fine
1871
+ }
1872
+ const message = `rolled back to ${command.version} (was ${currentVersion})`;
1873
+ deps.writeStdout(message);
1874
+ return message;
1875
+ }
1876
+ // ── versions command (local, no daemon socket needed) ──
1877
+ if (command.kind === "versions") {
1878
+ const versions = deps.listCliVersions?.() ?? [];
1879
+ if (versions.length === 0) {
1880
+ const message = "no versions installed";
1881
+ deps.writeStdout(message);
1882
+ return message;
1883
+ }
1884
+ const current = deps.getCurrentCliVersion?.();
1885
+ const previous = deps.getPreviousCliVersion?.();
1886
+ const lines = versions.map((v) => {
1887
+ let line = v;
1888
+ if (v === current)
1889
+ line += " * current";
1890
+ if (v === previous)
1891
+ line += " (previous)";
1892
+ return line;
1893
+ });
1894
+ const message = lines.join("\n");
1895
+ deps.writeStdout(message);
1896
+ return message;
1897
+ }
1898
+ /* v8 ignore stop */
1796
1899
  if (command.kind === "daemon.logs" && deps.tailLogs) {
1797
1900
  deps.tailLogs();
1798
1901
  return "";
@@ -38,9 +38,13 @@ const fs = __importStar(require("fs"));
38
38
  const os = __importStar(require("os"));
39
39
  const path = __importStar(require("path"));
40
40
  const runtime_1 = require("../../nerves/runtime");
41
- const CLI_PACKAGE_SPECIFIER = "@ouro.bot/cli@alpha";
42
41
  const WRAPPER_SCRIPT = `#!/bin/sh
43
- exec npx --prefer-online --yes ${CLI_PACKAGE_SPECIFIER} "$@"
42
+ ENTRY="$HOME/.ouro-cli/CurrentVersion/node_modules/@ouro.bot/cli/dist/heart/daemon/ouro-entry.js"
43
+ if [ ! -e "$ENTRY" ]; then
44
+ echo "ouro not installed. Run: npx ouro.bot" >&2
45
+ exit 1
46
+ fi
47
+ exec node "$ENTRY" "$@"
44
48
  `;
45
49
  function detectShellProfile(homeDir, shell) {
46
50
  if (!shell)
@@ -67,6 +71,30 @@ function buildPathExportLine(binDir, shell) {
67
71
  }
68
72
  return `\n# Added by ouro\nexport PATH="${binDir}:$PATH"\n`;
69
73
  }
74
+ /**
75
+ * Remove lines matching the old ouro PATH block from shell profile content.
76
+ * Returns the cleaned content.
77
+ */
78
+ function removeOldPathBlock(content, oldBinDir) {
79
+ const lines = content.split("\n");
80
+ const result = [];
81
+ let i = 0;
82
+ while (i < lines.length) {
83
+ // Detect "# Added by ouro" followed by a PATH export containing the old binDir
84
+ if (lines[i].trim() === "# Added by ouro" && i + 1 < lines.length && lines[i + 1].includes(oldBinDir)) {
85
+ // Skip both lines (comment + export)
86
+ i += 2;
87
+ // Also skip trailing blank line if present
88
+ /* v8 ignore next -- edge: trailing blank line presence varies @preserve */
89
+ if (i < lines.length && lines[i].trim() === "")
90
+ i++;
91
+ continue;
92
+ }
93
+ result.push(lines[i]);
94
+ i++;
95
+ }
96
+ return result.join("\n");
97
+ }
70
98
  function installOuroCommand(deps = {}) {
71
99
  /* v8 ignore start -- dep defaults: only used in real runtime, tests always inject @preserve */
72
100
  const platform = deps.platform ?? process.platform;
@@ -77,6 +105,9 @@ function installOuroCommand(deps = {}) {
77
105
  const readFileSync = deps.readFileSync ?? ((p, enc) => fs.readFileSync(p, enc));
78
106
  const appendFileSync = deps.appendFileSync ?? fs.appendFileSync;
79
107
  const chmodSync = deps.chmodSync ?? fs.chmodSync;
108
+ const unlinkSync = deps.unlinkSync ?? fs.unlinkSync;
109
+ const rmdirSync = deps.rmdirSync ?? fs.rmdirSync;
110
+ const readdirSync = deps.readdirSync ?? ((p) => fs.readdirSync(p).map(String));
80
111
  const envPath = deps.envPath ?? process.env.PATH ?? "";
81
112
  const shell = deps.shell ?? process.env.SHELL;
82
113
  /* v8 ignore stop */
@@ -87,10 +118,61 @@ function installOuroCommand(deps = {}) {
87
118
  message: "skipped ouro PATH install on Windows",
88
119
  meta: { platform },
89
120
  });
90
- return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows" };
121
+ return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows", migratedFromOldPath: false };
91
122
  }
92
- const binDir = path.join(homeDir, ".local", "bin");
123
+ // Ensure ~/.ouro-cli/ directory layout exists
124
+ if (deps.ensureCliLayout) {
125
+ deps.ensureCliLayout();
126
+ }
127
+ const binDir = path.join(homeDir, ".ouro-cli", "bin");
93
128
  const scriptPath = path.join(binDir, "ouro");
129
+ // ── Migration from old ~/.local/bin/ouro ──
130
+ const oldBinDir = path.join(homeDir, ".local", "bin");
131
+ const oldScriptPath = path.join(oldBinDir, "ouro");
132
+ let migratedFromOldPath = false;
133
+ if (existsSync(oldScriptPath)) {
134
+ (0, runtime_1.emitNervesEvent)({
135
+ component: "daemon",
136
+ event: "daemon.ouro_path_migrate_start",
137
+ message: "migrating ouro from old PATH location",
138
+ meta: { oldScriptPath },
139
+ });
140
+ try {
141
+ unlinkSync(oldScriptPath);
142
+ migratedFromOldPath = true;
143
+ // Remove empty ~/.local/bin/ directory
144
+ if (existsSync(oldBinDir)) {
145
+ try {
146
+ const remaining = readdirSync(oldBinDir);
147
+ if (remaining.length === 0) {
148
+ rmdirSync(oldBinDir);
149
+ }
150
+ }
151
+ catch {
152
+ // Best effort cleanup
153
+ }
154
+ }
155
+ }
156
+ catch {
157
+ // Best effort migration — continue with new install
158
+ }
159
+ // Remove old PATH entry from shell profile
160
+ const profilePath = detectShellProfile(homeDir, shell);
161
+ /* v8 ignore start -- profile cleanup: only fires during migration from old layout @preserve */
162
+ if (profilePath) {
163
+ try {
164
+ const profileContent = readFileSync(profilePath, "utf-8");
165
+ if (profileContent.includes(oldBinDir)) {
166
+ const cleaned = removeOldPathBlock(profileContent, oldBinDir);
167
+ writeFileSync(profilePath, cleaned);
168
+ }
169
+ }
170
+ catch {
171
+ // Best effort profile cleanup
172
+ }
173
+ }
174
+ /* v8 ignore stop */
175
+ }
94
176
  (0, runtime_1.emitNervesEvent)({
95
177
  component: "daemon",
96
178
  event: "daemon.ouro_path_install_start",
@@ -113,7 +195,7 @@ function installOuroCommand(deps = {}) {
113
195
  message: "ouro command already installed",
114
196
  meta: { scriptPath },
115
197
  });
116
- return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed" };
198
+ return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed", migratedFromOldPath };
117
199
  }
118
200
  // Content is stale — repair by overwriting
119
201
  (0, runtime_1.emitNervesEvent)({
@@ -136,9 +218,9 @@ function installOuroCommand(deps = {}) {
136
218
  message: "failed to install ouro command",
137
219
  meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
138
220
  });
139
- return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error) };
221
+ return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error), migratedFromOldPath };
140
222
  }
141
- // Check if ~/.local/bin is already in PATH
223
+ // Check if ~/.ouro-cli/bin is already in PATH
142
224
  let shellProfileUpdated = null;
143
225
  const pathReady = isBinDirInPath(binDir, envPath);
144
226
  if (!pathReady) {
@@ -174,5 +256,5 @@ function installOuroCommand(deps = {}) {
174
256
  message: "ouro command installed",
175
257
  meta: { scriptPath, pathReady, shellProfileUpdated },
176
258
  });
177
- return { installed: true, scriptPath, pathReady, shellProfileUpdated };
259
+ return { installed: true, scriptPath, pathReady, shellProfileUpdated, migratedFromOldPath };
178
260
  }
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.getOuroCliHome = getOuroCliHome;
37
+ exports.getCurrentVersion = getCurrentVersion;
38
+ exports.getPreviousVersion = getPreviousVersion;
39
+ exports.listInstalledVersions = listInstalledVersions;
40
+ exports.installVersion = installVersion;
41
+ exports.activateVersion = activateVersion;
42
+ exports.ensureLayout = ensureLayout;
43
+ const fs = __importStar(require("fs"));
44
+ const os = __importStar(require("os"));
45
+ const path = __importStar(require("path"));
46
+ const runtime_1 = require("../../nerves/runtime");
47
+ function getOuroCliHome(homeDir) {
48
+ /* v8 ignore next -- dep default: tests always inject @preserve */
49
+ const home = homeDir ?? os.homedir();
50
+ return path.join(home, ".ouro-cli");
51
+ }
52
+ function getCurrentVersion(deps) {
53
+ const cliHome = getOuroCliHome(deps.homeDir);
54
+ /* v8 ignore next -- dep default: tests always inject @preserve */
55
+ const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
56
+ try {
57
+ const target = readlinkSync(path.join(cliHome, "CurrentVersion"));
58
+ return path.basename(target);
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ function getPreviousVersion(deps) {
65
+ const cliHome = getOuroCliHome(deps.homeDir);
66
+ /* v8 ignore next -- dep default: tests always inject @preserve */
67
+ const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
68
+ try {
69
+ const target = readlinkSync(path.join(cliHome, "previous"));
70
+ return path.basename(target);
71
+ }
72
+ catch {
73
+ return null;
74
+ }
75
+ }
76
+ function listInstalledVersions(deps) {
77
+ const cliHome = getOuroCliHome(deps.homeDir);
78
+ /* v8 ignore next -- dep default: tests always inject @preserve */
79
+ const readdirSync = deps.readdirSync ?? ((p, opts) => fs.readdirSync(p, opts));
80
+ try {
81
+ const entries = readdirSync(path.join(cliHome, "versions"), { withFileTypes: true });
82
+ return entries.filter((e) => e.isDirectory()).map((e) => e.name);
83
+ }
84
+ catch {
85
+ return [];
86
+ }
87
+ }
88
+ function installVersion(version, deps) {
89
+ const cliHome = getOuroCliHome(deps.homeDir);
90
+ /* v8 ignore start -- dep defaults: tests always inject @preserve */
91
+ const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
92
+ const execSync = deps.execSync ?? ((cmd, opts) => require("child_process").execSync(cmd, opts));
93
+ /* v8 ignore stop */
94
+ const versionDir = path.join(cliHome, "versions", version);
95
+ (0, runtime_1.emitNervesEvent)({
96
+ component: "daemon",
97
+ event: "daemon.cli_version_install_start",
98
+ message: "installing CLI version",
99
+ meta: { version, versionDir },
100
+ });
101
+ mkdirSync(versionDir, { recursive: true });
102
+ execSync(`npm install --prefix ${versionDir} @ouro.bot/cli@${version}`, { stdio: "pipe" });
103
+ (0, runtime_1.emitNervesEvent)({
104
+ component: "daemon",
105
+ event: "daemon.cli_version_install_end",
106
+ message: "CLI version installed",
107
+ meta: { version, versionDir },
108
+ });
109
+ }
110
+ function activateVersion(version, deps) {
111
+ const cliHome = getOuroCliHome(deps.homeDir);
112
+ /* v8 ignore start -- dep defaults: tests always inject @preserve */
113
+ const readlinkSync = deps.readlinkSync ?? fs.readlinkSync;
114
+ const unlinkSync = deps.unlinkSync ?? fs.unlinkSync;
115
+ const symlinkSync = deps.symlinkSync ?? fs.symlinkSync;
116
+ const existsSync = deps.existsSync ?? fs.existsSync;
117
+ /* v8 ignore stop */
118
+ const currentVersionPath = path.join(cliHome, "CurrentVersion");
119
+ const previousPath = path.join(cliHome, "previous");
120
+ const newTarget = path.join(cliHome, "versions", version);
121
+ (0, runtime_1.emitNervesEvent)({
122
+ component: "daemon",
123
+ event: "daemon.cli_version_activate",
124
+ message: "activating CLI version",
125
+ meta: { version },
126
+ });
127
+ // Read old CurrentVersion target (may not exist)
128
+ let oldTarget = null;
129
+ try {
130
+ oldTarget = readlinkSync(currentVersionPath);
131
+ }
132
+ catch {
133
+ // No current version — first install
134
+ }
135
+ // Update previous symlink to point to old current
136
+ if (oldTarget) {
137
+ try {
138
+ unlinkSync(previousPath);
139
+ }
140
+ catch {
141
+ // previous symlink may not exist yet
142
+ }
143
+ symlinkSync(oldTarget, previousPath);
144
+ }
145
+ // Update CurrentVersion symlink
146
+ if (existsSync(currentVersionPath)) {
147
+ unlinkSync(currentVersionPath);
148
+ }
149
+ symlinkSync(newTarget, currentVersionPath);
150
+ }
151
+ function ensureLayout(deps) {
152
+ const cliHome = getOuroCliHome(deps.homeDir);
153
+ /* v8 ignore next -- dep default: tests always inject @preserve */
154
+ const mkdirSync = deps.mkdirSync ?? fs.mkdirSync;
155
+ mkdirSync(cliHome, { recursive: true });
156
+ mkdirSync(path.join(cliHome, "bin"), { recursive: true });
157
+ mkdirSync(path.join(cliHome, "versions"), { recursive: true });
158
+ (0, runtime_1.emitNervesEvent)({
159
+ component: "daemon",
160
+ event: "daemon.cli_layout_ensured",
161
+ message: "CLI directory layout ensured",
162
+ meta: { cliHome },
163
+ });
164
+ }
@@ -192,6 +192,8 @@ my bones give me the \`ouro\` cli. always pass \`--agent ${agentName}\`:
192
192
  ouro auth switch --agent ${agentName} --provider <provider>
193
193
  ouro mcp list --agent ${agentName}
194
194
  ouro mcp call --agent ${agentName} <server> <tool> --args '{...}'
195
+ ouro versions --agent ${agentName}
196
+ ouro rollback --agent ${agentName} [<version>]
195
197
  ouro --help
196
198
 
197
199
  provider/model changes via \`ouro config model\` or \`ouro auth switch\` take effect on the next turn automatically — no restart needed.`;
@@ -174,6 +174,8 @@ exports.OURO_CLI_TRUST_MANIFEST = {
174
174
  auth: "family",
175
175
  "auth verify": "family",
176
176
  "auth switch": "family",
177
+ rollback: "family",
178
+ versions: "acquaintance",
177
179
  };
178
180
  // --- trust level comparison ---
179
181
  const LEVEL_ORDER = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.78",
3
+ "version": "0.1.0-alpha.79",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",