kradle 0.6.8 → 0.6.10

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
@@ -226,13 +226,11 @@ Get details and logs for a specific run:
226
226
  ```bash
227
227
  kradle challenge runs get <run-id>
228
228
  kradle challenge runs get <run-id> --no-logs # Skip fetching logs
229
- kradle challenge runs get <run-id> --no-summary # Skip AI summary
230
229
  ```
231
230
 
232
231
  This displays:
233
232
  - Run metadata (status, duration, end state)
234
233
  - Participant results (agent, winner status, score)
235
- - AI-generated summary (unless `--no-summary` is used)
236
234
  - Log entries with timestamps and levels (unless `--no-logs` is used)
237
235
 
238
236
  ## Experiment Commands
@@ -407,7 +405,7 @@ Locations are synced to the cloud when you push the world.
407
405
 
408
406
  ### List Agents
409
407
 
410
- List all agents registered in the system:
408
+ List all agents registered in the system with their model and pricing information (input/output cost per million tokens):
411
409
 
412
410
  ```bash
413
411
  kradle agent list
@@ -415,7 +413,7 @@ kradle agent list
415
413
 
416
414
  ## AI Docs Commands
417
415
 
418
- Output LLM-focused documentation to stdout. These commands are designed to provide AI agents with comprehensive reference material about the Kradle CLI and API.
416
+ Output LLM-focused documentation to stdout, or sync local AI docs with the latest version from the CLI.
419
417
 
420
418
  ### CLI Reference
421
419
 
@@ -436,6 +434,19 @@ kradle ai-docs challenges-sdk 0.2.1 # Uses specific version
436
434
 
437
435
  This fetches the documentation from unpkg.com, matching the SDK version in your project.
438
436
 
437
+ ### Sync AI Docs
438
+
439
+ Sync project AI docs (`AGENTS.md`, `CLAUDE.md`) to the latest version bundled with the CLI:
440
+
441
+ ```bash
442
+ kradle ai-docs sync # Interactive confirmation
443
+ kradle ai-docs sync --dry-run # Preview changes without applying
444
+ kradle ai-docs sync --yes # Skip confirmation prompts
445
+ kradle ai-docs sync --add-missing # Also create files missing locally
446
+ ```
447
+
448
+ This compares your local `AGENTS.md` and `CLAUDE.md` against the templates bundled with the installed CLI version. Useful after upgrading `kradle` to pick up new AI doc templates.
449
+
439
450
  ## Publishing a New Version
440
451
 
441
452
  The CLI uses GitHub Actions for automated releases. To publish a new version:
@@ -2,6 +2,49 @@ import { Command } from "@oclif/core";
2
2
  import pc from "picocolors";
3
3
  import { ApiClient } from "../../lib/api-client.js";
4
4
  import { getConfigFlags } from "../../lib/flags.js";
5
+ async function fetchOpenRouterPricing() {
6
+ const pricing = new Map();
7
+ try {
8
+ const response = await fetch("https://openrouter.ai/api/v1/models", {
9
+ signal: AbortSignal.timeout(5000),
10
+ });
11
+ if (!response.ok)
12
+ return pricing;
13
+ const data = (await response.json());
14
+ for (const model of data.data) {
15
+ const promptCost = Number.parseFloat(model.pricing.prompt);
16
+ const completionCost = Number.parseFloat(model.pricing.completion);
17
+ if (!Number.isNaN(promptCost) && !Number.isNaN(completionCost) && promptCost >= 0 && completionCost >= 0) {
18
+ pricing.set(model.id, {
19
+ input: promptCost * 1_000_000,
20
+ output: completionCost * 1_000_000,
21
+ });
22
+ }
23
+ }
24
+ }
25
+ catch {
26
+ // Silently fail - pricing is best-effort
27
+ }
28
+ return pricing;
29
+ }
30
+ function formatCost(cost) {
31
+ if (cost === 0)
32
+ return "$0";
33
+ if (cost < 0.01)
34
+ return `$${cost.toFixed(4)}`;
35
+ return `$${cost.toFixed(2)}`;
36
+ }
37
+ function colorizeCost(formatted, cost) {
38
+ if (cost === undefined)
39
+ return pc.dim("-");
40
+ if (cost < 1)
41
+ return pc.green(formatted);
42
+ if (cost < 5)
43
+ return pc.cyan(formatted);
44
+ if (cost < 15)
45
+ return pc.yellow(formatted);
46
+ return pc.red(formatted);
47
+ }
5
48
  export default class List extends Command {
6
49
  static description = "List all agents";
7
50
  static examples = ["<%= config.bin %> <%= command.id %>"];
@@ -12,11 +55,33 @@ export default class List extends Command {
12
55
  const { flags } = await this.parse(List);
13
56
  const api = new ApiClient(flags["api-url"], flags["api-key"]);
14
57
  this.log(pc.blue(">> Loading agents..."));
15
- const agents = await api.listKradleAgents();
58
+ const [agents, pricing] = await Promise.all([api.listKradleAgents(), fetchOpenRouterPricing()]);
16
59
  agents.sort((a, b) => a.username?.localeCompare(b.username || "") || 0);
60
+ // Calculate column widths
61
+ const rows = agents.map((agent) => {
62
+ const username = agent.username || "";
63
+ const model = agent.agentConfig && "model" in agent.agentConfig ? agent.agentConfig.model : "";
64
+ const costs = model ? pricing.get(model) : undefined;
65
+ const inputRaw = costs ? formatCost(costs.input) : "-";
66
+ const outputRaw = costs ? formatCost(costs.output) : "-";
67
+ const inputColored = colorizeCost(inputRaw, costs?.input);
68
+ const outputColored = colorizeCost(outputRaw, costs?.output);
69
+ return { username, model, inputRaw, outputRaw, inputColored, outputColored };
70
+ });
71
+ const nameWidth = Math.max("Agent".length, ...rows.map((r) => r.username.length));
72
+ const modelWidth = Math.max("Model".length, ...rows.map((r) => r.model.length));
73
+ const inputWidth = Math.max("Input/MTok".length, ...rows.map((r) => r.inputRaw.length));
74
+ const outputWidth = Math.max("Output/MTok".length, ...rows.map((r) => r.outputRaw.length));
17
75
  this.log(pc.bold(`\nFound ${agents.length} agents:\n`));
18
- for (const agent of agents) {
19
- this.log(pc.bold(`- ${agent.username}`));
76
+ // Header
77
+ const header = ` ${pc.bold("Agent".padEnd(nameWidth))} ${pc.bold("Model".padEnd(modelWidth))} ${pc.bold("Input/MTok".padEnd(inputWidth))} ${pc.bold("Output/MTok".padEnd(outputWidth))}`;
78
+ this.log(header);
79
+ this.log(` ${"─".repeat(nameWidth)} ${"─".repeat(modelWidth)} ${"─".repeat(inputWidth)} ${"─".repeat(outputWidth)}`);
80
+ for (const row of rows) {
81
+ // Pad using raw (uncolored) width, then replace with colored version
82
+ const inputPad = " ".repeat(Math.max(0, inputWidth - row.inputRaw.length));
83
+ const outputPad = " ".repeat(Math.max(0, outputWidth - row.outputRaw.length));
84
+ this.log(` ${pc.bold(row.username.padEnd(nameWidth))} ${row.model.padEnd(modelWidth)} ${row.inputColored}${inputPad} ${row.outputColored}${outputPad}`);
20
85
  }
21
86
  }
22
87
  }
@@ -1,5 +1,5 @@
1
1
  import { Command } from "@oclif/core";
2
- export default class Update extends Command {
2
+ export default class Sync extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static flags: {
@@ -3,11 +3,11 @@ import path from "node:path";
3
3
  import { Command, Flags } from "@oclif/core";
4
4
  import enquirer from "enquirer";
5
5
  import pc from "picocolors";
6
- import { getStaticResourcePath } from "../lib/utils.js";
6
+ import { getStaticResourcePath } from "../../lib/utils.js";
7
7
  // Files that should be synced on update (documentation files only)
8
8
  const SYNC_FILES = ["AGENTS.md", "CLAUDE.md"];
9
- export default class Update extends Command {
10
- static description = "Update project template files (AGENTS.md, CLAUDE.md) to the latest version from the CLI";
9
+ export default class Sync extends Command {
10
+ static description = "Sync project AI docs (AGENTS.md, CLAUDE.md) to the latest version from the CLI";
11
11
  static examples = [
12
12
  "<%= config.bin %> <%= command.id %>",
13
13
  "<%= config.bin %> <%= command.id %> --dry-run",
@@ -96,7 +96,7 @@ export default class Update extends Command {
96
96
  this.log(pc.dim(" (file contents differ - will be replaced with template version)"));
97
97
  }
98
98
  async run() {
99
- const { flags } = await this.parse(Update);
99
+ const { flags } = await this.parse(Sync);
100
100
  const isDryRun = flags["dry-run"];
101
101
  const skipPrompts = flags.yes;
102
102
  const addMissing = flags["add-missing"];
@@ -19,7 +19,6 @@ export default class Run extends Command {
19
19
  "arena-image": import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
20
20
  "no-open": import("@oclif/core/interfaces").BooleanFlag<boolean>;
21
21
  "no-wait": import("@oclif/core/interfaces").BooleanFlag<boolean>;
22
- "no-summary": import("@oclif/core/interfaces").BooleanFlag<boolean>;
23
22
  screenshots: import("@oclif/core/interfaces").BooleanFlag<boolean>;
24
23
  };
25
24
  private pollForCompletion;
@@ -51,20 +51,14 @@ export default class Run extends Command {
51
51
  description: "Don't wait for the run to complete (fire and forget)",
52
52
  default: false,
53
53
  }),
54
- "no-summary": Flags.boolean({
55
- description: "Don't wait for the AI-generated summary",
56
- default: false,
57
- }),
58
54
  screenshots: Flags.boolean({
59
55
  description: "Open the run URL with screenshots mode enabled",
60
56
  default: false,
61
57
  }),
62
58
  ...getConfigFlags("api-key", "api-url", "web-url", "studio-url", "studio-api-url", "challenges-path"),
63
59
  };
64
- async pollForCompletion(api, runId, waitForSummary) {
60
+ async pollForCompletion(api, runId) {
65
61
  let lastStatus = "";
66
- let reachedTerminal = false;
67
- let waitingForSummary = false;
68
62
  const startTime = Date.now();
69
63
  while (true) {
70
64
  // Check for timeout
@@ -83,18 +77,6 @@ export default class Run extends Command {
83
77
  }
84
78
  // Check for terminal state
85
79
  if (TERMINAL_STATUSES.includes(currentStatus)) {
86
- if (!reachedTerminal) {
87
- reachedTerminal = true;
88
- }
89
- // If we need to wait for summary and it's not available yet, keep polling
90
- if (waitForSummary && !result.summary) {
91
- if (!waitingForSummary) {
92
- waitingForSummary = true;
93
- this.log(pc.dim(`Waiting for summary...`));
94
- }
95
- await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
96
- continue;
97
- }
98
80
  this.log("");
99
81
  this.displayRunResult(result);
100
82
  return;
@@ -119,10 +101,6 @@ export default class Run extends Command {
119
101
  this.log(` ${winnerIcon} ${participantId}: ${pr.agent}${score}`);
120
102
  }
121
103
  }
122
- if (result.summary) {
123
- this.log(pc.bold("\n=== Summary ===\n"));
124
- this.log(result.summary);
125
- }
126
104
  }
127
105
  /**
128
106
  * Parse inline agent arguments into participants.
@@ -409,7 +387,7 @@ export default class Run extends Command {
409
387
  }
410
388
  if (!flags["no-wait"]) {
411
389
  this.log(pc.blue("\n>> Waiting for run to complete...\n"));
412
- await this.pollForCompletion(api, runId, !flags["no-summary"]);
390
+ await this.pollForCompletion(api, runId);
413
391
  }
414
392
  }
415
393
  else {
@@ -9,7 +9,6 @@ export default class GetRun extends Command {
9
9
  "api-key": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
10
  "api-url": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
11
11
  "no-logs": import("@oclif/core/interfaces").BooleanFlag<boolean>;
12
- "no-summary": import("@oclif/core/interfaces").BooleanFlag<boolean>;
13
12
  };
14
13
  run(): Promise<void>;
15
14
  }
@@ -35,7 +35,6 @@ export default class GetRun extends Command {
35
35
  static examples = [
36
36
  "<%= config.bin %> <%= command.id %> abc123",
37
37
  "<%= config.bin %> <%= command.id %> abc123 --no-logs",
38
- "<%= config.bin %> <%= command.id %> abc123 --no-summary",
39
38
  ];
40
39
  static args = {
41
40
  runId: Args.string({
@@ -48,17 +47,12 @@ export default class GetRun extends Command {
48
47
  description: "Skip fetching and displaying logs",
49
48
  default: false,
50
49
  }),
51
- "no-summary": Flags.boolean({
52
- description: "Skip displaying the AI-generated summary",
53
- default: false,
54
- }),
55
50
  ...getConfigFlags("api-key", "api-url"),
56
51
  };
57
52
  async run() {
58
53
  const { args, flags } = await this.parse(GetRun);
59
54
  const api = new ApiClient(flags["api-url"], flags["api-key"]);
60
55
  const showLogs = !flags["no-logs"];
61
- const showSummary = !flags["no-summary"];
62
56
  this.log(pc.blue(`>> Loading run ${args.runId}...`));
63
57
  try {
64
58
  const [runResult, logs] = await Promise.all([
@@ -100,11 +94,6 @@ export default class GetRun extends Command {
100
94
  this.log(`${participantId.padEnd(widths[0])} ${agentPadded} ${winner} ${score.padEnd(widths[3])} ${timeToSuccess}`);
101
95
  }
102
96
  }
103
- // AI Summary
104
- if (showSummary && runResult.summary) {
105
- this.log(pc.bold("\n=== Summary ===\n"));
106
- this.log(runResult.summary);
107
- }
108
97
  // Logs
109
98
  if (showLogs) {
110
99
  // Filter out logs with 'prompt' key (verbose LLM prompts)
@@ -1 +1 @@
1
- export declare const MINECRAFT_ARENA_MANAGER_TAG = "8534933";
1
+ export declare const MINECRAFT_ARENA_MANAGER_TAG = "14f7331";
@@ -1,2 +1,2 @@
1
1
  // Managed by https://github.com/Kradle-ai/arena-minecraft/actions/workflows/release.yaml
2
- export const MINECRAFT_ARENA_MANAGER_TAG = "8534933";
2
+ export const MINECRAFT_ARENA_MANAGER_TAG = "14f7331";
@@ -169,17 +169,16 @@ export class Challenge {
169
169
  */
170
170
  async buildLocal(options = { validate: true }) {
171
171
  const { validate = true } = options;
172
+ // Ensure challenge exists locally
173
+ if (!this.exists()) {
174
+ throw new Error(`Challenge "${this.shortSlug}" does not exist locally. Make sure both the challenge.ts file and the config.ts file exist.`);
175
+ }
172
176
  // Start validation service initialization in parallel with build (if validation enabled)
173
177
  // This overlaps the init time with the build process.
174
178
  let validationService = null;
175
179
  if (validate) {
176
180
  validationService = ValidationService.create(this.builtDatapackPath);
177
181
  }
178
- // Ensure challenge exists locally
179
- if (!this.exists()) {
180
- await validationService?.close();
181
- throw new Error(`Challenge "${this.shortSlug}" does not exist locally. Make sure both the challenge.ts file and the config.ts file exist.`);
182
- }
183
182
  const config = await this.loadConfig();
184
183
  // Build datapack (validation service initializes in parallel)
185
184
  console.log(pc.blue(`>> Building datapack: ${this.shortSlug}`));
@@ -390,6 +389,7 @@ export class Challenge {
390
389
  challengeConfig: challengeData.challengeConfig,
391
390
  description: challengeData.description,
392
391
  task: challengeData.task,
392
+ scoreLabel: challengeData.scoreLabel,
393
393
  roles: challengeData.roles,
394
394
  objective: challengeData.objective,
395
395
  endStates: sortedEndStates,
@@ -257,9 +257,12 @@ export class Experimenter {
257
257
  this.currentVersion = version;
258
258
  // Load manifest
259
259
  const manifest = await this.loadManifest(version);
260
- // We have 2 mandatory tags: "exp-<experiment-name>" and "exp-<experiment-name>-v<version>"
261
- const experimentTag = `exp-${this.name}`;
262
- const versionTag = `${experimentTag}-v${version}`;
260
+ // Fetch the user's slug to prefix tags for uniqueness
261
+ const human = await this.api.getHuman();
262
+ const prefix = human.username;
263
+ // We have 2 mandatory tags: "<user>(<exp-name>)" and "<user>(<exp-name>-v<version>)"
264
+ const experimentTag = `${prefix}(exp-${this.name})`;
265
+ const versionTag = `${prefix}(exp-${this.name}-v${version})`;
263
266
  const tags = [experimentTag, versionTag, ...(manifest.tags ?? [])];
264
267
  // Create runner
265
268
  this.runner = new Runner(manifest.runs, this.api, this.webUrl, {
@@ -14,6 +14,23 @@ const SERVER_READY_TIMEOUT_MS = 120_000;
14
14
  const SERVER_READY_POLL_MS = 3_000;
15
15
  const CONTAINER_NAME_PREFIX_MC = "kradle-mc-server";
16
16
  const CONTAINER_NAME_PREFIX_ARENA = "kradle-arena";
17
+ // Debug environment variables — mirrors the flags in Studio's MinecraftDomainAdapter.
18
+ //
19
+ // DEBUG_LOCAL_ARENA_MINECRAFT=true
20
+ // Uses a local dev image ("local-arena-minecraft-dev") and volume-mounts
21
+ // the arena-minecraft source tree so changes are picked up without rebuilding.
22
+ // Build the image with "docker compose build" inside the arena-minecraft repo.
23
+ //
24
+ // DEBUG_LOCAL_ARENA_MINECRAFT_WAIT=true
25
+ // Additionally starts Node with --inspect-wait so it pauses until a debugger
26
+ // attaches (requires Node 22+). Without this flag, --inspect is used instead.
27
+ //
28
+ // ARENA_MINECRAFT_SOURCE_PATH
29
+ // Path to the arena-minecraft source tree. Defaults to "../arena-minecraft"
30
+ // relative to cwd.
31
+ const DEBUG_LOCAL_ARENA = process.env.DEBUG_LOCAL_ARENA_MINECRAFT === "true";
32
+ const DEBUG_LOCAL_ARENA_WAIT = process.env.DEBUG_LOCAL_ARENA_MINECRAFT_WAIT === "true";
33
+ const ARENA_DEBUG_PORT = 9229;
17
34
  export class LocalRunner {
18
35
  config;
19
36
  mcContainerId = null;
@@ -232,6 +249,9 @@ export class LocalRunner {
232
249
  containerName,
233
250
  "-p",
234
251
  `${MC_PORT}:${MC_PORT}`,
252
+ // The arena container shares this container's network, so we must
253
+ // publish the debug port here.
254
+ ...(DEBUG_LOCAL_ARENA ? ["-p", `${ARENA_DEBUG_PORT}:${ARENA_DEBUG_PORT}`] : []),
235
255
  "-v",
236
256
  `${serverDir}:/data`,
237
257
  "-e",
@@ -280,9 +300,38 @@ export class LocalRunner {
280
300
  `PROMPT_AGENT_BASE_URL=${agentBaseUrl}`,
281
301
  "-e",
282
302
  "MAX_IDLE_TIME=-1",
283
- arenaImage,
284
- `--job=${jobPayload}`,
285
303
  ];
304
+ if (DEBUG_LOCAL_ARENA) {
305
+ // Volume-mount the local source tree so edits are reflected without
306
+ // rebuilding the image.
307
+ const sourcePath = process.env.ARENA_MINECRAFT_SOURCE_PATH
308
+ ? path.resolve(process.env.ARENA_MINECRAFT_SOURCE_PATH)
309
+ : path.resolve("../arena-minecraft/src");
310
+ args.push("-v", `${sourcePath}:/usr/src/app/src`);
311
+ // Override the entrypoint to run via ts-node so we execute from source.
312
+ args.push("--entrypoint", "node");
313
+ // Extra logging in debug mode.
314
+ args.push("-e", "LOGGING=true");
315
+ // Use the local dev image (built via "docker compose build" in arena-minecraft).
316
+ args.push("local-arena-minecraft-dev:latest");
317
+ // Node inspector flags — must come after the image name (they are the
318
+ // container CMD, not docker args).
319
+ if (DEBUG_LOCAL_ARENA_WAIT) {
320
+ args.push(`--inspect-wait=0.0.0.0:${ARENA_DEBUG_PORT}`);
321
+ }
322
+ else {
323
+ args.push(`--inspect=0.0.0.0:${ARENA_DEBUG_PORT}`);
324
+ }
325
+ // Run from TypeScript source via ts-node.
326
+ args.push("--loader", "ts-node/esm", "--no-warnings", "/usr/src/app/src/index.ts");
327
+ // Job payload comes last.
328
+ args.push(`--job=${jobPayload}`);
329
+ }
330
+ else {
331
+ // Production path: use the pre-built image.
332
+ args.push(arenaImage);
333
+ args.push(`--job=${jobPayload}`);
334
+ }
286
335
  // Use execFileSync to bypass shell — the JSON payload contains quotes,
287
336
  // parentheses, and other characters that break shell parsing.
288
337
  const result = execFileSync("docker", args, {
@@ -64,9 +64,13 @@ export declare const ChallengeSchema: z.ZodObject<{
64
64
  z: z.ZodNumber;
65
65
  }, z.core.$strip>>;
66
66
  }, z.core.$strip>>>;
67
+ tickRate: z.ZodOptional<z.ZodNumber>;
68
+ startLives: z.ZodOptional<z.ZodNumber>;
69
+ watcherCommands: z.ZodOptional<z.ZodString>;
67
70
  }, z.core.$strip>;
68
71
  description: z.ZodOptional<z.ZodString>;
69
72
  task: z.ZodOptional<z.ZodString>;
73
+ scoreLabel: z.ZodOptional<z.ZodString>;
70
74
  roles: z.ZodRecord<z.ZodString, z.ZodObject<{
71
75
  description: z.ZodOptional<z.ZodString>;
72
76
  specificTask: z.ZodOptional<z.ZodString>;
@@ -107,8 +111,12 @@ export declare const ChallengeConfigSchema: z.ZodObject<{
107
111
  z: z.ZodNumber;
108
112
  }, z.core.$strip>>;
109
113
  }, z.core.$strip>>>;
114
+ tickRate: z.ZodOptional<z.ZodNumber>;
115
+ startLives: z.ZodOptional<z.ZodNumber>;
116
+ watcherCommands: z.ZodOptional<z.ZodString>;
110
117
  }, z.core.$strip>;
111
118
  task: z.ZodOptional<z.ZodString>;
119
+ scoreLabel: z.ZodOptional<z.ZodString>;
112
120
  roles: z.ZodRecord<z.ZodString, z.ZodObject<{
113
121
  description: z.ZodOptional<z.ZodString>;
114
122
  specificTask: z.ZodOptional<z.ZodString>;
@@ -153,9 +161,13 @@ export declare const ChallengesResponseSchema: z.ZodObject<{
153
161
  z: z.ZodNumber;
154
162
  }, z.core.$strip>>;
155
163
  }, z.core.$strip>>>;
164
+ tickRate: z.ZodOptional<z.ZodNumber>;
165
+ startLives: z.ZodOptional<z.ZodNumber>;
166
+ watcherCommands: z.ZodOptional<z.ZodString>;
156
167
  }, z.core.$strip>;
157
168
  description: z.ZodOptional<z.ZodString>;
158
169
  task: z.ZodOptional<z.ZodString>;
170
+ scoreLabel: z.ZodOptional<z.ZodString>;
159
171
  roles: z.ZodRecord<z.ZodString, z.ZodObject<{
160
172
  description: z.ZodOptional<z.ZodString>;
161
173
  specificTask: z.ZodOptional<z.ZodString>;
@@ -209,6 +221,25 @@ export declare const DownloadUrlResponseSchema: z.ZodObject<{
209
221
  downloadUrl: z.ZodString;
210
222
  expiresAt: z.ZodString;
211
223
  }, z.core.$strip>;
224
+ export declare const PromptConfigSchema: z.ZodObject<{
225
+ model: z.ZodString;
226
+ persona: z.ZodOptional<z.ZodString>;
227
+ respondWithCode: z.ZodOptional<z.ZodBoolean>;
228
+ skinUrl: z.ZodOptional<z.ZodString>;
229
+ }, z.core.$strip>;
230
+ export declare const SdkV0ConfigSchema: z.ZodObject<{
231
+ url: z.ZodString;
232
+ skinUrl: z.ZodOptional<z.ZodString>;
233
+ }, z.core.$strip>;
234
+ export declare const AgentConfigSchema: z.ZodUnion<readonly [z.ZodObject<{
235
+ model: z.ZodString;
236
+ persona: z.ZodOptional<z.ZodString>;
237
+ respondWithCode: z.ZodOptional<z.ZodBoolean>;
238
+ skinUrl: z.ZodOptional<z.ZodString>;
239
+ }, z.core.$strip>, z.ZodObject<{
240
+ url: z.ZodString;
241
+ skinUrl: z.ZodOptional<z.ZodString>;
242
+ }, z.core.$strip>, z.ZodObject<{}, z.core.$strip>]>;
212
243
  export declare const AgentSchema: z.ZodObject<{
213
244
  username: z.ZodOptional<z.ZodString>;
214
245
  name: z.ZodOptional<z.ZodString>;
@@ -223,6 +254,21 @@ export declare const AgentSchema: z.ZodObject<{
223
254
  deletionTime: z.ZodOptional<z.ZodString>;
224
255
  creatorId: z.ZodOptional<z.ZodString>;
225
256
  originalCreatorId: z.ZodOptional<z.ZodString>;
257
+ agentType: z.ZodOptional<z.ZodEnum<{
258
+ prompt: "prompt";
259
+ experimental: "experimental";
260
+ sdk_v0: "sdk_v0";
261
+ kradleverse_human: "kradleverse_human";
262
+ }>>;
263
+ agentConfig: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
264
+ model: z.ZodString;
265
+ persona: z.ZodOptional<z.ZodString>;
266
+ respondWithCode: z.ZodOptional<z.ZodBoolean>;
267
+ skinUrl: z.ZodOptional<z.ZodString>;
268
+ }, z.core.$strip>, z.ZodObject<{
269
+ url: z.ZodString;
270
+ skinUrl: z.ZodOptional<z.ZodString>;
271
+ }, z.core.$strip>, z.ZodObject<{}, z.core.$strip>]>>;
226
272
  }, z.core.$strip>;
227
273
  export declare const AgentsResponseSchema: z.ZodObject<{
228
274
  agents: z.ZodArray<z.ZodObject<{
@@ -239,6 +285,21 @@ export declare const AgentsResponseSchema: z.ZodObject<{
239
285
  deletionTime: z.ZodOptional<z.ZodString>;
240
286
  creatorId: z.ZodOptional<z.ZodString>;
241
287
  originalCreatorId: z.ZodOptional<z.ZodString>;
288
+ agentType: z.ZodOptional<z.ZodEnum<{
289
+ prompt: "prompt";
290
+ experimental: "experimental";
291
+ sdk_v0: "sdk_v0";
292
+ kradleverse_human: "kradleverse_human";
293
+ }>>;
294
+ agentConfig: z.ZodOptional<z.ZodUnion<readonly [z.ZodObject<{
295
+ model: z.ZodString;
296
+ persona: z.ZodOptional<z.ZodString>;
297
+ respondWithCode: z.ZodOptional<z.ZodBoolean>;
298
+ skinUrl: z.ZodOptional<z.ZodString>;
299
+ }, z.core.$strip>, z.ZodObject<{
300
+ url: z.ZodString;
301
+ skinUrl: z.ZodOptional<z.ZodString>;
302
+ }, z.core.$strip>, z.ZodObject<{}, z.core.$strip>]>>;
242
303
  }, z.core.$strip>>;
243
304
  nextPageToken: z.ZodOptional<z.ZodString>;
244
305
  }, z.core.$strip>;
@@ -303,7 +364,6 @@ export declare const RunResultResponseSchema: z.ZodObject<{
303
364
  score: z.ZodOptional<z.ZodNumber>;
304
365
  timeToSuccess: z.ZodOptional<z.ZodNumber>;
305
366
  }, z.core.$strip>>>;
306
- summary: z.ZodOptional<z.ZodString>;
307
367
  }, z.core.$strip>;
308
368
  export declare const WorldSchema: z.ZodObject<{
309
369
  id: z.ZodString;
@@ -31,9 +31,13 @@ export const ChallengeSchema = z.object({
31
31
  datapack: z.boolean(),
32
32
  gameMode: z.enum(["survival", "creative", "adventure", "spectator"]),
33
33
  locations: ChallengeLocationsSchema.optional(),
34
+ tickRate: z.number().min(5).max(25).optional(),
35
+ startLives: z.number().optional(),
36
+ watcherCommands: z.string().optional(),
34
37
  }),
35
38
  description: z.string().optional(),
36
39
  task: z.string().optional(),
40
+ scoreLabel: z.string().optional(),
37
41
  roles: z.record(z.string(), z.object({
38
42
  description: z.string().optional(),
39
43
  specificTask: z.string().optional(),
@@ -90,6 +94,17 @@ export const DownloadUrlResponseSchema = z.object({
90
94
  downloadUrl: z.string(),
91
95
  expiresAt: z.string(),
92
96
  });
97
+ export const PromptConfigSchema = z.object({
98
+ model: z.string(),
99
+ persona: z.string().optional(),
100
+ respondWithCode: z.boolean().optional(),
101
+ skinUrl: z.string().optional(),
102
+ });
103
+ export const SdkV0ConfigSchema = z.object({
104
+ url: z.string(),
105
+ skinUrl: z.string().optional(),
106
+ });
107
+ export const AgentConfigSchema = z.union([PromptConfigSchema, SdkV0ConfigSchema, z.object({})]);
93
108
  export const AgentSchema = z.object({
94
109
  username: z.string().optional(),
95
110
  name: z.string().optional(),
@@ -100,6 +115,8 @@ export const AgentSchema = z.object({
100
115
  deletionTime: z.string().optional(),
101
116
  creatorId: z.string().optional(),
102
117
  originalCreatorId: z.string().optional(),
118
+ agentType: z.enum(["prompt", "experimental", "sdk_v0", "kradleverse_human"]).optional(),
119
+ agentConfig: AgentConfigSchema.optional(),
103
120
  });
104
121
  export const AgentsResponseSchema = z.object({
105
122
  agents: z.array(AgentSchema),
@@ -146,7 +163,6 @@ export const RunResultResponseSchema = z.object({
146
163
  endTime: z.string().optional(),
147
164
  aggregatedResults: RunAggregatedResultsSchema.optional(),
148
165
  participantResults: z.record(z.string(), RunParticipantResultSchema).optional(),
149
- summary: z.string().optional(),
150
166
  });
151
167
  export const WorldSchema = z.object({
152
168
  id: z.string(),
@@ -42,39 +42,37 @@
42
42
  "init.js"
43
43
  ]
44
44
  },
45
- "update": {
45
+ "agent:list": {
46
46
  "aliases": [],
47
47
  "args": {},
48
- "description": "Update project template files (AGENTS.md, CLAUDE.md) to the latest version from the CLI",
48
+ "description": "List all agents",
49
49
  "examples": [
50
- "<%= config.bin %> <%= command.id %>",
51
- "<%= config.bin %> <%= command.id %> --dry-run",
52
- "<%= config.bin %> <%= command.id %> --yes"
50
+ "<%= config.bin %> <%= command.id %>"
53
51
  ],
54
52
  "flags": {
55
- "dry-run": {
56
- "description": "Preview changes without applying them",
57
- "name": "dry-run",
58
- "allowNo": false,
59
- "type": "boolean"
60
- },
61
- "yes": {
62
- "char": "y",
63
- "description": "Skip confirmation prompts",
64
- "name": "yes",
65
- "allowNo": false,
66
- "type": "boolean"
53
+ "api-key": {
54
+ "description": "Kradle API key",
55
+ "env": "KRADLE_API_KEY",
56
+ "name": "api-key",
57
+ "required": true,
58
+ "hasDynamicHelp": false,
59
+ "multiple": false,
60
+ "type": "option"
67
61
  },
68
- "add-missing": {
69
- "description": "Add files that exist in template but not locally",
70
- "name": "add-missing",
71
- "allowNo": false,
72
- "type": "boolean"
62
+ "api-url": {
63
+ "description": "Kradle Web API URL",
64
+ "env": "KRADLE_API_URL",
65
+ "name": "api-url",
66
+ "required": true,
67
+ "default": "https://api.kradle.ai/v0",
68
+ "hasDynamicHelp": false,
69
+ "multiple": false,
70
+ "type": "option"
73
71
  }
74
72
  },
75
73
  "hasDynamicHelp": false,
76
74
  "hiddenAliases": [],
77
- "id": "update",
75
+ "id": "agent:list",
78
76
  "pluginAlias": "kradle",
79
77
  "pluginName": "kradle",
80
78
  "pluginType": "core",
@@ -84,7 +82,8 @@
84
82
  "relativePath": [
85
83
  "dist",
86
84
  "commands",
87
- "update.js"
85
+ "agent",
86
+ "list.js"
88
87
  ]
89
88
  },
90
89
  "ai-docs:challenges-sdk": {
@@ -143,6 +142,52 @@
143
142
  "cli.js"
144
143
  ]
145
144
  },
145
+ "ai-docs:sync": {
146
+ "aliases": [],
147
+ "args": {},
148
+ "description": "Sync project AI docs (AGENTS.md, CLAUDE.md) to the latest version from the CLI",
149
+ "examples": [
150
+ "<%= config.bin %> <%= command.id %>",
151
+ "<%= config.bin %> <%= command.id %> --dry-run",
152
+ "<%= config.bin %> <%= command.id %> --yes"
153
+ ],
154
+ "flags": {
155
+ "dry-run": {
156
+ "description": "Preview changes without applying them",
157
+ "name": "dry-run",
158
+ "allowNo": false,
159
+ "type": "boolean"
160
+ },
161
+ "yes": {
162
+ "char": "y",
163
+ "description": "Skip confirmation prompts",
164
+ "name": "yes",
165
+ "allowNo": false,
166
+ "type": "boolean"
167
+ },
168
+ "add-missing": {
169
+ "description": "Add files that exist in template but not locally",
170
+ "name": "add-missing",
171
+ "allowNo": false,
172
+ "type": "boolean"
173
+ }
174
+ },
175
+ "hasDynamicHelp": false,
176
+ "hiddenAliases": [],
177
+ "id": "ai-docs:sync",
178
+ "pluginAlias": "kradle",
179
+ "pluginName": "kradle",
180
+ "pluginType": "core",
181
+ "strict": true,
182
+ "enableJsonFlag": false,
183
+ "isESM": true,
184
+ "relativePath": [
185
+ "dist",
186
+ "commands",
187
+ "ai-docs",
188
+ "sync.js"
189
+ ]
190
+ },
146
191
  "ai-docs:workflow": {
147
192
  "aliases": [],
148
193
  "args": {},
@@ -582,12 +627,6 @@
582
627
  "allowNo": false,
583
628
  "type": "boolean"
584
629
  },
585
- "no-summary": {
586
- "description": "Don't wait for the AI-generated summary",
587
- "name": "no-summary",
588
- "allowNo": false,
589
- "type": "boolean"
590
- },
591
630
  "screenshots": {
592
631
  "description": "Open the run URL with screenshots mode enabled",
593
632
  "name": "screenshots",
@@ -1390,50 +1429,6 @@
1390
1429
  "push.js"
1391
1430
  ]
1392
1431
  },
1393
- "agent:list": {
1394
- "aliases": [],
1395
- "args": {},
1396
- "description": "List all agents",
1397
- "examples": [
1398
- "<%= config.bin %> <%= command.id %>"
1399
- ],
1400
- "flags": {
1401
- "api-key": {
1402
- "description": "Kradle API key",
1403
- "env": "KRADLE_API_KEY",
1404
- "name": "api-key",
1405
- "required": true,
1406
- "hasDynamicHelp": false,
1407
- "multiple": false,
1408
- "type": "option"
1409
- },
1410
- "api-url": {
1411
- "description": "Kradle Web API URL",
1412
- "env": "KRADLE_API_URL",
1413
- "name": "api-url",
1414
- "required": true,
1415
- "default": "https://api.kradle.ai/v0",
1416
- "hasDynamicHelp": false,
1417
- "multiple": false,
1418
- "type": "option"
1419
- }
1420
- },
1421
- "hasDynamicHelp": false,
1422
- "hiddenAliases": [],
1423
- "id": "agent:list",
1424
- "pluginAlias": "kradle",
1425
- "pluginName": "kradle",
1426
- "pluginType": "core",
1427
- "strict": true,
1428
- "enableJsonFlag": false,
1429
- "isESM": true,
1430
- "relativePath": [
1431
- "dist",
1432
- "commands",
1433
- "agent",
1434
- "list.js"
1435
- ]
1436
- },
1437
1432
  "challenge:runs:get": {
1438
1433
  "aliases": [],
1439
1434
  "args": {
@@ -1446,8 +1441,7 @@
1446
1441
  "description": "Get details and logs for a specific run",
1447
1442
  "examples": [
1448
1443
  "<%= config.bin %> <%= command.id %> abc123",
1449
- "<%= config.bin %> <%= command.id %> abc123 --no-logs",
1450
- "<%= config.bin %> <%= command.id %> abc123 --no-summary"
1444
+ "<%= config.bin %> <%= command.id %> abc123 --no-logs"
1451
1445
  ],
1452
1446
  "flags": {
1453
1447
  "no-logs": {
@@ -1456,12 +1450,6 @@
1456
1450
  "allowNo": false,
1457
1451
  "type": "boolean"
1458
1452
  },
1459
- "no-summary": {
1460
- "description": "Skip displaying the AI-generated summary",
1461
- "name": "no-summary",
1462
- "allowNo": false,
1463
- "type": "boolean"
1464
- },
1465
1453
  "api-key": {
1466
1454
  "description": "Kradle API key",
1467
1455
  "env": "KRADLE_API_KEY",
@@ -1555,5 +1543,5 @@
1555
1543
  ]
1556
1544
  }
1557
1545
  },
1558
- "version": "0.6.8"
1546
+ "version": "0.6.10"
1559
1547
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kradle",
3
- "version": "0.6.8",
3
+ "version": "0.6.10",
4
4
  "description": "Kradle's CLI. Manage challenges, experiments, agents and more!",
5
5
  "keywords": [
6
6
  "cli"
@@ -421,7 +421,6 @@ When no agents are specified, the command enters interactive mode:
421
421
  | `--arena-image` | | Override arena-minecraft Docker image URL (env: `KRADLE_ARENA_IMAGE`) |
422
422
  | `--no-open` | | Don't open the run URL in the browser |
423
423
  | `--no-wait` | | Don't wait for completion (fire and forget) |
424
- | `--no-summary` | | Don't wait for the AI-generated summary |
425
424
 
426
425
  **Behavior (remote, default):**
427
426
  1. Parses inline agents or enters interactive mode for agent selection
@@ -545,7 +544,6 @@ Gets details and optionally logs for a specific run.
545
544
  ```bash
546
545
  kradle challenge runs get <run-id>
547
546
  kradle challenge runs get <run-id> --no-logs
548
- kradle challenge runs get <run-id> --no-summary
549
547
  ```
550
548
 
551
549
  **Arguments:**
@@ -557,7 +555,6 @@ kradle challenge runs get <run-id> --no-summary
557
555
  | Flag | Description | Default |
558
556
  |------|-------------|---------|
559
557
  | `--no-logs` | Skip fetching and displaying logs | false |
560
- | `--no-summary` | Skip displaying the AI-generated summary | false |
561
558
 
562
559
  **Output sections:**
563
560
 
@@ -570,9 +567,7 @@ kradle challenge runs get <run-id> --no-summary
570
567
  3. **Participant Results** - Per-participant breakdown (table format):
571
568
  - Participant ID, Agent name, Winner status, Score, Time to Success
572
569
 
573
- 4. **Summary** - AI-generated summary of the run (unless `--no-summary` is used)
574
-
575
- 5. **Logs** - Log entries from the run (unless `--no-logs` is used):
570
+ 4. **Logs** - Log entries from the run (unless `--no-logs` is used):
576
571
  - Timestamp, Level (colored), Participant ID, Message
577
572
  - JSON messages are automatically parsed and formatted
578
573
 
@@ -590,9 +585,6 @@ kradle challenge runs get abc123def456
590
585
  # Get details without logs (faster)
591
586
  kradle challenge runs get abc123def456 --no-logs
592
587
 
593
- # Get details without AI summary
594
- kradle challenge runs get abc123def456 --no-summary
595
-
596
588
  # Get details for a run (full ID)
597
589
  kradle challenge runs get 12345678-1234-1234-1234-123456789012
598
590
  ```
@@ -1145,7 +1137,9 @@ export const config = {
1145
1137
 
1146
1138
  ### `kradle agent list`
1147
1139
 
1148
- Lists all agents registered in the Kradle system.
1140
+ Lists all agents registered in the Kradle system with their model and pricing information.
1141
+
1142
+ Pricing (input/output cost per million tokens) is fetched from the OpenRouter API. Models without pricing data show `-`.
1149
1143
 
1150
1144
  **Usage:**
1151
1145
  ```bash
@@ -1154,10 +1148,13 @@ kradle agent list
1154
1148
 
1155
1149
  **Output format:**
1156
1150
  ```
1157
- Agents:
1158
- - team-kradle:baseline-agent
1159
- - team-kradle:advanced-agent
1160
- - my-username:custom-agent
1151
+ Found 451 agents:
1152
+
1153
+ Agent Model Input/MTok Output/MTok
1154
+ ───────────────────────────── ─────────────────────────── ────────── ───────────
1155
+ team-kradle:claude-sonnet-4-6 anthropic/claude-sonnet-4.6 $3.00 $15.00
1156
+ team-kradle:gemini-2-5-flash google/gemini-2.5-flash $0.30 $2.50
1157
+ team-kradle:gpt-4o openai/gpt-4o $2.50 $10.00
1161
1158
  ```
1162
1159
 
1163
1160
  **Example:**
@@ -1234,6 +1231,46 @@ kradle ai-docs challenges-sdk > api-reference.md
1234
1231
 
1235
1232
  ---
1236
1233
 
1234
+ ### `kradle ai-docs sync`
1235
+
1236
+ Syncs project AI docs (`AGENTS.md`, `CLAUDE.md`) to the latest version bundled with the CLI.
1237
+
1238
+ **Usage:**
1239
+ ```bash
1240
+ kradle ai-docs sync
1241
+ kradle ai-docs sync --dry-run
1242
+ kradle ai-docs sync --yes
1243
+ kradle ai-docs sync --add-missing
1244
+ ```
1245
+
1246
+ **Flags:**
1247
+ | Flag | Description | Default |
1248
+ |------|-------------|---------|
1249
+ | `--dry-run` | Preview changes without applying them | `false` |
1250
+ | `--yes` / `-y` | Skip confirmation prompts | `false` |
1251
+ | `--add-missing` | Add files that exist in template but not locally | `false` |
1252
+
1253
+ **Behavior:**
1254
+ - Compares local `AGENTS.md` and `CLAUDE.md` against the templates in the CLI's `static/project_template/` directory
1255
+ - Shows status for each file: identical, different, or missing locally
1256
+ - Displays line count diff for changed files
1257
+ - Prompts for confirmation before overwriting (unless `--yes`)
1258
+ - Missing files are only created when `--add-missing` is specified
1259
+
1260
+ **Examples:**
1261
+ ```bash
1262
+ # Check what would change
1263
+ kradle ai-docs sync --dry-run
1264
+
1265
+ # Update all changed files without prompting
1266
+ kradle ai-docs sync --yes
1267
+
1268
+ # Update changed files and create any missing ones
1269
+ kradle ai-docs sync --yes --add-missing
1270
+ ```
1271
+
1272
+ ---
1273
+
1237
1274
  ## Common Workflows
1238
1275
 
1239
1276
  ### Creating and Testing a New Challenge
@@ -1336,6 +1373,7 @@ kradle challenge build --all --public
1336
1373
  | `kradle world pull [slug]` | Pull world from cloud |
1337
1374
  | `kradle world list` | List all worlds |
1338
1375
  | `kradle world delete <slug>` | Delete world |
1339
- | `kradle agent list` | List available agents |
1376
+ | `kradle agent list` | List agents with model pricing |
1340
1377
  | `kradle ai-docs cli` | Output CLI reference for LLMs |
1341
1378
  | `kradle ai-docs challenges-sdk [version]` | Output SDK reference for LLMs |
1379
+ | `kradle ai-docs sync` | Sync project AI docs to latest CLI version |