Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 7 additions & 20 deletions packages/admin-cli/src/configure-providers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ import { validateProviderCredential } from "./provider-validation.js";
import { parseNumericMultiSelect, type Prompt } from "./prompt.js";
import { createTerminalPrompt } from "./prompt.js";
import { createWranglerClient, type WranglerClient } from "./wrangler.js";
import {
createConsoleOutput,
safeErrorMessage,
type Output,
} from "./console-output.js";

export interface Output {
log(message: string): void;
error(message: string): void;
}
export type { Output } from "./console-output.js";

export type ProviderValidator = (
provider: LLMProviderDefinition,
Expand Down Expand Up @@ -71,7 +73,7 @@ async function promptForProviderSelection(prompt: Prompt, output: Output): Promi
try {
return parseNumericMultiSelect(response, LLM_PROVIDERS.length);
} catch (error) {
output.error(getSafeErrorMessage(error));
output.error(safeErrorMessage(error));
}
}
}
Expand Down Expand Up @@ -134,18 +136,3 @@ async function validateSelection(
}
}
}

function createConsoleOutput(): Output {
return {
log(message) {
console.log(message);
},
error(message) {
console.error(message);
},
};
}

function getSafeErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : "Unknown error.";
}
15 changes: 15 additions & 0 deletions packages/admin-cli/src/console-output.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export interface Output {
log(message: string): void;
error(message: string): void;
}

export function createConsoleOutput(): Output {
return {
log: (message) => console.log(message),
error: (message) => console.error(message),
};
}

export function safeErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : "Unknown error.";
}
24 changes: 7 additions & 17 deletions packages/admin-cli/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { configureProviders, type Output } from "./configure-providers.js";
import { configureProviders } from "./configure-providers.js";
import {
createConsoleOutput,
safeErrorMessage,
type Output,
} from "./console-output.js";
import {
createTerminalPrompt,
PromptCancelledError,
Expand Down Expand Up @@ -58,7 +63,7 @@ export async function runCli(
return;
}

output.error(getSafeErrorMessage(error));
output.error(safeErrorMessage(error));
setExitCode(1);
}
}
Expand All @@ -70,21 +75,6 @@ function printHelp(output: Output) {
output.log(" --help, -h Show this help message");
}

function createConsoleOutput(): Output {
return {
log(message) {
console.log(message);
},
error(message) {
console.error(message);
},
};
}

function getSafeErrorMessage(error: unknown): string {
return error instanceof Error ? error.message : "Unknown error.";
}

if (isDirectExecution()) {
await runCli(process.argv.slice(2));
}
Expand Down
10 changes: 1 addition & 9 deletions packages/cli/src/args.ts
Original file line number Diff line number Diff line change
Expand Up @@ -182,19 +182,11 @@ function readOptionValue(argv: string[], index: number, option: string): string
return value;
}

function parsePositiveInteger(value: string, option: string): number {
const number = Number(value);
if (!Number.isInteger(number) || number <= 0) {
throw new Error(`${option} must be a positive integer`);
}
return number;
}

export function usage(): string {
return [
"Usage:",
" codevil init [--endpoint URL] [--api-key KEY] [--provider PROVIDER] [--plan-model MODEL] [--exec-model MODEL]",
" codevil run --repo REPO_URL [--provider PROVIDER] [--plan-model MODEL] [--exec-model MODEL] [--max-cost COST] [--max-time TIME] [--max-steps N] [--debug] <prompt>",
" codevil run --repo REPO_URL [--provider PROVIDER] [--plan-model MODEL] [--exec-model MODEL] [--max-time TIME] [--debug] <prompt>",
" codevil models list [--provider PROVIDER]",
" codevil models check <provider>/<model>",
].join("\n");
Expand Down
6 changes: 1 addition & 5 deletions packages/cli/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { dirname, join } from "node:path";

import { DEFAULT_CONFIG, type Config } from "@codevil/shared";
import { DEFAULT_CONFIG, isRecord, type Config } from "@codevil/shared";

export interface ConfigPathOptions {
home?: string;
Expand Down Expand Up @@ -86,10 +86,6 @@ function validateConfig(value: unknown): Config {
};
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}

function isNodeError(error: unknown): error is NodeJS.ErrnoException {
return error instanceof Error && "code" in error;
}
5 changes: 1 addition & 4 deletions packages/cli/src/events.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { DOToCLIEvent } from "@codevil/shared";
import {
isRecord,
parseReplayEvent,
ReplayBatchFrameSchema,
SnapshotFrameSchema,
Expand Down Expand Up @@ -147,7 +148,3 @@ function renderAgentEvent(event: unknown): string[] {
}
return ["Agent event received"];
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
12 changes: 0 additions & 12 deletions packages/cli/src/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,3 @@ export function checkAgentRunnableModel(providerId: string, modelId: string): bo
return false;
}
}

export function parseProviderModelRef(ref: string): { provider: string; modelId: string } {
const slash = ref.indexOf("/");
if (slash <= 0 || slash === ref.length - 1) {
throw new Error(`Expected provider/model, got: ${ref}`);
}

return {
provider: ref.slice(0, slash),
modelId: ref.slice(slash + 1),
};
}
6 changes: 1 addition & 5 deletions packages/cli/src/session-client.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { Config } from "@codevil/shared";
import { isRecord, type Config } from "@codevil/shared";

import type { RunCommand } from "./args.js";

Expand Down Expand Up @@ -82,7 +82,3 @@ export function buildWebSocketUrl(url: string, cursor: number): string {
wsUrl.searchParams.set("cursor", cursor.toString());
return wsUrl.toString();
}

function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null;
}
6 changes: 5 additions & 1 deletion packages/cli/test/args.test.mjs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import assert from "node:assert/strict";
import test from "node:test";

import { parseCommand } from "../dist/args.js";
import { parseCommand, usage } from "../dist/args.js";

test("parses run command with repo, prompt, models, and time guard options", () => {
const command = parseCommand([
Expand Down Expand Up @@ -71,3 +71,7 @@ test("rejects removed guard options", () => {
/Unknown option: --max-cost/,
);
});

test("usage omits removed guard options", () => {
assert.doesNotMatch(usage(), /--max-cost|--max-steps/);
});
12 changes: 0 additions & 12 deletions packages/sandbox-image/src/dependency-cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -267,18 +267,6 @@ function strategyFromMarker(
};
}

function readPackageManager(packageJson: string): string | undefined {
try {
const parsed = JSON.parse(readFileSync(packageJson, "utf8")) as {
packageManager?: unknown;
};
if (typeof parsed.packageManager !== "string") return undefined;
return parsed.packageManager.split("@", 1)[0];
} catch {
return undefined;
}
}

function packageManifestHasInstallLifecycleScript(packageJson: string): boolean {
try {
const parsed = JSON.parse(readFileSync(packageJson, "utf8")) as {
Expand Down
5 changes: 0 additions & 5 deletions packages/sandbox-image/src/logging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,6 @@ export function setSandboxTraceFromSession(sessionId: string): void {
bootstrapLogger.withSessionId(sessionId);
}

export function setSandboxTraceId(traceId: string, sessionId?: string): void {
bootstrapLogger.withTraceId(traceId);
if (sessionId) bootstrapLogger.withSessionId(sessionId);
}

export function sandboxLogException(
event: string,
error: unknown,
Expand Down
4 changes: 0 additions & 4 deletions packages/sandbox-image/src/pi-cost.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,4 @@ export function costFromSessionStats(session: AgentSession): CostInfo {
};
}

export function emptySessionCostSnapshot(): SessionCostSnapshot {
return { inputTokens: 0, outputTokens: 0, costUsd: 0 };
}

export { zeroCost };
30 changes: 2 additions & 28 deletions packages/sandbox-image/src/preview-manager.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { createServer, request as httpRequest } from "node:http";
import { request as httpRequest } from "node:http";
import { spawn, type ChildProcess } from "node:child_process";
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
import { basename, dirname, join, relative, resolve } from "node:path";
import { basename, join, relative, resolve } from "node:path";
import type { Readable } from "node:stream";

import type { PreviewApp, PreviewFramework } from "@codevil/shared";
Expand Down Expand Up @@ -265,17 +265,6 @@ export function detectPreviewApps(root: string): PreviewApp[] {
return apps;
}

/**
* Backward-compatible single-result wrapper kept for legacy callers and tests.
* Returns the first detected app's command (preferring root over subpackages).
*/
export function detectPreviewCommand(root: string): PreviewCommand | undefined {
const apps = detectPreviewApps(root);
if (apps.length === 0) return undefined;
const first = apps[0];
return appToCommand(first, root);
}

export function appToCommand(app: PreviewApp, root: string): PreviewCommand {
const relCwd = relativeCwd(root, app.cwd);
const command: PreviewCommand = relCwd
Expand Down Expand Up @@ -639,18 +628,3 @@ function withRecentLogs(message: string, recentLogs: string[]): string {
function stripAnsi(text: string): string {
return text.replace(/\[[0-9;?]*[ -/]*[@-~]/g, "");
}

export function fakePreviewServer(port: number): Promise<{ close(): Promise<void> }> {
const server = createServer((_req, res) => {
res.writeHead(200, { "content-type": "text/plain" });
res.end("ok");
});
return new Promise((resolve, reject) => {
server.on("error", reject);
server.listen(port, "127.0.0.1", () => {
resolve({
close: () => new Promise((closeResolve) => server.close(() => closeResolve())),
});
});
});
}
2 changes: 0 additions & 2 deletions packages/sandbox-image/src/repo-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import {
DEPENDENCY_ARTIFACT_FORMAT_VERSION,
computeDependencyFingerprint,
dependencyArtifactsPresent,
dependencyCleanExcludesForMarker,
dependencyMarkerMatches,
detectJavaScriptDependencyStrategy,
readDependencyArtifactMarker,
removeDependencyArtifactMarker,
removeJavaScriptDependencyArtifacts,
repositoryHasInstallLifecycleScripts,
Expand Down
5 changes: 1 addition & 4 deletions packages/sandbox-image/src/runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ import {
import {
readDependencyArtifactMarker,
dependencyCleanExcludesForMarker,
type DependencyArtifactMarker,
} from "./dependency-cache.js";
import { executePrompt, planPrompt, refinePrompt } from "./prompts.js";
import { parsePreviewSuggestion } from "./preview-parsers.js";
Expand All @@ -37,8 +36,6 @@ import {
type Verifier,
RepositoryVerifier,
ShellCommandRunner,
detectSetupCommand,
detectVerificationCommand,
runVerificationLoop,
} from "./verification.js";
export {
Expand All @@ -47,7 +44,7 @@ export {
detectSetupCommand,
detectVerificationCommand,
} from "./verification.js";
export { detectPreviewApps, detectPreviewCommand } from "./preview-manager.js";
export { detectPreviewApps } from "./preview-manager.js";

export type {
AgentStartOptions,
Expand Down
18 changes: 12 additions & 6 deletions packages/sandbox-image/test/runtime.test.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import {
SandboxRuntime,
ShellCommandRunner,
detectPreviewApps,
detectPreviewCommand,
detectSetupCommand,
detectVerificationCommand,
parsePreviewDiscovery,
Expand Down Expand Up @@ -509,7 +508,7 @@ test("detectSetupCommand uses non-interactive npm install flags", async () => {
}
});

test("detectPreviewCommand prefers Vite dev scripts and port 5173", async () => {
test("detectPreviewApps prefers Vite dev scripts and port 5173", async () => {
const workspace = await mkdtemp(join(tmpdir(), "codevil-preview-vite-"));
try {
await writeFile(join(workspace, "package.json"), JSON.stringify({
Expand All @@ -518,7 +517,11 @@ test("detectPreviewCommand prefers Vite dev scripts and port 5173", async () =>
}));
await writeFile(join(workspace, "pnpm-lock.yaml"), "");

assert.deepEqual(detectPreviewCommand(workspace), {
assert.deepEqual(detectPreviewApps(workspace)[0], {
key: ".",
name: workspace.split("/").at(-1),
cwd: workspace,
framework: "vite",
command: "pnpm dev -- --host 0.0.0.0 --port 5173",
port: 5173,
});
Expand Down Expand Up @@ -619,18 +622,21 @@ test("detectPreviewApps uses the root package manager for workspace apps", async
}
});

test("detectPreviewCommand remaps Next.js away from port 3000", async () => {
test("detectPreviewApps remaps Next.js away from port 3000", async () => {
const workspace = await mkdtemp(join(tmpdir(), "codevil-preview-next-"));
try {
await writeFile(join(workspace, "package.json"), JSON.stringify({
scripts: { dev: "next dev" },
dependencies: { next: "^15.0.0" },
}));

assert.deepEqual(detectPreviewCommand(workspace), {
assert.deepEqual(detectPreviewApps(workspace)[0], {
key: ".",
name: workspace.split("/").at(-1),
cwd: workspace,
framework: "next",
command: "npm run dev -- --hostname 0.0.0.0 --port 3001",
port: 3001,
readinessTimeoutMs: 120_000,
});
} finally {
await rm(workspace, { recursive: true, force: true });
Expand Down
Loading