kradle 0.0.2 → 0.3.1
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 +422 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +14 -0
- package/dist/commands/agent/list.d.ts +10 -0
- package/dist/commands/agent/list.js +22 -0
- package/dist/commands/ai-docs/challenges-sdk.d.ts +21 -0
- package/dist/commands/ai-docs/challenges-sdk.js +100 -0
- package/dist/commands/ai-docs/cli.d.ts +6 -0
- package/dist/commands/ai-docs/cli.js +13 -0
- package/dist/commands/challenge/build.d.ts +17 -0
- package/dist/commands/challenge/build.js +53 -0
- package/dist/commands/challenge/create.d.ts +16 -0
- package/dist/commands/challenge/create.js +88 -0
- package/dist/commands/challenge/delete.d.ts +15 -0
- package/dist/commands/challenge/delete.js +99 -0
- package/dist/commands/challenge/list.d.ts +11 -0
- package/dist/commands/challenge/list.js +49 -0
- package/dist/commands/challenge/pull.d.ts +15 -0
- package/dist/commands/challenge/pull.js +182 -0
- package/dist/commands/challenge/run.d.ts +18 -0
- package/dist/commands/challenge/run.js +55 -0
- package/dist/commands/challenge/watch.d.ts +15 -0
- package/dist/commands/challenge/watch.js +114 -0
- package/dist/commands/experiment/create.d.ts +13 -0
- package/dist/commands/experiment/create.js +80 -0
- package/dist/commands/experiment/list.d.ts +7 -0
- package/dist/commands/experiment/list.js +53 -0
- package/dist/commands/experiment/recordings.d.ts +19 -0
- package/dist/commands/experiment/recordings.js +416 -0
- package/dist/commands/experiment/run.d.ts +17 -0
- package/dist/commands/experiment/run.js +67 -0
- package/dist/commands/init.d.ts +10 -0
- package/dist/commands/init.js +165 -0
- package/dist/commands/world/delete.d.ts +14 -0
- package/dist/commands/world/delete.js +80 -0
- package/dist/commands/world/import.d.ts +14 -0
- package/dist/commands/world/import.js +120 -0
- package/dist/commands/world/list.d.ts +10 -0
- package/dist/commands/world/list.js +46 -0
- package/dist/commands/world/pull.d.ts +14 -0
- package/dist/commands/world/pull.js +134 -0
- package/dist/commands/world/push.d.ts +16 -0
- package/dist/commands/world/push.js +131 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/lib/api-client.d.ts +133 -0
- package/dist/lib/api-client.js +346 -0
- package/dist/lib/arguments.d.ts +12 -0
- package/dist/lib/arguments.js +46 -0
- package/dist/lib/challenge.d.ts +60 -0
- package/dist/lib/challenge.js +197 -0
- package/dist/lib/experiment/experimenter.d.ts +92 -0
- package/dist/lib/experiment/experimenter.js +368 -0
- package/dist/lib/experiment/index.d.ts +4 -0
- package/dist/lib/experiment/index.js +4 -0
- package/dist/lib/experiment/runner.d.ts +82 -0
- package/dist/lib/experiment/runner.js +309 -0
- package/dist/lib/experiment/tui.d.ts +20 -0
- package/dist/lib/experiment/tui.js +130 -0
- package/dist/lib/experiment/types.d.ts +134 -0
- package/dist/lib/experiment/types.js +88 -0
- package/dist/lib/flags.d.ts +47 -0
- package/dist/lib/flags.js +63 -0
- package/dist/lib/schemas.d.ts +242 -0
- package/dist/lib/schemas.js +112 -0
- package/dist/lib/utils.d.ts +96 -0
- package/dist/lib/utils.js +199 -0
- package/dist/lib/world.d.ts +21 -0
- package/dist/lib/world.js +102 -0
- package/oclif.manifest.json +1167 -0
- package/package.json +91 -8
- package/static/ai_docs/LLM_CLI_REFERENCE.md +964 -0
- package/static/challenge.ts +31 -0
- package/static/experiment_template.ts +114 -0
- package/static/project_template/AGENTS.md +17 -0
- package/static/project_template/CLAUDE.md +1 -0
- package/static/project_template/dev.env +5 -0
- package/static/project_template/package.json +17 -0
- package/static/project_template/prod.env +5 -0
- package/static/project_template/template-run.json +10 -0
- package/static/project_template/tsconfig.json +17 -0
- package/postinstall.js +0 -13
package/README.md
ADDED
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
<!-- This file is for human consumption. If you are an LLM or any AI Agent, make sure to read static/ai_docs/LLM_CLI_REFERENCE.md for an exhaustive explanation of this package. -->
|
|
2
|
+
|
|
3
|
+
# Kradle CLI
|
|
4
|
+
|
|
5
|
+
Kradle's CLI for managing Minecraft challenges, experiments, agents, and more!
|
|
6
|
+
|
|
7
|
+
* [Installation](#installation)
|
|
8
|
+
* [Autocomplete](#autocomplete)
|
|
9
|
+
* [Configuration](#configuration)
|
|
10
|
+
* [Challenge](#challenge-commands)
|
|
11
|
+
* [Experiments](#experiment-commands)
|
|
12
|
+
* [Worlds](#world-commands)
|
|
13
|
+
* [Agents](#agent-commands)
|
|
14
|
+
* [AI Docs](#ai-docs-commands)
|
|
15
|
+
* [Publishing a New Version](#publishing-a-new-version)
|
|
16
|
+
* [Development](#development)
|
|
17
|
+
* [Architecture](#architecture)
|
|
18
|
+
|
|
19
|
+
## Installation
|
|
20
|
+
|
|
21
|
+
1. Install Kradle's CLI globally
|
|
22
|
+
```
|
|
23
|
+
npm i -g kradle
|
|
24
|
+
```
|
|
25
|
+
2. Initialize a new directory to store challenges and experiments
|
|
26
|
+
```
|
|
27
|
+
kradle init
|
|
28
|
+
```
|
|
29
|
+
3. Congrats 🎉 You can now create a new challenge or a new experiment:
|
|
30
|
+
```
|
|
31
|
+
kradle challenge create <challenge-name>
|
|
32
|
+
kradle experiment create <experiment-name>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
In addition, you can enable [autocomplete](#Autocomplete).
|
|
36
|
+
|
|
37
|
+
## Autocomplete
|
|
38
|
+
|
|
39
|
+
Kradle CLI supports shell autocomplete for faster command entry. After installation, enable autocomplete for your shell:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
kradle autocomplete
|
|
43
|
+
# Follow the instructions printed
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
The command will display instructions for your specific shell.
|
|
47
|
+
|
|
48
|
+
After setup, you will be able to use Tab to autocomplete:
|
|
49
|
+
```bash
|
|
50
|
+
kradle challenge <TAB> # Shows: build, create, list, run, upload, watch, etc.
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
The CLI requires a `.env` file with your Kradle API key and environment settings.
|
|
56
|
+
|
|
57
|
+
**For new projects:** Run `kradle init` which will prompt for your API key and create the `.env` automatically.
|
|
58
|
+
|
|
59
|
+
**Manual setup:** Create a `.env` file in your project root:
|
|
60
|
+
```bash
|
|
61
|
+
KRADLE_API_KEY=your-api-key-here
|
|
62
|
+
KRADLE_API_URL=https://dev.kradle.ai/api
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
Get your API key at: https://dev.kradle.ai/settings#api-keys
|
|
66
|
+
|
|
67
|
+
## Challenge Commands
|
|
68
|
+
|
|
69
|
+
### Create Challenge
|
|
70
|
+
|
|
71
|
+
Create a new challenge locally and in the cloud:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
kradle challenge create <challenge-name>
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
This creates a `challenges/<challenge-name>/` folder with:
|
|
78
|
+
- `challenge.ts`: The entrypoint defining challenge behavior
|
|
79
|
+
- `config.ts`: TypeScript file with challenge metadata (auto-generated from cloud API)
|
|
80
|
+
|
|
81
|
+
### Build Challenge
|
|
82
|
+
|
|
83
|
+
Build challenge datapack and upload both config and datapack:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
kradle challenge build <challenge-name>
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
This command:
|
|
90
|
+
1. Creates the challenge in the cloud (if it doesn't already exists)
|
|
91
|
+
2. Uploads `config.ts` to cloud (if it exists)
|
|
92
|
+
3. Builds the datapack by executing `challenge.ts`
|
|
93
|
+
4. Uploads the datapack to GCS
|
|
94
|
+
|
|
95
|
+
### Delete Challenge
|
|
96
|
+
|
|
97
|
+
Delete a challenge locally, from the cloud, or both:
|
|
98
|
+
|
|
99
|
+
```bash
|
|
100
|
+
# Will ask confirmation for local & cloud deletion
|
|
101
|
+
kradle challenge delete <challenge-name>
|
|
102
|
+
|
|
103
|
+
# Doesn't ask for confirmation
|
|
104
|
+
kradle challenge delete <challenge-name> --yes
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### List Challenges
|
|
108
|
+
|
|
109
|
+
List all challenges (local and cloud):
|
|
110
|
+
|
|
111
|
+
```bash
|
|
112
|
+
kradle challenge list
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Pull Challenge
|
|
116
|
+
|
|
117
|
+
Download a challenge from the cloud and extract source files locally:
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
kradle challenge pull # Interactive selection
|
|
121
|
+
kradle challenge pull <challenge-name> # Pull your own challenge
|
|
122
|
+
kradle challenge pull <team-name>:<challenge-name> # Pull a public challenge from another team
|
|
123
|
+
kradle challenge pull <challenge-name> --yes # Skip confirmation when overwriting
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
This downloads the challenge tarball, extracts `challenge.ts` and `config.ts`, and builds the datapack locally.
|
|
127
|
+
|
|
128
|
+
### Watch Challenge
|
|
129
|
+
|
|
130
|
+
Watch a challenge for changes and auto-rebuild/upload:
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
kradle challenge watch <challenge-name>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Uses file watching with debouncing (300ms) and hash comparison to minimize unnecessary rebuilds.
|
|
137
|
+
|
|
138
|
+
### Run Challenge
|
|
139
|
+
|
|
140
|
+
Run a challenge in production or studio environment:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
kradle challenge run <challenge-name>
|
|
144
|
+
kradle challenge run <challenge-name> --studio # Run in local studio environment
|
|
145
|
+
kradle challenge run <team-name>:<challenge-name> # Run a public challenge from another team
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Experiment Commands
|
|
149
|
+
|
|
150
|
+
Experiments allow you to run batches of challenge runs with different agents and configurations, then analyze the results. This is useful for benchmarking agents, testing challenge difficulty, or gathering statistics across many runs.
|
|
151
|
+
|
|
152
|
+
### Concepts
|
|
153
|
+
|
|
154
|
+
**Experiment**: A named collection of run configurations defined in a `config.ts` file. Each experiment lives in `experiments/<name>/`.
|
|
155
|
+
|
|
156
|
+
**Version**: A snapshot of an experiment execution. When you run an experiment, it creates a version containing:
|
|
157
|
+
- A copy of the `config.ts` at that point in time
|
|
158
|
+
- A `manifest.json` with the generated list of runs
|
|
159
|
+
- A `progress.json` tracking the status of each run
|
|
160
|
+
|
|
161
|
+
Versions are stored in `experiments/<name>/versions/001/`, `002/`, etc. This allows you to:
|
|
162
|
+
- Resume an interrupted experiment from where it left off
|
|
163
|
+
- Re-run the same experiment with `--new` to create a fresh version
|
|
164
|
+
- Compare results across different versions
|
|
165
|
+
|
|
166
|
+
### Create Experiment
|
|
167
|
+
|
|
168
|
+
Create a new experiment with a template config file:
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
kradle experiment create <name>
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
This creates `experiments/<name>/config.ts` with a template that you can customize. The config exports a `main()` function that returns a manifest with:
|
|
175
|
+
- `runs`: Array of run configurations (challenge + participants)
|
|
176
|
+
- `tags`: Optional tags applied to all runs for filtering in analytics
|
|
177
|
+
|
|
178
|
+
### Run Experiment
|
|
179
|
+
|
|
180
|
+
Execute or resume an experiment:
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
kradle experiment run <name> # Resume current version or create first one
|
|
184
|
+
kradle experiment run <name> --new-version # Start a new version
|
|
185
|
+
kradle experiment run <name> --max-concurrent 10 # Control parallelism (default: 5)
|
|
186
|
+
kradle experiment run <name> --download-recordings # Auto-download recordings as runs complete
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
The run command:
|
|
190
|
+
1. Creates a new version (or resumes the current one)
|
|
191
|
+
2. Generates a manifest by executing `config.ts`
|
|
192
|
+
3. Displays an interactive TUI showing run progress
|
|
193
|
+
4. Saves progress periodically (allows resuming if interrupted)
|
|
194
|
+
5. Opens Metabase dashboard with results when complete
|
|
195
|
+
|
|
196
|
+
### Download Recordings
|
|
197
|
+
|
|
198
|
+
Download gameplay recordings from completed experiment runs:
|
|
199
|
+
|
|
200
|
+
```bash
|
|
201
|
+
kradle experiment recordings <name> # Interactive selection of run and participant
|
|
202
|
+
kradle experiment recordings <name> <run-id> # Download specific run
|
|
203
|
+
kradle experiment recordings <name> --all # Download all runs and participants
|
|
204
|
+
kradle experiment recordings <name> --version 2 # Download from specific version
|
|
205
|
+
kradle experiment recordings <name> <run-id> --all # Download all participants for a run
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
Recordings are saved to `experiments/<name>/versions/<version>/recordings/<run-id>/`.
|
|
209
|
+
|
|
210
|
+
### List Experiments
|
|
211
|
+
|
|
212
|
+
List all local experiments:
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
kradle experiment list
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
## World Commands
|
|
219
|
+
|
|
220
|
+
Worlds are Minecraft world saves that can be used as starting points for challenges.
|
|
221
|
+
|
|
222
|
+
### Import World
|
|
223
|
+
|
|
224
|
+
Import a Minecraft world folder from your local filesystem:
|
|
225
|
+
|
|
226
|
+
```bash
|
|
227
|
+
kradle world import ~/minecraft/saves/MyWorld # Auto-generate slug from folder name
|
|
228
|
+
kradle world import ~/minecraft/saves/MyWorld --as my-world # Specify custom slug
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
This validates the folder contains `level.dat`, packages it as a tarball, creates a `config.ts`, and uploads to the cloud.
|
|
232
|
+
|
|
233
|
+
### Push World
|
|
234
|
+
|
|
235
|
+
Upload world config and tarball to the cloud:
|
|
236
|
+
|
|
237
|
+
```bash
|
|
238
|
+
kradle world push my-world # Push single world
|
|
239
|
+
kradle world push my-world another-world # Push multiple worlds
|
|
240
|
+
kradle world push --all # Push all local worlds
|
|
241
|
+
kradle world push my-world --public # Push and set visibility to public
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Pull World
|
|
245
|
+
|
|
246
|
+
Download a world from the cloud:
|
|
247
|
+
|
|
248
|
+
```bash
|
|
249
|
+
kradle world pull # Interactive selection
|
|
250
|
+
kradle world pull my-world # Pull specific world
|
|
251
|
+
kradle world pull username:their-world # Pull from another user
|
|
252
|
+
kradle world pull my-world --yes # Skip confirmation when overwriting
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### List Worlds
|
|
256
|
+
|
|
257
|
+
List all worlds (local and cloud):
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
kradle world list
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Shows sync status for each world (synced, cloud only, or local only).
|
|
264
|
+
|
|
265
|
+
### Delete World
|
|
266
|
+
|
|
267
|
+
Delete a world locally, from the cloud, or both:
|
|
268
|
+
|
|
269
|
+
```bash
|
|
270
|
+
kradle world delete my-world # Interactive confirmation
|
|
271
|
+
kradle world delete my-world --yes # Skip confirmation
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Agent Commands
|
|
275
|
+
|
|
276
|
+
### List Agents
|
|
277
|
+
|
|
278
|
+
List all agents registered in the system:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
kradle agent list
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
## AI Docs Commands
|
|
285
|
+
|
|
286
|
+
Output LLM-focused documentation to stdout. These commands are designed to provide AI agents with comprehensive reference material about the Kradle CLI and API.
|
|
287
|
+
|
|
288
|
+
### CLI Reference
|
|
289
|
+
|
|
290
|
+
Output the CLI reference documentation:
|
|
291
|
+
|
|
292
|
+
```bash
|
|
293
|
+
kradle ai-docs cli
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
### Challenges SDK Reference
|
|
297
|
+
|
|
298
|
+
Output the API reference documentation for the `@kradle/challenges-sdk` package:
|
|
299
|
+
|
|
300
|
+
```bash
|
|
301
|
+
kradle ai-docs challenges-sdk # Uses locally installed or latest version
|
|
302
|
+
kradle ai-docs challenges-sdk 0.2.1 # Uses specific version
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
This fetches the documentation from unpkg.com, matching the SDK version in your project.
|
|
306
|
+
|
|
307
|
+
## Publishing a New Version
|
|
308
|
+
|
|
309
|
+
The CLI uses GitHub Actions for automated releases. To publish a new version:
|
|
310
|
+
|
|
311
|
+
1. **Go to Actions** in the GitHub repository
|
|
312
|
+
2. **Select "Create Release PR"** workflow from the sidebar
|
|
313
|
+
3. **Click "Run workflow"** and choose the release type:
|
|
314
|
+
- `patch` - Bug fixes (0.0.5 → 0.0.6)
|
|
315
|
+
- `minor` - New features (0.0.5 → 0.1.0)
|
|
316
|
+
- `major` - Breaking changes (0.0.5 → 1.0.0)
|
|
317
|
+
4. **Review and merge** the automatically created PR
|
|
318
|
+
5. **Done!** The package is automatically published to npm when the PR is merged
|
|
319
|
+
|
|
320
|
+
|
|
321
|
+
## Development
|
|
322
|
+
|
|
323
|
+
### Setup
|
|
324
|
+
|
|
325
|
+
This CLI requires linking to be used locally:
|
|
326
|
+
|
|
327
|
+
```bash
|
|
328
|
+
npm install
|
|
329
|
+
npm run build
|
|
330
|
+
npm link
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
The repository provides the `kradle` CLI command. It runs compiled JavaScript from `dist/`:
|
|
334
|
+
- It requires running `npm run build` after every code change
|
|
335
|
+
- You can use `npm run watch` to make sure your code automatically recompiles after any change
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
### Build & Lint
|
|
339
|
+
|
|
340
|
+
```bash
|
|
341
|
+
npm run build # Compile TypeScript to dist/
|
|
342
|
+
npm run lint # Check for linting issues
|
|
343
|
+
npm run lint:fix # Auto-fix linting issues
|
|
344
|
+
npm run format # Format code with Biome
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Running Tests
|
|
348
|
+
|
|
349
|
+
The CLI has integration tests that verify commands work correctly with the dev API.
|
|
350
|
+
|
|
351
|
+
**Setup:**
|
|
352
|
+
|
|
353
|
+
1. Copy `.env.test.example` to `.env.test`
|
|
354
|
+
2. Add your Kradle API key (from https://dev.kradle.ai/settings/api-keys)
|
|
355
|
+
|
|
356
|
+
```bash
|
|
357
|
+
cp .env.test.example .env.test
|
|
358
|
+
# Edit .env.test and add your API key
|
|
359
|
+
```
|
|
360
|
+
|
|
361
|
+
**Run tests:**
|
|
362
|
+
|
|
363
|
+
```bash
|
|
364
|
+
npm test # Run all tests
|
|
365
|
+
npm run test:watch # Run tests in watch mode
|
|
366
|
+
npm run test:integration # Run integration tests
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Note:** Integration tests make real API calls to the dev environment and may create/delete challenges.
|
|
370
|
+
|
|
371
|
+
**CI Configuration:** Integration tests run in GitHub Actions on PRs. The `KRADLE_API_KEY` secret must be configured in the repository settings.
|
|
372
|
+
|
|
373
|
+
### Challenge Structure
|
|
374
|
+
|
|
375
|
+
Each challenge is a folder in `challenges/<slug>/` containing:
|
|
376
|
+
|
|
377
|
+
- **`challenge.ts`**: Entrypoint that defines challenge behavior using the Sandstone API
|
|
378
|
+
- **`config.ts`**: TypeScript file exporting challenge metadata (name, visibility, roles, objectives, etc.)
|
|
379
|
+
|
|
380
|
+
**Workflow:**
|
|
381
|
+
1. `kradle challenge create <slug>` creates the folder with `challenge.ts`
|
|
382
|
+
2. The create command automatically builds, uploads, and downloads the config from the cloud API
|
|
383
|
+
3. The downloaded JSON is converted into a typed TypeScript `config.ts` file
|
|
384
|
+
4. `kradle challenge build <slug>` automatically uploads `config.ts` (if it exists) before building the datapack
|
|
385
|
+
5. You can modify `config.ts` locally and run `build` to sync changes to the cloud
|
|
386
|
+
|
|
387
|
+
## Architecture
|
|
388
|
+
|
|
389
|
+
The CLI is built with:
|
|
390
|
+
|
|
391
|
+
- **oclif**: CLI framework
|
|
392
|
+
- **enquirer**: Interactive prompts
|
|
393
|
+
- **listr2**: Task list UI
|
|
394
|
+
- **ink**: React-based terminal UI (for experiments)
|
|
395
|
+
- **react**: UI components for ink
|
|
396
|
+
- **picocolors**: Terminal colors
|
|
397
|
+
- **zod**: Schema validation
|
|
398
|
+
- **chokidar**: File watching
|
|
399
|
+
- **biome**: Linting and formatting
|
|
400
|
+
|
|
401
|
+
### Project Structure
|
|
402
|
+
|
|
403
|
+
```
|
|
404
|
+
kradle-cli/
|
|
405
|
+
├── src/
|
|
406
|
+
│ ├── commands/ # CLI commands
|
|
407
|
+
│ │ ├── agent/ # Agent commands
|
|
408
|
+
│ │ ├── ai-docs/ # AI documentation commands
|
|
409
|
+
│ │ ├── challenge/ # Challenge management commands
|
|
410
|
+
│ │ ├── experiment/ # Experiment commands
|
|
411
|
+
│ │ └── world/ # World management commands
|
|
412
|
+
│ └── lib/ # Core libraries
|
|
413
|
+
│ └── experiment/ # Experiment system
|
|
414
|
+
├── tests/ # Integration tests
|
|
415
|
+
│ ├── helpers/ # Test utilities
|
|
416
|
+
│ └── integration/ # Integration test suites
|
|
417
|
+
│ ├── challenge/ # Challenge command tests
|
|
418
|
+
│ ├── experiment/ # Experiment command tests
|
|
419
|
+
│ └── world/ # World command tests
|
|
420
|
+
└── static/ # Template files
|
|
421
|
+
└── project_template/ # Files for kradle init
|
|
422
|
+
```
|
package/bin/run.cmd
ADDED
package/bin/run.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env -S node --no-warnings
|
|
2
|
+
|
|
3
|
+
import { execute } from "@oclif/core";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import dotenv from "dotenv";
|
|
7
|
+
|
|
8
|
+
// Load .env file from cwd if it exists
|
|
9
|
+
const envPath = path.join(process.cwd(), ".env");
|
|
10
|
+
if (fs.existsSync(envPath)) {
|
|
11
|
+
dotenv.config({ path: envPath, quiet: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
await execute({ dir: import.meta.url });
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class List extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static flags: {
|
|
6
|
+
"api-key": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
7
|
+
"api-url": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
8
|
+
};
|
|
9
|
+
run(): Promise<void>;
|
|
10
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { ApiClient } from "../../lib/api-client.js";
|
|
4
|
+
import { getConfigFlags } from "../../lib/flags.js";
|
|
5
|
+
export default class List extends Command {
|
|
6
|
+
static description = "List all agents";
|
|
7
|
+
static examples = ["<%= config.bin %> <%= command.id %>"];
|
|
8
|
+
static flags = {
|
|
9
|
+
...getConfigFlags("api-key", "api-url"),
|
|
10
|
+
};
|
|
11
|
+
async run() {
|
|
12
|
+
const { flags } = await this.parse(List);
|
|
13
|
+
const api = new ApiClient(flags["api-url"], flags["api-key"]);
|
|
14
|
+
this.log(pc.blue(">> Loading agents..."));
|
|
15
|
+
const agents = await api.listKradleAgents();
|
|
16
|
+
agents.sort((a, b) => a.username?.localeCompare(b.username || "") || 0);
|
|
17
|
+
this.log(pc.bold(`\nFound ${agents.length} agents:\n`));
|
|
18
|
+
for (const agent of agents) {
|
|
19
|
+
this.log(pc.bold(`- ${agent.username}`));
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class ChallengesSdk extends Command {
|
|
3
|
+
static args: {
|
|
4
|
+
version: import("@oclif/core/interfaces").Arg<string | undefined, Record<string, unknown>>;
|
|
5
|
+
};
|
|
6
|
+
static description: string;
|
|
7
|
+
static examples: string[];
|
|
8
|
+
run(): Promise<void>;
|
|
9
|
+
private fetchLLMReadme;
|
|
10
|
+
/**
|
|
11
|
+
* Get the LLM README from the installed version of the SDK.
|
|
12
|
+
* @returns The LLM README, or null if not found.
|
|
13
|
+
*/
|
|
14
|
+
private getLLMReadmeFromInstalledVersion;
|
|
15
|
+
/**
|
|
16
|
+
* Detect the specified version of the SDK by walking up the directory tree
|
|
17
|
+
* looking for a package.json that contains the SDK dependency.
|
|
18
|
+
* @returns The specified version of the SDK, or null if not found.
|
|
19
|
+
*/
|
|
20
|
+
private detectPackageJsonVersion;
|
|
21
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Args, Command } from "@oclif/core";
|
|
5
|
+
import pc from "picocolors";
|
|
6
|
+
const SDK_PACKAGE_NAME = "@kradle/challenges-sdk";
|
|
7
|
+
const DOCS_FILENAME = "LLM_README.md";
|
|
8
|
+
export default class ChallengesSdk extends Command {
|
|
9
|
+
static args = {
|
|
10
|
+
version: Args.string({
|
|
11
|
+
description: "SDK version to fetch docs for. If not specified, the locally installed version will be used if available, otherwise the latest version will be used.",
|
|
12
|
+
required: false,
|
|
13
|
+
}),
|
|
14
|
+
};
|
|
15
|
+
static description = "Output the @kradle/challenges-sdk API reference documentation for LLMs";
|
|
16
|
+
static examples = [
|
|
17
|
+
"<%= config.bin %> <%= command.id %>",
|
|
18
|
+
"<%= config.bin %> <%= command.id %> 0.2.1",
|
|
19
|
+
"<%= config.bin %> <%= command.id %> latest",
|
|
20
|
+
];
|
|
21
|
+
async run() {
|
|
22
|
+
const { args } = await this.parse(ChallengesSdk);
|
|
23
|
+
if (args.version) {
|
|
24
|
+
const content = await this.fetchLLMReadme(args.version);
|
|
25
|
+
this.log(content);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const llmReadmeFromInstalled = this.getLLMReadmeFromInstalledVersion();
|
|
29
|
+
if (llmReadmeFromInstalled) {
|
|
30
|
+
this.log(llmReadmeFromInstalled);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.log(pc.yellow("No version found. Trying to resolve version from package.json..."));
|
|
34
|
+
const packageJsonVersion = this.detectPackageJsonVersion();
|
|
35
|
+
if (packageJsonVersion) {
|
|
36
|
+
this.log(pc.yellow(`Using version ${packageJsonVersion} from package.json`));
|
|
37
|
+
const content = await this.fetchLLMReadme(packageJsonVersion);
|
|
38
|
+
this.log(content);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
this.log(pc.yellow("No version found. Using latest version."));
|
|
42
|
+
const content = await this.fetchLLMReadme("latest");
|
|
43
|
+
this.log(content);
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
async fetchLLMReadme(version) {
|
|
47
|
+
const url = `https://cdn.jsdelivr.net/npm/${SDK_PACKAGE_NAME}@${version}/${DOCS_FILENAME}`;
|
|
48
|
+
try {
|
|
49
|
+
const response = await fetch(url);
|
|
50
|
+
return response.text();
|
|
51
|
+
}
|
|
52
|
+
catch (error) {
|
|
53
|
+
this.error("Network error: Could not reach jsdelivr.net. Check your internet connection.");
|
|
54
|
+
throw error;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Get the LLM README from the installed version of the SDK.
|
|
59
|
+
* @returns The LLM README, or null if not found.
|
|
60
|
+
*/
|
|
61
|
+
getLLMReadmeFromInstalledVersion() {
|
|
62
|
+
try {
|
|
63
|
+
const require = createRequire(import.meta.url);
|
|
64
|
+
const sdkPkgPath = require.resolve(`${SDK_PACKAGE_NAME}/package.json`, {
|
|
65
|
+
paths: [process.cwd()],
|
|
66
|
+
});
|
|
67
|
+
const llmReadme = fs.readFileSync(path.join(path.dirname(sdkPkgPath), DOCS_FILENAME), "utf-8");
|
|
68
|
+
return llmReadme;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Detect the specified version of the SDK by walking up the directory tree
|
|
76
|
+
* looking for a package.json that contains the SDK dependency.
|
|
77
|
+
* @returns The specified version of the SDK, or null if not found.
|
|
78
|
+
*/
|
|
79
|
+
detectPackageJsonVersion() {
|
|
80
|
+
let currentDir = process.cwd();
|
|
81
|
+
const root = path.parse(currentDir).root;
|
|
82
|
+
while (currentDir !== root) {
|
|
83
|
+
console.log("Checking", currentDir);
|
|
84
|
+
try {
|
|
85
|
+
const pkgPath = path.join(currentDir, "package.json");
|
|
86
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
|
|
87
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
88
|
+
const version = deps[SDK_PACKAGE_NAME];
|
|
89
|
+
if (version) {
|
|
90
|
+
return version;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
// package.json not found or invalid, continue to parent
|
|
95
|
+
}
|
|
96
|
+
currentDir = path.dirname(currentDir);
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import { Command } from "@oclif/core";
|
|
3
|
+
import { getStaticResourcePath } from "../../lib/utils.js";
|
|
4
|
+
export default class Cli extends Command {
|
|
5
|
+
static description = "Output the Kradle CLI reference documentation for LLMs";
|
|
6
|
+
static examples = ["<%= config.bin %> <%= command.id %>"];
|
|
7
|
+
async run() {
|
|
8
|
+
await this.parse(Cli);
|
|
9
|
+
const docPath = getStaticResourcePath("ai_docs/LLM_CLI_REFERENCE.md");
|
|
10
|
+
const content = await fs.readFile(docPath, "utf-8");
|
|
11
|
+
this.log(content);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class Build extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
challengeSlug: import("@oclif/core/interfaces").Arg<string | undefined>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
"api-key": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
"api-url": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
"challenges-path": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
public: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
};
|
|
15
|
+
static strict: boolean;
|
|
16
|
+
run(): Promise<void>;
|
|
17
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Command, Flags, loadHelpClass } from "@oclif/core";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { ApiClient } from "../../lib/api-client.js";
|
|
4
|
+
import { getChallengeSlugArgument } from "../../lib/arguments.js";
|
|
5
|
+
import { Challenge } from "../../lib/challenge.js";
|
|
6
|
+
import { getConfigFlags } from "../../lib/flags.js";
|
|
7
|
+
export default class Build extends Command {
|
|
8
|
+
static description = "Build and upload challenge datapack and config";
|
|
9
|
+
static examples = [
|
|
10
|
+
"<%= config.bin %> <%= command.id %> my-challenge",
|
|
11
|
+
"<%= config.bin %> <%= command.id %> my-challenge my-other-challenge",
|
|
12
|
+
"<%= config.bin %> <%= command.id %> --all",
|
|
13
|
+
];
|
|
14
|
+
static args = {
|
|
15
|
+
challengeSlug: getChallengeSlugArgument({
|
|
16
|
+
description: "Challenge slug to build and upload. Can be used multiple times to build and upload multiple challenges at once. Incompatible with --all flag.",
|
|
17
|
+
required: false,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
all: Flags.boolean({ char: "a", description: "Build all challenges in the challenges directory", default: false }),
|
|
22
|
+
public: Flags.boolean({ char: "p", description: "Upload challenges as public.", default: false }),
|
|
23
|
+
...getConfigFlags("api-key", "api-url", "challenges-path"),
|
|
24
|
+
};
|
|
25
|
+
static strict = false;
|
|
26
|
+
async run() {
|
|
27
|
+
const { argv, flags } = await this.parse(Build);
|
|
28
|
+
if (flags.all && argv.length > 0) {
|
|
29
|
+
this.error(pc.red("Cannot use --all flag with challenge slugs"));
|
|
30
|
+
}
|
|
31
|
+
if (!flags.all && argv.length === 0) {
|
|
32
|
+
// Show help if no challenge slugs are provided - https://github.com/oclif/oclif/issues/183#issuecomment-1933104981
|
|
33
|
+
await new (await loadHelpClass(this.config))(this.config).showHelp([Build.id]);
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (flags.all) {
|
|
37
|
+
this.log(pc.blue("Building all challenges"));
|
|
38
|
+
}
|
|
39
|
+
const challengeSlugs = flags.all ? await Challenge.getLocalChallenges() : argv;
|
|
40
|
+
const api = new ApiClient(flags["api-url"], flags["api-key"]);
|
|
41
|
+
for (const challengeSlug of challengeSlugs) {
|
|
42
|
+
const challenge = new Challenge(challengeSlug, flags["challenges-path"]);
|
|
43
|
+
this.log(pc.blue(`==== Building challenge: ${challenge.shortSlug} ====`));
|
|
44
|
+
try {
|
|
45
|
+
await challenge.buildAndUpload(api, flags.public);
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
this.error(pc.red(`Build failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
49
|
+
}
|
|
50
|
+
this.log();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|