@ouro.bot/cli 0.1.0-alpha.110 → 0.1.0-alpha.112

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,18 @@
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.112",
6
+ "changes": [
7
+ "`ouro up` no longer prints the 'ouro updated to X (was Y)' message twice when the update flow re-execs from a newly installed version."
8
+ ]
9
+ },
10
+ {
11
+ "version": "0.1.0-alpha.111",
12
+ "changes": [
13
+ "The PATH installer now repairs a stale ~/.local/bin/ouro launcher in place instead of deleting it, so a shadowing old wrapper can no longer cause the wrong CLI version to load and produce backwards 'ouro updated to X (was Y)' messages."
14
+ ]
15
+ },
4
16
  {
5
17
  "version": "0.1.0-alpha.110",
6
18
  "changes": [
@@ -1484,9 +1484,9 @@ async function performSystemSetup(deps) {
1484
1484
  if (deps.installOuroCommand) {
1485
1485
  try {
1486
1486
  const installResult = deps.installOuroCommand();
1487
- /* v8 ignore next -- migration hint: only fires once during old→new layout migration @preserve */
1488
- if (installResult.migratedFromOldPath) {
1489
- deps.writeStdout("migrated ouro to ~/.ouro-cli/ open a new terminal or run: source ~/.zshrc");
1487
+ /* v8 ignore next -- old-launcher repair hint: fires when stale ~/.local/bin/ouro is fixed @preserve */
1488
+ if (installResult.repairedOldLauncher) {
1489
+ deps.writeStdout("repaired stale ouro launcher at ~/.local/bin/ouro");
1490
1490
  }
1491
1491
  }
1492
1492
  catch (error) {
@@ -1884,9 +1884,12 @@ async function runOuroCli(args, deps = createDefaultOuroCliDeps()) {
1884
1884
  // hooks overwrite it. This detects when npx downloaded a newer CLI.
1885
1885
  const previousCliVersion = readFirstBundleMetaVersion(bundlesRoot);
1886
1886
  const updateSummary = await (0, update_hooks_1.applyPendingUpdates)(bundlesRoot, currentVersion);
1887
- // Notify about CLI binary update (npx downloaded a new version)
1887
+ // Notify about CLI binary update (npx downloaded a new version).
1888
+ // Skip when the symlink already points to the running version — that
1889
+ // means path 1 (checkForCliUpdate + reExecFromNewVersion) already
1890
+ // printed the update message before re-exec.
1888
1891
  /* v8 ignore start -- CLI update detection: tested via daemon-cli-version-detect.test.ts @preserve */
1889
- if (previousCliVersion && previousCliVersion !== currentVersion) {
1892
+ if (previousCliVersion && previousCliVersion !== currentVersion && linkedVersionBeforeUp !== currentVersion) {
1890
1893
  deps.writeStdout(`ouro updated to ${currentVersion} (was ${previousCliVersion})`);
1891
1894
  const changelogCommand = (0, ouro_version_manager_1.buildChangelogCommand)(previousCliVersion, currentVersion);
1892
1895
  /* v8 ignore next -- buildChangelogCommand is non-null when previous/current runtime versions differ @preserve */
@@ -71,29 +71,15 @@ function buildPathExportLine(binDir, shell) {
71
71
  }
72
72
  return `\n# Added by ouro\nexport PATH="${binDir}:$PATH"\n`;
73
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++;
74
+ function isWrapperCurrent(scriptPath, existsSync, readFileSync) {
75
+ if (!existsSync(scriptPath))
76
+ return false;
77
+ try {
78
+ return readFileSync(scriptPath, "utf-8") === WRAPPER_SCRIPT;
79
+ }
80
+ catch {
81
+ return false;
95
82
  }
96
- return result.join("\n");
97
83
  }
98
84
  function installOuroCommand(deps = {}) {
99
85
  /* v8 ignore start -- dep defaults: only used in real runtime, tests always inject @preserve */
@@ -105,9 +91,6 @@ function installOuroCommand(deps = {}) {
105
91
  const readFileSync = deps.readFileSync ?? ((p, enc) => fs.readFileSync(p, enc));
106
92
  const appendFileSync = deps.appendFileSync ?? fs.appendFileSync;
107
93
  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));
111
94
  const envPath = deps.envPath ?? process.env.PATH ?? "";
112
95
  const shell = deps.shell ?? process.env.SHELL;
113
96
  /* v8 ignore stop */
@@ -118,7 +101,7 @@ function installOuroCommand(deps = {}) {
118
101
  message: "skipped ouro PATH install on Windows",
119
102
  meta: { platform },
120
103
  });
121
- return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows", migratedFromOldPath: false };
104
+ return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: "windows", repairedOldLauncher: false };
122
105
  }
123
106
  // Ensure ~/.ouro-cli/ directory layout exists
124
107
  if (deps.ensureCliLayout) {
@@ -126,85 +109,47 @@ function installOuroCommand(deps = {}) {
126
109
  }
127
110
  const binDir = path.join(homeDir, ".ouro-cli", "bin");
128
111
  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)) {
112
+ const oldScriptPath = path.join(homeDir, ".local", "bin", "ouro");
113
+ const modernCurrent = isWrapperCurrent(scriptPath, existsSync, readFileSync);
114
+ const oldExists = existsSync(oldScriptPath);
115
+ const oldCurrent = oldExists && isWrapperCurrent(oldScriptPath, existsSync, readFileSync);
116
+ // ── Repair old ~/.local/bin/ouro launcher ──
117
+ // If the old launcher exists with stale content it can shadow the modern
118
+ // path and cause the wrong CLI version to run. Overwrite it with the
119
+ // current wrapper so both paths resolve to ~/.ouro-cli/CurrentVersion.
120
+ let repairedOldLauncher = false;
121
+ if (oldExists && !oldCurrent) {
134
122
  (0, runtime_1.emitNervesEvent)({
135
123
  component: "daemon",
136
- event: "daemon.ouro_path_migrate_start",
137
- message: "migrating ouro from old PATH location",
124
+ event: "daemon.ouro_path_repair_old",
125
+ message: "repairing stale old launcher at ~/.local/bin/ouro",
138
126
  meta: { oldScriptPath },
139
127
  });
140
128
  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
- }
129
+ writeFileSync(oldScriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
130
+ chmodSync(oldScriptPath, 0o755);
131
+ repairedOldLauncher = true;
155
132
  }
156
133
  catch {
157
- // Best effort migration continue with new install
134
+ // Best effort — old launcher repair failure must not block modern install
158
135
  }
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
136
  }
176
- (0, runtime_1.emitNervesEvent)({
177
- component: "daemon",
178
- event: "daemon.ouro_path_install_start",
179
- message: "installing ouro command to PATH",
180
- meta: { scriptPath, binDir },
181
- });
182
- // If ouro already exists, check content and repair if stale
183
- if (existsSync(scriptPath)) {
184
- let existingContent = "";
185
- try {
186
- existingContent = readFileSync(scriptPath, "utf-8");
187
- }
188
- catch {
189
- // Can't read — treat as stale, will overwrite below
190
- }
191
- if (existingContent === WRAPPER_SCRIPT) {
192
- (0, runtime_1.emitNervesEvent)({
193
- component: "daemon",
194
- event: "daemon.ouro_path_install_skip",
195
- message: "ouro command already installed",
196
- meta: { scriptPath },
197
- });
198
- return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed", migratedFromOldPath };
199
- }
200
- // Content is stale — repair by overwriting
137
+ // ── Fast-path: modern wrapper already current ──
138
+ if (modernCurrent) {
201
139
  (0, runtime_1.emitNervesEvent)({
202
140
  component: "daemon",
203
- event: "daemon.ouro_path_install_repair",
204
- message: "repairing stale ouro wrapper script",
141
+ event: "daemon.ouro_path_install_skip",
142
+ message: "ouro command already installed",
205
143
  meta: { scriptPath },
206
144
  });
145
+ return { installed: false, scriptPath, pathReady: isBinDirInPath(binDir, envPath), shellProfileUpdated: null, skippedReason: "already-installed", repairedOldLauncher };
207
146
  }
147
+ (0, runtime_1.emitNervesEvent)({
148
+ component: "daemon",
149
+ event: "daemon.ouro_path_install_start",
150
+ message: existsSync(scriptPath) ? "repairing stale ouro wrapper script" : "installing ouro command to PATH",
151
+ meta: { scriptPath, binDir },
152
+ });
208
153
  try {
209
154
  mkdirSync(binDir, { recursive: true });
210
155
  writeFileSync(scriptPath, WRAPPER_SCRIPT, { mode: 0o755 });
@@ -218,7 +163,7 @@ function installOuroCommand(deps = {}) {
218
163
  message: "failed to install ouro command",
219
164
  meta: { error: error instanceof Error ? error.message : /* v8 ignore next -- defensive: non-Error catch branch @preserve */ String(error) },
220
165
  });
221
- return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error), migratedFromOldPath };
166
+ return { installed: false, scriptPath: null, pathReady: false, shellProfileUpdated: null, skippedReason: error instanceof Error ? error.message : /* v8 ignore next -- defensive @preserve */ String(error), repairedOldLauncher };
222
167
  }
223
168
  // Check if ~/.ouro-cli/bin is already in PATH
224
169
  let shellProfileUpdated = null;
@@ -256,5 +201,5 @@ function installOuroCommand(deps = {}) {
256
201
  message: "ouro command installed",
257
202
  meta: { scriptPath, pathReady, shellProfileUpdated },
258
203
  });
259
- return { installed: true, scriptPath, pathReady, shellProfileUpdated, migratedFromOldPath };
204
+ return { installed: true, scriptPath, pathReady, shellProfileUpdated, repairedOldLauncher };
260
205
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ouro.bot/cli",
3
- "version": "0.1.0-alpha.110",
3
+ "version": "0.1.0-alpha.112",
4
4
  "main": "dist/heart/daemon/ouro-entry.js",
5
5
  "bin": {
6
6
  "cli": "dist/heart/daemon/ouro-bot-entry.js",