137 lines
3.8 KiB
TypeScript
137 lines
3.8 KiB
TypeScript
import "dotenv/config";
|
|
import { query, type Options, type SDKMessage } from "@anthropic-ai/claude-agent-sdk";
|
|
import { pathToFileURL } from "node:url";
|
|
import {
|
|
buildClaudeAuthEnv,
|
|
getConfig,
|
|
resolveAnthropicToken,
|
|
type AppConfig,
|
|
} from "../config.js";
|
|
import { createSessionContext } from "./session-context.js";
|
|
|
|
function requiredPrompt(argv: string[]): string {
|
|
const prompt = argv.slice(2).join(" ").trim();
|
|
if (!prompt) {
|
|
throw new Error('Usage: npm run claude -- "your prompt"');
|
|
}
|
|
return prompt;
|
|
}
|
|
|
|
function buildOptions(config = getConfig()): Options {
|
|
return {
|
|
maxTurns: config.provider.claudeMaxTurns,
|
|
...(config.provider.claudeModel ? { model: config.provider.claudeModel } : {}),
|
|
...(config.provider.claudeCodePath
|
|
? { pathToClaudeCodeExecutable: config.provider.claudeCodePath }
|
|
: {}),
|
|
};
|
|
}
|
|
|
|
type ClaudeAuthOptionOverrides = {
|
|
apiKey?: string;
|
|
authToken?: string;
|
|
};
|
|
|
|
function buildClaudeAuthOptionOverrides(
|
|
config: Readonly<AppConfig>,
|
|
): ClaudeAuthOptionOverrides {
|
|
if (config.provider.anthropicOauthToken) {
|
|
return { authToken: config.provider.anthropicOauthToken };
|
|
}
|
|
|
|
const token = resolveAnthropicToken(config.provider);
|
|
return token ? { apiKey: token } : {};
|
|
}
|
|
|
|
export async function readClaudeResult(
|
|
session: AsyncIterable<SDKMessage>,
|
|
): Promise<string> {
|
|
let result = "";
|
|
|
|
for await (const message of session) {
|
|
if (message.type === "result" && message.subtype === "success") {
|
|
result = message.result.trim();
|
|
}
|
|
|
|
if (message.type === "result" && message.subtype !== "success") {
|
|
const detail = message.errors.join("; ");
|
|
throw new Error(
|
|
`Claude query failed (${message.subtype})${detail ? `: ${detail}` : ""}`,
|
|
);
|
|
}
|
|
}
|
|
|
|
if (!result) {
|
|
throw new Error("Claude run completed without a final result.");
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
type RunClaudePromptDependencies = {
|
|
config?: Readonly<AppConfig>;
|
|
createSessionContextFn?: typeof createSessionContext;
|
|
queryFn?: typeof query;
|
|
writeOutput?: (output: string) => void;
|
|
};
|
|
|
|
export async function runClaudePrompt(
|
|
prompt: string,
|
|
dependencies: RunClaudePromptDependencies = {},
|
|
): Promise<void> {
|
|
const config = dependencies.config ?? getConfig();
|
|
const createSessionContextFn = dependencies.createSessionContextFn ?? createSessionContext;
|
|
const queryFn = dependencies.queryFn ?? query;
|
|
const writeOutput = dependencies.writeOutput ?? ((output: string) => console.log(output));
|
|
const sessionContext = await createSessionContextFn("claude", {
|
|
prompt,
|
|
workspaceRoot: process.cwd(),
|
|
config,
|
|
});
|
|
|
|
try {
|
|
const runtimeEnv = {
|
|
...sessionContext.runtimeInjection.env,
|
|
...buildClaudeAuthEnv(config.provider),
|
|
};
|
|
const authOptionOverrides = buildClaudeAuthOptionOverrides(config);
|
|
|
|
const finalResponse = await sessionContext.runInSession(async () => {
|
|
const session = queryFn({
|
|
prompt: sessionContext.promptWithContext,
|
|
options: {
|
|
...buildOptions(config),
|
|
...authOptionOverrides,
|
|
...(sessionContext.mcp.claudeMcpServers
|
|
? { mcpServers: sessionContext.mcp.claudeMcpServers as Options["mcpServers"] }
|
|
: {}),
|
|
cwd: sessionContext.runtimeInjection.workingDirectory,
|
|
env: runtimeEnv,
|
|
},
|
|
});
|
|
|
|
try {
|
|
return await readClaudeResult(session);
|
|
} finally {
|
|
session.close();
|
|
}
|
|
});
|
|
|
|
writeOutput(finalResponse);
|
|
} finally {
|
|
await sessionContext.close();
|
|
}
|
|
}
|
|
|
|
async function main(): Promise<void> {
|
|
const prompt = requiredPrompt(process.argv);
|
|
await runClaudePrompt(prompt);
|
|
}
|
|
|
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
main().catch((error: unknown) => {
|
|
console.error(error instanceof Error ? error.message : String(error));
|
|
process.exitCode = 1;
|
|
});
|
|
}
|