Merge origin/main into dev (second round)

Resolve 21 conflicts from latest main (provider profiles, Windows fixes, 0.1.55-rc.1).
Fix all type errors and syntax issues from merge artifacts.
This commit is contained in:
Mohamed Boudra
2026-04-14 04:07:36 +07:00
249 changed files with 9118 additions and 4973 deletions
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@getpaseo/cli",
"version": "0.1.54",
"version": "0.1.55-rc.1",
"description": "Paseo CLI - control your AI coding agents from the command line",
"type": "module",
"files": [
@@ -24,8 +24,8 @@
},
"dependencies": {
"@clack/prompts": "^1.0.0",
"@getpaseo/relay": "0.1.54",
"@getpaseo/server": "0.1.54",
"@getpaseo/relay": "0.1.55-rc.1",
"@getpaseo/server": "0.1.55-rc.1",
"chalk": "^5.3.0",
"commander": "^12.0.0",
"mime-types": "^2.1.35",
@@ -1,5 +1,5 @@
import { spawnSync } from "node:child_process";
import { platform } from "node:os";
import { execCommand } from "@getpaseo/server";
export interface NodePathFromPidResult {
nodePath: string | null;
@@ -13,63 +13,37 @@ function normalizeError(error: unknown): string {
return String(error);
}
function resolveNodePathFromPidUnix(pid: number): NodePathFromPidResult {
const result = spawnSync("ps", ["-o", "comm=", "-p", String(pid)], {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
if (result.error) {
return { nodePath: null, error: `ps failed: ${normalizeError(result.error)}` };
async function resolveNodePathFromPidUnix(pid: number): Promise<NodePathFromPidResult> {
try {
const { stdout } = await execCommand("ps", ["-o", "comm=", "-p", String(pid)]);
const resolved = stdout.trim();
return resolved
? { nodePath: resolved }
: { nodePath: null, error: "ps returned an empty command path" };
} catch (error) {
return { nodePath: null, error: `ps failed: ${normalizeError(error)}` };
}
if ((result.status ?? 1) !== 0) {
const details = result.stderr?.trim();
return {
nodePath: null,
error: details ? `ps failed: ${details}` : `ps exited with code ${result.status ?? 1}`,
};
}
const resolved = result.stdout.trim();
return resolved
? { nodePath: resolved }
: { nodePath: null, error: "ps returned an empty command path" };
}
function runProcessProbe(
async function runProcessProbe(
command: string,
args: string[],
): {
): Promise<{
resolved: string | null;
error?: string;
} {
const result = spawnSync(command, args, {
encoding: "utf8",
stdio: ["ignore", "pipe", "pipe"],
});
if (result.error) {
return { resolved: null, error: `${command} failed: ${normalizeError(result.error)}` };
}> {
try {
const { stdout } = await execCommand(command, args);
const resolved = stdout.trim();
return resolved
? { resolved }
: { resolved: null, error: `${command} returned no executable path` };
} catch (error) {
return { resolved: null, error: `${command} failed: ${normalizeError(error)}` };
}
if ((result.status ?? 1) !== 0) {
const details = result.stderr?.trim();
return {
resolved: null,
error: details
? `${command} failed: ${details}`
: `${command} exited with code ${result.status ?? 1}`,
};
}
const resolved = result.stdout.trim();
return resolved
? { resolved }
: { resolved: null, error: `${command} returned no executable path` };
}
function resolveNodePathFromPidWindows(pid: number): NodePathFromPidResult {
async function resolveNodePathFromPidWindows(pid: number): Promise<NodePathFromPidResult> {
const probes: Array<{
label: string;
command: string;
@@ -103,7 +77,7 @@ function resolveNodePathFromPidWindows(pid: number): NodePathFromPidResult {
const errors: string[] = [];
for (const probe of probes) {
const result = runProcessProbe(probe.command, probe.args);
const result = await runProcessProbe(probe.command, probe.args);
if (result.resolved) {
const resolved = probe.parseValue ? probe.parseValue(result.resolved) : result.resolved;
if (resolved) {
@@ -123,8 +97,8 @@ function resolveNodePathFromPidWindows(pid: number): NodePathFromPidResult {
};
}
export function resolveNodePathFromPid(pid: number): NodePathFromPidResult {
export async function resolveNodePathFromPid(pid: number): Promise<NodePathFromPidResult> {
return platform() === "win32"
? resolveNodePathFromPidWindows(pid)
: resolveNodePathFromPidUnix(pid);
? await resolveNodePathFromPidWindows(pid)
: await resolveNodePathFromPidUnix(pid);
}
+8 -9
View File
@@ -1,10 +1,11 @@
import type { Command } from "commander";
import { execFile } from "node:child_process";
import { createRequire } from "node:module";
import { promisify } from "node:util";
import { getOrCreateServerId, findExecutable, applyProviderEnv } from "@getpaseo/server";
const execFileAsync = promisify(execFile);
import {
getOrCreateServerId,
findExecutable,
applyProviderEnv,
execCommand,
} from "@getpaseo/server";
import { tryConnectToDaemon } from "../../utils/client.js";
import type { CommandOptions, ListResult, OutputSchema } from "../../output/index.js";
import { resolveLocalDaemonState, resolveTcpHostFromListen } from "./local-daemon.js";
@@ -178,11 +179,9 @@ async function checkProviderBinary(
}
const env = applyProviderEnv(process.env);
try {
const { stdout } = await execFileAsync(binaryPath, ["--version"], {
encoding: "utf8",
const { stdout } = await execCommand(binaryPath, ["--version"], {
timeout: 5000,
env,
windowsHide: true,
});
return { path: binaryPath, version: stdout.trim() || null };
} catch {
@@ -224,7 +223,7 @@ export async function runStatusCommand(
if (!state.running) {
daemonNode = "-";
} else if (state.pidInfo?.pid) {
const fromPid = resolveNodePathFromPid(state.pidInfo.pid);
const fromPid = await resolveNodePathFromPid(state.pidInfo.pid);
daemonNode = fromPid.nodePath ?? `unknown (${fromPid.error ?? "could not resolve from PID"})`;
} else {
daemonNode = "unknown (no PID available)";
+41 -7
View File
@@ -1,10 +1,12 @@
import type { Command } from "commander";
import type { CommandOptions, ListResult, OutputSchema } from "../../output/index.js";
import { AGENT_PROVIDER_DEFINITIONS } from "@getpaseo/server";
import { tryConnectToDaemon } from "../../utils/client.js";
/** Provider list item for display */
export interface ProviderListItem {
provider: string;
label: string;
status: string;
defaultMode: string;
modes: string;
@@ -13,16 +15,22 @@ export interface ProviderListItem {
/** Derive provider list from the manifest — single source of truth */
const PROVIDERS: ProviderListItem[] = AGENT_PROVIDER_DEFINITIONS.map((def) => ({
provider: def.id,
label: def.label,
status: "available",
defaultMode: def.defaultModeId ?? "-",
modes: def.modes.length > 0 ? def.modes.map((m) => m.label).join(", ") : "-",
}));
function getStaticProviders(): ProviderListItem[] {
return PROVIDERS;
}
/** Schema for provider ls output */
export const providerLsSchema: OutputSchema<ProviderListItem> = {
idField: "provider",
columns: [
{ header: "PROVIDER", field: "provider", width: 12 },
{ header: "LABEL", field: "label", width: 16 },
{
header: "STATUS",
field: "status",
@@ -45,13 +53,39 @@ export interface ProviderLsOptions extends CommandOptions {
}
export async function runLsCommand(
_options: ProviderLsOptions,
options: ProviderLsOptions,
_command: Command,
): Promise<ProviderLsResult> {
// Provider data is static - no daemon connection needed
return {
type: "list",
data: PROVIDERS,
schema: providerLsSchema,
};
const client = await tryConnectToDaemon({ host: options.host });
if (!client) {
return {
type: "list",
data: getStaticProviders(),
schema: providerLsSchema,
};
}
try {
const snapshot = await client.getProvidersSnapshot();
return {
type: "list",
data: snapshot.entries.map((entry) => ({
provider: entry.provider,
label: entry.label ?? entry.provider,
status: entry.status === "ready" ? "available" : entry.status,
defaultMode: entry.defaultModeId ?? "default",
modes: (entry.modes ?? []).map((mode) => mode.label).join(", "),
})),
schema: providerLsSchema,
};
} catch {
return {
type: "list",
data: getStaticProviders(),
schema: providerLsSchema,
};
} finally {
await client.close().catch(() => {});
}
}
+8 -13
View File
@@ -129,7 +129,12 @@ try {
assert(result.stdout.includes("claude"), "output should include claude");
assert(result.stdout.includes("codex"), "output should include codex");
assert(result.stdout.includes("opencode"), "output should include opencode");
assert(result.stdout.includes("available"), "output should show available status");
assert(
result.stdout.includes("available") ||
result.stdout.includes("loading") ||
result.stdout.includes("unavailable"),
"output should show a provider status",
);
console.log("✓ provider ls lists all providers\n");
}
@@ -140,7 +145,7 @@ try {
assert.strictEqual(result.exitCode, 0, "should exit 0");
const data = JSON.parse(result.stdout.trim());
assert(Array.isArray(data), "output should be an array");
assert.strictEqual(data.length, 5, "should have 5 providers");
assert(data.length >= 3, `should have at least 3 providers, got ${data.length}`);
assert(
data.some((p: { provider: string }) => p.provider === "claude"),
"should include claude",
@@ -153,14 +158,6 @@ try {
data.some((p: { provider: string }) => p.provider === "opencode"),
"should include opencode",
);
assert(
data.some((p: { provider: string }) => p.provider === "copilot"),
"should include copilot",
);
assert(
data.some((p: { provider: string }) => p.provider === "pi"),
"should include pi",
);
console.log("✓ provider ls --json outputs valid JSON\n");
}
@@ -170,12 +167,10 @@ try {
const result = await ctx.paseo(["provider", "ls", "--quiet"]);
assert.strictEqual(result.exitCode, 0, "should exit 0");
const lines = result.stdout.trim().split("\n");
assert.strictEqual(lines.length, 5, "should have 5 lines");
assert(lines.length >= 3, `should have at least 3 lines, got ${lines.length}`);
assert(lines.includes("claude"), "should include claude");
assert(lines.includes("codex"), "should include codex");
assert(lines.includes("opencode"), "should include opencode");
assert(lines.includes("copilot"), "should include copilot");
assert(lines.includes("pi"), "should include pi");
console.log("✓ provider ls --quiet outputs provider names only\n");
}