@ekaone/json-cli 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.cjs +92 -22
- package/dist/index.cjs +34 -6
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +34 -6
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -29,18 +29,21 @@ var p = __toESM(require("@clack/prompts"), 1);
|
|
|
29
29
|
// src/providers/claude.ts
|
|
30
30
|
var import_sdk = __toESM(require("@anthropic-ai/sdk"), 1);
|
|
31
31
|
function createClaudeProvider(apiKey) {
|
|
32
|
-
const client = new import_sdk.default({
|
|
32
|
+
const client = new import_sdk.default({
|
|
33
|
+
apiKey: apiKey ?? process.env.ANTHROPIC_API_KEY
|
|
34
|
+
});
|
|
33
35
|
return {
|
|
34
36
|
name: "claude",
|
|
35
37
|
async generate(userPrompt, systemPrompt) {
|
|
36
38
|
const message = await client.messages.create({
|
|
37
|
-
model: "claude-
|
|
39
|
+
model: "claude-sonnet-4-6",
|
|
38
40
|
max_tokens: 1024,
|
|
39
41
|
system: systemPrompt,
|
|
40
42
|
messages: [{ role: "user", content: userPrompt }]
|
|
41
43
|
});
|
|
42
44
|
const block = message.content[0];
|
|
43
|
-
if (block.type !== "text")
|
|
45
|
+
if (block.type !== "text")
|
|
46
|
+
throw new Error("Unexpected response type from Claude");
|
|
44
47
|
return block.text;
|
|
45
48
|
}
|
|
46
49
|
};
|
|
@@ -117,7 +120,20 @@ var CATALOG = {
|
|
|
117
120
|
pnpm: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
118
121
|
yarn: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
119
122
|
bun: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
120
|
-
git: [
|
|
123
|
+
git: [
|
|
124
|
+
"init",
|
|
125
|
+
"add",
|
|
126
|
+
"commit",
|
|
127
|
+
"push",
|
|
128
|
+
"pull",
|
|
129
|
+
"clone",
|
|
130
|
+
"status",
|
|
131
|
+
"log",
|
|
132
|
+
"branch",
|
|
133
|
+
"checkout",
|
|
134
|
+
"merge",
|
|
135
|
+
"stash"
|
|
136
|
+
],
|
|
121
137
|
shell: ["any"]
|
|
122
138
|
// escape hatch — always requires extra confirmation
|
|
123
139
|
};
|
|
@@ -127,7 +143,7 @@ var StepSchema = import_zod.z.object({
|
|
|
127
143
|
command: import_zod.z.string(),
|
|
128
144
|
args: import_zod.z.array(import_zod.z.string()).default([]),
|
|
129
145
|
description: import_zod.z.string(),
|
|
130
|
-
cwd: import_zod.z.string().optional()
|
|
146
|
+
cwd: import_zod.z.string().nullable().optional()
|
|
131
147
|
// optional working directory override
|
|
132
148
|
});
|
|
133
149
|
var PlanSchema = import_zod.z.object({
|
|
@@ -170,6 +186,7 @@ Rules:
|
|
|
170
186
|
- Each step must have a clear, short description
|
|
171
187
|
- NEVER generate a "cd" step \u2014 each step runs in a separate process so "cd" has no effect
|
|
172
188
|
- If subsequent steps need to run inside a cloned directory, set the "cwd" field instead
|
|
189
|
+
- For commands that take a message or value argument (like git commit -m), always keep the full message as a single array element, e.g. args: ["-m", "Update changes"] never args: ["-m", "Update", "changes"]
|
|
173
190
|
|
|
174
191
|
Respond ONLY with valid JSON matching this exact shape, no markdown, no explanation:
|
|
175
192
|
{
|
|
@@ -177,10 +194,19 @@ Respond ONLY with valid JSON matching this exact shape, no markdown, no explanat
|
|
|
177
194
|
"steps": [
|
|
178
195
|
{
|
|
179
196
|
"id": 1,
|
|
197
|
+
"type": "git",
|
|
198
|
+
"command": "commit",
|
|
199
|
+
"args": ["-m", "Update changes"],
|
|
200
|
+
"description": "Commit staged changes",
|
|
201
|
+
"cwd": null
|
|
202
|
+
},
|
|
203
|
+
{
|
|
204
|
+
"id": 2,
|
|
180
205
|
"type": "pnpm",
|
|
181
206
|
"command": "run",
|
|
182
|
-
"args": ["dev"],
|
|
183
|
-
"description": "Start dev server"
|
|
207
|
+
"args": ["dev", "--port", "3000"],
|
|
208
|
+
"description": "Start dev server",
|
|
209
|
+
"cwd": "my-repo"
|
|
184
210
|
}
|
|
185
211
|
]
|
|
186
212
|
}`;
|
|
@@ -231,8 +257,13 @@ async function runStep(step) {
|
|
|
231
257
|
});
|
|
232
258
|
return { success: true };
|
|
233
259
|
} catch (err) {
|
|
234
|
-
const
|
|
235
|
-
|
|
260
|
+
const parts = [
|
|
261
|
+
`Command: ${bin} ${args.join(" ")}`,
|
|
262
|
+
err?.exitCode ? `Exit code: ${err.exitCode}` : null,
|
|
263
|
+
err?.stderr ? `Reason: ${err.stderr.trim()}` : null,
|
|
264
|
+
!err?.stderr ? err?.message ?? String(err) : null
|
|
265
|
+
].filter(Boolean).join("\n ");
|
|
266
|
+
return { success: false, error: parts };
|
|
236
267
|
}
|
|
237
268
|
}
|
|
238
269
|
async function runPlan(plan, onStep) {
|
|
@@ -249,28 +280,59 @@ async function runPlan(plan, onStep) {
|
|
|
249
280
|
}
|
|
250
281
|
|
|
251
282
|
// src/cli.ts
|
|
283
|
+
function showHelp() {
|
|
284
|
+
p.intro("json-cli \u2014 AI-powered CLI task runner");
|
|
285
|
+
p.log.message(`Usage
|
|
286
|
+
json-cli "<your goal>" [options]
|
|
287
|
+
`);
|
|
288
|
+
p.log.message(
|
|
289
|
+
`Options
|
|
290
|
+
--provider <name> AI provider: claude | openai | ollama (default: claude)
|
|
291
|
+
--yes Skip confirmation prompt
|
|
292
|
+
--dry-run Show plan without executing
|
|
293
|
+
--help Show this help message`
|
|
294
|
+
);
|
|
295
|
+
p.log.message(
|
|
296
|
+
`Examples
|
|
297
|
+
json-cli "please run tests"
|
|
298
|
+
json-cli "run tests and build"
|
|
299
|
+
json-cli "run tests and build" --yes
|
|
300
|
+
json-cli "git add, commit with message 'fix: bug', push"
|
|
301
|
+
json-cli "clone https://github.com/user/repo, install deps, run dev"
|
|
302
|
+
json-cli "run tests and publish" --provider openai
|
|
303
|
+
json-cli "run tests" --dry-run`
|
|
304
|
+
);
|
|
305
|
+
p.outro("Docs: https://github.com/ekaone/json-cli");
|
|
306
|
+
}
|
|
252
307
|
function parseArgs() {
|
|
253
308
|
const args = process.argv.slice(2);
|
|
309
|
+
if (args.length === 0 || args.includes("--help")) {
|
|
310
|
+
showHelp();
|
|
311
|
+
process.exit(0);
|
|
312
|
+
}
|
|
254
313
|
const providerFlag = args.indexOf("--provider");
|
|
255
314
|
const provider = providerFlag !== -1 ? args[providerFlag + 1] : "claude";
|
|
315
|
+
const yes = args.includes("--yes");
|
|
316
|
+
const dryRun = args.includes("--dry-run");
|
|
256
317
|
const prompt = args.filter(
|
|
257
318
|
(a, i) => !a.startsWith("--") && (providerFlag === -1 || i !== providerFlag + 1)
|
|
258
319
|
).join(" ");
|
|
259
320
|
if (!prompt) {
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
);
|
|
263
|
-
process.exit(1);
|
|
321
|
+
showHelp();
|
|
322
|
+
process.exit(0);
|
|
264
323
|
}
|
|
265
|
-
return { prompt, provider };
|
|
324
|
+
return { prompt, provider, yes, dryRun };
|
|
266
325
|
}
|
|
267
326
|
function formatStep(step) {
|
|
268
|
-
const
|
|
327
|
+
const formatArg = (arg) => arg.includes(" ") ? `"${arg}"` : arg;
|
|
328
|
+
const cmd = step.type === "shell" ? `${step.command} ${step.args.map(formatArg).join(" ")}` : `${step.type} ${step.command} ${step.args.map(formatArg).join(" ")}`.trim();
|
|
269
329
|
return `${cmd.padEnd(35)} \u2192 ${step.description}`;
|
|
270
330
|
}
|
|
271
331
|
async function main() {
|
|
272
|
-
const { prompt, provider: providerName } = parseArgs();
|
|
273
|
-
p.intro(
|
|
332
|
+
const { prompt, provider: providerName, yes, dryRun } = parseArgs();
|
|
333
|
+
p.intro(
|
|
334
|
+
`json-cli \u2014 powered by ${providerName}${dryRun ? " (dry run)" : ""}`
|
|
335
|
+
);
|
|
274
336
|
const spinner2 = p.spinner();
|
|
275
337
|
spinner2.start("Thinking...");
|
|
276
338
|
let plan;
|
|
@@ -297,10 +359,18 @@ async function main() {
|
|
|
297
359
|
"Plan contains shell commands \u2014 review carefully before proceeding."
|
|
298
360
|
);
|
|
299
361
|
}
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
362
|
+
if (dryRun) {
|
|
363
|
+
p.outro("Dry run complete \u2014 no commands were executed.");
|
|
364
|
+
setTimeout(() => process.exit(0), 50);
|
|
365
|
+
}
|
|
366
|
+
if (!yes) {
|
|
367
|
+
const confirmed = await p.confirm({ message: "Proceed?" });
|
|
368
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
369
|
+
p.cancel("Aborted.");
|
|
370
|
+
setTimeout(() => process.exit(0), 50);
|
|
371
|
+
}
|
|
372
|
+
} else {
|
|
373
|
+
p.log.info("Skipping confirmation (--yes)");
|
|
304
374
|
}
|
|
305
375
|
console.log("");
|
|
306
376
|
const result = await runPlan(plan, (step, i, total) => {
|
|
@@ -313,7 +383,7 @@ async function main() {
|
|
|
313
383
|
`\u274C Failed at step ${result.failedStep?.id}: ${result.failedStep?.description}
|
|
314
384
|
${result.error ?? ""}`
|
|
315
385
|
);
|
|
316
|
-
process.exit(1);
|
|
386
|
+
setTimeout(() => process.exit(1), 50);
|
|
317
387
|
}
|
|
318
388
|
}
|
|
319
389
|
main();
|
package/dist/index.cjs
CHANGED
|
@@ -32,7 +32,20 @@ var CATALOG = {
|
|
|
32
32
|
pnpm: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
33
33
|
yarn: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
34
34
|
bun: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
35
|
-
git: [
|
|
35
|
+
git: [
|
|
36
|
+
"init",
|
|
37
|
+
"add",
|
|
38
|
+
"commit",
|
|
39
|
+
"push",
|
|
40
|
+
"pull",
|
|
41
|
+
"clone",
|
|
42
|
+
"status",
|
|
43
|
+
"log",
|
|
44
|
+
"branch",
|
|
45
|
+
"checkout",
|
|
46
|
+
"merge",
|
|
47
|
+
"stash"
|
|
48
|
+
],
|
|
36
49
|
shell: ["any"]
|
|
37
50
|
// escape hatch — always requires extra confirmation
|
|
38
51
|
};
|
|
@@ -42,7 +55,7 @@ var StepSchema = import_zod.z.object({
|
|
|
42
55
|
command: import_zod.z.string(),
|
|
43
56
|
args: import_zod.z.array(import_zod.z.string()).default([]),
|
|
44
57
|
description: import_zod.z.string(),
|
|
45
|
-
cwd: import_zod.z.string().optional()
|
|
58
|
+
cwd: import_zod.z.string().nullable().optional()
|
|
46
59
|
// optional working directory override
|
|
47
60
|
});
|
|
48
61
|
var PlanSchema = import_zod.z.object({
|
|
@@ -85,6 +98,7 @@ Rules:
|
|
|
85
98
|
- Each step must have a clear, short description
|
|
86
99
|
- NEVER generate a "cd" step \u2014 each step runs in a separate process so "cd" has no effect
|
|
87
100
|
- If subsequent steps need to run inside a cloned directory, set the "cwd" field instead
|
|
101
|
+
- For commands that take a message or value argument (like git commit -m), always keep the full message as a single array element, e.g. args: ["-m", "Update changes"] never args: ["-m", "Update", "changes"]
|
|
88
102
|
|
|
89
103
|
Respond ONLY with valid JSON matching this exact shape, no markdown, no explanation:
|
|
90
104
|
{
|
|
@@ -92,10 +106,19 @@ Respond ONLY with valid JSON matching this exact shape, no markdown, no explanat
|
|
|
92
106
|
"steps": [
|
|
93
107
|
{
|
|
94
108
|
"id": 1,
|
|
109
|
+
"type": "git",
|
|
110
|
+
"command": "commit",
|
|
111
|
+
"args": ["-m", "Update changes"],
|
|
112
|
+
"description": "Commit staged changes",
|
|
113
|
+
"cwd": null
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": 2,
|
|
95
117
|
"type": "pnpm",
|
|
96
118
|
"command": "run",
|
|
97
|
-
"args": ["dev"],
|
|
98
|
-
"description": "Start dev server"
|
|
119
|
+
"args": ["dev", "--port", "3000"],
|
|
120
|
+
"description": "Start dev server",
|
|
121
|
+
"cwd": "my-repo"
|
|
99
122
|
}
|
|
100
123
|
]
|
|
101
124
|
}`;
|
|
@@ -146,8 +169,13 @@ async function runStep(step) {
|
|
|
146
169
|
});
|
|
147
170
|
return { success: true };
|
|
148
171
|
} catch (err) {
|
|
149
|
-
const
|
|
150
|
-
|
|
172
|
+
const parts = [
|
|
173
|
+
`Command: ${bin} ${args.join(" ")}`,
|
|
174
|
+
err?.exitCode ? `Exit code: ${err.exitCode}` : null,
|
|
175
|
+
err?.stderr ? `Reason: ${err.stderr.trim()}` : null,
|
|
176
|
+
!err?.stderr ? err?.message ?? String(err) : null
|
|
177
|
+
].filter(Boolean).join("\n ");
|
|
178
|
+
return { success: false, error: parts };
|
|
151
179
|
}
|
|
152
180
|
}
|
|
153
181
|
async function runPlan(plan, onStep) {
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/catalog.ts","../src/planner.ts","../src/runner.ts"],"sourcesContent":["/**\n * @file index.ts\n * @description Core entry point for @ekaone/json-cli.\n * @author Eka Prasetia\n * @website https://prasetia.me\n * @license MIT\n */\n\nexport { generatePlan } from \"./planner.js\";\nexport { runPlan } from \"./runner.js\";\nexport type { Plan, Step } from \"./catalog.js\";\nexport type { AIProvider } from \"./providers/types.js\";\n","import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Allowed commands per type — the whitelist that prevents hallucination\n// ---------------------------------------------------------------------------\nexport const CATALOG = {\n npm: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"ci\"],\n pnpm: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n yarn: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n bun: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n git: [\"init\", \"add\", \"commit\", \"push\", \"pull\", \"clone\", \"status\", \"log\"],\n shell: [\"any\"], // escape hatch — always requires extra confirmation\n} as const;\n\nexport type CommandType = keyof typeof CATALOG;\n\n// ---------------------------------------------------------------------------\n// Zod schemas — Layer 2 defense against hallucinated output\n// ---------------------------------------------------------------------------\nexport const StepSchema = z.object({\n id: z.number(),\n type: z.enum([\"npm\", \"pnpm\", \"yarn\", \"bun\", \"git\", \"shell\"]),\n command: z.string(),\n args: z.array(z.string()).default([]),\n description: z.string(),\n cwd: z.string().optional(), // optional working directory override\n});\n\nexport const PlanSchema = z.object({\n goal: z.string(),\n steps: z.array(StepSchema).min(1).max(10),\n});\n\nexport type Step = z.infer<typeof StepSchema>;\nexport type Plan = z.infer<typeof PlanSchema>;\n\n// ---------------------------------------------------------------------------\n// Catalog validation — Layer 3: check command is in whitelist\n// ---------------------------------------------------------------------------\nexport function validateStep(step: Step): { valid: boolean; reason?: string } {\n const allowed = CATALOG[step.type];\n\n // shell type is always allowed but flagged for extra confirmation\n if (step.type === \"shell\") {\n return { valid: true };\n }\n\n if (!allowed.includes(step.command as never)) {\n return {\n valid: false,\n reason: `\"${step.command}\" is not an allowed command for type \"${step.type}\". Allowed: ${allowed.join(\", \")}`,\n };\n }\n\n return { valid: true };\n}\n\n// ---------------------------------------------------------------------------\n// Build the catalog string injected into AI system prompt\n// ---------------------------------------------------------------------------\nexport function buildCatalogPrompt(): string {\n const lines = Object.entries(CATALOG).map(([type, commands]) => {\n const list = commands[0] === \"any\" ? \"any shell command (use sparingly)\" : commands.join(\", \");\n return ` - ${type}: [${list}]`;\n });\n\n return `Allowed command types and commands:\\n${lines.join(\"\\n\")}`;\n}\n","import { buildCatalogPrompt, PlanSchema, validateStep } from \"./catalog.js\";\nimport type { AIProvider } from \"./providers/types.js\";\nimport type { Plan } from \"./catalog.js\";\n\n// ---------------------------------------------------------------------------\n// System prompt — constrains AI to only produce catalog-valid JSON\n// ---------------------------------------------------------------------------\nfunction buildSystemPrompt(): string {\n return `You are a CLI task planner. Given a user's goal, generate a JSON execution plan.\n\n${buildCatalogPrompt()}\n\nRules:\n- ONLY use command types and commands listed above\n- Prefer pnpm over npm unless the user specifies otherwise\n- Use \"shell\" type only when no other type fits\n- Keep steps minimal — don't add unnecessary steps\n- Each step must have a clear, short description\n- NEVER generate a \"cd\" step — each step runs in a separate process so \"cd\" has no effect\n- If subsequent steps need to run inside a cloned directory, set the \"cwd\" field instead\n\nRespond ONLY with valid JSON matching this exact shape, no markdown, no explanation:\n{\n \"goal\": \"string describing the overall goal\",\n \"steps\": [\n {\n \"id\": 1,\n \"type\": \"pnpm\",\n \"command\": \"run\",\n \"args\": [\"dev\"],\n \"description\": \"Start dev server\"\n }\n ]\n}`;\n}\n\n// ---------------------------------------------------------------------------\n// Main planner function\n// ---------------------------------------------------------------------------\nexport async function generatePlan(\n userPrompt: string,\n provider: AIProvider,\n): Promise<Plan> {\n const raw = await provider.generate(userPrompt, buildSystemPrompt());\n\n // Strip markdown fences if any provider wraps output\n const cleaned = raw.replace(/```json|```/g, \"\").trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch {\n throw new Error(`AI returned invalid JSON:\\n${cleaned}`);\n }\n\n // Layer 2: Zod shape validation\n const result = PlanSchema.safeParse(parsed);\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` - ${i.path.join(\".\")}: ${i.message}`)\n .join(\"\\n\");\n throw new Error(`Plan failed schema validation:\\n${issues}`);\n }\n\n // Layer 3: Catalog whitelist validation\n for (const step of result.data.steps) {\n const check = validateStep(step);\n if (!check.valid) {\n throw new Error(\n `Step ${step.id} failed catalog validation: ${check.reason}`,\n );\n }\n }\n\n return result.data;\n}\n","import { execa } from \"execa\";\nimport type { Plan, Step } from \"./catalog.js\";\n\n// ---------------------------------------------------------------------------\n// Build the actual shell command from a Step\n// ---------------------------------------------------------------------------\nfunction resolveCommand(step: Step): { bin: string; args: string[] } {\n if (step.type === \"shell\") {\n // shell: command is the binary, args are the args\n return { bin: step.command, args: step.args };\n }\n\n // For npm/pnpm/yarn/bun/git: binary is the type, command + args follow\n return { bin: step.type, args: [step.command, ...step.args] };\n}\n\n// ---------------------------------------------------------------------------\n// Run a single step, streaming stdout/stderr live\n// ---------------------------------------------------------------------------\nexport async function runStep(step: Step): Promise<{ success: boolean; error?: string }> {\n const { bin, args } = resolveCommand(step);\n\n try {\n await execa(bin, args, {\n cwd: step.cwd ?? process.cwd(),\n stdout: \"inherit\", // stream directly to terminal\n stderr: \"inherit\",\n });\n return { success: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { success: false, error: message };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run the full plan, stopping on first failure\n// ---------------------------------------------------------------------------\nexport async function runPlan(\n plan: Plan,\n onStep: (step: Step, index: number, total: number) => void\n): Promise<{ success: boolean; failedStep?: Step; error?: string }> {\n const total = plan.steps.length;\n\n for (let i = 0; i < total; i++) {\n const step = plan.steps[i];\n onStep(step, i, total);\n\n const result = await runStep(step);\n\n if (!result.success) {\n return { success: false, failedStep: step, error: result.error };\n }\n }\n\n return { success: true };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAA,iBAAkB;AAKX,IAAM,UAAU;AAAA,EACrB,KAAO,CAAC,WAAW,OAAO,SAAS,QAAQ,WAAW,IAAI;AAAA,EAC1D,MAAO,CAAC,WAAW,OAAO,SAAS,QAAQ,WAAW,OAAO,QAAQ;AAAA,EACrE,MAAO,CAAC,WAAW,OAAO,SAAS,QAAQ,WAAW,OAAO,QAAQ;AAAA,EACrE,KAAO,CAAC,WAAW,OAAO,SAAS,QAAQ,WAAW,OAAO,QAAQ;AAAA,EACrE,KAAO,CAAC,QAAQ,OAAO,UAAU,QAAQ,QAAQ,SAAS,UAAU,KAAK;AAAA,EACzE,OAAO,CAAC,KAAK;AAAA;AACf;AAOO,IAAM,aAAa,aAAE,OAAO;AAAA,EACjC,IAAa,aAAE,OAAO;AAAA,EACtB,MAAa,aAAE,KAAK,CAAC,OAAO,QAAQ,QAAQ,OAAO,OAAO,OAAO,CAAC;AAAA,EAClE,SAAa,aAAE,OAAO;AAAA,EACtB,MAAa,aAAE,MAAM,aAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,EAC3C,aAAa,aAAE,OAAO;AAAA,EACtB,KAAa,aAAE,OAAO,EAAE,SAAS;AAAA;AACnC,CAAC;AAEM,IAAM,aAAa,aAAE,OAAO;AAAA,EACjC,MAAO,aAAE,OAAO;AAAA,EAChB,OAAO,aAAE,MAAM,UAAU,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE;AAC1C,CAAC;AAQM,SAAS,aAAa,MAAiD;AAC5E,QAAM,UAAU,QAAQ,KAAK,IAAI;AAGjC,MAAI,KAAK,SAAS,SAAS;AACzB,WAAO,EAAE,OAAO,KAAK;AAAA,EACvB;AAEA,MAAI,CAAC,QAAQ,SAAS,KAAK,OAAgB,GAAG;AAC5C,WAAO;AAAA,MACL,OAAO;AAAA,MACP,QAAQ,IAAI,KAAK,OAAO,yCAAyC,KAAK,IAAI,eAAe,QAAQ,KAAK,IAAI,CAAC;AAAA,IAC7G;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,KAAK;AACvB;AAKO,SAAS,qBAA6B;AAC3C,QAAM,QAAQ,OAAO,QAAQ,OAAO,EAAE,IAAI,CAAC,CAAC,MAAM,QAAQ,MAAM;AAC9D,UAAM,OAAO,SAAS,CAAC,MAAM,QAAQ,sCAAsC,SAAS,KAAK,IAAI;AAC7F,WAAO,OAAO,IAAI,MAAM,IAAI;AAAA,EAC9B,CAAC;AAED,SAAO;AAAA,EAAwC,MAAM,KAAK,IAAI,CAAC;AACjE;;;AC5DA,SAAS,oBAA4B;AACnC,SAAO;AAAA;AAAA,EAEP,mBAAmB,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAwBtB;AAKA,eAAsB,aACpB,YACA,UACe;AACf,QAAM,MAAM,MAAM,SAAS,SAAS,YAAY,kBAAkB,CAAC;AAGnE,QAAM,UAAU,IAAI,QAAQ,gBAAgB,EAAE,EAAE,KAAK;AAErD,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,OAAO;AAAA,EAC7B,QAAQ;AACN,UAAM,IAAI,MAAM;AAAA,EAA8B,OAAO,EAAE;AAAA,EACzD;AAGA,QAAM,SAAS,WAAW,UAAU,MAAM;AAC1C,MAAI,CAAC,OAAO,SAAS;AACnB,UAAM,SAAS,OAAO,MAAM,OACzB,IAAI,CAAC,MAAM,OAAO,EAAE,KAAK,KAAK,GAAG,CAAC,KAAK,EAAE,OAAO,EAAE,EAClD,KAAK,IAAI;AACZ,UAAM,IAAI,MAAM;AAAA,EAAmC,MAAM,EAAE;AAAA,EAC7D;AAGA,aAAW,QAAQ,OAAO,KAAK,OAAO;AACpC,UAAM,QAAQ,aAAa,IAAI;AAC/B,QAAI,CAAC,MAAM,OAAO;AAChB,YAAM,IAAI;AAAA,QACR,QAAQ,KAAK,EAAE,+BAA+B,MAAM,MAAM;AAAA,MAC5D;AAAA,IACF;AAAA,EACF;AAEA,SAAO,OAAO;AAChB;;;AC3EA,mBAAsB;AAMtB,SAAS,eAAe,MAA6C;AACnE,MAAI,KAAK,SAAS,SAAS;AAEzB,WAAO,EAAE,KAAK,KAAK,SAAS,MAAM,KAAK,KAAK;AAAA,EAC9C;AAGA,SAAO,EAAE,KAAK,KAAK,MAAM,MAAM,CAAC,KAAK,SAAS,GAAG,KAAK,IAAI,EAAE;AAC9D;AAKA,eAAsB,QAAQ,MAA2D;AACvF,QAAM,EAAE,KAAK,KAAK,IAAI,eAAe,IAAI;AAEzC,MAAI;AACF,cAAM,oBAAM,KAAK,MAAM;AAAA,MACrB,KAAQ,KAAK,OAAO,QAAQ,IAAI;AAAA,MAChC,QAAQ;AAAA;AAAA,MACR,QAAQ;AAAA,IACV,CAAC;AACD,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,WAAO,EAAE,SAAS,OAAO,OAAO,QAAQ;AAAA,EAC1C;AACF;AAKA,eAAsB,QACpB,MACA,QACkE;AAClE,QAAM,QAAQ,KAAK,MAAM;AAEzB,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,UAAM,OAAO,KAAK,MAAM,CAAC;AACzB,WAAO,MAAM,GAAG,KAAK;AAErB,UAAM,SAAS,MAAM,QAAQ,IAAI;AAEjC,QAAI,CAAC,OAAO,SAAS;AACnB,aAAO,EAAE,SAAS,OAAO,YAAY,MAAM,OAAO,OAAO,MAAM;AAAA,IACjE;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,KAAK;AACzB;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/catalog.ts","../src/planner.ts","../src/runner.ts"],"sourcesContent":["/**\n * @file index.ts\n * @description Core entry point for @ekaone/json-cli.\n * @author Eka Prasetia\n * @website https://prasetia.me\n * @license MIT\n */\n\nexport { generatePlan } from \"./planner.js\";\nexport { runPlan } from \"./runner.js\";\nexport type { Plan, Step } from \"./catalog.js\";\nexport type { AIProvider } from \"./providers/types.js\";\n","import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Allowed commands per type — the whitelist that prevents hallucination\n// ---------------------------------------------------------------------------\nexport const CATALOG = {\n npm: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"ci\"],\n pnpm: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n yarn: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n bun: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n git: [\n \"init\",\n \"add\",\n \"commit\",\n \"push\",\n \"pull\",\n \"clone\",\n \"status\",\n \"log\",\n \"branch\",\n \"checkout\",\n \"merge\",\n \"stash\",\n ],\n shell: [\"any\"], // escape hatch — always requires extra confirmation\n} as const;\n\nexport type CommandType = keyof typeof CATALOG;\n\n// ---------------------------------------------------------------------------\n// Zod schemas — Layer 2 defense against hallucinated output\n// ---------------------------------------------------------------------------\nexport const StepSchema = z.object({\n id: z.number(),\n type: z.enum([\"npm\", \"pnpm\", \"yarn\", \"bun\", \"git\", \"shell\"]),\n command: z.string(),\n args: z.array(z.string()).default([]),\n description: z.string(),\n cwd: z.string().nullable().optional(), // optional working directory override\n});\n\nexport const PlanSchema = z.object({\n goal: z.string(),\n steps: z.array(StepSchema).min(1).max(10),\n});\n\nexport type Step = z.infer<typeof StepSchema>;\nexport type Plan = z.infer<typeof PlanSchema>;\n\n// ---------------------------------------------------------------------------\n// Catalog validation — Layer 3: check command is in whitelist\n// ---------------------------------------------------------------------------\nexport function validateStep(step: Step): { valid: boolean; reason?: string } {\n const allowed = CATALOG[step.type];\n\n // shell type is always allowed but flagged for extra confirmation\n if (step.type === \"shell\") {\n return { valid: true };\n }\n\n if (!allowed.includes(step.command as never)) {\n return {\n valid: false,\n reason: `\"${step.command}\" is not an allowed command for type \"${step.type}\". Allowed: ${allowed.join(\", \")}`,\n };\n }\n\n return { valid: true };\n}\n\n// ---------------------------------------------------------------------------\n// Build the catalog string injected into AI system prompt\n// ---------------------------------------------------------------------------\nexport function buildCatalogPrompt(): string {\n const lines = Object.entries(CATALOG).map(([type, commands]) => {\n const list =\n commands[0] === \"any\"\n ? \"any shell command (use sparingly)\"\n : commands.join(\", \");\n return ` - ${type}: [${list}]`;\n });\n\n return `Allowed command types and commands:\\n${lines.join(\"\\n\")}`;\n}\n","import { buildCatalogPrompt, PlanSchema, validateStep } from \"./catalog.js\";\nimport type { AIProvider } from \"./providers/types.js\";\nimport type { Plan } from \"./catalog.js\";\n\n// ---------------------------------------------------------------------------\n// System prompt — constrains AI to only produce catalog-valid JSON\n// ---------------------------------------------------------------------------\nfunction buildSystemPrompt(): string {\n return `You are a CLI task planner. Given a user's goal, generate a JSON execution plan.\n\n${buildCatalogPrompt()}\n\nRules:\n- ONLY use command types and commands listed above\n- Prefer pnpm over npm unless the user specifies otherwise\n- Use \"shell\" type only when no other type fits\n- Keep steps minimal — don't add unnecessary steps\n- Each step must have a clear, short description\n- NEVER generate a \"cd\" step — each step runs in a separate process so \"cd\" has no effect\n- If subsequent steps need to run inside a cloned directory, set the \"cwd\" field instead\n- For commands that take a message or value argument (like git commit -m), always keep the full message as a single array element, e.g. args: [\"-m\", \"Update changes\"] never args: [\"-m\", \"Update\", \"changes\"]\n\nRespond ONLY with valid JSON matching this exact shape, no markdown, no explanation:\n{\n \"goal\": \"string describing the overall goal\",\n \"steps\": [\n {\n \"id\": 1,\n \"type\": \"git\",\n \"command\": \"commit\",\n \"args\": [\"-m\", \"Update changes\"],\n \"description\": \"Commit staged changes\",\n \"cwd\": null\n },\n {\n \"id\": 2,\n \"type\": \"pnpm\",\n \"command\": \"run\",\n \"args\": [\"dev\", \"--port\", \"3000\"],\n \"description\": \"Start dev server\",\n \"cwd\": \"my-repo\"\n }\n ]\n}`;\n}\n\n// ---------------------------------------------------------------------------\n// Main planner function\n// ---------------------------------------------------------------------------\nexport async function generatePlan(\n userPrompt: string,\n provider: AIProvider,\n): Promise<Plan> {\n const raw = await provider.generate(userPrompt, buildSystemPrompt());\n\n // Strip markdown fences if any provider wraps output\n const cleaned = raw.replace(/```json|```/g, \"\").trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch {\n throw new Error(`AI returned invalid JSON:\\n${cleaned}`);\n }\n\n // Layer 2: Zod shape validation\n const result = PlanSchema.safeParse(parsed);\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` - ${i.path.join(\".\")}: ${i.message}`)\n .join(\"\\n\");\n throw new Error(`Plan failed schema validation:\\n${issues}`);\n }\n\n // Layer 3: Catalog whitelist validation\n for (const step of result.data.steps) {\n const check = validateStep(step);\n if (!check.valid) {\n throw new Error(\n `Step ${step.id} failed catalog validation: ${check.reason}`,\n );\n }\n }\n\n return result.data;\n}\n","import { execa } from \"execa\";\nimport type { Plan, Step } from \"./catalog.js\";\n\n// ---------------------------------------------------------------------------\n// Build the actual shell command from a Step\n// ---------------------------------------------------------------------------\nfunction resolveCommand(step: Step): { bin: string; args: string[] } {\n if (step.type === \"shell\") {\n // shell: command is the binary, args are the args\n return { bin: step.command, args: step.args };\n }\n\n // For npm/pnpm/yarn/bun/git: binary is the type, command + args follow\n return { bin: step.type, args: [step.command, ...step.args] };\n}\n\n// ---------------------------------------------------------------------------\n// Run a single step, streaming stdout/stderr live\n// ---------------------------------------------------------------------------\nexport async function runStep(\n step: Step,\n): Promise<{ success: boolean; error?: string }> {\n const { bin, args } = resolveCommand(step);\n\n try {\n await execa(bin, args, {\n cwd: step.cwd ?? process.cwd(),\n stdout: \"inherit\", // stream directly to terminal\n stderr: \"inherit\",\n });\n return { success: true };\n } catch (err: any) {\n const parts = [\n `Command: ${bin} ${args.join(\" \")}`,\n err?.exitCode ? `Exit code: ${err.exitCode}` : null,\n err?.stderr ? `Reason: ${err.stderr.trim()}` : null,\n !err?.stderr ? (err?.message ?? String(err)) : null,\n ]\n .filter(Boolean)\n .join(\"\\n \");\n\n return { success: false, error: parts };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run the full plan, stopping on first failure\n// ---------------------------------------------------------------------------\nexport async function runPlan(\n plan: Plan,\n onStep: (step: Step, index: number, total: number) => void,\n): Promise<{ success: boolean; failedStep?: Step; error?: string }> {\n const total = plan.steps.length;\n\n for (let i = 0; i < total; i++) {\n const step = plan.steps[i];\n onStep(step, i, total);\n\n const result = await runStep(step);\n\n if (!result.success) {\n return { success: false, failedStep: step, error: result.error };\n }\n }\n\n return { success: true };\n}\n"],"mappings":"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","names":[]}
|
package/dist/index.d.cts
CHANGED
|
@@ -18,7 +18,7 @@ declare const StepSchema: z.ZodObject<{
|
|
|
18
18
|
command: z.ZodString;
|
|
19
19
|
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
20
20
|
description: z.ZodString;
|
|
21
|
-
cwd: z.ZodOptional<z.ZodString
|
|
21
|
+
cwd: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
22
22
|
}, z.core.$strip>;
|
|
23
23
|
declare const PlanSchema: z.ZodObject<{
|
|
24
24
|
goal: z.ZodString;
|
|
@@ -35,7 +35,7 @@ declare const PlanSchema: z.ZodObject<{
|
|
|
35
35
|
command: z.ZodString;
|
|
36
36
|
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
37
37
|
description: z.ZodString;
|
|
38
|
-
cwd: z.ZodOptional<z.ZodString
|
|
38
|
+
cwd: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
39
39
|
}, z.core.$strip>>;
|
|
40
40
|
}, z.core.$strip>;
|
|
41
41
|
type Step = z.infer<typeof StepSchema>;
|
package/dist/index.d.ts
CHANGED
|
@@ -18,7 +18,7 @@ declare const StepSchema: z.ZodObject<{
|
|
|
18
18
|
command: z.ZodString;
|
|
19
19
|
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
20
20
|
description: z.ZodString;
|
|
21
|
-
cwd: z.ZodOptional<z.ZodString
|
|
21
|
+
cwd: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
22
22
|
}, z.core.$strip>;
|
|
23
23
|
declare const PlanSchema: z.ZodObject<{
|
|
24
24
|
goal: z.ZodString;
|
|
@@ -35,7 +35,7 @@ declare const PlanSchema: z.ZodObject<{
|
|
|
35
35
|
command: z.ZodString;
|
|
36
36
|
args: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
37
37
|
description: z.ZodString;
|
|
38
|
-
cwd: z.ZodOptional<z.ZodString
|
|
38
|
+
cwd: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
39
39
|
}, z.core.$strip>>;
|
|
40
40
|
}, z.core.$strip>;
|
|
41
41
|
type Step = z.infer<typeof StepSchema>;
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,20 @@ var CATALOG = {
|
|
|
5
5
|
pnpm: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
6
6
|
yarn: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
7
7
|
bun: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
8
|
-
git: [
|
|
8
|
+
git: [
|
|
9
|
+
"init",
|
|
10
|
+
"add",
|
|
11
|
+
"commit",
|
|
12
|
+
"push",
|
|
13
|
+
"pull",
|
|
14
|
+
"clone",
|
|
15
|
+
"status",
|
|
16
|
+
"log",
|
|
17
|
+
"branch",
|
|
18
|
+
"checkout",
|
|
19
|
+
"merge",
|
|
20
|
+
"stash"
|
|
21
|
+
],
|
|
9
22
|
shell: ["any"]
|
|
10
23
|
// escape hatch — always requires extra confirmation
|
|
11
24
|
};
|
|
@@ -15,7 +28,7 @@ var StepSchema = z.object({
|
|
|
15
28
|
command: z.string(),
|
|
16
29
|
args: z.array(z.string()).default([]),
|
|
17
30
|
description: z.string(),
|
|
18
|
-
cwd: z.string().optional()
|
|
31
|
+
cwd: z.string().nullable().optional()
|
|
19
32
|
// optional working directory override
|
|
20
33
|
});
|
|
21
34
|
var PlanSchema = z.object({
|
|
@@ -58,6 +71,7 @@ Rules:
|
|
|
58
71
|
- Each step must have a clear, short description
|
|
59
72
|
- NEVER generate a "cd" step \u2014 each step runs in a separate process so "cd" has no effect
|
|
60
73
|
- If subsequent steps need to run inside a cloned directory, set the "cwd" field instead
|
|
74
|
+
- For commands that take a message or value argument (like git commit -m), always keep the full message as a single array element, e.g. args: ["-m", "Update changes"] never args: ["-m", "Update", "changes"]
|
|
61
75
|
|
|
62
76
|
Respond ONLY with valid JSON matching this exact shape, no markdown, no explanation:
|
|
63
77
|
{
|
|
@@ -65,10 +79,19 @@ Respond ONLY with valid JSON matching this exact shape, no markdown, no explanat
|
|
|
65
79
|
"steps": [
|
|
66
80
|
{
|
|
67
81
|
"id": 1,
|
|
82
|
+
"type": "git",
|
|
83
|
+
"command": "commit",
|
|
84
|
+
"args": ["-m", "Update changes"],
|
|
85
|
+
"description": "Commit staged changes",
|
|
86
|
+
"cwd": null
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
"id": 2,
|
|
68
90
|
"type": "pnpm",
|
|
69
91
|
"command": "run",
|
|
70
|
-
"args": ["dev"],
|
|
71
|
-
"description": "Start dev server"
|
|
92
|
+
"args": ["dev", "--port", "3000"],
|
|
93
|
+
"description": "Start dev server",
|
|
94
|
+
"cwd": "my-repo"
|
|
72
95
|
}
|
|
73
96
|
]
|
|
74
97
|
}`;
|
|
@@ -119,8 +142,13 @@ async function runStep(step) {
|
|
|
119
142
|
});
|
|
120
143
|
return { success: true };
|
|
121
144
|
} catch (err) {
|
|
122
|
-
const
|
|
123
|
-
|
|
145
|
+
const parts = [
|
|
146
|
+
`Command: ${bin} ${args.join(" ")}`,
|
|
147
|
+
err?.exitCode ? `Exit code: ${err.exitCode}` : null,
|
|
148
|
+
err?.stderr ? `Reason: ${err.stderr.trim()}` : null,
|
|
149
|
+
!err?.stderr ? err?.message ?? String(err) : null
|
|
150
|
+
].filter(Boolean).join("\n ");
|
|
151
|
+
return { success: false, error: parts };
|
|
124
152
|
}
|
|
125
153
|
}
|
|
126
154
|
async function runPlan(plan, onStep) {
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/catalog.ts","../src/planner.ts","../src/runner.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Allowed commands per type — the whitelist that prevents hallucination\n// ---------------------------------------------------------------------------\nexport const CATALOG = {\n npm: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"ci\"],\n pnpm: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n yarn: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n bun: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n git: [\"init\", \"add\", \"commit\", \"push\", \"pull\", \"clone\", \"status\", \"log\"],\n shell: [\"any\"], // escape hatch — always requires extra confirmation\n} as const;\n\nexport type CommandType = keyof typeof CATALOG;\n\n// ---------------------------------------------------------------------------\n// Zod schemas — Layer 2 defense against hallucinated output\n// ---------------------------------------------------------------------------\nexport const StepSchema = z.object({\n id: z.number(),\n type: z.enum([\"npm\", \"pnpm\", \"yarn\", \"bun\", \"git\", \"shell\"]),\n command: z.string(),\n args: z.array(z.string()).default([]),\n description: z.string(),\n cwd: z.string().optional(), // optional working directory override\n});\n\nexport const PlanSchema = z.object({\n goal: z.string(),\n steps: z.array(StepSchema).min(1).max(10),\n});\n\nexport type Step = z.infer<typeof StepSchema>;\nexport type Plan = z.infer<typeof PlanSchema>;\n\n// ---------------------------------------------------------------------------\n// Catalog validation — Layer 3: check command is in whitelist\n// ---------------------------------------------------------------------------\nexport function validateStep(step: Step): { valid: boolean; reason?: string } {\n const allowed = CATALOG[step.type];\n\n // shell type is always allowed but flagged for extra confirmation\n if (step.type === \"shell\") {\n return { valid: true };\n }\n\n if (!allowed.includes(step.command as never)) {\n return {\n valid: false,\n reason: `\"${step.command}\" is not an allowed command for type \"${step.type}\". Allowed: ${allowed.join(\", \")}`,\n };\n }\n\n return { valid: true };\n}\n\n// ---------------------------------------------------------------------------\n// Build the catalog string injected into AI system prompt\n// ---------------------------------------------------------------------------\nexport function buildCatalogPrompt(): string {\n const lines = Object.entries(CATALOG).map(([type, commands]) => {\n const list = commands[0] === \"any\" ? \"any shell command (use sparingly)\" : commands.join(\", \");\n return ` - ${type}: [${list}]`;\n });\n\n return `Allowed command types and commands:\\n${lines.join(\"\\n\")}`;\n}\n","import { buildCatalogPrompt, PlanSchema, validateStep } from \"./catalog.js\";\nimport type { AIProvider } from \"./providers/types.js\";\nimport type { Plan } from \"./catalog.js\";\n\n// ---------------------------------------------------------------------------\n// System prompt — constrains AI to only produce catalog-valid JSON\n// ---------------------------------------------------------------------------\nfunction buildSystemPrompt(): string {\n return `You are a CLI task planner. Given a user's goal, generate a JSON execution plan.\n\n${buildCatalogPrompt()}\n\nRules:\n- ONLY use command types and commands listed above\n- Prefer pnpm over npm unless the user specifies otherwise\n- Use \"shell\" type only when no other type fits\n- Keep steps minimal — don't add unnecessary steps\n- Each step must have a clear, short description\n- NEVER generate a \"cd\" step — each step runs in a separate process so \"cd\" has no effect\n- If subsequent steps need to run inside a cloned directory, set the \"cwd\" field instead\n\nRespond ONLY with valid JSON matching this exact shape, no markdown, no explanation:\n{\n \"goal\": \"string describing the overall goal\",\n \"steps\": [\n {\n \"id\": 1,\n \"type\": \"pnpm\",\n \"command\": \"run\",\n \"args\": [\"dev\"],\n \"description\": \"Start dev server\"\n }\n ]\n}`;\n}\n\n// ---------------------------------------------------------------------------\n// Main planner function\n// ---------------------------------------------------------------------------\nexport async function generatePlan(\n userPrompt: string,\n provider: AIProvider,\n): Promise<Plan> {\n const raw = await provider.generate(userPrompt, buildSystemPrompt());\n\n // Strip markdown fences if any provider wraps output\n const cleaned = raw.replace(/```json|```/g, \"\").trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch {\n throw new Error(`AI returned invalid JSON:\\n${cleaned}`);\n }\n\n // Layer 2: Zod shape validation\n const result = PlanSchema.safeParse(parsed);\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` - ${i.path.join(\".\")}: ${i.message}`)\n .join(\"\\n\");\n throw new Error(`Plan failed schema validation:\\n${issues}`);\n }\n\n // Layer 3: Catalog whitelist validation\n for (const step of result.data.steps) {\n const check = validateStep(step);\n if (!check.valid) {\n throw new Error(\n `Step ${step.id} failed catalog validation: ${check.reason}`,\n );\n }\n }\n\n return result.data;\n}\n","import { execa } from \"execa\";\nimport type { Plan, Step } from \"./catalog.js\";\n\n// ---------------------------------------------------------------------------\n// Build the actual shell command from a Step\n// ---------------------------------------------------------------------------\nfunction resolveCommand(step: Step): { bin: string; args: string[] } {\n if (step.type === \"shell\") {\n // shell: command is the binary, args are the args\n return { bin: step.command, args: step.args };\n }\n\n // For npm/pnpm/yarn/bun/git: binary is the type, command + args follow\n return { bin: step.type, args: [step.command, ...step.args] };\n}\n\n// ---------------------------------------------------------------------------\n// Run a single step, streaming stdout/stderr live\n// ---------------------------------------------------------------------------\nexport async function runStep(step: Step): Promise<{ success: boolean; error?: string }> {\n const { bin, args } = resolveCommand(step);\n\n try {\n await execa(bin, args, {\n cwd: step.cwd ?? process.cwd(),\n stdout: \"inherit\", // stream directly to terminal\n stderr: \"inherit\",\n });\n return { success: true };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n return { success: false, error: message };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run the full plan, stopping on first failure\n// ---------------------------------------------------------------------------\nexport async function runPlan(\n plan: Plan,\n onStep: (step: Step, index: number, total: number) => void\n): Promise<{ success: boolean; failedStep?: Step; error?: string }> {\n const total = plan.steps.length;\n\n for (let i = 0; i < total; i++) {\n const step = plan.steps[i];\n onStep(step, i, total);\n\n const result = await runStep(step);\n\n if (!result.success) {\n return { success: false, failedStep: step, error: result.error };\n }\n }\n\n return { success: true };\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/catalog.ts","../src/planner.ts","../src/runner.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// ---------------------------------------------------------------------------\n// Allowed commands per type — the whitelist that prevents hallucination\n// ---------------------------------------------------------------------------\nexport const CATALOG = {\n npm: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"ci\"],\n pnpm: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n yarn: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n bun: [\"install\", \"run\", \"build\", \"test\", \"publish\", \"add\", \"remove\"],\n git: [\n \"init\",\n \"add\",\n \"commit\",\n \"push\",\n \"pull\",\n \"clone\",\n \"status\",\n \"log\",\n \"branch\",\n \"checkout\",\n \"merge\",\n \"stash\",\n ],\n shell: [\"any\"], // escape hatch — always requires extra confirmation\n} as const;\n\nexport type CommandType = keyof typeof CATALOG;\n\n// ---------------------------------------------------------------------------\n// Zod schemas — Layer 2 defense against hallucinated output\n// ---------------------------------------------------------------------------\nexport const StepSchema = z.object({\n id: z.number(),\n type: z.enum([\"npm\", \"pnpm\", \"yarn\", \"bun\", \"git\", \"shell\"]),\n command: z.string(),\n args: z.array(z.string()).default([]),\n description: z.string(),\n cwd: z.string().nullable().optional(), // optional working directory override\n});\n\nexport const PlanSchema = z.object({\n goal: z.string(),\n steps: z.array(StepSchema).min(1).max(10),\n});\n\nexport type Step = z.infer<typeof StepSchema>;\nexport type Plan = z.infer<typeof PlanSchema>;\n\n// ---------------------------------------------------------------------------\n// Catalog validation — Layer 3: check command is in whitelist\n// ---------------------------------------------------------------------------\nexport function validateStep(step: Step): { valid: boolean; reason?: string } {\n const allowed = CATALOG[step.type];\n\n // shell type is always allowed but flagged for extra confirmation\n if (step.type === \"shell\") {\n return { valid: true };\n }\n\n if (!allowed.includes(step.command as never)) {\n return {\n valid: false,\n reason: `\"${step.command}\" is not an allowed command for type \"${step.type}\". Allowed: ${allowed.join(\", \")}`,\n };\n }\n\n return { valid: true };\n}\n\n// ---------------------------------------------------------------------------\n// Build the catalog string injected into AI system prompt\n// ---------------------------------------------------------------------------\nexport function buildCatalogPrompt(): string {\n const lines = Object.entries(CATALOG).map(([type, commands]) => {\n const list =\n commands[0] === \"any\"\n ? \"any shell command (use sparingly)\"\n : commands.join(\", \");\n return ` - ${type}: [${list}]`;\n });\n\n return `Allowed command types and commands:\\n${lines.join(\"\\n\")}`;\n}\n","import { buildCatalogPrompt, PlanSchema, validateStep } from \"./catalog.js\";\nimport type { AIProvider } from \"./providers/types.js\";\nimport type { Plan } from \"./catalog.js\";\n\n// ---------------------------------------------------------------------------\n// System prompt — constrains AI to only produce catalog-valid JSON\n// ---------------------------------------------------------------------------\nfunction buildSystemPrompt(): string {\n return `You are a CLI task planner. Given a user's goal, generate a JSON execution plan.\n\n${buildCatalogPrompt()}\n\nRules:\n- ONLY use command types and commands listed above\n- Prefer pnpm over npm unless the user specifies otherwise\n- Use \"shell\" type only when no other type fits\n- Keep steps minimal — don't add unnecessary steps\n- Each step must have a clear, short description\n- NEVER generate a \"cd\" step — each step runs in a separate process so \"cd\" has no effect\n- If subsequent steps need to run inside a cloned directory, set the \"cwd\" field instead\n- For commands that take a message or value argument (like git commit -m), always keep the full message as a single array element, e.g. args: [\"-m\", \"Update changes\"] never args: [\"-m\", \"Update\", \"changes\"]\n\nRespond ONLY with valid JSON matching this exact shape, no markdown, no explanation:\n{\n \"goal\": \"string describing the overall goal\",\n \"steps\": [\n {\n \"id\": 1,\n \"type\": \"git\",\n \"command\": \"commit\",\n \"args\": [\"-m\", \"Update changes\"],\n \"description\": \"Commit staged changes\",\n \"cwd\": null\n },\n {\n \"id\": 2,\n \"type\": \"pnpm\",\n \"command\": \"run\",\n \"args\": [\"dev\", \"--port\", \"3000\"],\n \"description\": \"Start dev server\",\n \"cwd\": \"my-repo\"\n }\n ]\n}`;\n}\n\n// ---------------------------------------------------------------------------\n// Main planner function\n// ---------------------------------------------------------------------------\nexport async function generatePlan(\n userPrompt: string,\n provider: AIProvider,\n): Promise<Plan> {\n const raw = await provider.generate(userPrompt, buildSystemPrompt());\n\n // Strip markdown fences if any provider wraps output\n const cleaned = raw.replace(/```json|```/g, \"\").trim();\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch {\n throw new Error(`AI returned invalid JSON:\\n${cleaned}`);\n }\n\n // Layer 2: Zod shape validation\n const result = PlanSchema.safeParse(parsed);\n if (!result.success) {\n const issues = result.error.issues\n .map((i) => ` - ${i.path.join(\".\")}: ${i.message}`)\n .join(\"\\n\");\n throw new Error(`Plan failed schema validation:\\n${issues}`);\n }\n\n // Layer 3: Catalog whitelist validation\n for (const step of result.data.steps) {\n const check = validateStep(step);\n if (!check.valid) {\n throw new Error(\n `Step ${step.id} failed catalog validation: ${check.reason}`,\n );\n }\n }\n\n return result.data;\n}\n","import { execa } from \"execa\";\nimport type { Plan, Step } from \"./catalog.js\";\n\n// ---------------------------------------------------------------------------\n// Build the actual shell command from a Step\n// ---------------------------------------------------------------------------\nfunction resolveCommand(step: Step): { bin: string; args: string[] } {\n if (step.type === \"shell\") {\n // shell: command is the binary, args are the args\n return { bin: step.command, args: step.args };\n }\n\n // For npm/pnpm/yarn/bun/git: binary is the type, command + args follow\n return { bin: step.type, args: [step.command, ...step.args] };\n}\n\n// ---------------------------------------------------------------------------\n// Run a single step, streaming stdout/stderr live\n// ---------------------------------------------------------------------------\nexport async function runStep(\n step: Step,\n): Promise<{ success: boolean; error?: string }> {\n const { bin, args } = resolveCommand(step);\n\n try {\n await execa(bin, args, {\n cwd: step.cwd ?? process.cwd(),\n stdout: \"inherit\", // stream directly to terminal\n stderr: \"inherit\",\n });\n return { success: true };\n } catch (err: any) {\n const parts = [\n `Command: ${bin} ${args.join(\" \")}`,\n err?.exitCode ? `Exit code: ${err.exitCode}` : null,\n err?.stderr ? `Reason: ${err.stderr.trim()}` : null,\n !err?.stderr ? (err?.message ?? String(err)) : null,\n ]\n .filter(Boolean)\n .join(\"\\n \");\n\n return { success: false, error: parts };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Run the full plan, stopping on first failure\n// ---------------------------------------------------------------------------\nexport async function runPlan(\n plan: Plan,\n onStep: (step: Step, index: number, total: number) => void,\n): Promise<{ success: boolean; failedStep?: Step; error?: string }> {\n const total = plan.steps.length;\n\n for (let i = 0; i < total; i++) {\n const step = plan.steps[i];\n onStep(step, i, total);\n\n const result = await runStep(step);\n\n if (!result.success) {\n return { success: false, failedStep: step, error: result.error };\n }\n }\n\n return { success: true };\n}\n"],"mappings":"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","names":[]}
|