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
58 changes: 25 additions & 33 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 20 additions & 3 deletions src/projects/cli.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -216,7 +216,7 @@ describe("runProjectSubcommand", () => {
expect(cancelled).toEqual(["wrong-session"]);
});

it("explains payment challenges from the web build endpoint", async () => {
it("points plan-limit responses to usage", async () => {
const client = {
startBuild: async () => {
throw new ProjectClientError("request failed: 402", 402);
Expand All @@ -227,8 +227,25 @@ describe("runProjectSubcommand", () => {
const result = await runProject(["project", "build", "Build", "it"], client);

expect(result.code).toBe(1);
expect(result.stderr.join("\n")).toContain("payment challenge");
expect(result.stderr.join("\n")).toContain("web build OAuth gate");
expect(result.stderr.join("\n")).toContain("codebase usage");
});

it("formats build cooldown retry guidance", async () => {
const client = {
startBuild: async () => {
throw new ProjectClientError(
"request failed: 429 build_cooldown — Build cooldown active. Retry in 313s.",
429,
313_000,
);
},
hasCredentials: () => true,
} as unknown as ProjectClient;

const result = await runProject(["project", "build", "Build", "it"], client);

expect(result.code).toBe(1);
expect(result.stderr.join("\n")).toContain("retry in 5m 13s");
});

it("waits for a completed build and prints its preview URL", async () => {
Expand Down
16 changes: 13 additions & 3 deletions src/projects/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ export async function runProjectSubcommand(argv: string[], options: ProjectCliOp
if (e instanceof ProjectClientError) {
err(`error: ${e.message}`);
if (e.status === 402) {
err(
"hint: codebase.design returned a payment challenge before accepting OAuth. Run `codebase auth login`; if this persists, the web build OAuth gate needs to be deployed.",
);
err("hint: run `codebase usage` to check your remaining builds, plan limits, and reset date.");
}
if (e.status === 403) {
err("hint: run `codebase auth login` again so the CLI can request builds:read/builds:write.");
}
if (e.status === 429 && e.retryAfterMs) {
err(`hint: retry in ${formatRetryDelay(e.retryAfterMs)}.`);
}
return e.status === 404 ? 4 : 1;
}
err(`error: ${e instanceof Error ? e.message : String(e)}`);
Expand Down Expand Up @@ -526,6 +527,15 @@ function formatTimelineDuration(durationMs: number): string {
return `${(durationMs / 1000).toFixed(durationMs < 10_000 ? 1 : 0)}s`;
}

function formatRetryDelay(durationMs: number): string {
const totalSeconds = Math.max(1, Math.ceil(durationMs / 1000));
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
if (minutes === 0) return `${seconds}s`;
if (seconds === 0) return `${minutes}m`;
return `${minutes}m ${seconds}s`;
}

async function printPreview(
client: ProjectClient,
sessionId: string,
Expand Down
Loading