@kradle/cli 0.2.1 → 0.2.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 CHANGED
@@ -1,3 +1,5 @@
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
+
1
3
  # Kradle CLI
2
4
 
3
5
  Kradle's CLI for managing Minecraft challenges, experiments, agents, and more!
@@ -8,6 +10,7 @@ Kradle's CLI for managing Minecraft challenges, experiments, agents, and more!
8
10
  * [Challenge](#challenge-commands)
9
11
  * [Experiments](#experiment-commands)
10
12
  * [Agents](#agent-commands)
13
+ * [AI Docs](#ai-docs-commands)
11
14
  * [Publishing a New Version](#publishing-a-new-version)
12
15
  * [Development](#development)
13
16
  * [Architecture](#architecture)
@@ -125,18 +128,9 @@ Run a challenge in production or studio environment:
125
128
  ```bash
126
129
  kradle challenge run <challenge-name>
127
130
  kradle challenge run <challenge-name> --studio # Run in local studio environment
131
+ kradle challenge run <team-name>:<challenge-name> # Run a public challenge from another team
128
132
  ```
129
133
 
130
- ### Multi-Upload
131
-
132
- Interactively select and upload multiple challenges:
133
-
134
- ```bash
135
- kradle challenge multi-upload
136
- ```
137
-
138
- Provides an interactive UI to select multiple challenges and uploads them in parallel.
139
-
140
134
  ## Experiment Commands
141
135
 
142
136
  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.
@@ -172,9 +166,10 @@ This creates `experiments/<name>/config.ts` with a template that you can customi
172
166
  Execute or resume an experiment:
173
167
 
174
168
  ```bash
175
- kradle experiment run <name> # Resume current version or create first one
176
- kradle experiment run <name> --new-version # Start a new version
177
- kradle experiment run <name> --max-concurrent 10 # Control parallelism (default: 5)
169
+ kradle experiment run <name> # Resume current version or create first one
170
+ kradle experiment run <name> --new-version # Start a new version
171
+ kradle experiment run <name> --max-concurrent 10 # Control parallelism (default: 5)
172
+ kradle experiment run <name> --download-recordings # Auto-download recordings as runs complete
178
173
  ```
179
174
 
180
175
  The run command:
@@ -184,6 +179,20 @@ The run command:
184
179
  4. Saves progress periodically (allows resuming if interrupted)
185
180
  5. Opens Metabase dashboard with results when complete
186
181
 
182
+ ### Download Recordings
183
+
184
+ Download gameplay recordings from completed experiment runs:
185
+
186
+ ```bash
187
+ kradle experiment recordings <name> # Interactive selection of run and participant
188
+ kradle experiment recordings <name> <run-id> # Download specific run
189
+ kradle experiment recordings <name> --all # Download all runs and participants
190
+ kradle experiment recordings <name> --version 2 # Download from specific version
191
+ kradle experiment recordings <name> <run-id> --all # Download all participants for a run
192
+ ```
193
+
194
+ Recordings are saved to `experiments/<name>/versions/<version>/recordings/<run-id>/`.
195
+
187
196
  ### List Experiments
188
197
 
189
198
  List all local experiments:
@@ -202,6 +211,26 @@ List all agents registered in the system:
202
211
  kradle agent list
203
212
  ```
204
213
 
214
+ ## AI Docs Commands
215
+
216
+ Output LLM-focused documentation to stdout. These commands are designed to provide AI agents with comprehensive reference material about the Kradle CLI and API.
217
+
218
+ ### CLI Reference
219
+
220
+ Output the CLI reference documentation:
221
+
222
+ ```bash
223
+ kradle ai-docs cli
224
+ ```
225
+
226
+ ### API Reference
227
+
228
+ Output the API reference documentation for the `@kradle/challenges` package:
229
+
230
+ ```bash
231
+ kradle ai-docs api
232
+ ```
233
+
205
234
  ## Publishing a New Version
206
235
 
207
236
  The CLI uses GitHub Actions for automated releases. To publish a new version:
@@ -303,6 +332,7 @@ kradle-cli/
303
332
  ├── src/
304
333
  │ ├── commands/ # CLI commands
305
334
  │ │ ├── agent/ # Agent commands
335
+ │ │ ├── ai-docs/ # AI documentation commands
306
336
  │ │ ├── challenge/ # Challenge management commands
307
337
  │ │ └── experiment/ # Experiment commands
308
338
  │ └── lib/ # Core libraries
@@ -0,0 +1,6 @@
1
+ import { Command } from "@oclif/core";
2
+ export default class Api 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 Api extends Command {
5
+ static description = "Output the Kradle API reference documentation for LLMs";
6
+ static examples = ["<%= config.bin %> <%= command.id %>"];
7
+ async run() {
8
+ await this.parse(Api);
9
+ const docPath = getStaticResourcePath("ai_docs/LLM_API_REFERENCE.md");
10
+ const content = await fs.readFile(docPath, "utf-8");
11
+ this.log(content);
12
+ }
13
+ }
@@ -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
+ }
@@ -11,7 +11,6 @@ export default class Run extends Command {
11
11
  "web-url": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
12
12
  "studio-api-url": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
13
13
  "studio-url": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
14
- "challenges-path": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
15
14
  studio: import("@oclif/core/interfaces").BooleanFlag<boolean>;
16
15
  open: import("@oclif/core/interfaces").BooleanFlag<boolean>;
17
16
  };
@@ -2,7 +2,6 @@ import { Command, Flags } from "@oclif/core";
2
2
  import pc from "picocolors";
3
3
  import { ApiClient } from "../../lib/api-client.js";
4
4
  import { getChallengeSlugArgument } from "../../lib/arguments.js";
5
- import { Challenge } from "../../lib/challenge.js";
6
5
  import { getConfigFlags } from "../../lib/flags.js";
7
6
  import { loadTemplateRun, openInBrowser } from "../../lib/utils.js";
8
7
  export default class Run extends Command {
@@ -10,27 +9,31 @@ export default class Run extends Command {
10
9
  static examples = [
11
10
  "<%= config.bin %> <%= command.id %> my-challenge",
12
11
  "<%= config.bin %> <%= command.id %> my-challenge --studio",
12
+ "<%= config.bin %> <%= command.id %> team-name:my-challenge",
13
13
  ];
14
14
  static args = {
15
- challengeSlug: getChallengeSlugArgument({ description: "Challenge slug to run" }),
15
+ challengeSlug: getChallengeSlugArgument({
16
+ description: "Challenge slug to run (e.g., 'my-challenge' or 'team-name:my-challenge')",
17
+ allowTeam: true,
18
+ }),
16
19
  };
17
20
  static flags = {
18
21
  studio: Flags.boolean({ char: "s", description: "Run in studio environment", default: false }),
19
22
  open: Flags.boolean({ char: "o", description: "Open the run URL in the browser", default: false }),
20
- ...getConfigFlags("api-key", "api-url", "challenges-path", "web-url", "studio-url", "studio-api-url"),
23
+ ...getConfigFlags("api-key", "api-url", "web-url", "studio-url", "studio-api-url"),
21
24
  };
22
25
  async run() {
23
26
  const { args, flags } = await this.parse(Run);
24
27
  const apiUrl = flags.studio ? flags["studio-api-url"] : flags["api-url"];
25
28
  const studioApi = new ApiClient(apiUrl, flags["api-key"], flags.studio);
26
- const challenge = new Challenge(args.challengeSlug, flags["challenges-path"]);
29
+ const challengeSlug = args.challengeSlug;
27
30
  try {
28
31
  const { participants } = (await loadTemplateRun());
29
32
  const template = {
30
- challenge: challenge.shortSlug,
33
+ challenge: challengeSlug,
31
34
  participants,
32
35
  };
33
- this.log(pc.blue(`>> Running challenge: ${challenge.shortSlug}${flags.studio ? " (studio)" : ""}...`));
36
+ this.log(pc.blue(`>> Running challenge: ${challengeSlug}${flags.studio ? " (studio)" : ""}...`));
34
37
  const response = await studioApi.runChallenge(template);
35
38
  if (response.runIds && response.runIds.length > 0) {
36
39
  const baseUrl = flags.studio ? flags["studio-url"] : flags["web-url"];
@@ -104,13 +104,15 @@ export default class Init extends Command {
104
104
  const templateDir = getStaticResourcePath("project_template");
105
105
  const templateFiles = await readDirSorted(templateDir);
106
106
  for (const file of templateFiles) {
107
- if (file.name.endsWith(".env")) {
108
- // We ignore .env as it will be created later
107
+ // We ignore .env as it will be created later
108
+ if (file.name.endsWith(".env") || file.isDirectory()) {
109
109
  continue;
110
110
  }
111
111
  const srcPath = path.join(file.parentPath, file.name);
112
112
  const srcRelativePath = path.relative(templateDir, srcPath);
113
113
  const destPath = path.join(targetDir, srcRelativePath);
114
+ // Ensure the destination directory exists before copying the file
115
+ await fs.mkdir(path.dirname(destPath), { recursive: true });
114
116
  await fs.copyFile(srcPath, destPath);
115
117
  }
116
118
  // Create .env file based on the environment
@@ -1,8 +1,12 @@
1
1
  import type { Arg } from "@oclif/core/interfaces";
2
2
  /**
3
- * Returns a "challenge slug" argument, and validates it to be a valid challenge slug.
3
+ * Returns a "challenge slug" argument, validating it to be a valid challenge slug.
4
+ * @param description - Description for the argument
5
+ * @param required - Whether the argument is required (default: true)
6
+ * @param allowTeam - Whether to allow namespaced slugs like "team-name:my-challenge" (default: false)
4
7
  */
5
- export declare function getChallengeSlugArgument<R extends boolean = true>({ description, required, }: {
8
+ export declare function getChallengeSlugArgument<R extends boolean = true>({ description, required, allowTeam, }: {
6
9
  description: string;
7
10
  required?: R;
11
+ allowTeam?: boolean;
8
12
  }): Arg<R extends true ? string : string | undefined>;
@@ -1,15 +1,27 @@
1
1
  import { Args } from "@oclif/core";
2
- const CHALLENGE_SLUG_REGEX = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
2
+ // Base pattern for a slug segment (lowercase alphanumeric with hyphens, no leading/trailing hyphens)
3
+ const SLUG_SEGMENT = "[a-z0-9]+(?:-[a-z0-9]+)*";
4
+ // Local challenge slug pattern: just the challenge name (no namespace)
5
+ const LOCAL_SLUG_REGEX = new RegExp(`^${SLUG_SEGMENT}$`);
6
+ // Full challenge slug pattern: optional namespace prefix (e.g., "team-name:") followed by the challenge slug
7
+ const NAMESPACED_SLUG_REGEX = new RegExp(`^(?:${SLUG_SEGMENT}:)?${SLUG_SEGMENT}$`);
3
8
  /**
4
- * Returns a "challenge slug" argument, and validates it to be a valid challenge slug.
9
+ * Returns a "challenge slug" argument, validating it to be a valid challenge slug.
10
+ * @param description - Description for the argument
11
+ * @param required - Whether the argument is required (default: true)
12
+ * @param allowTeam - Whether to allow namespaced slugs like "team-name:my-challenge" (default: false)
5
13
  */
6
- export function getChallengeSlugArgument({ description, required, }) {
14
+ export function getChallengeSlugArgument({ description, required, allowTeam = false, }) {
15
+ const regex = allowTeam ? NAMESPACED_SLUG_REGEX : LOCAL_SLUG_REGEX;
16
+ const errorMessage = allowTeam
17
+ ? "Challenge slugs must be lowercase alphanumeric characters and hyphens, and must not start or end with a hyphen. Optionally, a team prefix can be provided (e.g., 'team-name:my-challenge')."
18
+ : "Challenge slugs must be lowercase alphanumeric characters and hyphens, and must not start or end with a hyphen.";
7
19
  const arg = Args.string({
8
20
  description,
9
21
  required: required ?? true,
10
22
  parse: async (input) => {
11
- if (!CHALLENGE_SLUG_REGEX.test(input)) {
12
- throw new Error(`Invalid challenge slug: ${input}. Challenge slugs must be lowercase alphanumeric characters and hyphens, and must not start or end with a hyphen.`);
23
+ if (!regex.test(input)) {
24
+ throw new Error(`Invalid challenge slug: ${input}. ${errorMessage}`);
13
25
  }
14
26
  return input;
15
27
  },
@@ -361,7 +361,7 @@
361
361
  "aliases": [],
362
362
  "args": {
363
363
  "challengeSlug": {
364
- "description": "Challenge slug to run",
364
+ "description": "Challenge slug to run (e.g., 'my-challenge' or 'team-name:my-challenge')",
365
365
  "name": "challengeSlug",
366
366
  "required": true
367
367
  }
@@ -369,7 +369,8 @@
369
369
  "description": "Run a challenge",
370
370
  "examples": [
371
371
  "<%= config.bin %> <%= command.id %> my-challenge",
372
- "<%= config.bin %> <%= command.id %> my-challenge --studio"
372
+ "<%= config.bin %> <%= command.id %> my-challenge --studio",
373
+ "<%= config.bin %> <%= command.id %> team-name:my-challenge"
373
374
  ],
374
375
  "flags": {
375
376
  "studio": {
@@ -405,15 +406,6 @@
405
406
  "multiple": false,
406
407
  "type": "option"
407
408
  },
408
- "challenges-path": {
409
- "description": "Absolute path to the challenges directory",
410
- "env": "KRADLE_CHALLENGES_PATH",
411
- "name": "challenges-path",
412
- "default": "~/Documents/kradle-studio/challenges",
413
- "hasDynamicHelp": false,
414
- "multiple": false,
415
- "type": "option"
416
- },
417
409
  "web-url": {
418
410
  "description": "Kradle Web URL, used to display the run URL",
419
411
  "env": "KRADLE_WEB_URL",
@@ -526,6 +518,54 @@
526
518
  "watch.js"
527
519
  ]
528
520
  },
521
+ "ai-docs:api": {
522
+ "aliases": [],
523
+ "args": {},
524
+ "description": "Output the Kradle API reference documentation for LLMs",
525
+ "examples": [
526
+ "<%= config.bin %> <%= command.id %>"
527
+ ],
528
+ "flags": {},
529
+ "hasDynamicHelp": false,
530
+ "hiddenAliases": [],
531
+ "id": "ai-docs:api",
532
+ "pluginAlias": "@kradle/cli",
533
+ "pluginName": "@kradle/cli",
534
+ "pluginType": "core",
535
+ "strict": true,
536
+ "enableJsonFlag": false,
537
+ "isESM": true,
538
+ "relativePath": [
539
+ "dist",
540
+ "commands",
541
+ "ai-docs",
542
+ "api.js"
543
+ ]
544
+ },
545
+ "ai-docs:cli": {
546
+ "aliases": [],
547
+ "args": {},
548
+ "description": "Output the Kradle CLI reference documentation for LLMs",
549
+ "examples": [
550
+ "<%= config.bin %> <%= command.id %>"
551
+ ],
552
+ "flags": {},
553
+ "hasDynamicHelp": false,
554
+ "hiddenAliases": [],
555
+ "id": "ai-docs:cli",
556
+ "pluginAlias": "@kradle/cli",
557
+ "pluginName": "@kradle/cli",
558
+ "pluginType": "core",
559
+ "strict": true,
560
+ "enableJsonFlag": false,
561
+ "isESM": true,
562
+ "relativePath": [
563
+ "dist",
564
+ "commands",
565
+ "ai-docs",
566
+ "cli.js"
567
+ ]
568
+ },
529
569
  "experiment:create": {
530
570
  "aliases": [],
531
571
  "args": {
@@ -760,5 +800,5 @@
760
800
  ]
761
801
  }
762
802
  },
763
- "version": "0.2.1"
803
+ "version": "0.2.3"
764
804
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kradle/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "description": "Kradle's CLI. Manage challenges, experiments, agents and more!",
5
5
  "keywords": [
6
6
  "cli"
@@ -81,6 +81,9 @@
81
81
  },
82
82
  "experiment": {
83
83
  "description": "Manage and run experiments"
84
+ },
85
+ "ai-docs": {
86
+ "description": "Output LLM reference documentation"
84
87
  }
85
88
  }
86
89
  }