Skip to content

Instantly share code, notes, and snippets.

@muniter
Created April 3, 2026 23:46
Show Gist options
  • Select an option

  • Save muniter/219c5fde3d4bfa4b6f16c31407e88d8f to your computer and use it in GitHub Desktop.

Select an option

Save muniter/219c5fde3d4bfa4b6f16c31407e88d8f to your computer and use it in GitHub Desktop.
stately/agents claude code
javierlopez in (v2)~ -> npx tsx example/file_searcher.ts
What file are you looking for? I json file from my Downloads foer
Summary: Found JSON files in your Downloads folder inside the `pixel-perfect-match-604-main` project. No standalone JSON files exist at the top level of Downloads — all meaningful JSON files are inside that downloaded project's root (excluding node_modules).
Candidates:
1. /Users/javierlopez/Downloads/pixel-perfect-match-604-main/package.json
Root-level package.json of the downloaded project — the most commonly referenced JSON file in a frontend project.
2. /Users/javierlopez/Downloads/pixel-perfect-match-604-main/tsconfig.json
Root TypeScript configuration file for the downloaded project.
3. /Users/javierlopez/Downloads/pixel-perfect-match-604-main/tsconfig.app.json
App-specific TypeScript config in the downloaded project.
4. /Users/javierlopez/Downloads/pixel-perfect-match-604-main/tsconfig.node.json
Node-specific TypeScript config in the downloaded project.
Type a number to select a file, "refine" to continue searching, or press enter to cancel: 1
Done:
{
"selected": {
"path": "/Users/javierlopez/Downloads/pixel-perfect-match-604-main/package.json",
"reason": "Root-level package.json of the downloaded project — the most commonly referenced JSON file in a frontend project."
}
}
javierlopez in (v2)~ ->
import dotenv from 'dotenv';
import { promisify } from 'node:util';
import { execFile } from 'node:child_process';
import { createInterface } from 'node:readline/promises';
import { stdin as input, stdout as output } from 'node:process';
import { z } from 'zod';
import {
createAdapter,
createAgentMachine,
decide,
} from '../src/index.js';
dotenv.config({ override: true });
const execFileAsync = promisify(execFile);
const fileCandidateSchema = z.object({
path: z.string(),
reason: z.string(),
});
const searchResultSchema = z.object({
summary: z.string(),
candidates: z.array(fileCandidateSchema).max(5),
});
type FileCandidate = z.infer<typeof fileCandidateSchema>;
type SearchResult = z.infer<typeof searchResultSchema>;
function buildSearchPrompt(args: { query: string; refinements: string[] }): string {
return [
'You are a file finder working inside a code repository.',
'Use the available tools to inspect the workspace and find files that best match the user request.',
'Return 1 to 5 candidate files. Prefer precise matches over broad guesses.',
'',
`User request: ${args.query}`,
args.refinements.length > 0
? `Refinements so far:\n${args.refinements.map((item, index) => `${index + 1}. ${item}`).join('\n')}`
: 'Refinements so far: none',
'',
'For each candidate, explain briefly why it is relevant.',
'Only return files that actually exist in the workspace.',
].join('\n');
}
async function runClaude(args: {
prompt: string;
sessionId: string | null;
tools: string;
schema: Record<string, unknown>;
model: string;
workdir: string;
}): Promise<{ session_id: string; structured_output: unknown }> {
const cliArgs = [
'-p',
'--output-format',
'json',
'--model',
args.model,
'--permission-mode',
'bypassPermissions',
'--add-dir',
args.workdir,
'--tools',
args.tools,
'--json-schema',
JSON.stringify(args.schema),
...(args.sessionId ? ['--resume', args.sessionId] : []),
args.prompt,
];
const { stdout, stderr } = await execFileAsync('claude', cliArgs, {
cwd: args.workdir,
maxBuffer: 5 * 1024 * 1024,
});
const payloadText = stdout.trim();
if (!payloadText) {
throw new Error(stderr.trim() || 'Claude returned no output.');
}
const payload = JSON.parse(payloadText) as {
is_error?: boolean;
errors?: string[];
session_id?: string;
structured_output?: unknown;
};
if (payload.is_error) {
throw new Error(payload.errors?.join('\n') || 'Claude returned an error.');
}
if (!payload.session_id) {
throw new Error('Claude response did not include a session_id.');
}
return {
session_id: payload.session_id,
structured_output: payload.structured_output,
};
}
function createClaudeCodeAdapter(workdir: string) {
let sessionId: string | null = null;
return createAdapter({
async decide({ prompt, options, model }) {
const searchOption = options.showResults;
if (!searchOption?.schema) {
throw new Error('ClaudeCode adapter expects a showResults option with a schema.');
}
const cliResult = await runClaude({
prompt,
sessionId,
tools: 'Glob,Grep,Read',
schema: {
type: 'object',
properties: {
summary: { type: 'string' },
candidates: {
type: 'array',
maxItems: 5,
items: {
type: 'object',
properties: {
path: { type: 'string' },
reason: { type: 'string' },
},
required: ['path', 'reason'],
additionalProperties: false,
},
},
},
required: ['summary', 'candidates'],
additionalProperties: false,
},
model: model === 'claude-code' ? 'sonnet' : model,
workdir,
});
sessionId = cliResult.session_id;
const parsed = searchResultSchema.parse(cliResult.structured_output);
return {
choice: 'showResults',
data: parsed,
};
},
});
}
const searchAdapter = createClaudeCodeAdapter(process.cwd());
const fileSearcherMachine = createAgentMachine({
id: 'file-searcher',
schemas: {
input: z.object({
rootDir: z.string(),
}),
events: {
'user.search': z.object({
query: z.string(),
}),
'user.selectCandidate': z.object({
index: z.number().int().min(0),
}),
'user.refineSearch': z.object({
feedback: z.string(),
}),
'user.cancel': z.object({}),
},
},
context: (input: { rootDir: string }) => ({
rootDir: input.rootDir,
query: '',
refinements: [] as string[],
summary: null as string | null,
candidates: [] as FileCandidate[],
selected: null as FileCandidate | null,
}),
adapter: searchAdapter,
initial: 'collectingRequest',
states: {
collectingRequest: {
on: {
'user.search': ({ event }) => ({
target: 'searching',
context: {
query: event.query,
refinements: [],
summary: null,
candidates: [],
selected: null,
},
}),
'user.cancel': () => ({ target: 'cancelled' }),
},
},
searching: decide({
model: 'claude-code',
prompt: ({ context }) =>
buildSearchPrompt({
query: context.query as string,
refinements: context.refinements as string[],
}),
options: {
showResults: {
description: 'Return the best matching file candidates for the search request.',
schema: searchResultSchema,
},
},
onDone: ({ result }) => ({
target: 'reviewingCandidates',
context: {
summary: result.data.summary,
candidates: result.data.candidates,
},
}),
}),
reviewingCandidates: {
on: {
'user.selectCandidate': ({ event, context }) => {
const candidate = context.candidates[event.index];
if (!candidate) {
throw new Error(`Candidate index ${event.index} is out of bounds.`);
}
return {
target: 'done',
context: {
selected: candidate,
},
};
},
'user.refineSearch': ({ event, context }) => ({
target: 'searching',
context: {
refinements: [...context.refinements, event.feedback],
},
}),
'user.cancel': () => ({ target: 'cancelled' }),
},
},
done: {
type: 'final',
output: ({ context }) => ({
selected: context.selected,
}),
},
cancelled: {
type: 'final',
output: ({ context }) => ({
cancelled: true,
lastSummary: context.summary,
}),
},
},
});
async function main() {
const rl = createInterface({ input, output });
let state = fileSearcherMachine.getInitialState({ rootDir: process.cwd() });
try {
for (;;) {
const result = await fileSearcherMachine.execute(state);
if (result.status === 'error') {
console.error('\nSearch failed:', result.error);
return;
}
if (result.status === 'done') {
console.log('\nDone:');
console.log(JSON.stringify(result.output, null, 2));
return;
}
if (String(result.value) === 'collectingRequest') {
const query = await rl.question('What file are you looking for? ');
if (!query.trim()) {
state = fileSearcherMachine.transition(result.state, { type: 'user.cancel' });
continue;
}
state = fileSearcherMachine.transition(result.state, {
type: 'user.search',
query,
});
continue;
}
if (String(result.value) === 'reviewingCandidates') {
console.log(`\nSummary: ${result.context.summary ?? 'No summary provided.'}`);
console.log('\nCandidates:');
result.context.candidates.forEach((candidate, index) => {
console.log(`${index + 1}. ${candidate.path}`);
console.log(` ${candidate.reason}`);
});
const answer = await rl.question(
'\nType a number to select a file, "refine" to continue searching, or press enter to cancel: '
);
if (!answer.trim()) {
state = fileSearcherMachine.transition(result.state, { type: 'user.cancel' });
continue;
}
if (answer.trim().toLowerCase() === 'refine') {
const feedback = await rl.question('How should Claude refine the search? ');
state = fileSearcherMachine.transition(result.state, {
type: 'user.refineSearch',
feedback,
});
continue;
}
const index = Number(answer) - 1;
if (!Number.isInteger(index)) {
console.log('Please enter a valid number or "refine".');
continue;
}
state = fileSearcherMachine.transition(result.state, {
type: 'user.selectCandidate',
index,
});
}
}
} finally {
rl.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment