@oh-my-pi/pi-coding-agent 13.6.0 → 13.6.1
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.md +7 -0
- package/package.json +7 -7
- package/src/cli/update-cli.ts +127 -67
- package/src/config/model-registry.ts +1 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [13.6.1] - 2026-03-03
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Fixed `omp update` silently succeeding without actually updating the binary when the update channel (bun global vs compiled binary) doesn't match the installation method ([#247](https://github.com/can1357/oh-my-pi/issues/247))
|
|
10
|
+
- Added post-update verification that checks the resolved `omp` binary reports the expected version, with actionable warnings on mismatch
|
|
11
|
+
- `omp update` now detects when the `omp` in PATH is not managed by bun and falls back to binary replacement instead of updating the wrong location
|
|
5
12
|
## [13.6.0] - 2026-03-03
|
|
6
13
|
### Added
|
|
7
14
|
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
4
|
-
"version": "13.6.
|
|
4
|
+
"version": "13.6.1",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/can1357/oh-my-pi",
|
|
7
7
|
"author": "Can Boluk",
|
|
@@ -41,12 +41,12 @@
|
|
|
41
41
|
},
|
|
42
42
|
"dependencies": {
|
|
43
43
|
"@mozilla/readability": "^0.6",
|
|
44
|
-
"@oh-my-pi/omp-stats": "13.6.
|
|
45
|
-
"@oh-my-pi/pi-agent-core": "13.6.
|
|
46
|
-
"@oh-my-pi/pi-ai": "13.6.
|
|
47
|
-
"@oh-my-pi/pi-natives": "13.6.
|
|
48
|
-
"@oh-my-pi/pi-tui": "13.6.
|
|
49
|
-
"@oh-my-pi/pi-utils": "13.6.
|
|
44
|
+
"@oh-my-pi/omp-stats": "13.6.1",
|
|
45
|
+
"@oh-my-pi/pi-agent-core": "13.6.1",
|
|
46
|
+
"@oh-my-pi/pi-ai": "13.6.1",
|
|
47
|
+
"@oh-my-pi/pi-natives": "13.6.1",
|
|
48
|
+
"@oh-my-pi/pi-tui": "13.6.1",
|
|
49
|
+
"@oh-my-pi/pi-utils": "13.6.1",
|
|
50
50
|
"@sinclair/typebox": "^0.34",
|
|
51
51
|
"@xterm/headless": "^6.0",
|
|
52
52
|
"ajv": "^8.18",
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -4,29 +4,20 @@
|
|
|
4
4
|
* Handles `omp update` to check for and install updates.
|
|
5
5
|
* Uses bun if available, otherwise downloads binary from GitHub releases.
|
|
6
6
|
*/
|
|
7
|
-
import { execSync, spawnSync } from "node:child_process";
|
|
8
7
|
import * as fs from "node:fs";
|
|
8
|
+
import * as path from "node:path";
|
|
9
9
|
import { pipeline } from "node:stream/promises";
|
|
10
10
|
import { APP_NAME, isEnoent, VERSION } from "@oh-my-pi/pi-utils";
|
|
11
|
+
import { $ } from "bun";
|
|
11
12
|
import chalk from "chalk";
|
|
12
13
|
import { theme } from "../modes/theme/theme";
|
|
13
14
|
|
|
14
|
-
/**
|
|
15
|
-
* Detect if we're running as a Bun compiled binary.
|
|
16
|
-
*/
|
|
17
|
-
const isBunBinary =
|
|
18
|
-
Bun.env.PI_COMPILED ||
|
|
19
|
-
import.meta.url.includes("$bunfs") ||
|
|
20
|
-
import.meta.url.includes("~BUN") ||
|
|
21
|
-
import.meta.url.includes("%7EBUN");
|
|
22
|
-
|
|
23
15
|
const REPO = "can1357/oh-my-pi";
|
|
24
16
|
const PACKAGE = "@oh-my-pi/pi-coding-agent";
|
|
25
17
|
|
|
26
18
|
interface ReleaseInfo {
|
|
27
19
|
tag: string;
|
|
28
20
|
version: string;
|
|
29
|
-
assets: Array<{ name: string; url: string }>;
|
|
30
21
|
}
|
|
31
22
|
|
|
32
23
|
/**
|
|
@@ -44,18 +35,59 @@ export function parseUpdateArgs(args: string[]): { force: boolean; check: boolea
|
|
|
44
35
|
};
|
|
45
36
|
}
|
|
46
37
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
*/
|
|
50
|
-
function hasBun(): boolean {
|
|
38
|
+
async function getBunGlobalBinDir(): Promise<string | undefined> {
|
|
39
|
+
if (!Bun.which("bun")) return undefined;
|
|
51
40
|
try {
|
|
52
|
-
const result =
|
|
53
|
-
|
|
41
|
+
const result = await $`bun pm bin -g`.quiet().nothrow();
|
|
42
|
+
if (result.exitCode !== 0) return undefined;
|
|
43
|
+
const output = result.text().trim();
|
|
44
|
+
return output.length > 0 ? output : undefined;
|
|
54
45
|
} catch {
|
|
55
|
-
return
|
|
46
|
+
return undefined;
|
|
56
47
|
}
|
|
57
48
|
}
|
|
58
49
|
|
|
50
|
+
function getRealPathOrOriginal(filePath: string): string {
|
|
51
|
+
try {
|
|
52
|
+
return fs.realpathSync(filePath);
|
|
53
|
+
} catch {
|
|
54
|
+
return filePath;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function normalizePathForComparison(filePath: string): string {
|
|
59
|
+
const normalized = path.normalize(filePath);
|
|
60
|
+
if (process.platform === "win32") return normalized.toLowerCase();
|
|
61
|
+
return normalized;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isPathInDirectory(filePath: string, directoryPath: string): boolean {
|
|
65
|
+
const normalizedPath = normalizePathForComparison(getRealPathOrOriginal(filePath));
|
|
66
|
+
const normalizedDirectory = normalizePathForComparison(getRealPathOrOriginal(directoryPath));
|
|
67
|
+
const relativePath = path.relative(normalizedDirectory, normalizedPath);
|
|
68
|
+
return relativePath === "" || (!relativePath.startsWith("..") && !path.isAbsolute(relativePath));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
interface UpdateTarget {
|
|
72
|
+
method: "bun" | "binary";
|
|
73
|
+
path: string;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function resolveUpdateMethod(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
|
|
77
|
+
if (!bunBinDir) return "binary";
|
|
78
|
+
return isPathInDirectory(ompPath, bunBinDir) ? "bun" : "binary";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function _resolveUpdateMethodForTest(ompPath: string, bunBinDir: string | undefined): "bun" | "binary" {
|
|
82
|
+
return resolveUpdateMethod(ompPath, bunBinDir);
|
|
83
|
+
}
|
|
84
|
+
async function resolveUpdateTarget(): Promise<UpdateTarget> {
|
|
85
|
+
const ompPath = resolveOmpPath() ?? process.execPath;
|
|
86
|
+
const bunBinDir = await getBunGlobalBinDir();
|
|
87
|
+
const method = resolveUpdateMethod(ompPath, bunBinDir);
|
|
88
|
+
return { method, path: ompPath };
|
|
89
|
+
}
|
|
90
|
+
|
|
59
91
|
/**
|
|
60
92
|
* Get the latest release info from the npm registry.
|
|
61
93
|
* Uses npm instead of GitHub API to avoid unauthenticated rate limiting.
|
|
@@ -70,15 +102,9 @@ async function getLatestRelease(): Promise<ReleaseInfo> {
|
|
|
70
102
|
const version = data.version;
|
|
71
103
|
const tag = `v${version}`;
|
|
72
104
|
|
|
73
|
-
// Construct deterministic GitHub release download URLs for the current platform
|
|
74
|
-
const makeAsset = (name: string) => ({
|
|
75
|
-
name,
|
|
76
|
-
url: `https://github.com/${REPO}/releases/download/${tag}/${name}`,
|
|
77
|
-
});
|
|
78
105
|
return {
|
|
79
106
|
tag,
|
|
80
107
|
version,
|
|
81
|
-
assets: [makeAsset(getBinaryName())],
|
|
82
108
|
};
|
|
83
109
|
}
|
|
84
110
|
|
|
@@ -140,60 +166,93 @@ function getBinaryName(): string {
|
|
|
140
166
|
return `${APP_NAME}-${os}-${archName}`;
|
|
141
167
|
}
|
|
142
168
|
|
|
169
|
+
/**
|
|
170
|
+
* Resolve the path that `omp` maps to in the user's PATH.
|
|
171
|
+
*/
|
|
172
|
+
function resolveOmpPath(): string | undefined {
|
|
173
|
+
return Bun.which(APP_NAME) ?? undefined;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Run the resolved omp binary and check if it reports the expected version.
|
|
178
|
+
*/
|
|
179
|
+
async function verifyInstalledVersion(
|
|
180
|
+
expectedVersion: string,
|
|
181
|
+
): Promise<{ ok: boolean; actual?: string; path?: string }> {
|
|
182
|
+
const ompPath = resolveOmpPath();
|
|
183
|
+
if (!ompPath) return { ok: false };
|
|
184
|
+
try {
|
|
185
|
+
const result = await $`${ompPath} --version`.quiet().nothrow();
|
|
186
|
+
if (result.exitCode !== 0) return { ok: false, path: ompPath };
|
|
187
|
+
const output = result.text().trim();
|
|
188
|
+
// Output format: "omp/X.Y.Z"
|
|
189
|
+
const match = output.match(/\/(\d+\.\d+\.\d+)/);
|
|
190
|
+
const actual = match?.[1];
|
|
191
|
+
return { ok: actual === expectedVersion, actual, path: ompPath };
|
|
192
|
+
} catch {
|
|
193
|
+
return { ok: false, path: ompPath };
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Print post-update verification result.
|
|
199
|
+
*/
|
|
200
|
+
async function printVerification(expectedVersion: string): Promise<void> {
|
|
201
|
+
const result = await verifyInstalledVersion(expectedVersion);
|
|
202
|
+
if (result.ok) {
|
|
203
|
+
console.log(chalk.green(`\n${theme.status.success} Updated to ${expectedVersion}`));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
if (result.actual) {
|
|
207
|
+
console.log(
|
|
208
|
+
chalk.yellow(
|
|
209
|
+
`\nWarning: ${APP_NAME} at ${result.path} still reports ${result.actual} (expected ${expectedVersion})`,
|
|
210
|
+
),
|
|
211
|
+
);
|
|
212
|
+
} else {
|
|
213
|
+
console.log(
|
|
214
|
+
chalk.yellow(`\nWarning: could not verify updated version${result.path ? ` at ${result.path}` : ""}`),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
console.log(
|
|
218
|
+
chalk.yellow(
|
|
219
|
+
`You may need to reinstall: curl -fsSL https://raw.githubusercontent.com/${REPO}/main/install.sh | bash`,
|
|
220
|
+
),
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
|
|
143
224
|
/**
|
|
144
225
|
* Update via bun package manager.
|
|
145
226
|
*/
|
|
146
227
|
async function updateViaBun(expectedVersion: string): Promise<void> {
|
|
147
228
|
console.log(chalk.dim("Updating via bun..."));
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
throw new Error("bun install failed", { cause: error });
|
|
229
|
+
const result = await $`bun install -g ${PACKAGE}@${expectedVersion}`.nothrow();
|
|
230
|
+
if (result.exitCode !== 0) {
|
|
231
|
+
throw new Error(`bun install failed with exit code ${result.exitCode}`);
|
|
152
232
|
}
|
|
153
233
|
|
|
154
|
-
|
|
155
|
-
try {
|
|
156
|
-
const result = spawnSync("bun", ["pm", "ls", "-g"], { encoding: "utf-8", stdio: "pipe" });
|
|
157
|
-
const output = result.stdout || "";
|
|
158
|
-
const match = output.match(new RegExp(`${PACKAGE.replace("/", "\\/")}@(\\S+)`));
|
|
159
|
-
if (match) {
|
|
160
|
-
const installedVersion = match[1];
|
|
161
|
-
if (compareVersions(installedVersion, expectedVersion) < 0) {
|
|
162
|
-
console.log(
|
|
163
|
-
chalk.yellow(`\nWarning: bun reports ${installedVersion} installed, expected ${expectedVersion}`),
|
|
164
|
-
);
|
|
165
|
-
console.log(chalk.yellow(`Try: bun install -g ${PACKAGE}@latest`));
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
} catch {
|
|
170
|
-
// Verification is best-effort, don't fail the update
|
|
171
|
-
}
|
|
172
|
-
console.log(chalk.green(`\n${theme.status.success} Update complete`));
|
|
234
|
+
await printVerification(expectedVersion);
|
|
173
235
|
}
|
|
174
236
|
|
|
175
237
|
/**
|
|
176
|
-
*
|
|
238
|
+
* Download a release binary to a target path, replacing an existing file.
|
|
177
239
|
*/
|
|
178
|
-
async function
|
|
240
|
+
async function updateViaBinaryAt(targetPath: string, expectedVersion: string): Promise<void> {
|
|
179
241
|
const binaryName = getBinaryName();
|
|
180
|
-
const
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
}
|
|
184
|
-
const
|
|
185
|
-
const tempPath = `${execPath}.new`;
|
|
186
|
-
const backupPath = `${execPath}.bak`;
|
|
242
|
+
const tag = `v${expectedVersion}`;
|
|
243
|
+
const url = `https://github.com/${REPO}/releases/download/${tag}/${binaryName}`;
|
|
244
|
+
|
|
245
|
+
const tempPath = `${targetPath}.new`;
|
|
246
|
+
const backupPath = `${targetPath}.bak`;
|
|
187
247
|
console.log(chalk.dim(`Downloading ${binaryName}…`));
|
|
188
248
|
|
|
189
|
-
|
|
190
|
-
const response = await fetch(asset.url, { redirect: "follow" });
|
|
249
|
+
const response = await fetch(url, { redirect: "follow" });
|
|
191
250
|
if (!response.ok || !response.body) {
|
|
192
251
|
throw new Error(`Download failed: ${response.statusText}`);
|
|
193
252
|
}
|
|
194
253
|
const fileStream = fs.createWriteStream(tempPath, { mode: 0o755 });
|
|
195
254
|
await pipeline(response.body, fileStream);
|
|
196
|
-
|
|
255
|
+
|
|
197
256
|
console.log(chalk.dim("Installing update..."));
|
|
198
257
|
try {
|
|
199
258
|
try {
|
|
@@ -201,15 +260,15 @@ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
|
|
|
201
260
|
} catch (err) {
|
|
202
261
|
if (!isEnoent(err)) throw err;
|
|
203
262
|
}
|
|
204
|
-
await fs.promises.rename(
|
|
205
|
-
await fs.promises.rename(tempPath,
|
|
263
|
+
await fs.promises.rename(targetPath, backupPath);
|
|
264
|
+
await fs.promises.rename(tempPath, targetPath);
|
|
206
265
|
await fs.promises.unlink(backupPath);
|
|
207
266
|
|
|
208
|
-
|
|
267
|
+
await printVerification(expectedVersion);
|
|
209
268
|
console.log(chalk.dim(`Restart ${APP_NAME} to use the new version`));
|
|
210
269
|
} catch (err) {
|
|
211
|
-
if (fs.existsSync(backupPath) && !fs.existsSync(
|
|
212
|
-
await fs.promises.rename(backupPath,
|
|
270
|
+
if (fs.existsSync(backupPath) && !fs.existsSync(targetPath)) {
|
|
271
|
+
await fs.promises.rename(backupPath, targetPath);
|
|
213
272
|
}
|
|
214
273
|
if (fs.existsSync(tempPath)) {
|
|
215
274
|
await fs.promises.unlink(tempPath);
|
|
@@ -251,12 +310,13 @@ export async function runUpdateCommand(opts: { force: boolean; check: boolean })
|
|
|
251
310
|
return;
|
|
252
311
|
}
|
|
253
312
|
|
|
254
|
-
// Choose update method
|
|
313
|
+
// Choose update method based on the prioritized omp binary in PATH
|
|
255
314
|
try {
|
|
256
|
-
|
|
315
|
+
const target = await resolveUpdateTarget();
|
|
316
|
+
if (target.method === "bun") {
|
|
257
317
|
await updateViaBun(release.version);
|
|
258
318
|
} else {
|
|
259
|
-
await
|
|
319
|
+
await updateViaBinaryAt(target.path, release.version);
|
|
260
320
|
}
|
|
261
321
|
} catch (err) {
|
|
262
322
|
console.error(chalk.red(`Update failed: ${err}`));
|
|
@@ -6,13 +6,11 @@ import {
|
|
|
6
6
|
DEFAULT_LOCAL_TOKEN,
|
|
7
7
|
getBundledModels,
|
|
8
8
|
getBundledProviders,
|
|
9
|
-
getGitHubCopilotBaseUrl,
|
|
10
9
|
googleAntigravityModelManagerOptions,
|
|
11
10
|
googleGeminiCliModelManagerOptions,
|
|
12
11
|
type Model,
|
|
13
12
|
type ModelManagerOptions,
|
|
14
13
|
type ModelRefreshStrategy,
|
|
15
|
-
normalizeDomain,
|
|
16
14
|
type OAuthCredentials,
|
|
17
15
|
type OAuthLoginCallbacks,
|
|
18
16
|
openaiCodexModelManagerOptions,
|
|
@@ -539,17 +537,7 @@ export class ModelRegistry {
|
|
|
539
537
|
const builtInModels = this.#loadBuiltInModels(overrides, modelOverrides);
|
|
540
538
|
const combined = this.#mergeCustomModels(builtInModels, customModels);
|
|
541
539
|
|
|
542
|
-
|
|
543
|
-
const copilotCred = this.authStorage.getOAuthCredential("github-copilot");
|
|
544
|
-
if (copilotCred) {
|
|
545
|
-
const domain = copilotCred.enterpriseUrl
|
|
546
|
-
? (normalizeDomain(copilotCred.enterpriseUrl) ?? undefined)
|
|
547
|
-
: undefined;
|
|
548
|
-
const baseUrl = getGitHubCopilotBaseUrl(copilotCred.access, domain);
|
|
549
|
-
this.#models = combined.map(m => (m.provider === "github-copilot" ? { ...m, baseUrl } : m));
|
|
550
|
-
} else {
|
|
551
|
-
this.#models = combined;
|
|
552
|
-
}
|
|
540
|
+
this.#models = combined;
|
|
553
541
|
}
|
|
554
542
|
|
|
555
543
|
/** Load built-in models, applying provider and per-model overrides */
|