diff --git a/packages/coding-agent/CHANGELOG.md b/packages/coding-agent/CHANGELOG.md index 540e6aaa..921de6cd 100644 --- a/packages/coding-agent/CHANGELOG.md +++ b/packages/coding-agent/CHANGELOG.md @@ -6,6 +6,7 @@ - RpcClient now forwards subprocess stderr to parent process in real-time ([#2805](https://github.com/badlogic/pi-mono/issues/2805)) - Theme file watcher now handles async `fs.watch` error events instead of crashing the process ([#2791](https://github.com/badlogic/pi-mono/issues/2791)) +- Fixed CLI extension paths like `git:gist.github.com/...` being incorrectly resolved against cwd instead of being passed through to the package manager ([#2845](https://github.com/badlogic/pi-mono/pull/2845) by [@aliou](https://github.com/aliou)) ## [0.65.0] - 2026-04-03 diff --git a/packages/coding-agent/src/core/package-manager.ts b/packages/coding-agent/src/core/package-manager.ts index 39c2751d..10781274 100644 --- a/packages/coding-agent/src/core/package-manager.ts +++ b/packages/coding-agent/src/core/package-manager.ts @@ -7,6 +7,7 @@ import ignore from "ignore"; import { minimatch } from "minimatch"; import { CONFIG_DIR_NAME } from "../config.js"; import { type GitSource, parseGitUrl } from "../utils/git.js"; +import { isLocalPath } from "../utils/paths.js"; import { isStdoutTakenOver } from "./output-guard.js"; import type { PackageSource, SettingsManager } from "./settings-manager.js"; @@ -1258,15 +1259,7 @@ export class DefaultPackageManager implements PackageManager { }; } - const trimmed = source.trim(); - const isWindowsAbsolutePath = /^[A-Za-z]:[\\/]|^\\\\/.test(trimmed); - const isLocalPathLike = - trimmed.startsWith(".") || - trimmed.startsWith("/") || - trimmed === "~" || - trimmed.startsWith("~/") || - isWindowsAbsolutePath; - if (isLocalPathLike) { + if (isLocalPath(source)) { return { type: "local", path: source }; } diff --git a/packages/coding-agent/src/core/resource-loader.ts b/packages/coding-agent/src/core/resource-loader.ts index 9450c9ed..8377eff4 100644 --- a/packages/coding-agent/src/core/resource-loader.ts +++ b/packages/coding-agent/src/core/resource-loader.ts @@ -8,6 +8,7 @@ import type { ResourceDiagnostic } from "./diagnostics.js"; export type { ResourceCollision, ResourceDiagnostic } from "./diagnostics.js"; +import { isLocalPath } from "../utils/paths.js"; import { createEventBus, type EventBus } from "./event-bus.js"; import { createExtensionRuntime, loadExtensionFromFactory, loadExtensions } from "./extensions/loader.js"; import type { Extension, ExtensionFactory, ExtensionRuntime, LoadExtensionsResult } from "./extensions/types.js"; @@ -404,7 +405,7 @@ export class DefaultResourceLoader implements ResourceLoader { } for (const p of this.additionalExtensionPaths) { - if (!existsSync(p)) { + if (isLocalPath(p) && !existsSync(p)) { extensionsResult.errors.push({ path: p, error: `Extension path does not exist: ${p}` }); } } @@ -418,7 +419,7 @@ export class DefaultResourceLoader implements ResourceLoader { this.lastSkillPaths = skillPaths; this.updateSkillsFromPaths(skillPaths, metadataByPath); for (const p of this.additionalSkillPaths) { - if (!existsSync(p) && !this.skillDiagnostics.some((d) => d.path === p)) { + if (isLocalPath(p) && !existsSync(p) && !this.skillDiagnostics.some((d) => d.path === p)) { this.skillDiagnostics.push({ type: "error", message: "Skill path does not exist", path: p }); } } @@ -430,7 +431,7 @@ export class DefaultResourceLoader implements ResourceLoader { this.lastPromptPaths = promptPaths; this.updatePromptsFromPaths(promptPaths, metadataByPath); for (const p of this.additionalPromptTemplatePaths) { - if (!existsSync(p) && !this.promptDiagnostics.some((d) => d.path === p)) { + if (isLocalPath(p) && !existsSync(p) && !this.promptDiagnostics.some((d) => d.path === p)) { this.promptDiagnostics.push({ type: "error", message: "Prompt template path does not exist", path: p }); } } diff --git a/packages/coding-agent/src/main.ts b/packages/coding-agent/src/main.ts index da6d9f5c..30fe779d 100644 --- a/packages/coding-agent/src/main.ts +++ b/packages/coding-agent/src/main.ts @@ -35,6 +35,7 @@ import { runMigrations, showDeprecationWarnings } from "./migrations.js"; import { InteractiveMode, runPrintMode, runRpcMode } from "./modes/index.js"; import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme.js"; import { handleConfigCommand, handlePackageCommand } from "./package-manager-cli.js"; +import { isLocalPath } from "./utils/paths.js"; /** * Read all content from piped stdin. @@ -371,7 +372,7 @@ function buildSessionOptions( } function resolveCliPaths(cwd: string, paths: string[] | undefined): string[] | undefined { - return paths?.map((value) => resolve(cwd, value)); + return paths?.map((value) => (isLocalPath(value) ? resolve(cwd, value) : value)); } export async function main(args: string[]) { diff --git a/packages/coding-agent/src/utils/paths.ts b/packages/coding-agent/src/utils/paths.ts new file mode 100644 index 00000000..4884d15c --- /dev/null +++ b/packages/coding-agent/src/utils/paths.ts @@ -0,0 +1,20 @@ +/** + * Returns true if the value is NOT a package source (npm:, git:, etc.) + * or a URL protocol. Bare names and relative paths without ./ prefix + * are considered local. + */ +export function isLocalPath(value: string): boolean { + const trimmed = value.trim(); + // Known non-local prefixes + if ( + trimmed.startsWith("npm:") || + trimmed.startsWith("git:") || + trimmed.startsWith("github:") || + trimmed.startsWith("http:") || + trimmed.startsWith("https:") || + trimmed.startsWith("ssh:") + ) { + return false; + } + return true; +}