@ekaone/json-cli 0.1.1 → 0.1.3
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/README.md +112 -28
- package/dist/cli.cjs +76 -16
- package/dist/index.cjs +26 -4
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +26 -4
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -16,80 +16,164 @@ pnpm install -g @ekaone/json-cli
|
|
|
16
16
|
yarn global add @ekaone/json-cli
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Setup
|
|
20
20
|
|
|
21
|
-
```
|
|
21
|
+
```bash
|
|
22
22
|
export ANTHROPIC_API_KEY=your_key_here
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
+
> Windows PowerShell: `$env:ANTHROPIC_API_KEY="your_key_here"`
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Usage
|
|
30
|
+
|
|
31
|
+
### Single intent
|
|
32
|
+
|
|
25
33
|
```bash
|
|
26
34
|
json-cli "please run tests"
|
|
35
|
+
json-cli "please build"
|
|
36
|
+
json-cli "check git status"
|
|
27
37
|
```
|
|
28
38
|
|
|
29
|
-
###
|
|
39
|
+
### Multi-intent — the fun part 🔥
|
|
40
|
+
|
|
41
|
+
Chain multiple commands in plain English using **"then"**, **"and"**, **"after that"**:
|
|
30
42
|
|
|
31
43
|
```bash
|
|
32
|
-
|
|
44
|
+
json-cli "run tests and then build"
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
json-cli "run typecheck, test, and then check git status"
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
json-cli "please run dev with port 5000"
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
json-cli "install deps, run tests, then build"
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Full release flow in one command 🚀
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
json-cli "run tests, build, git add all, commit with message 'release v0.1.0', push, then publish"
|
|
33
63
|
```
|
|
34
64
|
|
|
35
|
-
|
|
65
|
+
```
|
|
66
|
+
📋 Plan (6 steps):
|
|
67
|
+
1. pnpm test → Run test suite
|
|
68
|
+
2. pnpm build → Build package
|
|
69
|
+
3. git add . → Stage all changes
|
|
70
|
+
4. git commit -m "release v0.1.0" → Commit release
|
|
71
|
+
5. git push → Push to remote
|
|
72
|
+
6. pnpm publish → Publish to npm
|
|
73
|
+
|
|
74
|
+
Proceed? › y
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### More crazy examples
|
|
36
78
|
|
|
37
79
|
```bash
|
|
38
|
-
#
|
|
39
|
-
|
|
80
|
+
# Full dev startup
|
|
81
|
+
json-cli "install deps and run dev on port 3000"
|
|
82
|
+
|
|
83
|
+
# Audit and fix
|
|
84
|
+
json-cli "run npm audit, then update all deps"
|
|
85
|
+
|
|
86
|
+
# Branch and commit workflow
|
|
87
|
+
json-cli "check git status, add all files, commit with message 'feat: add multi-intent support', then push"
|
|
88
|
+
|
|
89
|
+
# Test everything before shipping
|
|
90
|
+
json-cli "run typecheck, run tests, build, then publish"
|
|
40
91
|
|
|
41
|
-
#
|
|
42
|
-
|
|
92
|
+
# Clone and install
|
|
93
|
+
json-cli "clone https://github.com/ekaone/json-cli and then install deps"
|
|
43
94
|
|
|
44
|
-
#
|
|
45
|
-
|
|
95
|
+
# Check before commit
|
|
96
|
+
json-cli "run tests, check git diff, then git add and commit with message 'fix: catalog types'"
|
|
97
|
+
|
|
98
|
+
# Full CI-like flow locally
|
|
99
|
+
json-cli "install deps, run typecheck, run tests, build, git add, commit with message 'ci: local pipeline passed', push"
|
|
46
100
|
```
|
|
47
101
|
|
|
102
|
+
---
|
|
103
|
+
|
|
48
104
|
## How it works
|
|
49
105
|
|
|
50
106
|
```
|
|
51
|
-
User Prompt
|
|
107
|
+
User Prompt (plain English)
|
|
52
108
|
│
|
|
53
109
|
▼
|
|
54
110
|
AI Provider ← Claude / OpenAI / Ollama
|
|
55
|
-
│
|
|
111
|
+
│ extracts ALL intents, sequences them
|
|
56
112
|
▼
|
|
57
|
-
JSON Plan ← validated by Zod schema
|
|
113
|
+
JSON Plan ← validated by Zod schema (max 10 steps)
|
|
58
114
|
│
|
|
59
115
|
▼
|
|
60
116
|
Catalog Check ← whitelist prevents hallucinated commands
|
|
61
117
|
│
|
|
62
118
|
▼
|
|
63
|
-
Confirm (y/n) ←
|
|
119
|
+
Confirm (y/n) ← review the full plan before execution
|
|
64
120
|
│
|
|
65
121
|
▼
|
|
66
|
-
Runner ← executes
|
|
122
|
+
Runner ← executes step by step, streams output live
|
|
123
|
+
stops immediately on first failure
|
|
67
124
|
```
|
|
68
125
|
|
|
126
|
+
---
|
|
127
|
+
|
|
69
128
|
## Allowed commands
|
|
70
129
|
|
|
71
|
-
| Type
|
|
72
|
-
|
|
73
|
-
| pnpm | install, run, build, test, publish, add, remove |
|
|
74
|
-
| npm | install, run, build, test, publish, ci |
|
|
75
|
-
| yarn | install, run, build, test, publish, add, remove |
|
|
76
|
-
| bun | install, run, build, test, publish, add, remove |
|
|
77
|
-
| git | init, add, commit, push, pull, clone, status, log |
|
|
78
|
-
|
|
|
130
|
+
| Type | Commands |
|
|
131
|
+
|---------|----------|
|
|
132
|
+
| `pnpm` | install, run, build, test, publish, add, remove, update, dlx, why |
|
|
133
|
+
| `npm` | install, run, build, test, publish, ci, init, outdated, audit |
|
|
134
|
+
| `yarn` | install, run, build, test, publish, add, remove, why, upgrade |
|
|
135
|
+
| `bun` | install, run, build, test, publish, add, remove, x, update |
|
|
136
|
+
| `git` | init, add, commit, push, pull, clone, status, log, branch, checkout, merge, diff, stash |
|
|
137
|
+
| `fs` | mkdir, touch, cp, mv, ls `(coming soon)` |
|
|
138
|
+
| `shell` | any *(escape hatch — always requires extra confirmation)* |
|
|
139
|
+
|
|
140
|
+
> **Note:** Flags and arguments are unrestricted — `--port 5000`, `-m "message"`, `--force` etc. are all passed freely. Only the command itself is whitelisted.
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## AI Providers
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
# Claude (default)
|
|
148
|
+
json-cli "run tests and build"
|
|
149
|
+
|
|
150
|
+
# OpenAI
|
|
151
|
+
json-cli "run tests and build" --provider openai
|
|
152
|
+
|
|
153
|
+
# Ollama (local, no API key needed)
|
|
154
|
+
json-cli "run tests and build" --provider ollama
|
|
155
|
+
```
|
|
79
156
|
|
|
80
157
|
## Environment variables
|
|
81
158
|
|
|
82
159
|
```bash
|
|
83
|
-
ANTHROPIC_API_KEY=sk-ant-...
|
|
84
|
-
OPENAI_API_KEY=sk-...
|
|
160
|
+
ANTHROPIC_API_KEY=sk-ant-... # for Claude
|
|
161
|
+
OPENAI_API_KEY=sk-... # for OpenAI
|
|
85
162
|
```
|
|
86
163
|
|
|
87
|
-
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## Local development
|
|
88
167
|
|
|
89
168
|
```bash
|
|
169
|
+
pnpm install
|
|
170
|
+
pnpm dev "please run tests"
|
|
90
171
|
pnpm test
|
|
172
|
+
pnpm build
|
|
91
173
|
```
|
|
92
174
|
|
|
175
|
+
---
|
|
176
|
+
|
|
93
177
|
## License
|
|
94
178
|
|
|
95
179
|
MIT © [Eka Prasetia](https://prasetia.me/)
|
|
@@ -100,4 +184,4 @@ MIT © [Eka Prasetia](https://prasetia.me/)
|
|
|
100
184
|
- [GitHub Repository](https://github.com/ekaone/json-cli)
|
|
101
185
|
- [Issue Tracker](https://github.com/ekaone/json-cli/issues)
|
|
102
186
|
|
|
103
|
-
⭐ If this library helps you, please consider giving it a star on GitHub!
|
|
187
|
+
⭐ If this library helps you, please consider giving it a star on GitHub!
|
package/dist/cli.cjs
CHANGED
|
@@ -117,7 +117,20 @@ var CATALOG = {
|
|
|
117
117
|
pnpm: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
118
118
|
yarn: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
119
119
|
bun: ["install", "run", "build", "test", "publish", "add", "remove"],
|
|
120
|
-
git: [
|
|
120
|
+
git: [
|
|
121
|
+
"init",
|
|
122
|
+
"add",
|
|
123
|
+
"commit",
|
|
124
|
+
"push",
|
|
125
|
+
"pull",
|
|
126
|
+
"clone",
|
|
127
|
+
"status",
|
|
128
|
+
"log",
|
|
129
|
+
"branch",
|
|
130
|
+
"checkout",
|
|
131
|
+
"merge",
|
|
132
|
+
"stash"
|
|
133
|
+
],
|
|
121
134
|
shell: ["any"]
|
|
122
135
|
// escape hatch — always requires extra confirmation
|
|
123
136
|
};
|
|
@@ -168,6 +181,8 @@ Rules:
|
|
|
168
181
|
- Use "shell" type only when no other type fits
|
|
169
182
|
- Keep steps minimal \u2014 don't add unnecessary steps
|
|
170
183
|
- Each step must have a clear, short description
|
|
184
|
+
- NEVER generate a "cd" step \u2014 each step runs in a separate process so "cd" has no effect
|
|
185
|
+
- If subsequent steps need to run inside a cloned directory, set the "cwd" field instead
|
|
171
186
|
|
|
172
187
|
Respond ONLY with valid JSON matching this exact shape, no markdown, no explanation:
|
|
173
188
|
{
|
|
@@ -202,7 +217,9 @@ ${issues}`);
|
|
|
202
217
|
for (const step of result.data.steps) {
|
|
203
218
|
const check = validateStep(step);
|
|
204
219
|
if (!check.valid) {
|
|
205
|
-
throw new Error(
|
|
220
|
+
throw new Error(
|
|
221
|
+
`Step ${step.id} failed catalog validation: ${check.reason}`
|
|
222
|
+
);
|
|
206
223
|
}
|
|
207
224
|
}
|
|
208
225
|
return result.data;
|
|
@@ -227,8 +244,13 @@ async function runStep(step) {
|
|
|
227
244
|
});
|
|
228
245
|
return { success: true };
|
|
229
246
|
} catch (err) {
|
|
230
|
-
const
|
|
231
|
-
|
|
247
|
+
const parts = [
|
|
248
|
+
`Command: ${bin} ${args.join(" ")}`,
|
|
249
|
+
err?.exitCode ? `Exit code: ${err.exitCode}` : null,
|
|
250
|
+
err?.stderr ? `Reason: ${err.stderr.trim()}` : null,
|
|
251
|
+
!err?.stderr ? err?.message ?? String(err) : null
|
|
252
|
+
].filter(Boolean).join("\n ");
|
|
253
|
+
return { success: false, error: parts };
|
|
232
254
|
}
|
|
233
255
|
}
|
|
234
256
|
async function runPlan(plan, onStep) {
|
|
@@ -245,28 +267,58 @@ async function runPlan(plan, onStep) {
|
|
|
245
267
|
}
|
|
246
268
|
|
|
247
269
|
// src/cli.ts
|
|
270
|
+
function showHelp() {
|
|
271
|
+
p.intro("json-cli \u2014 AI-powered CLI task runner");
|
|
272
|
+
p.log.message(`Usage
|
|
273
|
+
json-cli "<your goal>" [options]
|
|
274
|
+
`);
|
|
275
|
+
p.log.message(
|
|
276
|
+
`Options
|
|
277
|
+
--provider <name> AI provider: claude | openai | ollama (default: claude)
|
|
278
|
+
--yes Skip confirmation prompt
|
|
279
|
+
--dry-run Show plan without executing
|
|
280
|
+
--help Show this help message`
|
|
281
|
+
);
|
|
282
|
+
p.log.message(
|
|
283
|
+
`Examples
|
|
284
|
+
json-cli "please run tests"
|
|
285
|
+
json-cli "run tests and build"
|
|
286
|
+
json-cli "run tests and build" --yes
|
|
287
|
+
json-cli "git add, commit with message 'fix: bug', push"
|
|
288
|
+
json-cli "clone https://github.com/user/repo, install deps, run dev"
|
|
289
|
+
json-cli "run tests and publish" --provider openai
|
|
290
|
+
json-cli "run tests" --dry-run`
|
|
291
|
+
);
|
|
292
|
+
p.outro("Docs: https://github.com/ekaone/json-cli");
|
|
293
|
+
}
|
|
248
294
|
function parseArgs() {
|
|
249
295
|
const args = process.argv.slice(2);
|
|
296
|
+
if (args.length === 0 || args.includes("--help")) {
|
|
297
|
+
showHelp();
|
|
298
|
+
process.exit(0);
|
|
299
|
+
}
|
|
250
300
|
const providerFlag = args.indexOf("--provider");
|
|
251
301
|
const provider = providerFlag !== -1 ? args[providerFlag + 1] : "claude";
|
|
302
|
+
const yes = args.includes("--yes");
|
|
303
|
+
const dryRun = args.includes("--dry-run");
|
|
252
304
|
const prompt = args.filter(
|
|
253
305
|
(a, i) => !a.startsWith("--") && (providerFlag === -1 || i !== providerFlag + 1)
|
|
254
306
|
).join(" ");
|
|
255
307
|
if (!prompt) {
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
);
|
|
259
|
-
process.exit(1);
|
|
308
|
+
showHelp();
|
|
309
|
+
process.exit(0);
|
|
260
310
|
}
|
|
261
|
-
return { prompt, provider };
|
|
311
|
+
return { prompt, provider, yes, dryRun };
|
|
262
312
|
}
|
|
263
313
|
function formatStep(step) {
|
|
264
314
|
const cmd = step.type === "shell" ? `${step.command} ${step.args.join(" ")}` : `${step.type} ${step.command} ${step.args.join(" ")}`.trim();
|
|
265
315
|
return `${cmd.padEnd(35)} \u2192 ${step.description}`;
|
|
266
316
|
}
|
|
267
317
|
async function main() {
|
|
268
|
-
const { prompt, provider: providerName } = parseArgs();
|
|
269
|
-
p.intro(
|
|
318
|
+
const { prompt, provider: providerName, yes, dryRun } = parseArgs();
|
|
319
|
+
p.intro(
|
|
320
|
+
`json-cli \u2014 powered by ${providerName}${dryRun ? " (dry run)" : ""}`
|
|
321
|
+
);
|
|
270
322
|
const spinner2 = p.spinner();
|
|
271
323
|
spinner2.start("Thinking...");
|
|
272
324
|
let plan;
|
|
@@ -293,10 +345,18 @@ async function main() {
|
|
|
293
345
|
"Plan contains shell commands \u2014 review carefully before proceeding."
|
|
294
346
|
);
|
|
295
347
|
}
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
348
|
+
if (dryRun) {
|
|
349
|
+
p.outro("Dry run complete \u2014 no commands were executed.");
|
|
350
|
+
setTimeout(() => process.exit(0), 50);
|
|
351
|
+
}
|
|
352
|
+
if (!yes) {
|
|
353
|
+
const confirmed = await p.confirm({ message: "Proceed?" });
|
|
354
|
+
if (p.isCancel(confirmed) || !confirmed) {
|
|
355
|
+
p.cancel("Aborted.");
|
|
356
|
+
setTimeout(() => process.exit(0), 50);
|
|
357
|
+
}
|
|
358
|
+
} else {
|
|
359
|
+
p.log.info("Skipping confirmation (--yes)");
|
|
300
360
|
}
|
|
301
361
|
console.log("");
|
|
302
362
|
const result = await runPlan(plan, (step, i, total) => {
|
|
@@ -309,7 +369,7 @@ async function main() {
|
|
|
309
369
|
`\u274C Failed at step ${result.failedStep?.id}: ${result.failedStep?.description}
|
|
310
370
|
${result.error ?? ""}`
|
|
311
371
|
);
|
|
312
|
-
process.exit(1);
|
|
372
|
+
setTimeout(() => process.exit(1), 50);
|
|
313
373
|
}
|
|
314
374
|
}
|
|
315
375
|
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
|
};
|
|
@@ -83,6 +96,8 @@ Rules:
|
|
|
83
96
|
- Use "shell" type only when no other type fits
|
|
84
97
|
- Keep steps minimal \u2014 don't add unnecessary steps
|
|
85
98
|
- Each step must have a clear, short description
|
|
99
|
+
- NEVER generate a "cd" step \u2014 each step runs in a separate process so "cd" has no effect
|
|
100
|
+
- If subsequent steps need to run inside a cloned directory, set the "cwd" field instead
|
|
86
101
|
|
|
87
102
|
Respond ONLY with valid JSON matching this exact shape, no markdown, no explanation:
|
|
88
103
|
{
|
|
@@ -117,7 +132,9 @@ ${issues}`);
|
|
|
117
132
|
for (const step of result.data.steps) {
|
|
118
133
|
const check = validateStep(step);
|
|
119
134
|
if (!check.valid) {
|
|
120
|
-
throw new Error(
|
|
135
|
+
throw new Error(
|
|
136
|
+
`Step ${step.id} failed catalog validation: ${check.reason}`
|
|
137
|
+
);
|
|
121
138
|
}
|
|
122
139
|
}
|
|
123
140
|
return result.data;
|
|
@@ -142,8 +159,13 @@ async function runStep(step) {
|
|
|
142
159
|
});
|
|
143
160
|
return { success: true };
|
|
144
161
|
} catch (err) {
|
|
145
|
-
const
|
|
146
|
-
|
|
162
|
+
const parts = [
|
|
163
|
+
`Command: ${bin} ${args.join(" ")}`,
|
|
164
|
+
err?.exitCode ? `Exit code: ${err.exitCode}` : null,
|
|
165
|
+
err?.stderr ? `Reason: ${err.stderr.trim()}` : null,
|
|
166
|
+
!err?.stderr ? err?.message ?? String(err) : null
|
|
167
|
+
].filter(Boolean).join("\n ");
|
|
168
|
+
return { success: false, error: parts };
|
|
147
169
|
}
|
|
148
170
|
}
|
|
149
171
|
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\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(userPrompt: string, provider: AIProvider): 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.map((i) => ` - ${i.path.join(\".\")}: ${i.message}`).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(`Step ${step.id} failed catalog validation: ${check.reason}`);\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/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().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\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(\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.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
|
};
|
|
@@ -56,6 +69,8 @@ Rules:
|
|
|
56
69
|
- Use "shell" type only when no other type fits
|
|
57
70
|
- Keep steps minimal \u2014 don't add unnecessary steps
|
|
58
71
|
- Each step must have a clear, short description
|
|
72
|
+
- NEVER generate a "cd" step \u2014 each step runs in a separate process so "cd" has no effect
|
|
73
|
+
- If subsequent steps need to run inside a cloned directory, set the "cwd" field instead
|
|
59
74
|
|
|
60
75
|
Respond ONLY with valid JSON matching this exact shape, no markdown, no explanation:
|
|
61
76
|
{
|
|
@@ -90,7 +105,9 @@ ${issues}`);
|
|
|
90
105
|
for (const step of result.data.steps) {
|
|
91
106
|
const check = validateStep(step);
|
|
92
107
|
if (!check.valid) {
|
|
93
|
-
throw new Error(
|
|
108
|
+
throw new Error(
|
|
109
|
+
`Step ${step.id} failed catalog validation: ${check.reason}`
|
|
110
|
+
);
|
|
94
111
|
}
|
|
95
112
|
}
|
|
96
113
|
return result.data;
|
|
@@ -115,8 +132,13 @@ async function runStep(step) {
|
|
|
115
132
|
});
|
|
116
133
|
return { success: true };
|
|
117
134
|
} catch (err) {
|
|
118
|
-
const
|
|
119
|
-
|
|
135
|
+
const parts = [
|
|
136
|
+
`Command: ${bin} ${args.join(" ")}`,
|
|
137
|
+
err?.exitCode ? `Exit code: ${err.exitCode}` : null,
|
|
138
|
+
err?.stderr ? `Reason: ${err.stderr.trim()}` : null,
|
|
139
|
+
!err?.stderr ? err?.message ?? String(err) : null
|
|
140
|
+
].filter(Boolean).join("\n ");
|
|
141
|
+
return { success: false, error: parts };
|
|
120
142
|
}
|
|
121
143
|
}
|
|
122
144
|
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\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(userPrompt: string, provider: AIProvider): 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.map((i) => ` - ${i.path.join(\".\")}: ${i.message}`).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(`Step ${step.id} failed catalog validation: ${check.reason}`);\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().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\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(\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":[]}
|