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.
Files changed (82) hide show
  1. package/README.md +422 -0
  2. package/bin/run.cmd +3 -0
  3. package/bin/run.js +14 -0
  4. package/dist/commands/agent/list.d.ts +10 -0
  5. package/dist/commands/agent/list.js +22 -0
  6. package/dist/commands/ai-docs/challenges-sdk.d.ts +21 -0
  7. package/dist/commands/ai-docs/challenges-sdk.js +100 -0
  8. package/dist/commands/ai-docs/cli.d.ts +6 -0
  9. package/dist/commands/ai-docs/cli.js +13 -0
  10. package/dist/commands/challenge/build.d.ts +17 -0
  11. package/dist/commands/challenge/build.js +53 -0
  12. package/dist/commands/challenge/create.d.ts +16 -0
  13. package/dist/commands/challenge/create.js +88 -0
  14. package/dist/commands/challenge/delete.d.ts +15 -0
  15. package/dist/commands/challenge/delete.js +99 -0
  16. package/dist/commands/challenge/list.d.ts +11 -0
  17. package/dist/commands/challenge/list.js +49 -0
  18. package/dist/commands/challenge/pull.d.ts +15 -0
  19. package/dist/commands/challenge/pull.js +182 -0
  20. package/dist/commands/challenge/run.d.ts +18 -0
  21. package/dist/commands/challenge/run.js +55 -0
  22. package/dist/commands/challenge/watch.d.ts +15 -0
  23. package/dist/commands/challenge/watch.js +114 -0
  24. package/dist/commands/experiment/create.d.ts +13 -0
  25. package/dist/commands/experiment/create.js +80 -0
  26. package/dist/commands/experiment/list.d.ts +7 -0
  27. package/dist/commands/experiment/list.js +53 -0
  28. package/dist/commands/experiment/recordings.d.ts +19 -0
  29. package/dist/commands/experiment/recordings.js +416 -0
  30. package/dist/commands/experiment/run.d.ts +17 -0
  31. package/dist/commands/experiment/run.js +67 -0
  32. package/dist/commands/init.d.ts +10 -0
  33. package/dist/commands/init.js +165 -0
  34. package/dist/commands/world/delete.d.ts +14 -0
  35. package/dist/commands/world/delete.js +80 -0
  36. package/dist/commands/world/import.d.ts +14 -0
  37. package/dist/commands/world/import.js +120 -0
  38. package/dist/commands/world/list.d.ts +10 -0
  39. package/dist/commands/world/list.js +46 -0
  40. package/dist/commands/world/pull.d.ts +14 -0
  41. package/dist/commands/world/pull.js +134 -0
  42. package/dist/commands/world/push.d.ts +16 -0
  43. package/dist/commands/world/push.js +131 -0
  44. package/dist/index.d.ts +1 -0
  45. package/dist/index.js +1 -0
  46. package/dist/lib/api-client.d.ts +133 -0
  47. package/dist/lib/api-client.js +346 -0
  48. package/dist/lib/arguments.d.ts +12 -0
  49. package/dist/lib/arguments.js +46 -0
  50. package/dist/lib/challenge.d.ts +60 -0
  51. package/dist/lib/challenge.js +197 -0
  52. package/dist/lib/experiment/experimenter.d.ts +92 -0
  53. package/dist/lib/experiment/experimenter.js +368 -0
  54. package/dist/lib/experiment/index.d.ts +4 -0
  55. package/dist/lib/experiment/index.js +4 -0
  56. package/dist/lib/experiment/runner.d.ts +82 -0
  57. package/dist/lib/experiment/runner.js +309 -0
  58. package/dist/lib/experiment/tui.d.ts +20 -0
  59. package/dist/lib/experiment/tui.js +130 -0
  60. package/dist/lib/experiment/types.d.ts +134 -0
  61. package/dist/lib/experiment/types.js +88 -0
  62. package/dist/lib/flags.d.ts +47 -0
  63. package/dist/lib/flags.js +63 -0
  64. package/dist/lib/schemas.d.ts +242 -0
  65. package/dist/lib/schemas.js +112 -0
  66. package/dist/lib/utils.d.ts +96 -0
  67. package/dist/lib/utils.js +199 -0
  68. package/dist/lib/world.d.ts +21 -0
  69. package/dist/lib/world.js +102 -0
  70. package/oclif.manifest.json +1167 -0
  71. package/package.json +91 -8
  72. package/static/ai_docs/LLM_CLI_REFERENCE.md +964 -0
  73. package/static/challenge.ts +31 -0
  74. package/static/experiment_template.ts +114 -0
  75. package/static/project_template/AGENTS.md +17 -0
  76. package/static/project_template/CLAUDE.md +1 -0
  77. package/static/project_template/dev.env +5 -0
  78. package/static/project_template/package.json +17 -0
  79. package/static/project_template/prod.env +5 -0
  80. package/static/project_template/template-run.json +10 -0
  81. package/static/project_template/tsconfig.json +17 -0
  82. 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
@@ -0,0 +1,3 @@
1
+ @echo off
2
+
3
+ node "%~dp0\run" %*
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,6 @@
1
+ import { Command } from "@oclif/core";
2
+ export default class Cli extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ run(): Promise<void>;
6
+ }
@@ -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
+ }