Skip to content

Commit 08b03be

Browse files
committed
Add fix command registration and update agent UI label handling
1 parent 2f71990 commit 08b03be

3 files changed

Lines changed: 135 additions & 1 deletion

File tree

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import { confirm, input } from "@inquirer/prompts";
2+
import { Command } from "commander";
3+
import { runClaudeAgent } from "../lib/claude-agent.js";
4+
import { CliError } from "../lib/errors.js";
5+
import { isNonInteractiveEnv } from "../lib/interactive.js";
6+
7+
type FixOptions = {
8+
error?: string,
9+
outputDir?: string,
10+
yes?: boolean,
11+
};
12+
13+
const MAX_ERROR_LENGTH = 8000;
14+
15+
async function readStdin(): Promise<string> {
16+
if (process.stdin.isTTY) return "";
17+
const chunks: Buffer[] = [];
18+
for await (const chunk of process.stdin) {
19+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk) : chunk);
20+
}
21+
return Buffer.concat(chunks).toString("utf-8").trim();
22+
}
23+
24+
export function registerFixCommand(program: Command) {
25+
program
26+
.command("fix")
27+
.description("Use an AI agent to fix a Stack Auth error in your project")
28+
.option("--error <text>", "The error message to fix (also accepts stdin)")
29+
.option("--output-dir <dir>", "Directory of the project to fix (defaults to cwd)")
30+
.option("-y, --yes", "Skip the confirmation prompt")
31+
.action(async (opts: FixOptions) => {
32+
try {
33+
await runFix(opts);
34+
} catch (error: unknown) {
35+
if (error != null && typeof error === "object" && "name" in error && error.name === "ExitPromptError") {
36+
console.log("\nAborted.");
37+
process.exit(0);
38+
}
39+
throw error;
40+
}
41+
});
42+
}
43+
44+
async function runFix(opts: FixOptions) {
45+
const outputDir = opts.outputDir ?? process.cwd();
46+
47+
let errorText = (opts.error ?? "").trim();
48+
if (!errorText) {
49+
const piped = await readStdin();
50+
if (piped) errorText = piped;
51+
}
52+
if (!errorText) {
53+
if (isNonInteractiveEnv()) {
54+
throw new CliError("No error provided. Pass --error \"...\" or pipe the error to stdin.");
55+
}
56+
errorText = (await input({
57+
message: "Paste the Stack Auth error you want fixed:",
58+
validate: (v) => v.trim().length > 0 || "Error text is required",
59+
})).trim();
60+
}
61+
62+
if (errorText.length > MAX_ERROR_LENGTH) {
63+
errorText = errorText.slice(0, MAX_ERROR_LENGTH);
64+
}
65+
66+
console.log("\nError to fix:\n");
67+
console.log(" " + errorText.split("\n").join("\n "));
68+
console.log();
69+
70+
if (!opts.yes && !isNonInteractiveEnv()) {
71+
console.log(`Working directory: ${outputDir}`);
72+
const ok = await confirm({
73+
message: "Run the AI agent to fix this error?",
74+
default: true,
75+
});
76+
if (!ok) {
77+
console.log("Aborted.");
78+
return;
79+
}
80+
}
81+
82+
const prompt = buildFixPrompt(errorText);
83+
const success = await runClaudeAgent({
84+
prompt,
85+
cwd: outputDir,
86+
label: "Fixing Stack Auth error...",
87+
});
88+
89+
if (!success) {
90+
throw new CliError("The AI agent was unable to complete the fix. See the output above for details.");
91+
}
92+
}
93+
94+
function buildFixPrompt(errorText: string): string {
95+
return [
96+
"You are fixing a Stack Auth (https://stack-auth.com, package `@stackframe/*`) integration error in the user's project.",
97+
"",
98+
"YOUR JOB: actually apply the fix to the files on disk using the Edit/Write tools. Do not just diagnose and stop. Do not just describe what to do. Make the edits.",
99+
"",
100+
"Workflow (do all of these — do not skip steps):",
101+
"1. Read the files needed to understand the error: package.json, stack.config.ts if present, .env / .env.local, the file(s) referenced in the stack trace, app/layout.* or pages/_app.*, and any handler route (e.g. app/handler/[...stack]/page.tsx).",
102+
"2. Diagnose the Stack Auth root cause (e.g. missing StackProvider wrapping, missing env vars, wrong handler route path, incorrect stack.config.ts, wrong import from @stackframe/*, missing API keys, missing `stackServerApp` instance, etc.).",
103+
"3. Apply the minimal fix using Edit/Write. Actually modify the files. If env vars are missing, instruct the user clearly (do not invent secret values).",
104+
"4. After editing, verify your change by re-reading the affected file(s).",
105+
"",
106+
"GUARDRAILS:",
107+
"- If, after reading the relevant files, the error is clearly NOT caused by Stack Auth, stop and explain why instead of editing.",
108+
"- No unrelated refactors, formatting changes, dependency upgrades, or cleanup.",
109+
"- No destructive shell commands (`rm -rf`, `git reset --hard`, force pushes, deleting branches, anything outside the project directory).",
110+
"- Never print secret values (STACK_SECRET_SERVER_KEY, etc.) — refer to env vars by name only.",
111+
"",
112+
"The user pasted the following error:",
113+
"",
114+
"<<<ERROR_START>>>",
115+
errorText,
116+
"<<<ERROR_END>>>",
117+
"",
118+
"FINAL OUTPUT FORMAT — your last assistant message MUST be exactly this markdown structure, with nothing before or after it:",
119+
"",
120+
"## Error",
121+
"<one or two sentence plain-language summary of what went wrong>",
122+
"",
123+
"## Files changed",
124+
"- `path/to/file1` — <one-line description of the change>",
125+
"- `path/to/file2` — <one-line description of the change>",
126+
"(If you didn't change any files, write `_None_` here and explain why in the Solution section.)",
127+
"",
128+
"## Solution",
129+
"<2–5 sentences: what the root cause was, what you changed and why, and any follow-up the user must do themselves (e.g. set an env var, restart the dev server).>",
130+
].join("\n");
131+
}

packages/stack-cli/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { registerConfigCommand } from "./commands/config-file.js";
1010
import { registerInitCommand } from "./commands/init.js";
1111
import { registerProjectCommand } from "./commands/project.js";
1212
import { registerEmulatorCommand } from "./commands/emulator.js";
13+
import { registerFixCommand } from "./commands/fix.js";
1314

1415
const __filename = fileURLToPath(import.meta.url);
1516
const __dirname = dirname(__filename);
@@ -31,6 +32,7 @@ registerConfigCommand(program);
3132
registerInitCommand(program);
3233
registerProjectCommand(program);
3334
registerEmulatorCommand(program);
35+
registerFixCommand(program);
3436

3537
async function main() {
3638
try {

packages/stack-cli/src/lib/claude-agent.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -150,8 +150,9 @@ function stripClaudeCodeEnv(): Record<string, string> {
150150
export async function runClaudeAgent(options: {
151151
prompt: string,
152152
cwd: string,
153+
label?: string,
153154
}): Promise<boolean> {
154-
const ui = new AgentProgressUI("Setting up Stack Auth...");
155+
const ui = new AgentProgressUI(options.label ?? "Setting up Stack Auth...");
155156
ui.start();
156157

157158
try {

0 commit comments

Comments
 (0)