@kradle/cli 0.2.4 → 0.2.5
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 +13 -0
- package/dist/commands/challenge/pull.d.ts +15 -0
- package/dist/commands/challenge/pull.js +182 -0
- package/dist/lib/api-client.d.ts +6 -0
- package/dist/lib/api-client.js +8 -0
- package/dist/lib/arguments.d.ts +1 -6
- package/dist/lib/arguments.js +6 -9
- package/oclif.manifest.json +118 -49
- package/package.json +1 -1
- package/static/ai_docs/LLM_CLI_REFERENCE.md +56 -0
package/README.md
CHANGED
|
@@ -111,6 +111,19 @@ List all challenges (local and cloud):
|
|
|
111
111
|
kradle challenge list
|
|
112
112
|
```
|
|
113
113
|
|
|
114
|
+
### Pull Challenge
|
|
115
|
+
|
|
116
|
+
Download a challenge from the cloud and extract source files locally:
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
kradle challenge pull # Interactive selection
|
|
120
|
+
kradle challenge pull <challenge-name> # Pull your own challenge
|
|
121
|
+
kradle challenge pull <team-name>:<challenge-name> # Pull a public challenge from another team
|
|
122
|
+
kradle challenge pull <challenge-name> --yes # Skip confirmation when overwriting
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
This downloads the challenge tarball, extracts `challenge.ts` and `config.ts`, and builds the datapack locally.
|
|
126
|
+
|
|
114
127
|
### Watch Challenge
|
|
115
128
|
|
|
116
129
|
Watch a challenge for changes and auto-rebuild/upload:
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from "@oclif/core";
|
|
2
|
+
export default class Pull extends Command {
|
|
3
|
+
static description: string;
|
|
4
|
+
static examples: string[];
|
|
5
|
+
static args: {
|
|
6
|
+
challengeSlug: import("@oclif/core/interfaces").Arg<string | undefined>;
|
|
7
|
+
};
|
|
8
|
+
static flags: {
|
|
9
|
+
"api-key": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
10
|
+
"api-url": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
11
|
+
"challenges-path": import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
|
|
12
|
+
yes: import("@oclif/core/interfaces").BooleanFlag<boolean>;
|
|
13
|
+
};
|
|
14
|
+
run(): Promise<void>;
|
|
15
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import fs from "node:fs/promises";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { Command, Flags } from "@oclif/core";
|
|
5
|
+
import enquirer from "enquirer";
|
|
6
|
+
import { Listr } from "listr2";
|
|
7
|
+
import pc from "picocolors";
|
|
8
|
+
import * as tar from "tar";
|
|
9
|
+
import { ApiClient } from "../../lib/api-client.js";
|
|
10
|
+
import { extractShortSlug, getChallengeSlugArgument } from "../../lib/arguments.js";
|
|
11
|
+
import { Challenge, SOURCE_FOLDER } from "../../lib/challenge.js";
|
|
12
|
+
import { getConfigFlags } from "../../lib/flags.js";
|
|
13
|
+
export default class Pull extends Command {
|
|
14
|
+
static description = "Pull a challenge from the cloud and extract source files locally";
|
|
15
|
+
static examples = [
|
|
16
|
+
"<%= config.bin %> <%= command.id %>",
|
|
17
|
+
"<%= config.bin %> <%= command.id %> my-challenge",
|
|
18
|
+
"<%= config.bin %> <%= command.id %> username:my-challenge",
|
|
19
|
+
"<%= config.bin %> <%= command.id %> my-challenge --yes",
|
|
20
|
+
];
|
|
21
|
+
static args = {
|
|
22
|
+
challengeSlug: getChallengeSlugArgument({
|
|
23
|
+
description: "Challenge slug to pull (interactive selection if omitted)",
|
|
24
|
+
required: false,
|
|
25
|
+
allowTeam: true,
|
|
26
|
+
}),
|
|
27
|
+
};
|
|
28
|
+
static flags = {
|
|
29
|
+
yes: Flags.boolean({ char: "y", description: "Skip confirmation prompts", default: false }),
|
|
30
|
+
...getConfigFlags("api-key", "api-url", "challenges-path"),
|
|
31
|
+
};
|
|
32
|
+
async run() {
|
|
33
|
+
const { args, flags } = await this.parse(Pull);
|
|
34
|
+
const api = new ApiClient(flags["api-url"], flags["api-key"]);
|
|
35
|
+
let challengeSlug = args.challengeSlug;
|
|
36
|
+
if (!challengeSlug) {
|
|
37
|
+
const [kradleChallenges, cloudChallenges, localChallenges] = await Promise.all([
|
|
38
|
+
api.listKradleChallenges(),
|
|
39
|
+
api.listChallenges(),
|
|
40
|
+
Challenge.getLocalChallenges(),
|
|
41
|
+
]);
|
|
42
|
+
const allChallenges = [...kradleChallenges, ...cloudChallenges];
|
|
43
|
+
if (allChallenges.length === 0) {
|
|
44
|
+
this.error(pc.red("No challenges found in the cloud."));
|
|
45
|
+
}
|
|
46
|
+
const localSet = new Set(localChallenges);
|
|
47
|
+
const slugs = allChallenges.map((c) => c.slug).sort();
|
|
48
|
+
const choices = slugs.map((slug) => {
|
|
49
|
+
const shortSlug = extractShortSlug(slug);
|
|
50
|
+
const isLocal = localSet.has(shortSlug);
|
|
51
|
+
const status = isLocal ? pc.yellow(" (local)") : "";
|
|
52
|
+
return {
|
|
53
|
+
name: slug,
|
|
54
|
+
message: `${slug}${status}`,
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
const response = await enquirer.prompt({
|
|
58
|
+
type: "select",
|
|
59
|
+
name: "challenge",
|
|
60
|
+
message: "Select a challenge to pull",
|
|
61
|
+
choices,
|
|
62
|
+
});
|
|
63
|
+
challengeSlug = response.challenge;
|
|
64
|
+
}
|
|
65
|
+
const shortSlug = extractShortSlug(challengeSlug);
|
|
66
|
+
const challenge = new Challenge(shortSlug, flags["challenges-path"]);
|
|
67
|
+
const existsInCloud = await api.challengeExists(challengeSlug);
|
|
68
|
+
if (!existsInCloud) {
|
|
69
|
+
this.error(pc.red(`Challenge "${challengeSlug}" does not exist in the cloud.`));
|
|
70
|
+
}
|
|
71
|
+
const existsLocally = existsSync(challenge.challengeDir);
|
|
72
|
+
const hasLocalFiles = existsLocally && (existsSync(challenge.challengePath) || existsSync(challenge.configPath));
|
|
73
|
+
if (hasLocalFiles && !flags.yes) {
|
|
74
|
+
this.log(pc.bold(`\nChallenge: ${pc.cyan(challenge.shortSlug)}`));
|
|
75
|
+
this.log(` Local folder exists: ${pc.yellow(challenge.challengeDir)}`);
|
|
76
|
+
if (existsSync(challenge.challengePath)) {
|
|
77
|
+
this.log(` challenge.ts: ${pc.yellow("exists (will be overwritten)")}`);
|
|
78
|
+
}
|
|
79
|
+
if (existsSync(challenge.configPath)) {
|
|
80
|
+
this.log(` config.ts: ${pc.yellow("exists (will be overwritten)")}`);
|
|
81
|
+
}
|
|
82
|
+
this.log("");
|
|
83
|
+
try {
|
|
84
|
+
const response = await enquirer.prompt({
|
|
85
|
+
type: "confirm",
|
|
86
|
+
name: "confirm",
|
|
87
|
+
message: `Overwrite local challenge files? ${pc.red("This cannot be undone.")}`,
|
|
88
|
+
initial: false,
|
|
89
|
+
});
|
|
90
|
+
if (!response.confirm) {
|
|
91
|
+
this.log(pc.yellow("Pull cancelled"));
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
this.log(pc.yellow("\nPull cancelled"));
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
const tempTarballPath = path.join(flags["challenges-path"], `${challenge.shortSlug}-pull-temp.tar.gz`);
|
|
101
|
+
const tasks = new Listr([
|
|
102
|
+
{
|
|
103
|
+
title: "Downloading challenge tarball",
|
|
104
|
+
task: async (_, task) => {
|
|
105
|
+
const { downloadUrl } = await api.getChallengeDownloadUrl(challengeSlug);
|
|
106
|
+
const response = await fetch(downloadUrl);
|
|
107
|
+
if (!response.ok) {
|
|
108
|
+
throw new Error(`Failed to download: ${response.status} ${response.statusText}`);
|
|
109
|
+
}
|
|
110
|
+
const buffer = await response.arrayBuffer();
|
|
111
|
+
await fs.mkdir(path.dirname(tempTarballPath), { recursive: true });
|
|
112
|
+
await fs.writeFile(tempTarballPath, Buffer.from(buffer));
|
|
113
|
+
task.title = "Downloaded challenge tarball";
|
|
114
|
+
},
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
title: "Creating challenge directory",
|
|
118
|
+
task: async (_, task) => {
|
|
119
|
+
await fs.mkdir(challenge.challengeDir, { recursive: true });
|
|
120
|
+
task.title = `Created directory: ${challenge.challengeDir}`;
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
title: "Extracting source files",
|
|
125
|
+
task: async (_, task) => {
|
|
126
|
+
const filesToExtract = [`${SOURCE_FOLDER}/challenge.ts`, `${SOURCE_FOLDER}/config.ts`];
|
|
127
|
+
const tempExtractDir = path.join(flags["challenges-path"], `${challenge.shortSlug}-extract-temp`);
|
|
128
|
+
await fs.mkdir(tempExtractDir, { recursive: true });
|
|
129
|
+
try {
|
|
130
|
+
await tar.extract({
|
|
131
|
+
file: tempTarballPath,
|
|
132
|
+
cwd: tempExtractDir,
|
|
133
|
+
filter: (entryPath) => filesToExtract.some((f) => entryPath === f),
|
|
134
|
+
});
|
|
135
|
+
const srcChallengeTs = path.join(tempExtractDir, SOURCE_FOLDER, "challenge.ts");
|
|
136
|
+
const srcConfigTs = path.join(tempExtractDir, SOURCE_FOLDER, "config.ts");
|
|
137
|
+
let extractedCount = 0;
|
|
138
|
+
if (existsSync(srcChallengeTs)) {
|
|
139
|
+
await fs.copyFile(srcChallengeTs, challenge.challengePath);
|
|
140
|
+
extractedCount++;
|
|
141
|
+
}
|
|
142
|
+
if (existsSync(srcConfigTs)) {
|
|
143
|
+
await fs.copyFile(srcConfigTs, challenge.configPath);
|
|
144
|
+
extractedCount++;
|
|
145
|
+
}
|
|
146
|
+
if (extractedCount === 0) {
|
|
147
|
+
throw new Error(`No source files found in tarball. The challenge may not have been built with source files.`);
|
|
148
|
+
}
|
|
149
|
+
task.title = `Extracted ${extractedCount} source file(s)`;
|
|
150
|
+
}
|
|
151
|
+
finally {
|
|
152
|
+
await fs.rm(tempExtractDir, { recursive: true, force: true });
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
title: "Cleaning up",
|
|
158
|
+
task: async (_, task) => {
|
|
159
|
+
await fs.rm(tempTarballPath, { force: true });
|
|
160
|
+
task.title = "Cleaned up temporary files";
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
{
|
|
164
|
+
title: "Building datapack",
|
|
165
|
+
task: async (_, task) => {
|
|
166
|
+
await challenge.build(true);
|
|
167
|
+
task.title = "Built datapack";
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
]);
|
|
171
|
+
try {
|
|
172
|
+
await tasks.run();
|
|
173
|
+
this.log(pc.green(`\n✓ Challenge pulled: ${challenge.shortSlug}`));
|
|
174
|
+
this.log(pc.dim(` → challenge.ts: ${challenge.challengePath}`));
|
|
175
|
+
this.log(pc.dim(` → config.ts: ${challenge.configPath}`));
|
|
176
|
+
}
|
|
177
|
+
catch (error) {
|
|
178
|
+
await fs.rm(tempTarballPath, { force: true }).catch(() => { });
|
|
179
|
+
this.error(pc.red(`Pull failed: ${error instanceof Error ? error.message : String(error)}`));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
package/dist/lib/api-client.d.ts
CHANGED
|
@@ -66,6 +66,12 @@ export declare class ApiClient {
|
|
|
66
66
|
* @returns The upload URL.
|
|
67
67
|
*/
|
|
68
68
|
getChallengeUploadUrl(slug: string): Promise<string>;
|
|
69
|
+
/**
|
|
70
|
+
* Get the download URL for a challenge datapack.
|
|
71
|
+
* @param slug - The slug of the challenge.
|
|
72
|
+
* @returns The download URL and expiration time.
|
|
73
|
+
*/
|
|
74
|
+
getChallengeDownloadUrl(slug: string): Promise<RecordingDownloadUrlResponse>;
|
|
69
75
|
runChallenge(runData: {
|
|
70
76
|
challenge: string;
|
|
71
77
|
participants: unknown[];
|
package/dist/lib/api-client.js
CHANGED
|
@@ -210,6 +210,14 @@ export class ApiClient {
|
|
|
210
210
|
const response = await this.get(`challenges/${slug}/datapackUploadUrl`, {}, UploadUrlResponseSchema);
|
|
211
211
|
return response.uploadUrl;
|
|
212
212
|
}
|
|
213
|
+
/**
|
|
214
|
+
* Get the download URL for a challenge datapack.
|
|
215
|
+
* @param slug - The slug of the challenge.
|
|
216
|
+
* @returns The download URL and expiration time.
|
|
217
|
+
*/
|
|
218
|
+
async getChallengeDownloadUrl(slug) {
|
|
219
|
+
return this.get(`challenges/${slug}/datapackDownloadUrl`, {}, RecordingDownloadUrlResponseSchema);
|
|
220
|
+
}
|
|
213
221
|
async runChallenge(runData) {
|
|
214
222
|
const url = "jobs";
|
|
215
223
|
const payload = this.isStudio ? runData : { ...runData, jobType: "background" };
|
package/dist/lib/arguments.d.ts
CHANGED
|
@@ -1,10 +1,5 @@
|
|
|
1
1
|
import type { Arg } from "@oclif/core/interfaces";
|
|
2
|
-
|
|
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)
|
|
7
|
-
*/
|
|
2
|
+
export declare function extractShortSlug(slug: string): string;
|
|
8
3
|
export declare function getChallengeSlugArgument<R extends boolean = true>({ description, required, allowTeam, }: {
|
|
9
4
|
description: string;
|
|
10
5
|
required?: R;
|
package/dist/lib/arguments.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
1
|
import { Args } from "@oclif/core";
|
|
2
|
-
// Base pattern for a slug segment (lowercase alphanumeric with hyphens, no leading/trailing hyphens)
|
|
3
2
|
const SLUG_SEGMENT = "[a-z0-9]+(?:-[a-z0-9]+)*";
|
|
4
|
-
// Local challenge slug pattern: just the challenge name (no namespace)
|
|
5
3
|
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
4
|
const NAMESPACED_SLUG_REGEX = new RegExp(`^(?:${SLUG_SEGMENT}:)?${SLUG_SEGMENT}$`);
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
5
|
+
export function extractShortSlug(slug) {
|
|
6
|
+
if (slug.includes(":")) {
|
|
7
|
+
return slug.split(":")[1];
|
|
8
|
+
}
|
|
9
|
+
return slug;
|
|
10
|
+
}
|
|
14
11
|
export function getChallengeSlugArgument({ description, required, allowTeam = false, }) {
|
|
15
12
|
const regex = allowTeam ? NAMESPACED_SLUG_REGEX : LOCAL_SLUG_REGEX;
|
|
16
13
|
const errorMessage = allowTeam
|
package/oclif.manifest.json
CHANGED
|
@@ -86,6 +86,54 @@
|
|
|
86
86
|
"list.js"
|
|
87
87
|
]
|
|
88
88
|
},
|
|
89
|
+
"ai-docs:api": {
|
|
90
|
+
"aliases": [],
|
|
91
|
+
"args": {},
|
|
92
|
+
"description": "Output the Kradle API reference documentation for LLMs",
|
|
93
|
+
"examples": [
|
|
94
|
+
"<%= config.bin %> <%= command.id %>"
|
|
95
|
+
],
|
|
96
|
+
"flags": {},
|
|
97
|
+
"hasDynamicHelp": false,
|
|
98
|
+
"hiddenAliases": [],
|
|
99
|
+
"id": "ai-docs:api",
|
|
100
|
+
"pluginAlias": "@kradle/cli",
|
|
101
|
+
"pluginName": "@kradle/cli",
|
|
102
|
+
"pluginType": "core",
|
|
103
|
+
"strict": true,
|
|
104
|
+
"enableJsonFlag": false,
|
|
105
|
+
"isESM": true,
|
|
106
|
+
"relativePath": [
|
|
107
|
+
"dist",
|
|
108
|
+
"commands",
|
|
109
|
+
"ai-docs",
|
|
110
|
+
"api.js"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
"ai-docs:cli": {
|
|
114
|
+
"aliases": [],
|
|
115
|
+
"args": {},
|
|
116
|
+
"description": "Output the Kradle CLI reference documentation for LLMs",
|
|
117
|
+
"examples": [
|
|
118
|
+
"<%= config.bin %> <%= command.id %>"
|
|
119
|
+
],
|
|
120
|
+
"flags": {},
|
|
121
|
+
"hasDynamicHelp": false,
|
|
122
|
+
"hiddenAliases": [],
|
|
123
|
+
"id": "ai-docs:cli",
|
|
124
|
+
"pluginAlias": "@kradle/cli",
|
|
125
|
+
"pluginName": "@kradle/cli",
|
|
126
|
+
"pluginType": "core",
|
|
127
|
+
"strict": true,
|
|
128
|
+
"enableJsonFlag": false,
|
|
129
|
+
"isESM": true,
|
|
130
|
+
"relativePath": [
|
|
131
|
+
"dist",
|
|
132
|
+
"commands",
|
|
133
|
+
"ai-docs",
|
|
134
|
+
"cli.js"
|
|
135
|
+
]
|
|
136
|
+
},
|
|
89
137
|
"challenge:build": {
|
|
90
138
|
"aliases": [],
|
|
91
139
|
"args": {
|
|
@@ -357,6 +405,75 @@
|
|
|
357
405
|
"list.js"
|
|
358
406
|
]
|
|
359
407
|
},
|
|
408
|
+
"challenge:pull": {
|
|
409
|
+
"aliases": [],
|
|
410
|
+
"args": {
|
|
411
|
+
"challengeSlug": {
|
|
412
|
+
"description": "Challenge slug to pull (interactive selection if omitted)",
|
|
413
|
+
"name": "challengeSlug",
|
|
414
|
+
"required": false
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
"description": "Pull a challenge from the cloud and extract source files locally",
|
|
418
|
+
"examples": [
|
|
419
|
+
"<%= config.bin %> <%= command.id %>",
|
|
420
|
+
"<%= config.bin %> <%= command.id %> my-challenge",
|
|
421
|
+
"<%= config.bin %> <%= command.id %> username:my-challenge",
|
|
422
|
+
"<%= config.bin %> <%= command.id %> my-challenge --yes"
|
|
423
|
+
],
|
|
424
|
+
"flags": {
|
|
425
|
+
"yes": {
|
|
426
|
+
"char": "y",
|
|
427
|
+
"description": "Skip confirmation prompts",
|
|
428
|
+
"name": "yes",
|
|
429
|
+
"allowNo": false,
|
|
430
|
+
"type": "boolean"
|
|
431
|
+
},
|
|
432
|
+
"api-key": {
|
|
433
|
+
"description": "Kradle API key",
|
|
434
|
+
"env": "KRADLE_API_KEY",
|
|
435
|
+
"name": "api-key",
|
|
436
|
+
"required": true,
|
|
437
|
+
"hasDynamicHelp": false,
|
|
438
|
+
"multiple": false,
|
|
439
|
+
"type": "option"
|
|
440
|
+
},
|
|
441
|
+
"api-url": {
|
|
442
|
+
"description": "Kradle Web API URL",
|
|
443
|
+
"env": "KRADLE_API_URL",
|
|
444
|
+
"name": "api-url",
|
|
445
|
+
"required": true,
|
|
446
|
+
"default": "https://api.kradle.ai/v0",
|
|
447
|
+
"hasDynamicHelp": false,
|
|
448
|
+
"multiple": false,
|
|
449
|
+
"type": "option"
|
|
450
|
+
},
|
|
451
|
+
"challenges-path": {
|
|
452
|
+
"description": "Absolute path to the challenges directory",
|
|
453
|
+
"env": "KRADLE_CHALLENGES_PATH",
|
|
454
|
+
"name": "challenges-path",
|
|
455
|
+
"default": "~/Documents/kradle-studio/challenges",
|
|
456
|
+
"hasDynamicHelp": false,
|
|
457
|
+
"multiple": false,
|
|
458
|
+
"type": "option"
|
|
459
|
+
}
|
|
460
|
+
},
|
|
461
|
+
"hasDynamicHelp": false,
|
|
462
|
+
"hiddenAliases": [],
|
|
463
|
+
"id": "challenge:pull",
|
|
464
|
+
"pluginAlias": "@kradle/cli",
|
|
465
|
+
"pluginName": "@kradle/cli",
|
|
466
|
+
"pluginType": "core",
|
|
467
|
+
"strict": true,
|
|
468
|
+
"enableJsonFlag": false,
|
|
469
|
+
"isESM": true,
|
|
470
|
+
"relativePath": [
|
|
471
|
+
"dist",
|
|
472
|
+
"commands",
|
|
473
|
+
"challenge",
|
|
474
|
+
"pull.js"
|
|
475
|
+
]
|
|
476
|
+
},
|
|
360
477
|
"challenge:run": {
|
|
361
478
|
"aliases": [],
|
|
362
479
|
"args": {
|
|
@@ -518,54 +635,6 @@
|
|
|
518
635
|
"watch.js"
|
|
519
636
|
]
|
|
520
637
|
},
|
|
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
|
-
},
|
|
569
638
|
"experiment:create": {
|
|
570
639
|
"aliases": [],
|
|
571
640
|
"args": {
|
|
@@ -800,5 +869,5 @@
|
|
|
800
869
|
]
|
|
801
870
|
}
|
|
802
871
|
},
|
|
803
|
-
"version": "0.2.
|
|
872
|
+
"version": "0.2.5"
|
|
804
873
|
}
|
package/package.json
CHANGED
|
@@ -223,6 +223,61 @@ kradle challenge list
|
|
|
223
223
|
|
|
224
224
|
---
|
|
225
225
|
|
|
226
|
+
### `kradle challenge pull [name]`
|
|
227
|
+
|
|
228
|
+
Downloads a challenge from the cloud and extracts source files locally.
|
|
229
|
+
|
|
230
|
+
**Usage:**
|
|
231
|
+
```bash
|
|
232
|
+
kradle challenge pull
|
|
233
|
+
kradle challenge pull <challenge-name>
|
|
234
|
+
kradle challenge pull <team-name>:<challenge-name>
|
|
235
|
+
kradle challenge pull <challenge-name> --yes
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**Arguments:**
|
|
239
|
+
| Argument | Description | Required |
|
|
240
|
+
|----------|-------------|----------|
|
|
241
|
+
| `challenge-name` | Challenge slug to pull. Can be a short slug (e.g., `my-challenge`) or include a team/user namespace (e.g., `team-name:my-challenge`). If omitted, shows interactive selection. | No |
|
|
242
|
+
|
|
243
|
+
**Flags:**
|
|
244
|
+
| Flag | Short | Description | Default |
|
|
245
|
+
|------|-------|-------------|---------|
|
|
246
|
+
| `--yes` | `-y` | Skip confirmation prompts when overwriting local files | false |
|
|
247
|
+
|
|
248
|
+
**What it does:**
|
|
249
|
+
1. Downloads the challenge tarball from cloud storage
|
|
250
|
+
2. Extracts `challenge.ts` and `config.ts` from the `.src` folder in the tarball
|
|
251
|
+
3. Places files in `challenges/<short-slug>/` directory
|
|
252
|
+
4. Builds the datapack locally
|
|
253
|
+
|
|
254
|
+
**Interactive mode (no slug argument):**
|
|
255
|
+
- Shows list of all cloud challenges (your own + team-kradle)
|
|
256
|
+
- Marks challenges that exist locally with "(local)" indicator
|
|
257
|
+
- Select a challenge to pull
|
|
258
|
+
|
|
259
|
+
**Confirmation prompt:**
|
|
260
|
+
When pulling over existing local files (without `--yes`), shows:
|
|
261
|
+
- Which files will be overwritten
|
|
262
|
+
- Asks for confirmation before proceeding
|
|
263
|
+
|
|
264
|
+
**Examples:**
|
|
265
|
+
```bash
|
|
266
|
+
# Interactive selection from all cloud challenges
|
|
267
|
+
kradle challenge pull
|
|
268
|
+
|
|
269
|
+
# Pull your own challenge
|
|
270
|
+
kradle challenge pull my-challenge
|
|
271
|
+
|
|
272
|
+
# Pull a public challenge from another team
|
|
273
|
+
kradle challenge pull team-kradle:example-challenge
|
|
274
|
+
|
|
275
|
+
# Pull without confirmation prompt
|
|
276
|
+
kradle challenge pull my-challenge --yes
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
---
|
|
280
|
+
|
|
226
281
|
### `kradle challenge watch <name>`
|
|
227
282
|
|
|
228
283
|
Watches a challenge for file changes and automatically rebuilds/uploads.
|
|
@@ -663,6 +718,7 @@ kradle challenge build --all --visibility public
|
|
|
663
718
|
| `kradle challenge build --all` | Build all challenges |
|
|
664
719
|
| `kradle challenge delete <name>` | Delete challenge |
|
|
665
720
|
| `kradle challenge list` | List all challenges |
|
|
721
|
+
| `kradle challenge pull [name]` | Pull challenge from cloud |
|
|
666
722
|
| `kradle challenge watch <name>` | Watch and auto-rebuild |
|
|
667
723
|
| `kradle challenge run <name>` | Run challenge |
|
|
668
724
|
| `kradle experiment create <name>` | Create new experiment |
|