kradle 0.4.1 → 0.4.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 +30 -2
- package/dist/commands/challenge/run.d.ts +5 -1
- package/dist/commands/challenge/run.js +96 -11
- package/dist/commands/challenge/runs/get.d.ts +14 -0
- package/dist/commands/challenge/runs/get.js +115 -0
- package/dist/commands/challenge/runs/list.d.ts +11 -0
- package/dist/commands/challenge/runs/list.js +42 -0
- package/dist/lib/api-client.d.ts +12 -2
- package/dist/lib/api-client.js +29 -4
- package/dist/lib/experiment/runner.js +2 -1
- package/dist/lib/experiment/types.d.ts +10 -10
- package/dist/lib/schemas.d.ts +70 -0
- package/dist/lib/schemas.js +22 -0
- package/dist/lib/utils.d.ts +18 -0
- package/dist/lib/utils.js +52 -0
- package/oclif.manifest.json +137 -11
- package/package.json +1 -1
- package/static/ai_docs/LLM_CLI_REFERENCE.md +130 -7
package/README.md
CHANGED
|
@@ -138,14 +138,41 @@ Uses file watching with debouncing (300ms) and hash comparison to minimize unnec
|
|
|
138
138
|
|
|
139
139
|
### Run Challenge
|
|
140
140
|
|
|
141
|
-
Run a challenge
|
|
141
|
+
Run a challenge and wait for completion:
|
|
142
142
|
|
|
143
143
|
```bash
|
|
144
144
|
kradle challenge run <challenge-name>
|
|
145
|
-
kradle challenge run <challenge-name> --studio
|
|
145
|
+
kradle challenge run <challenge-name> --studio # Run in local studio environment
|
|
146
146
|
kradle challenge run <team-name>:<challenge-name> # Run a public challenge from another team
|
|
147
|
+
kradle challenge run <challenge-name> --no-open # Don't open browser
|
|
148
|
+
kradle challenge run <challenge-name> --no-wait # Fire and forget (don't wait for completion)
|
|
147
149
|
```
|
|
148
150
|
|
|
151
|
+
By default, the command opens the run URL in your browser and polls until the run completes, then displays the outcome.
|
|
152
|
+
|
|
153
|
+
### List Runs
|
|
154
|
+
|
|
155
|
+
List your recent runs:
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
kradle challenge runs list # List 10 most recent runs
|
|
159
|
+
kradle challenge runs list --limit 20 # List 20 most recent runs
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### Get Run Details
|
|
163
|
+
|
|
164
|
+
Get details and logs for a specific run:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
kradle challenge runs get <run-id>
|
|
168
|
+
kradle challenge runs get <run-id> --no-logs # Skip fetching logs
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
This displays:
|
|
172
|
+
- Run metadata (status, duration, end state)
|
|
173
|
+
- Participant results (agent, winner status, score)
|
|
174
|
+
- Log entries with timestamps and levels (unless `--no-logs` is used)
|
|
175
|
+
|
|
149
176
|
## Experiment Commands
|
|
150
177
|
|
|
151
178
|
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.
|
|
@@ -424,6 +451,7 @@ kradle-cli/
|
|
|
424
451
|
│ │ ├── agent/ # Agent commands
|
|
425
452
|
│ │ ├── ai-docs/ # AI documentation commands
|
|
426
453
|
│ │ ├── challenge/ # Challenge management commands
|
|
454
|
+
│ │ │ └── runs/ # Run listing and logs commands
|
|
427
455
|
│ │ ├── experiment/ # Experiment commands
|
|
428
456
|
│ │ └── world/ # World management commands
|
|
429
457
|
│ └── lib/ # Core libraries
|
|
@@ -12,7 +12,11 @@ export default class Run extends Command {
|
|
|
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
14
|
studio: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
-
open: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
15
|
+
"no-open": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
|
+
"no-wait": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
17
|
+
"no-summary": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
16
18
|
};
|
|
19
|
+
private pollForCompletion;
|
|
20
|
+
private displayRunResult;
|
|
17
21
|
run(): Promise<void>;
|
|
18
22
|
}
|
|
@@ -3,13 +3,18 @@ import pc from "picocolors";
|
|
|
3
3
|
import { ApiClient } from "../../lib/api-client.js";
|
|
4
4
|
import { getChallengeSlugArgument } from "../../lib/arguments.js";
|
|
5
5
|
import { getConfigFlags } from "../../lib/flags.js";
|
|
6
|
-
import { loadTemplateRun, openInBrowser } from "../../lib/utils.js";
|
|
6
|
+
import { formatDuration, getRunStatusDisplay, loadTemplateRun, openInBrowser } from "../../lib/utils.js";
|
|
7
|
+
const POLL_INTERVAL_MS = 2000;
|
|
8
|
+
const MAX_POLL_TIME_MS = 30 * 60 * 1000; // 30 minutes
|
|
9
|
+
const TERMINAL_STATUSES = ["finished", "game_over", "error", "completed", "cancelled", "timeout", "failed"];
|
|
7
10
|
export default class Run extends Command {
|
|
8
11
|
static description = "Run a challenge";
|
|
9
12
|
static examples = [
|
|
10
13
|
"<%= config.bin %> <%= command.id %> my-challenge",
|
|
11
14
|
"<%= config.bin %> <%= command.id %> my-challenge --studio",
|
|
12
15
|
"<%= config.bin %> <%= command.id %> team-name:my-challenge",
|
|
16
|
+
"<%= config.bin %> <%= command.id %> my-challenge --no-open",
|
|
17
|
+
"<%= config.bin %> <%= command.id %> my-challenge --no-wait",
|
|
13
18
|
];
|
|
14
19
|
static args = {
|
|
15
20
|
challengeSlug: getChallengeSlugArgument({
|
|
@@ -19,9 +24,83 @@ export default class Run extends Command {
|
|
|
19
24
|
};
|
|
20
25
|
static flags = {
|
|
21
26
|
studio: Flags.boolean({ char: "s", description: "Run in studio environment", default: false }),
|
|
22
|
-
open: Flags.boolean({
|
|
27
|
+
"no-open": Flags.boolean({
|
|
28
|
+
description: "Don't open the run URL in the browser",
|
|
29
|
+
default: false,
|
|
30
|
+
}),
|
|
31
|
+
"no-wait": Flags.boolean({
|
|
32
|
+
description: "Don't wait for the run to complete (fire and forget)",
|
|
33
|
+
default: false,
|
|
34
|
+
}),
|
|
35
|
+
"no-summary": Flags.boolean({
|
|
36
|
+
description: "Don't wait for the AI-generated summary",
|
|
37
|
+
default: false,
|
|
38
|
+
}),
|
|
23
39
|
...getConfigFlags("api-key", "api-url", "web-url", "studio-url", "studio-api-url"),
|
|
24
40
|
};
|
|
41
|
+
async pollForCompletion(api, runId, waitForSummary) {
|
|
42
|
+
let lastStatus = "";
|
|
43
|
+
let reachedTerminal = false;
|
|
44
|
+
let waitingForSummary = false;
|
|
45
|
+
const startTime = Date.now();
|
|
46
|
+
while (true) {
|
|
47
|
+
// Check for timeout
|
|
48
|
+
const elapsed = Date.now() - startTime;
|
|
49
|
+
if (elapsed > MAX_POLL_TIME_MS) {
|
|
50
|
+
this.log(pc.yellow("\nTimed out waiting for run completion"));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const result = await api.getRunResult(runId);
|
|
54
|
+
const currentStatus = result.status;
|
|
55
|
+
// Show status changes
|
|
56
|
+
if (currentStatus !== lastStatus) {
|
|
57
|
+
const elapsedSecs = (elapsed / 1000).toFixed(0);
|
|
58
|
+
this.log(pc.dim(`[${elapsedSecs}s] Status: ${getRunStatusDisplay(currentStatus)}`));
|
|
59
|
+
lastStatus = currentStatus;
|
|
60
|
+
}
|
|
61
|
+
// Check for terminal state
|
|
62
|
+
if (TERMINAL_STATUSES.includes(currentStatus)) {
|
|
63
|
+
if (!reachedTerminal) {
|
|
64
|
+
reachedTerminal = true;
|
|
65
|
+
}
|
|
66
|
+
// If we need to wait for summary and it's not available yet, keep polling
|
|
67
|
+
if (waitForSummary && !result.summary) {
|
|
68
|
+
if (!waitingForSummary) {
|
|
69
|
+
waitingForSummary = true;
|
|
70
|
+
this.log(pc.dim(`Waiting for summary...`));
|
|
71
|
+
}
|
|
72
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
this.log("");
|
|
76
|
+
this.displayRunResult(result);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
displayRunResult(result) {
|
|
83
|
+
this.log(pc.bold("=== Run Complete ===\n"));
|
|
84
|
+
this.log(`${pc.dim("Status:")} ${getRunStatusDisplay(result.status)}`);
|
|
85
|
+
this.log(`${pc.dim("End State:")} ${result.endState || "-"}`);
|
|
86
|
+
this.log(`${pc.dim("Duration:")} ${formatDuration(result.totalTime)}`);
|
|
87
|
+
if (result.aggregatedResults) {
|
|
88
|
+
const agg = result.aggregatedResults;
|
|
89
|
+
this.log(`${pc.dim("Results:")} ${pc.green(String(agg.successfulParticipantCount))} successful / ${agg.participantCount} participants`);
|
|
90
|
+
}
|
|
91
|
+
if (result.participantResults && Object.keys(result.participantResults).length > 0) {
|
|
92
|
+
this.log(pc.dim("\nParticipants:"));
|
|
93
|
+
for (const [participantId, pr] of Object.entries(result.participantResults)) {
|
|
94
|
+
const winnerIcon = pr.winner ? pc.green("\u2713") : pc.red("\u2717");
|
|
95
|
+
const score = pr.score !== undefined ? ` (score: ${pr.score})` : "";
|
|
96
|
+
this.log(` ${winnerIcon} ${participantId}: ${pr.agent}${score}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (result.summary) {
|
|
100
|
+
this.log(pc.bold("\n=== Summary ===\n"));
|
|
101
|
+
this.log(result.summary);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
25
104
|
async run() {
|
|
26
105
|
const { args, flags } = await this.parse(Run);
|
|
27
106
|
const apiUrl = flags.studio ? flags["studio-api-url"] : flags["api-url"];
|
|
@@ -29,23 +108,29 @@ export default class Run extends Command {
|
|
|
29
108
|
const challengeSlug = args.challengeSlug;
|
|
30
109
|
try {
|
|
31
110
|
const { participants } = (await loadTemplateRun());
|
|
32
|
-
|
|
111
|
+
this.log(pc.blue(`>> Running challenge: ${challengeSlug}${flags.studio ? " (studio)" : ""}...`));
|
|
112
|
+
const response = await studioApi.runChallenge({
|
|
33
113
|
challenge: challengeSlug,
|
|
34
114
|
participants,
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
const response = await studioApi.runChallenge(template);
|
|
115
|
+
jobType: "foreground",
|
|
116
|
+
});
|
|
38
117
|
if (response.runIds && response.runIds.length > 0) {
|
|
118
|
+
const runId = response.runIds[0];
|
|
39
119
|
const baseUrl = flags.studio ? flags["studio-url"] : flags["web-url"];
|
|
40
|
-
const runUrl = `${baseUrl}/runs/${
|
|
41
|
-
this.log(pc.green("\n
|
|
120
|
+
const runUrl = `${baseUrl}/runs/${runId}`;
|
|
121
|
+
this.log(pc.green("\n\u2713 Challenge started!"));
|
|
122
|
+
this.log(pc.dim(`Run ID: ${runId}`));
|
|
42
123
|
this.log(pc.dim(`Run URL: ${runUrl}`));
|
|
43
|
-
if (flags
|
|
44
|
-
|
|
124
|
+
if (!flags["no-open"]) {
|
|
125
|
+
openInBrowser(runUrl);
|
|
126
|
+
}
|
|
127
|
+
if (!flags["no-wait"]) {
|
|
128
|
+
this.log(pc.blue("\n>> Waiting for run to complete...\n"));
|
|
129
|
+
await this.pollForCompletion(studioApi, runId, !flags["no-summary"]);
|
|
45
130
|
}
|
|
46
131
|
}
|
|
47
132
|
else {
|
|
48
|
-
this.log(pc.yellow("
|
|
133
|
+
this.log(pc.yellow("\u26a0 Challenge started but no run ID returned"));
|
|
49
134
|
}
|
|
50
135
|
}
|
|
51
136
|
catch (error) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class GetRun extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
runId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
|
|
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
|
+
"no-logs": import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { Args, Command, Flags } from "@oclif/core";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { ApiClient } from "../../../lib/api-client.js";
|
|
4
|
+
import { getConfigFlags } from "../../../lib/flags.js";
|
|
5
|
+
import { formatDuration, formatTime } from "../../../lib/utils.js";
|
|
6
|
+
function getLogLevelColor(level) {
|
|
7
|
+
switch (level.toLowerCase()) {
|
|
8
|
+
case "error":
|
|
9
|
+
return pc.red;
|
|
10
|
+
case "warn":
|
|
11
|
+
case "warning":
|
|
12
|
+
return pc.yellow;
|
|
13
|
+
case "info":
|
|
14
|
+
return pc.blue;
|
|
15
|
+
case "debug":
|
|
16
|
+
return pc.dim;
|
|
17
|
+
default:
|
|
18
|
+
return (text) => text;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function formatLogMessage(log) {
|
|
22
|
+
if ("parsedMessage" in log) {
|
|
23
|
+
return JSON.stringify(log.parsedMessage, null, 2);
|
|
24
|
+
}
|
|
25
|
+
return log.message;
|
|
26
|
+
}
|
|
27
|
+
export default class GetRun extends Command {
|
|
28
|
+
static description = "Get details and logs for a specific run";
|
|
29
|
+
static examples = [
|
|
30
|
+
"<%= config.bin %> <%= command.id %> abc123",
|
|
31
|
+
"<%= config.bin %> <%= command.id %> abc123 --no-logs",
|
|
32
|
+
];
|
|
33
|
+
static args = {
|
|
34
|
+
runId: Args.string({
|
|
35
|
+
description: "Run ID to get details for",
|
|
36
|
+
required: true,
|
|
37
|
+
}),
|
|
38
|
+
};
|
|
39
|
+
static flags = {
|
|
40
|
+
"no-logs": Flags.boolean({
|
|
41
|
+
description: "Skip fetching and displaying logs",
|
|
42
|
+
default: false,
|
|
43
|
+
}),
|
|
44
|
+
...getConfigFlags("api-key", "api-url"),
|
|
45
|
+
};
|
|
46
|
+
async run() {
|
|
47
|
+
const { args, flags } = await this.parse(GetRun);
|
|
48
|
+
const api = new ApiClient(flags["api-url"], flags["api-key"]);
|
|
49
|
+
const showLogs = !flags["no-logs"];
|
|
50
|
+
this.log(pc.blue(`>> Loading run ${args.runId}...`));
|
|
51
|
+
try {
|
|
52
|
+
const [runResult, logs] = await Promise.all([
|
|
53
|
+
api.getRunResult(args.runId),
|
|
54
|
+
showLogs ? api.getRunLogs(args.runId) : Promise.resolve([]),
|
|
55
|
+
]);
|
|
56
|
+
// Run metadata
|
|
57
|
+
this.log(pc.bold("\n=== Run Result ===\n"));
|
|
58
|
+
this.log(`${pc.dim("ID:")} ${runResult.id || args.runId}`);
|
|
59
|
+
this.log(`${pc.dim("Challenge:")} ${runResult.challenge || "-"}`);
|
|
60
|
+
this.log(`${pc.dim("Status:")} ${runResult.status}`);
|
|
61
|
+
this.log(`${pc.dim("End State:")} ${runResult.endState || "-"}`);
|
|
62
|
+
this.log(`${pc.dim("Finished:")} ${runResult.finishedStatus || "-"}`);
|
|
63
|
+
this.log(`${pc.dim("Duration:")} ${formatDuration(runResult.totalTime)}`);
|
|
64
|
+
this.log(`${pc.dim("End Time:")} ${formatTime(runResult.endTime)}`);
|
|
65
|
+
// Aggregated results
|
|
66
|
+
if (runResult.aggregatedResults) {
|
|
67
|
+
this.log(pc.bold("\n=== Aggregated Results ===\n"));
|
|
68
|
+
const agg = runResult.aggregatedResults;
|
|
69
|
+
this.log(`${pc.dim("Participants:")} ${agg.participantCount}`);
|
|
70
|
+
this.log(`${pc.dim("Successful:")} ${agg.successfulParticipantCount}`);
|
|
71
|
+
this.log(`${pc.dim("Unsuccessful:")} ${agg.unsuccessfulParticipantCount}`);
|
|
72
|
+
this.log(`${pc.dim("Total Time:")} ${formatDuration(agg.totalTime)}`);
|
|
73
|
+
}
|
|
74
|
+
// Participant results
|
|
75
|
+
if (runResult.participantResults && Object.keys(runResult.participantResults).length > 0) {
|
|
76
|
+
this.log(pc.bold("\n=== Participant Results ===\n"));
|
|
77
|
+
const headers = ["Participant", "Agent", "Winner", "Score", "Time to Success"];
|
|
78
|
+
const widths = [15, 35, 10, 10, 18];
|
|
79
|
+
this.log(headers.map((h, i) => h.padEnd(widths[i])).join(" "));
|
|
80
|
+
this.log("-".repeat(widths.reduce((a, b) => a + b + 1, 0)));
|
|
81
|
+
for (const [participantId, result] of Object.entries(runResult.participantResults)) {
|
|
82
|
+
const agentPadded = result.agent.padEnd(widths[1]);
|
|
83
|
+
const winnerText = result.winner ? "Yes" : "No";
|
|
84
|
+
const winnerPadded = winnerText.padEnd(widths[2]);
|
|
85
|
+
const winner = result.winner ? pc.green(winnerPadded) : pc.red(winnerPadded);
|
|
86
|
+
const score = result.score !== undefined ? String(result.score) : "-";
|
|
87
|
+
const timeToSuccess = result.timeToSuccess !== undefined ? formatDuration(result.timeToSuccess) : "-";
|
|
88
|
+
this.log(`${participantId.padEnd(widths[0])} ${agentPadded} ${winner} ${score.padEnd(widths[3])} ${timeToSuccess}`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
// Logs
|
|
92
|
+
if (showLogs) {
|
|
93
|
+
if (logs.length > 0) {
|
|
94
|
+
this.log(pc.bold("\n=== Logs ===\n"));
|
|
95
|
+
for (const log of logs) {
|
|
96
|
+
const time = formatTime(log.creationTime);
|
|
97
|
+
const levelColor = getLogLevelColor(log.level);
|
|
98
|
+
const levelText = `[${log.level.toUpperCase()}]`.padEnd(9);
|
|
99
|
+
const level = levelColor(levelText);
|
|
100
|
+
const participantText = `[${log.participantId}]`.padEnd(15);
|
|
101
|
+
const participant = pc.cyan(participantText);
|
|
102
|
+
const message = formatLogMessage(log);
|
|
103
|
+
this.log(`${pc.dim(time)} ${level} ${participant} ${message}`);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
this.log(pc.dim("\nNo logs found."));
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
this.error(pc.red(`Failed to get run: ${error instanceof Error ? error.message : String(error)}`));
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class ListRuns 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
|
+
limit: import("@oclif/core/interfaces").OptionFlag<number, import("@oclif/core/interfaces").CustomOptions>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Command, Flags } from "@oclif/core";
|
|
2
|
+
import pc from "picocolors";
|
|
3
|
+
import { ApiClient } from "../../../lib/api-client.js";
|
|
4
|
+
import { getConfigFlags } from "../../../lib/flags.js";
|
|
5
|
+
import { formatDuration, formatTime, getRunStatusDisplay } from "../../../lib/utils.js";
|
|
6
|
+
export default class ListRuns extends Command {
|
|
7
|
+
static description = "List recent runs";
|
|
8
|
+
static examples = ["<%= config.bin %> <%= command.id %>", "<%= config.bin %> <%= command.id %> --limit 20"];
|
|
9
|
+
static flags = {
|
|
10
|
+
limit: Flags.integer({
|
|
11
|
+
char: "n",
|
|
12
|
+
description: "Number of runs to display",
|
|
13
|
+
default: 10,
|
|
14
|
+
}),
|
|
15
|
+
...getConfigFlags("api-key", "api-url"),
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
const { flags } = await this.parse(ListRuns);
|
|
19
|
+
const api = new ApiClient(flags["api-url"], flags["api-key"]);
|
|
20
|
+
this.log(pc.blue(">> Loading runs..."));
|
|
21
|
+
const { runs } = await api.listRuns(flags.limit);
|
|
22
|
+
if (runs.length === 0) {
|
|
23
|
+
this.log(pc.yellow("\nNo runs found."));
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
this.log(pc.bold("\nRuns:\n"));
|
|
27
|
+
const headers = ["ID", "Challenge", "Status", "End State", "Duration", "Created"];
|
|
28
|
+
const widths = [38, 30, 15, 15, 12, 20];
|
|
29
|
+
this.log(headers.map((h, i) => h.padEnd(widths[i])).join(" "));
|
|
30
|
+
this.log("-".repeat(widths.reduce((a, b) => a + b + 1, 0)));
|
|
31
|
+
for (const run of runs) {
|
|
32
|
+
const challenge = run.challenge.length > 28 ? `${run.challenge.slice(0, 25)}...` : run.challenge;
|
|
33
|
+
const statusColored = getRunStatusDisplay(run.status);
|
|
34
|
+
const statusPadding = " ".repeat(Math.max(0, widths[2] - run.status.length));
|
|
35
|
+
const endState = run.endState || "-";
|
|
36
|
+
const duration = formatDuration(run.totalTime);
|
|
37
|
+
const created = formatTime(run.creationTime);
|
|
38
|
+
this.log(`${run.id.padEnd(widths[0])} ${challenge.padEnd(widths[1])} ${statusColored}${statusPadding} ${endState.padEnd(widths[3])} ${duration.padEnd(widths[4])} ${created}`);
|
|
39
|
+
}
|
|
40
|
+
this.log(pc.dim(`\nShowing ${runs.length} runs`));
|
|
41
|
+
}
|
|
42
|
+
}
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type z from "zod";
|
|
2
|
-
import { type AgentSchemaType, type ChallengeConfigSchemaType, type ChallengeSchemaType, type DashboardUrlResponse, type DownloadUrlResponse, HumanSchema, type ParsedLogEntry, type RecordingMetadata, type RunResultResponse, type RunStatusSchemaType, type WorldConfigSchemaType, type WorldSchemaType } from "./schemas.js";
|
|
2
|
+
import { type AgentSchemaType, type ChallengeConfigSchemaType, type ChallengeSchemaType, type DashboardUrlResponse, type DownloadUrlResponse, HumanSchema, type ParsedLogEntry, type RecordingMetadata, type Run, type RunResultResponse, type RunStatusSchemaType, type WorldConfigSchemaType, type WorldSchemaType } from "./schemas.js";
|
|
3
3
|
export declare class ApiClient {
|
|
4
4
|
private apiUrl;
|
|
5
5
|
private kradleApiKey;
|
|
@@ -75,7 +75,8 @@ export declare class ApiClient {
|
|
|
75
75
|
runChallenge(runData: {
|
|
76
76
|
challenge: string;
|
|
77
77
|
participants: unknown[];
|
|
78
|
-
|
|
78
|
+
jobType: "background" | "foreground";
|
|
79
|
+
}): Promise<{
|
|
79
80
|
runIds?: string[] | undefined;
|
|
80
81
|
participants?: Record<string, {
|
|
81
82
|
agent: string;
|
|
@@ -125,6 +126,15 @@ export declare class ApiClient {
|
|
|
125
126
|
* @returns Run result with status, end_state, and participant results.
|
|
126
127
|
*/
|
|
127
128
|
getRunResult(runId: string): Promise<RunResultResponse>;
|
|
129
|
+
/**
|
|
130
|
+
* List runs with pagination.
|
|
131
|
+
* @param limit - Maximum number of runs to return.
|
|
132
|
+
* @returns Object with runs array and optional nextPageToken.
|
|
133
|
+
*/
|
|
134
|
+
listRuns(limit?: number): Promise<{
|
|
135
|
+
runs: Run[];
|
|
136
|
+
nextPageToken?: string;
|
|
137
|
+
}>;
|
|
128
138
|
listWorlds(): Promise<WorldSchemaType[]>;
|
|
129
139
|
listKradleWorlds(): Promise<WorldSchemaType[]>;
|
|
130
140
|
getWorld(slug: string): Promise<WorldSchemaType>;
|
package/dist/lib/api-client.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import fs from "node:fs/promises";
|
|
3
|
-
import { AgentsResponseSchema, ChallengeSchema, ChallengesResponseSchema, DashboardUrlResponseSchema, DownloadUrlResponseSchema, HumanSchema, JobResponseSchema, RecordingsListResponseSchema, RunLogsResponseSchema, RunResultResponseSchema, RunStatusSchema, UploadUrlResponseSchema, WorldSchema, WorldsResponseSchema, } from "./schemas.js";
|
|
3
|
+
import { AgentsResponseSchema, ChallengeSchema, ChallengesResponseSchema, DashboardUrlResponseSchema, DownloadUrlResponseSchema, HumanSchema, JobResponseSchema, ListRunsResponseSchema, RecordingsListResponseSchema, RunLogsResponseSchema, RunResultResponseSchema, RunStatusSchema, UploadUrlResponseSchema, WorldSchema, WorldsResponseSchema, } from "./schemas.js";
|
|
4
4
|
const DEFAULT_PAGE_SIZE = 30;
|
|
5
5
|
const DEFAULT_CHALLENGE_SCHEMA = {
|
|
6
6
|
slug: "",
|
|
@@ -221,11 +221,10 @@ export class ApiClient {
|
|
|
221
221
|
async getChallengeDownloadUrl(slug) {
|
|
222
222
|
return this.get(`challenges/${slug}/datapackDownloadUrl`, {}, DownloadUrlResponseSchema);
|
|
223
223
|
}
|
|
224
|
-
async runChallenge(runData
|
|
224
|
+
async runChallenge(runData) {
|
|
225
225
|
const url = "jobs";
|
|
226
|
-
const payload = isBackground ? { ...runData, jobType: "background" } : runData;
|
|
227
226
|
return this.post(url, {
|
|
228
|
-
body: JSON.stringify(
|
|
227
|
+
body: JSON.stringify(runData),
|
|
229
228
|
}, JobResponseSchema);
|
|
230
229
|
}
|
|
231
230
|
async deleteChallenge(challengeId) {
|
|
@@ -309,6 +308,32 @@ export class ApiClient {
|
|
|
309
308
|
const url = `runs/${runId}`;
|
|
310
309
|
return this.get(url, {}, RunResultResponseSchema);
|
|
311
310
|
}
|
|
311
|
+
/**
|
|
312
|
+
* List runs with pagination.
|
|
313
|
+
* @param limit - Maximum number of runs to return.
|
|
314
|
+
* @returns Object with runs array and optional nextPageToken.
|
|
315
|
+
*/
|
|
316
|
+
async listRuns(limit = 10) {
|
|
317
|
+
const runs = [];
|
|
318
|
+
let currentToken;
|
|
319
|
+
// Use consistent page size for all requests (page tokens are tied to page size)
|
|
320
|
+
const pageSize = DEFAULT_PAGE_SIZE;
|
|
321
|
+
while (runs.length < limit) {
|
|
322
|
+
const params = new URLSearchParams();
|
|
323
|
+
params.set("page_size", String(pageSize));
|
|
324
|
+
if (currentToken) {
|
|
325
|
+
params.set("page_token", currentToken);
|
|
326
|
+
}
|
|
327
|
+
const response = await this.get(`runs?${params}`, {}, ListRunsResponseSchema);
|
|
328
|
+
runs.push(...response.runs);
|
|
329
|
+
if (!response.nextPageToken || response.runs.length === 0) {
|
|
330
|
+
currentToken = undefined;
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
currentToken = response.nextPageToken;
|
|
334
|
+
}
|
|
335
|
+
return { runs: runs.slice(0, limit), nextPageToken: currentToken };
|
|
336
|
+
}
|
|
312
337
|
async listWorlds() {
|
|
313
338
|
return this.listResource("worlds", "worlds", WorldsResponseSchema);
|
|
314
339
|
}
|
|
@@ -161,7 +161,8 @@ export class Runner {
|
|
|
161
161
|
const response = await this.api.runChallenge({
|
|
162
162
|
challenge: state.config.challenge_slug,
|
|
163
163
|
participants: state.config.participants,
|
|
164
|
-
|
|
164
|
+
jobType: "background",
|
|
165
|
+
});
|
|
165
166
|
if (!response.runIds || response.runIds.length === 0) {
|
|
166
167
|
throw new Error("No run ID returned from API");
|
|
167
168
|
}
|
|
@@ -29,17 +29,17 @@ export type RunStatus = "queued" | "initializing" | "watcher_connected" | "parti
|
|
|
29
29
|
export declare const ProgressEntrySchema: z.ZodObject<{
|
|
30
30
|
index: z.ZodNumber;
|
|
31
31
|
status: z.ZodEnum<{
|
|
32
|
+
finished: "finished";
|
|
33
|
+
game_over: "game_over";
|
|
34
|
+
completed: "completed";
|
|
35
|
+
started: "started";
|
|
36
|
+
initializing: "initializing";
|
|
32
37
|
error: "error";
|
|
33
38
|
queued: "queued";
|
|
34
|
-
initializing: "initializing";
|
|
35
39
|
watcher_connected: "watcher_connected";
|
|
36
40
|
participants_connected: "participants_connected";
|
|
37
|
-
started: "started";
|
|
38
41
|
running: "running";
|
|
39
42
|
recovering: "recovering";
|
|
40
|
-
completed: "completed";
|
|
41
|
-
game_over: "game_over";
|
|
42
|
-
finished: "finished";
|
|
43
43
|
}>;
|
|
44
44
|
runId: z.ZodOptional<z.ZodString>;
|
|
45
45
|
participantIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
@@ -52,17 +52,17 @@ export declare const ProgressSchema: z.ZodObject<{
|
|
|
52
52
|
entries: z.ZodArray<z.ZodObject<{
|
|
53
53
|
index: z.ZodNumber;
|
|
54
54
|
status: z.ZodEnum<{
|
|
55
|
+
finished: "finished";
|
|
56
|
+
game_over: "game_over";
|
|
57
|
+
completed: "completed";
|
|
58
|
+
started: "started";
|
|
59
|
+
initializing: "initializing";
|
|
55
60
|
error: "error";
|
|
56
61
|
queued: "queued";
|
|
57
|
-
initializing: "initializing";
|
|
58
62
|
watcher_connected: "watcher_connected";
|
|
59
63
|
participants_connected: "participants_connected";
|
|
60
|
-
started: "started";
|
|
61
64
|
running: "running";
|
|
62
65
|
recovering: "recovering";
|
|
63
|
-
completed: "completed";
|
|
64
|
-
game_over: "game_over";
|
|
65
|
-
finished: "finished";
|
|
66
66
|
}>;
|
|
67
67
|
runId: z.ZodOptional<z.ZodString>;
|
|
68
68
|
participantIds: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
package/dist/lib/schemas.d.ts
CHANGED
|
@@ -240,6 +240,7 @@ export declare const RunResultResponseSchema: z.ZodObject<{
|
|
|
240
240
|
score: z.ZodOptional<z.ZodNumber>;
|
|
241
241
|
timeToSuccess: z.ZodOptional<z.ZodNumber>;
|
|
242
242
|
}, z.core.$strip>>>;
|
|
243
|
+
summary: z.ZodOptional<z.ZodString>;
|
|
243
244
|
}, z.core.$strip>;
|
|
244
245
|
export declare const WorldSchema: z.ZodObject<{
|
|
245
246
|
id: z.ZodString;
|
|
@@ -279,6 +280,73 @@ export declare const WorldsResponseSchema: z.ZodObject<{
|
|
|
279
280
|
export declare const DashboardUrlResponseSchema: z.ZodObject<{
|
|
280
281
|
url: z.ZodString;
|
|
281
282
|
}, z.core.$strip>;
|
|
283
|
+
export declare const RunSchema: z.ZodObject<{
|
|
284
|
+
id: z.ZodString;
|
|
285
|
+
challenge: z.ZodString;
|
|
286
|
+
status: z.ZodString;
|
|
287
|
+
visibility: z.ZodOptional<z.ZodEnum<{
|
|
288
|
+
private: "private";
|
|
289
|
+
public: "public";
|
|
290
|
+
}>>;
|
|
291
|
+
creator: z.ZodOptional<z.ZodString>;
|
|
292
|
+
creationTime: z.ZodOptional<z.ZodString>;
|
|
293
|
+
updateTime: z.ZodOptional<z.ZodString>;
|
|
294
|
+
startTime: z.ZodOptional<z.ZodString>;
|
|
295
|
+
endTime: z.ZodOptional<z.ZodString>;
|
|
296
|
+
totalTime: z.ZodOptional<z.ZodNumber>;
|
|
297
|
+
endState: z.ZodOptional<z.ZodString>;
|
|
298
|
+
finishedStatus: z.ZodOptional<z.ZodString>;
|
|
299
|
+
aggregatedResults: z.ZodOptional<z.ZodObject<{
|
|
300
|
+
participantCount: z.ZodNumber;
|
|
301
|
+
successfulParticipantCount: z.ZodNumber;
|
|
302
|
+
successfulParticipantIds: z.ZodArray<z.ZodString>;
|
|
303
|
+
unsuccessfulParticipantCount: z.ZodNumber;
|
|
304
|
+
unsuccessfulParticipantIds: z.ZodArray<z.ZodString>;
|
|
305
|
+
totalTime: z.ZodNumber;
|
|
306
|
+
}, z.core.$strip>>;
|
|
307
|
+
participantResults: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
308
|
+
agent: z.ZodString;
|
|
309
|
+
winner: z.ZodBoolean;
|
|
310
|
+
score: z.ZodOptional<z.ZodNumber>;
|
|
311
|
+
timeToSuccess: z.ZodOptional<z.ZodNumber>;
|
|
312
|
+
}, z.core.$strip>>>;
|
|
313
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
314
|
+
}, z.core.$strip>;
|
|
315
|
+
export declare const ListRunsResponseSchema: z.ZodObject<{
|
|
316
|
+
runs: z.ZodArray<z.ZodObject<{
|
|
317
|
+
id: z.ZodString;
|
|
318
|
+
challenge: z.ZodString;
|
|
319
|
+
status: z.ZodString;
|
|
320
|
+
visibility: z.ZodOptional<z.ZodEnum<{
|
|
321
|
+
private: "private";
|
|
322
|
+
public: "public";
|
|
323
|
+
}>>;
|
|
324
|
+
creator: z.ZodOptional<z.ZodString>;
|
|
325
|
+
creationTime: z.ZodOptional<z.ZodString>;
|
|
326
|
+
updateTime: z.ZodOptional<z.ZodString>;
|
|
327
|
+
startTime: z.ZodOptional<z.ZodString>;
|
|
328
|
+
endTime: z.ZodOptional<z.ZodString>;
|
|
329
|
+
totalTime: z.ZodOptional<z.ZodNumber>;
|
|
330
|
+
endState: z.ZodOptional<z.ZodString>;
|
|
331
|
+
finishedStatus: z.ZodOptional<z.ZodString>;
|
|
332
|
+
aggregatedResults: z.ZodOptional<z.ZodObject<{
|
|
333
|
+
participantCount: z.ZodNumber;
|
|
334
|
+
successfulParticipantCount: z.ZodNumber;
|
|
335
|
+
successfulParticipantIds: z.ZodArray<z.ZodString>;
|
|
336
|
+
unsuccessfulParticipantCount: z.ZodNumber;
|
|
337
|
+
unsuccessfulParticipantIds: z.ZodArray<z.ZodString>;
|
|
338
|
+
totalTime: z.ZodNumber;
|
|
339
|
+
}, z.core.$strip>>;
|
|
340
|
+
participantResults: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodObject<{
|
|
341
|
+
agent: z.ZodString;
|
|
342
|
+
winner: z.ZodBoolean;
|
|
343
|
+
score: z.ZodOptional<z.ZodNumber>;
|
|
344
|
+
timeToSuccess: z.ZodOptional<z.ZodNumber>;
|
|
345
|
+
}, z.core.$strip>>>;
|
|
346
|
+
tags: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
347
|
+
}, z.core.$strip>>;
|
|
348
|
+
nextPageToken: z.ZodOptional<z.ZodString>;
|
|
349
|
+
}, z.core.$strip>;
|
|
282
350
|
export type ChallengeSchemaType = z.infer<typeof ChallengeSchema>;
|
|
283
351
|
export type ChallengeConfigSchemaType = z.infer<typeof ChallengeConfigSchema>;
|
|
284
352
|
export type ChallengesResponseType = z.infer<typeof ChallengesResponseSchema>;
|
|
@@ -303,3 +371,5 @@ export type WorldSchemaType = z.infer<typeof WorldSchema>;
|
|
|
303
371
|
export type WorldConfigSchemaType = z.infer<typeof WorldConfigSchema>;
|
|
304
372
|
export type WorldsResponseType = z.infer<typeof WorldsResponseSchema>;
|
|
305
373
|
export type DashboardUrlResponse = z.infer<typeof DashboardUrlResponseSchema>;
|
|
374
|
+
export type Run = z.infer<typeof RunSchema>;
|
|
375
|
+
export type ListRunsResponse = z.infer<typeof ListRunsResponseSchema>;
|
package/dist/lib/schemas.js
CHANGED
|
@@ -123,6 +123,7 @@ export const RunResultResponseSchema = z.object({
|
|
|
123
123
|
endTime: z.string().optional(),
|
|
124
124
|
aggregatedResults: RunAggregatedResultsSchema.optional(),
|
|
125
125
|
participantResults: z.record(z.string(), RunParticipantResultSchema).optional(),
|
|
126
|
+
summary: z.string().optional(),
|
|
126
127
|
});
|
|
127
128
|
export const WorldSchema = z.object({
|
|
128
129
|
id: z.string(),
|
|
@@ -147,3 +148,24 @@ export const WorldsResponseSchema = z.object({
|
|
|
147
148
|
export const DashboardUrlResponseSchema = z.object({
|
|
148
149
|
url: z.string(),
|
|
149
150
|
});
|
|
151
|
+
export const RunSchema = z.object({
|
|
152
|
+
id: z.string(),
|
|
153
|
+
challenge: z.string(),
|
|
154
|
+
status: z.string(),
|
|
155
|
+
visibility: z.enum(["private", "public"]).optional(),
|
|
156
|
+
creator: z.string().optional(),
|
|
157
|
+
creationTime: z.string().optional(),
|
|
158
|
+
updateTime: z.string().optional(),
|
|
159
|
+
startTime: z.string().optional(),
|
|
160
|
+
endTime: z.string().optional(),
|
|
161
|
+
totalTime: z.number().optional(),
|
|
162
|
+
endState: z.string().optional(),
|
|
163
|
+
finishedStatus: z.string().optional(),
|
|
164
|
+
aggregatedResults: RunAggregatedResultsSchema.optional(),
|
|
165
|
+
participantResults: z.record(z.string(), RunParticipantResultSchema).optional(),
|
|
166
|
+
tags: z.array(z.string()).optional(),
|
|
167
|
+
});
|
|
168
|
+
export const ListRunsResponseSchema = z.object({
|
|
169
|
+
runs: z.array(RunSchema),
|
|
170
|
+
nextPageToken: z.string().optional(),
|
|
171
|
+
});
|
package/dist/lib/utils.d.ts
CHANGED
|
@@ -94,3 +94,21 @@ export declare function loadTypescriptExport(filePath: string, exportName: strin
|
|
|
94
94
|
* @param url The URL to open.
|
|
95
95
|
*/
|
|
96
96
|
export declare function openInBrowser(url: string): void;
|
|
97
|
+
/**
|
|
98
|
+
* Format a duration in milliseconds to a human-readable string.
|
|
99
|
+
* @param ms Duration in milliseconds
|
|
100
|
+
* @returns Formatted string like "45.3s" or "2m 30s"
|
|
101
|
+
*/
|
|
102
|
+
export declare function formatDuration(ms: number | undefined): string;
|
|
103
|
+
/**
|
|
104
|
+
* Format an ISO timestamp to a locale string.
|
|
105
|
+
* @param isoString ISO 8601 timestamp
|
|
106
|
+
* @returns Formatted locale string or "-" if undefined
|
|
107
|
+
*/
|
|
108
|
+
export declare function formatTime(isoString: string | undefined): string;
|
|
109
|
+
/**
|
|
110
|
+
* Get a colored string for a run status.
|
|
111
|
+
* @param status The run status
|
|
112
|
+
* @returns Colored status string
|
|
113
|
+
*/
|
|
114
|
+
export declare function getRunStatusDisplay(status: string): string;
|
package/dist/lib/utils.js
CHANGED
|
@@ -3,6 +3,7 @@ import fs from "node:fs/promises";
|
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { fileURLToPath } from "node:url";
|
|
6
|
+
import pc from "picocolors";
|
|
6
7
|
export async function loadTemplateRun() {
|
|
7
8
|
const templatePath = path.resolve(process.cwd(), "template-run.json");
|
|
8
9
|
const content = await fs.readFile(templatePath, "utf-8");
|
|
@@ -197,3 +198,54 @@ export function openInBrowser(url) {
|
|
|
197
198
|
}
|
|
198
199
|
exec(command);
|
|
199
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Format a duration in milliseconds to a human-readable string.
|
|
203
|
+
* @param ms Duration in milliseconds
|
|
204
|
+
* @returns Formatted string like "45.3s" or "2m 30s"
|
|
205
|
+
*/
|
|
206
|
+
export function formatDuration(ms) {
|
|
207
|
+
if (ms === undefined)
|
|
208
|
+
return "-";
|
|
209
|
+
const seconds = ms / 1000;
|
|
210
|
+
if (seconds < 60)
|
|
211
|
+
return `${seconds.toFixed(1)}s`;
|
|
212
|
+
const minutes = Math.floor(seconds / 60);
|
|
213
|
+
const remainingSeconds = seconds % 60;
|
|
214
|
+
return `${minutes}m ${remainingSeconds.toFixed(0)}s`;
|
|
215
|
+
}
|
|
216
|
+
/**
|
|
217
|
+
* Format an ISO timestamp to a locale string.
|
|
218
|
+
* @param isoString ISO 8601 timestamp
|
|
219
|
+
* @returns Formatted locale string or "-" if undefined
|
|
220
|
+
*/
|
|
221
|
+
export function formatTime(isoString) {
|
|
222
|
+
if (!isoString)
|
|
223
|
+
return "-";
|
|
224
|
+
const date = new Date(isoString);
|
|
225
|
+
return date.toLocaleString();
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get a colored string for a run status.
|
|
229
|
+
* @param status The run status
|
|
230
|
+
* @returns Colored status string
|
|
231
|
+
*/
|
|
232
|
+
export function getRunStatusDisplay(status) {
|
|
233
|
+
switch (status) {
|
|
234
|
+
case "finished":
|
|
235
|
+
case "game_over":
|
|
236
|
+
case "completed":
|
|
237
|
+
return pc.green(status);
|
|
238
|
+
case "started":
|
|
239
|
+
return pc.blue(status);
|
|
240
|
+
case "initializing":
|
|
241
|
+
case "created":
|
|
242
|
+
return pc.yellow(status);
|
|
243
|
+
case "error":
|
|
244
|
+
case "failed":
|
|
245
|
+
case "cancelled":
|
|
246
|
+
case "timeout":
|
|
247
|
+
return pc.red(status);
|
|
248
|
+
default:
|
|
249
|
+
return status;
|
|
250
|
+
}
|
|
251
|
+
}
|
package/oclif.manifest.json
CHANGED
|
@@ -42,6 +42,50 @@
|
|
|
42
42
|
"init.js"
|
|
43
43
|
]
|
|
44
44
|
},
|
|
45
|
+
"agent:list": {
|
|
46
|
+
"aliases": [],
|
|
47
|
+
"args": {},
|
|
48
|
+
"description": "List all agents",
|
|
49
|
+
"examples": [
|
|
50
|
+
"<%= config.bin %> <%= command.id %>"
|
|
51
|
+
],
|
|
52
|
+
"flags": {
|
|
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"
|
|
61
|
+
},
|
|
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"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"hasDynamicHelp": false,
|
|
74
|
+
"hiddenAliases": [],
|
|
75
|
+
"id": "agent:list",
|
|
76
|
+
"pluginAlias": "kradle",
|
|
77
|
+
"pluginName": "kradle",
|
|
78
|
+
"pluginType": "core",
|
|
79
|
+
"strict": true,
|
|
80
|
+
"enableJsonFlag": false,
|
|
81
|
+
"isESM": true,
|
|
82
|
+
"relativePath": [
|
|
83
|
+
"dist",
|
|
84
|
+
"commands",
|
|
85
|
+
"agent",
|
|
86
|
+
"list.js"
|
|
87
|
+
]
|
|
88
|
+
},
|
|
45
89
|
"ai-docs:challenges-sdk": {
|
|
46
90
|
"aliases": [],
|
|
47
91
|
"args": {
|
|
@@ -451,7 +495,9 @@
|
|
|
451
495
|
"examples": [
|
|
452
496
|
"<%= config.bin %> <%= command.id %> my-challenge",
|
|
453
497
|
"<%= config.bin %> <%= command.id %> my-challenge --studio",
|
|
454
|
-
"<%= config.bin %> <%= command.id %> team-name:my-challenge"
|
|
498
|
+
"<%= config.bin %> <%= command.id %> team-name:my-challenge",
|
|
499
|
+
"<%= config.bin %> <%= command.id %> my-challenge --no-open",
|
|
500
|
+
"<%= config.bin %> <%= command.id %> my-challenge --no-wait"
|
|
455
501
|
],
|
|
456
502
|
"flags": {
|
|
457
503
|
"studio": {
|
|
@@ -461,10 +507,21 @@
|
|
|
461
507
|
"allowNo": false,
|
|
462
508
|
"type": "boolean"
|
|
463
509
|
},
|
|
464
|
-
"open": {
|
|
465
|
-
"
|
|
466
|
-
"
|
|
467
|
-
"
|
|
510
|
+
"no-open": {
|
|
511
|
+
"description": "Don't open the run URL in the browser",
|
|
512
|
+
"name": "no-open",
|
|
513
|
+
"allowNo": false,
|
|
514
|
+
"type": "boolean"
|
|
515
|
+
},
|
|
516
|
+
"no-wait": {
|
|
517
|
+
"description": "Don't wait for the run to complete (fire and forget)",
|
|
518
|
+
"name": "no-wait",
|
|
519
|
+
"allowNo": false,
|
|
520
|
+
"type": "boolean"
|
|
521
|
+
},
|
|
522
|
+
"no-summary": {
|
|
523
|
+
"description": "Don't wait for the AI-generated summary",
|
|
524
|
+
"name": "no-summary",
|
|
468
525
|
"allowNo": false,
|
|
469
526
|
"type": "boolean"
|
|
470
527
|
},
|
|
@@ -1198,14 +1255,82 @@
|
|
|
1198
1255
|
"push.js"
|
|
1199
1256
|
]
|
|
1200
1257
|
},
|
|
1201
|
-
"
|
|
1258
|
+
"challenge:runs:get": {
|
|
1259
|
+
"aliases": [],
|
|
1260
|
+
"args": {
|
|
1261
|
+
"runId": {
|
|
1262
|
+
"description": "Run ID to get details for",
|
|
1263
|
+
"name": "runId",
|
|
1264
|
+
"required": true
|
|
1265
|
+
}
|
|
1266
|
+
},
|
|
1267
|
+
"description": "Get details and logs for a specific run",
|
|
1268
|
+
"examples": [
|
|
1269
|
+
"<%= config.bin %> <%= command.id %> abc123",
|
|
1270
|
+
"<%= config.bin %> <%= command.id %> abc123 --no-logs"
|
|
1271
|
+
],
|
|
1272
|
+
"flags": {
|
|
1273
|
+
"no-logs": {
|
|
1274
|
+
"description": "Skip fetching and displaying logs",
|
|
1275
|
+
"name": "no-logs",
|
|
1276
|
+
"allowNo": false,
|
|
1277
|
+
"type": "boolean"
|
|
1278
|
+
},
|
|
1279
|
+
"api-key": {
|
|
1280
|
+
"description": "Kradle API key",
|
|
1281
|
+
"env": "KRADLE_API_KEY",
|
|
1282
|
+
"name": "api-key",
|
|
1283
|
+
"required": true,
|
|
1284
|
+
"hasDynamicHelp": false,
|
|
1285
|
+
"multiple": false,
|
|
1286
|
+
"type": "option"
|
|
1287
|
+
},
|
|
1288
|
+
"api-url": {
|
|
1289
|
+
"description": "Kradle Web API URL",
|
|
1290
|
+
"env": "KRADLE_API_URL",
|
|
1291
|
+
"name": "api-url",
|
|
1292
|
+
"required": true,
|
|
1293
|
+
"default": "https://api.kradle.ai/v0",
|
|
1294
|
+
"hasDynamicHelp": false,
|
|
1295
|
+
"multiple": false,
|
|
1296
|
+
"type": "option"
|
|
1297
|
+
}
|
|
1298
|
+
},
|
|
1299
|
+
"hasDynamicHelp": false,
|
|
1300
|
+
"hiddenAliases": [],
|
|
1301
|
+
"id": "challenge:runs:get",
|
|
1302
|
+
"pluginAlias": "kradle",
|
|
1303
|
+
"pluginName": "kradle",
|
|
1304
|
+
"pluginType": "core",
|
|
1305
|
+
"strict": true,
|
|
1306
|
+
"enableJsonFlag": false,
|
|
1307
|
+
"isESM": true,
|
|
1308
|
+
"relativePath": [
|
|
1309
|
+
"dist",
|
|
1310
|
+
"commands",
|
|
1311
|
+
"challenge",
|
|
1312
|
+
"runs",
|
|
1313
|
+
"get.js"
|
|
1314
|
+
]
|
|
1315
|
+
},
|
|
1316
|
+
"challenge:runs:list": {
|
|
1202
1317
|
"aliases": [],
|
|
1203
1318
|
"args": {},
|
|
1204
|
-
"description": "List
|
|
1319
|
+
"description": "List recent runs",
|
|
1205
1320
|
"examples": [
|
|
1206
|
-
"<%= config.bin %> <%= command.id %>"
|
|
1321
|
+
"<%= config.bin %> <%= command.id %>",
|
|
1322
|
+
"<%= config.bin %> <%= command.id %> --limit 20"
|
|
1207
1323
|
],
|
|
1208
1324
|
"flags": {
|
|
1325
|
+
"limit": {
|
|
1326
|
+
"char": "n",
|
|
1327
|
+
"description": "Number of runs to display",
|
|
1328
|
+
"name": "limit",
|
|
1329
|
+
"default": 10,
|
|
1330
|
+
"hasDynamicHelp": false,
|
|
1331
|
+
"multiple": false,
|
|
1332
|
+
"type": "option"
|
|
1333
|
+
},
|
|
1209
1334
|
"api-key": {
|
|
1210
1335
|
"description": "Kradle API key",
|
|
1211
1336
|
"env": "KRADLE_API_KEY",
|
|
@@ -1228,7 +1353,7 @@
|
|
|
1228
1353
|
},
|
|
1229
1354
|
"hasDynamicHelp": false,
|
|
1230
1355
|
"hiddenAliases": [],
|
|
1231
|
-
"id": "
|
|
1356
|
+
"id": "challenge:runs:list",
|
|
1232
1357
|
"pluginAlias": "kradle",
|
|
1233
1358
|
"pluginName": "kradle",
|
|
1234
1359
|
"pluginType": "core",
|
|
@@ -1238,10 +1363,11 @@
|
|
|
1238
1363
|
"relativePath": [
|
|
1239
1364
|
"dist",
|
|
1240
1365
|
"commands",
|
|
1241
|
-
"
|
|
1366
|
+
"challenge",
|
|
1367
|
+
"runs",
|
|
1242
1368
|
"list.js"
|
|
1243
1369
|
]
|
|
1244
1370
|
}
|
|
1245
1371
|
},
|
|
1246
|
-
"version": "0.4.
|
|
1372
|
+
"version": "0.4.3"
|
|
1247
1373
|
}
|
package/package.json
CHANGED
|
@@ -310,13 +310,15 @@ kradle challenge watch my-challenge
|
|
|
310
310
|
|
|
311
311
|
### `kradle challenge run <name>`
|
|
312
312
|
|
|
313
|
-
Runs a challenge with configured participants.
|
|
313
|
+
Runs a challenge with configured participants and waits for completion.
|
|
314
314
|
|
|
315
315
|
**Usage:**
|
|
316
316
|
```bash
|
|
317
317
|
kradle challenge run <challenge-name>
|
|
318
318
|
kradle challenge run <challenge-name> --studio
|
|
319
319
|
kradle challenge run <team-name>:<challenge-name>
|
|
320
|
+
kradle challenge run <challenge-name> --no-open
|
|
321
|
+
kradle challenge run <challenge-name> --no-wait
|
|
320
322
|
```
|
|
321
323
|
|
|
322
324
|
**Arguments:**
|
|
@@ -325,10 +327,20 @@ kradle challenge run <team-name>:<challenge-name>
|
|
|
325
327
|
| `challenge-name` | Challenge to run. Can be a short slug (e.g., `my-challenge`) or include a team/user namespace (e.g., `team-name:my-challenge`). The namespace is useful for running public challenges owned by other teams. If no namespace is provided, it defaults to the user's own namespace. | Yes |
|
|
326
328
|
|
|
327
329
|
**Flags:**
|
|
328
|
-
| Flag |
|
|
329
|
-
|
|
330
|
-
| `--studio` | Run in local studio environment instead of production |
|
|
331
|
-
| `--open` |
|
|
330
|
+
| Flag | Short | Description |
|
|
331
|
+
|------|-------|-------------|
|
|
332
|
+
| `--studio` | `-s` | Run in local studio environment instead of production |
|
|
333
|
+
| `--no-open` | | Don't open the run URL in the browser |
|
|
334
|
+
| `--no-wait` | | Don't wait for completion (fire and forget) |
|
|
335
|
+
| `--no-summary` | | Don't wait for the AI-generated summary |
|
|
336
|
+
|
|
337
|
+
**Behavior:**
|
|
338
|
+
1. Creates a run with the challenge and participants from `template-run.json`
|
|
339
|
+
2. Opens the run URL in the browser (unless `--no-open`)
|
|
340
|
+
3. Polls every 2 seconds for status changes until completion (unless `--no-wait`)
|
|
341
|
+
4. Displays the final outcome including status, duration, and participant results
|
|
342
|
+
|
|
343
|
+
**Terminal states:** The polling stops when the run reaches: `finished`, `game_over`, `error`, or `completed`.
|
|
332
344
|
|
|
333
345
|
**Prerequisites:**
|
|
334
346
|
- `template-run.json` must exist in project root with participant configuration
|
|
@@ -352,7 +364,7 @@ kradle challenge run <team-name>:<challenge-name>
|
|
|
352
364
|
|
|
353
365
|
**Examples:**
|
|
354
366
|
```bash
|
|
355
|
-
# Run
|
|
367
|
+
# Run and wait for completion (default behavior)
|
|
356
368
|
kradle challenge run my-challenge
|
|
357
369
|
|
|
358
370
|
# Run a public challenge from another team
|
|
@@ -360,6 +372,115 @@ kradle challenge run team-kradle:battle-royale
|
|
|
360
372
|
|
|
361
373
|
# Run in local studio
|
|
362
374
|
kradle challenge run my-challenge --studio
|
|
375
|
+
|
|
376
|
+
# Run without opening browser
|
|
377
|
+
kradle challenge run my-challenge --no-open
|
|
378
|
+
|
|
379
|
+
# Fire and forget (don't wait for completion)
|
|
380
|
+
kradle challenge run my-challenge --no-wait
|
|
381
|
+
|
|
382
|
+
# Fire and forget without browser
|
|
383
|
+
kradle challenge run my-challenge --no-open --no-wait
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
### `kradle challenge runs list`
|
|
389
|
+
|
|
390
|
+
Lists recent runs for the authenticated user.
|
|
391
|
+
|
|
392
|
+
**Usage:**
|
|
393
|
+
```bash
|
|
394
|
+
kradle challenge runs list
|
|
395
|
+
kradle challenge runs list --limit 20
|
|
396
|
+
```
|
|
397
|
+
|
|
398
|
+
**Flags:**
|
|
399
|
+
| Flag | Short | Description | Default |
|
|
400
|
+
|------|-------|-------------|---------|
|
|
401
|
+
| `--limit` | `-n` | Number of runs to display | 10 |
|
|
402
|
+
|
|
403
|
+
**Output format:**
|
|
404
|
+
```
|
|
405
|
+
Runs:
|
|
406
|
+
|
|
407
|
+
ID Challenge Status End State Duration Created
|
|
408
|
+
---------------------------------------------------------------------------------------------------------
|
|
409
|
+
abc123def... username:my-challenge finished game_over 45.3s 1/15/2025, 2:30 PM
|
|
410
|
+
xyz789abc... team-kradle:example error - 12.1s 1/15/2025, 2:15 PM
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
**Status colors:**
|
|
414
|
+
- Green: `finished`, `game_over`, `completed`
|
|
415
|
+
- Blue: `started`
|
|
416
|
+
- Yellow: `initializing`, `created`
|
|
417
|
+
- Red: `error`
|
|
418
|
+
|
|
419
|
+
**Examples:**
|
|
420
|
+
```bash
|
|
421
|
+
# List 10 most recent runs (default)
|
|
422
|
+
kradle challenge runs list
|
|
423
|
+
|
|
424
|
+
# List 20 most recent runs
|
|
425
|
+
kradle challenge runs list --limit 20
|
|
426
|
+
|
|
427
|
+
# List 5 runs
|
|
428
|
+
kradle challenge runs list -n 5
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
---
|
|
432
|
+
|
|
433
|
+
### `kradle challenge runs get <run-id>`
|
|
434
|
+
|
|
435
|
+
Gets details and optionally logs for a specific run.
|
|
436
|
+
|
|
437
|
+
**Usage:**
|
|
438
|
+
```bash
|
|
439
|
+
kradle challenge runs get <run-id>
|
|
440
|
+
kradle challenge runs get <run-id> --no-logs
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
**Arguments:**
|
|
444
|
+
| Argument | Description | Required |
|
|
445
|
+
|----------|-------------|----------|
|
|
446
|
+
| `run-id` | The ID of the run to get details for | Yes |
|
|
447
|
+
|
|
448
|
+
**Flags:**
|
|
449
|
+
| Flag | Description | Default |
|
|
450
|
+
|------|-------------|---------|
|
|
451
|
+
| `--no-logs` | Skip fetching and displaying logs | false |
|
|
452
|
+
|
|
453
|
+
**Output sections:**
|
|
454
|
+
|
|
455
|
+
1. **Run Result** - Metadata about the run:
|
|
456
|
+
- ID, Challenge, Status, End State, Finished Status, Duration, End Time
|
|
457
|
+
|
|
458
|
+
2. **Aggregated Results** - Summary statistics:
|
|
459
|
+
- Total participants, successful count, unsuccessful count, total time
|
|
460
|
+
|
|
461
|
+
3. **Participant Results** - Per-participant breakdown (table format):
|
|
462
|
+
- Participant ID, Agent name, Winner status, Score, Time to Success
|
|
463
|
+
|
|
464
|
+
4. **Logs** - Log entries from the run (unless `--no-logs` is used):
|
|
465
|
+
- Timestamp, Level (colored), Participant ID, Message
|
|
466
|
+
- JSON messages are automatically parsed and formatted
|
|
467
|
+
|
|
468
|
+
**Log level colors:**
|
|
469
|
+
- Red: `error`
|
|
470
|
+
- Yellow: `warn`, `warning`
|
|
471
|
+
- Blue: `info`
|
|
472
|
+
- Dim: `debug`
|
|
473
|
+
|
|
474
|
+
**Examples:**
|
|
475
|
+
```bash
|
|
476
|
+
# Get full details including logs
|
|
477
|
+
kradle challenge runs get abc123def456
|
|
478
|
+
|
|
479
|
+
# Get details without logs (faster)
|
|
480
|
+
kradle challenge runs get abc123def456 --no-logs
|
|
481
|
+
|
|
482
|
+
# Get details for a run (full ID)
|
|
483
|
+
kradle challenge runs get 12345678-1234-1234-1234-123456789012
|
|
363
484
|
```
|
|
364
485
|
|
|
365
486
|
---
|
|
@@ -1024,7 +1145,9 @@ kradle challenge build --all --visibility public
|
|
|
1024
1145
|
| `kradle challenge list` | List all challenges |
|
|
1025
1146
|
| `kradle challenge pull [name]` | Pull challenge from cloud |
|
|
1026
1147
|
| `kradle challenge watch <name>` | Watch and auto-rebuild |
|
|
1027
|
-
| `kradle challenge run <name>` | Run challenge |
|
|
1148
|
+
| `kradle challenge run <name>` | Run challenge and wait for completion |
|
|
1149
|
+
| `kradle challenge runs list` | List recent runs |
|
|
1150
|
+
| `kradle challenge runs get <run-id>` | Get details and logs for a run |
|
|
1028
1151
|
| `kradle experiment create <name>` | Create new experiment |
|
|
1029
1152
|
| `kradle experiment run <name>` | Run/resume experiment |
|
|
1030
1153
|
| `kradle experiment recordings <name>` | Download recordings |
|