@kradle/cli 0.2.7 → 0.2.8

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
@@ -9,6 +9,7 @@ Kradle's CLI for managing Minecraft challenges, experiments, agents, and more!
9
9
  * [Configuration](#configuration)
10
10
  * [Challenge](#challenge-commands)
11
11
  * [Experiments](#experiment-commands)
12
+ * [Worlds](#world-commands)
12
13
  * [Agents](#agent-commands)
13
14
  * [AI Docs](#ai-docs-commands)
14
15
  * [Publishing a New Version](#publishing-a-new-version)
@@ -214,6 +215,61 @@ List all local experiments:
214
215
  kradle experiment list
215
216
  ```
216
217
 
218
+ ## World Commands
219
+
220
+ Worlds are Minecraft world saves that can be used as starting points for challenges.
221
+
222
+ ### Import World
223
+
224
+ Import a Minecraft world folder from your local filesystem:
225
+
226
+ ```bash
227
+ kradle world import ~/minecraft/saves/MyWorld # Auto-generate slug from folder name
228
+ kradle world import ~/minecraft/saves/MyWorld --as my-world # Specify custom slug
229
+ ```
230
+
231
+ This validates the folder contains `level.dat`, packages it as a tarball, creates a `config.ts`, and uploads to the cloud.
232
+
233
+ ### Push World
234
+
235
+ Upload world config and tarball to the cloud:
236
+
237
+ ```bash
238
+ kradle world push my-world # Push single world
239
+ kradle world push my-world another-world # Push multiple worlds
240
+ kradle world push --all # Push all local worlds
241
+ ```
242
+
243
+ ### Pull World
244
+
245
+ Download a world from the cloud:
246
+
247
+ ```bash
248
+ kradle world pull # Interactive selection
249
+ kradle world pull my-world # Pull specific world
250
+ kradle world pull username:their-world # Pull from another user
251
+ kradle world pull my-world --yes # Skip confirmation when overwriting
252
+ ```
253
+
254
+ ### List Worlds
255
+
256
+ List all worlds (local and cloud):
257
+
258
+ ```bash
259
+ kradle world list
260
+ ```
261
+
262
+ Shows sync status for each world (synced, cloud only, or local only).
263
+
264
+ ### Delete World
265
+
266
+ Delete a world locally, from the cloud, or both:
267
+
268
+ ```bash
269
+ kradle world delete my-world # Interactive confirmation
270
+ kradle world delete my-world --yes # Skip confirmation
271
+ ```
272
+
217
273
  ## Agent Commands
218
274
 
219
275
  ### List Agents
@@ -347,14 +403,16 @@ kradle-cli/
347
403
  │ │ ├── agent/ # Agent commands
348
404
  │ │ ├── ai-docs/ # AI documentation commands
349
405
  │ │ ├── challenge/ # Challenge management commands
350
- │ │ └── experiment/ # Experiment commands
406
+ │ │ ├── experiment/ # Experiment commands
407
+ │ │ └── world/ # World management commands
351
408
  │ └── lib/ # Core libraries
352
409
  │ └── experiment/ # Experiment system
353
410
  ├── tests/ # Integration tests
354
411
  │ ├── helpers/ # Test utilities
355
412
  │ └── integration/ # Integration test suites
356
413
  │ ├── challenge/ # Challenge command tests
357
- └── experiment/ # Experiment command tests
414
+ ├── experiment/ # Experiment command tests
415
+ │ └── world/ # World command tests
358
416
  └── static/ # Template files
359
417
  └── project_template/ # Files for kradle init
360
418
  ```
package/bin/run.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node --no-warnings
1
+ #!/usr/bin/env -S node --no-warnings
2
2
 
3
3
  import { execute } from "@oclif/core";
4
4
  import path from "node:path";
@@ -3,11 +3,13 @@ export default class Push extends Command {
3
3
  static description: string;
4
4
  static examples: string[];
5
5
  static args: {
6
- worldSlug: import("@oclif/core/interfaces").Arg<string>;
6
+ worldSlug: import("@oclif/core/interfaces").Arg<string | undefined>;
7
7
  };
8
8
  static flags: {
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
+ all: import("@oclif/core/interfaces").BooleanFlag<boolean>;
11
12
  };
13
+ static strict: boolean;
12
14
  run(): Promise<void>;
13
15
  }
@@ -1,4 +1,4 @@
1
- import { Command } from "@oclif/core";
1
+ import { Command, Flags, loadHelpClass } from "@oclif/core";
2
2
  import { Listr } from "listr2";
3
3
  import pc from "picocolors";
4
4
  import { ApiClient } from "../../lib/api-client.js";
@@ -7,58 +7,102 @@ import { getConfigFlags } from "../../lib/flags.js";
7
7
  import { World } from "../../lib/world.js";
8
8
  export default class Push extends Command {
9
9
  static description = "Upload a world (config + tarball) to the cloud";
10
- static examples = ["<%= config.bin %> <%= command.id %> my-world"];
10
+ static examples = [
11
+ "<%= config.bin %> <%= command.id %> my-world",
12
+ "<%= config.bin %> <%= command.id %> my-world my-other-world",
13
+ "<%= config.bin %> <%= command.id %> --all",
14
+ ];
11
15
  static args = {
12
- worldSlug: getWorldSlugArgument({ description: "World slug to push" }),
16
+ worldSlug: getWorldSlugArgument({
17
+ description: "World slug to push. Can be used multiple times to push multiple worlds at once. Incompatible with --all flag.",
18
+ required: false,
19
+ }),
13
20
  };
14
21
  static flags = {
22
+ all: Flags.boolean({ char: "a", description: "Push all worlds in the worlds directory", default: false }),
15
23
  ...getConfigFlags("api-key", "api-url"),
16
24
  };
25
+ static strict = false;
17
26
  async run() {
18
- const { args, flags } = await this.parse(Push);
19
- const world = new World(args.worldSlug);
20
- if (!world.exists()) {
21
- this.error(pc.red(`World "${args.worldSlug}" does not exist locally.`));
27
+ const { argv, flags } = await this.parse(Push);
28
+ if (flags.all && argv.length > 0) {
29
+ this.error(pc.red("Cannot use --all flag with world slugs"));
22
30
  }
23
- if (!world.tarballExists()) {
24
- this.error(pc.red(`World tarball not found at ${world.tarballPath}`));
31
+ if (!flags.all && argv.length === 0) {
32
+ await new (await loadHelpClass(this.config))(this.config).showHelp([Push.id]);
33
+ return;
25
34
  }
26
- const isValid = await world.validateTarball();
27
- if (!isValid) {
28
- this.error(pc.red(`Invalid world tarball: level.dat not found in ${world.tarballPath}`));
35
+ if (flags.all) {
36
+ this.log(pc.blue("Pushing all worlds"));
29
37
  }
38
+ const worldSlugs = flags.all ? await World.getLocalWorlds() : argv;
30
39
  const api = new ApiClient(flags["api-url"], flags["api-key"]);
31
- const config = await world.loadConfig();
32
- const tasks = new Listr([
33
- {
34
- title: "Ensuring world exists in cloud",
35
- task: async (_ctx, task) => {
36
- const exists = await api.worldExists(args.worldSlug);
37
- if (!exists) {
38
- await api.createWorld(args.worldSlug, config);
39
- task.title = "Created world in cloud";
40
- }
41
- else {
42
- task.title = "World exists in cloud";
43
- }
40
+ const failedWorlds = [];
41
+ for (const worldSlug of worldSlugs) {
42
+ this.log(pc.blue(`==== Pushing world: ${worldSlug} ====`));
43
+ const world = new World(worldSlug);
44
+ if (!world.exists()) {
45
+ const reason = `World "${worldSlug}" does not exist locally`;
46
+ this.log(pc.red(`${reason}. Skipping.`));
47
+ this.log();
48
+ failedWorlds.push({ slug: worldSlug, reason });
49
+ continue;
50
+ }
51
+ if (!world.tarballExists()) {
52
+ const reason = `World tarball not found at ${world.tarballPath}`;
53
+ this.log(pc.red(`${reason}. Skipping.`));
54
+ this.log();
55
+ failedWorlds.push({ slug: worldSlug, reason });
56
+ continue;
57
+ }
58
+ const isValid = await world.validateTarball();
59
+ if (!isValid) {
60
+ const reason = `Invalid world tarball: level.dat not found in ${world.tarballPath}`;
61
+ this.log(pc.red(`${reason}. Skipping.`));
62
+ this.log();
63
+ failedWorlds.push({ slug: worldSlug, reason });
64
+ continue;
65
+ }
66
+ const config = await world.loadConfig();
67
+ const tasks = new Listr([
68
+ {
69
+ title: "Ensuring world exists in cloud",
70
+ task: async (_ctx, task) => {
71
+ const exists = await api.worldExists(worldSlug);
72
+ if (!exists) {
73
+ await api.createWorld(worldSlug, config);
74
+ task.title = "Created world in cloud";
75
+ }
76
+ else {
77
+ task.title = "World exists in cloud";
78
+ }
79
+ },
44
80
  },
45
- },
46
- {
47
- title: "Uploading config",
48
- task: async (_ctx, task) => {
49
- await api.updateWorld(args.worldSlug, config);
50
- task.title = "Config uploaded";
81
+ {
82
+ title: "Uploading config",
83
+ task: async (_ctx, task) => {
84
+ await api.updateWorld(worldSlug, config);
85
+ task.title = "Config uploaded";
86
+ },
51
87
  },
52
- },
53
- {
54
- title: "Uploading world",
55
- task: async (_ctx, task) => {
56
- await api.uploadWorldFile(args.worldSlug, world.tarballPath);
57
- task.title = "World uploaded";
88
+ {
89
+ title: "Uploading world",
90
+ task: async (_ctx, task) => {
91
+ await api.uploadWorldFile(worldSlug, world.tarballPath);
92
+ task.title = "World uploaded";
93
+ },
58
94
  },
59
- },
60
- ]);
61
- await tasks.run();
62
- this.log(pc.green(`\n✓ World pushed: ${args.worldSlug}`));
95
+ ]);
96
+ await tasks.run();
97
+ this.log(pc.green(`✓ World pushed: ${worldSlug}`));
98
+ this.log();
99
+ }
100
+ if (failedWorlds.length > 0) {
101
+ this.log(pc.red("\nFailed to push the following worlds:"));
102
+ for (const { slug, reason } of failedWorlds) {
103
+ this.log(pc.red(` - ${slug}: ${reason}`));
104
+ }
105
+ this.error(pc.red(`\n${failedWorlds.length} world(s) failed to push`));
106
+ }
63
107
  }
64
108
  }
@@ -1092,16 +1092,25 @@
1092
1092
  "aliases": [],
1093
1093
  "args": {
1094
1094
  "worldSlug": {
1095
- "description": "World slug to push",
1095
+ "description": "World slug to push. Can be used multiple times to push multiple worlds at once. Incompatible with --all flag.",
1096
1096
  "name": "worldSlug",
1097
- "required": true
1097
+ "required": false
1098
1098
  }
1099
1099
  },
1100
1100
  "description": "Upload a world (config + tarball) to the cloud",
1101
1101
  "examples": [
1102
- "<%= config.bin %> <%= command.id %> my-world"
1102
+ "<%= config.bin %> <%= command.id %> my-world",
1103
+ "<%= config.bin %> <%= command.id %> my-world my-other-world",
1104
+ "<%= config.bin %> <%= command.id %> --all"
1103
1105
  ],
1104
1106
  "flags": {
1107
+ "all": {
1108
+ "char": "a",
1109
+ "description": "Push all worlds in the worlds directory",
1110
+ "name": "all",
1111
+ "allowNo": false,
1112
+ "type": "boolean"
1113
+ },
1105
1114
  "api-key": {
1106
1115
  "description": "Kradle API key",
1107
1116
  "env": "KRADLE_API_KEY",
@@ -1128,7 +1137,7 @@
1128
1137
  "pluginAlias": "@kradle/cli",
1129
1138
  "pluginName": "@kradle/cli",
1130
1139
  "pluginType": "core",
1131
- "strict": true,
1140
+ "strict": false,
1132
1141
  "enableJsonFlag": false,
1133
1142
  "isESM": true,
1134
1143
  "relativePath": [
@@ -1139,5 +1148,5 @@
1139
1148
  ]
1140
1149
  }
1141
1150
  },
1142
- "version": "0.2.7"
1151
+ "version": "0.2.8"
1143
1152
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kradle/cli",
3
- "version": "0.2.7",
3
+ "version": "0.2.8",
4
4
  "description": "Kradle's CLI. Manage challenges, experiments, agents and more!",
5
5
  "keywords": [
6
6
  "cli"
@@ -9,6 +9,7 @@ This document provides exhaustive documentation for LLM agents using the Kradle
9
9
  - [Project Initialization](#project-initialization)
10
10
  - [Challenge Commands](#challenge-commands)
11
11
  - [Experiment Commands](#experiment-commands)
12
+ - [World Commands](#world-commands)
12
13
  - [Agent Commands](#agent-commands)
13
14
  - [AI Docs Commands](#ai-docs-commands)
14
15
  - [Common Workflows](#common-workflows)
@@ -559,6 +560,208 @@ kradle experiment list
559
560
 
560
561
  ---
561
562
 
563
+ ## World Commands
564
+
565
+ Worlds are Minecraft world saves that can be used as starting points for challenges.
566
+
567
+ ### `kradle world import <path>`
568
+
569
+ Imports a Minecraft world folder from your local filesystem and packages it for use with Kradle.
570
+
571
+ **Usage:**
572
+ ```bash
573
+ kradle world import ~/minecraft/saves/MyWorld
574
+ kradle world import ~/minecraft/saves/MyWorld --as my-world
575
+ ```
576
+
577
+ **Arguments:**
578
+ | Argument | Description | Required |
579
+ |----------|-------------|----------|
580
+ | `path` | Path to the Minecraft world folder (must contain `level.dat`) | Yes |
581
+
582
+ **Flags:**
583
+ | Flag | Description | Default |
584
+ |------|-------------|---------|
585
+ | `--as` | Slug for the world (defaults to folder name converted to slug) | Auto-generated |
586
+
587
+ **What it does:**
588
+ 1. Validates the folder contains `level.dat`
589
+ 2. Creates `worlds/<slug>/` directory
590
+ 3. Packages world as `world.tar.gz`
591
+ 4. Creates `config.ts` with default metadata
592
+ 5. Uploads to cloud
593
+
594
+ **Re-importing:** If the world already exists locally, re-import updates the tarball while preserving `config.ts`.
595
+
596
+ **Examples:**
597
+ ```bash
598
+ # Import with auto-generated slug
599
+ kradle world import ~/minecraft/saves/MyWorld
600
+
601
+ # Import with custom slug
602
+ kradle world import ~/minecraft/saves/MyWorld --as custom-world
603
+ ```
604
+
605
+ ---
606
+
607
+ ### `kradle world push <slug...>`
608
+
609
+ Uploads world config and tarball to the cloud.
610
+
611
+ **Usage:**
612
+ ```bash
613
+ kradle world push <world-slug>
614
+ kradle world push <world-slug> <another-world>
615
+ kradle world push --all
616
+ ```
617
+
618
+ **Arguments:**
619
+ | Argument | Description | Required |
620
+ |----------|-------------|----------|
621
+ | `world-slug` | World slug(s) to push. Can specify multiple slugs. | Yes (unless `--all`) |
622
+
623
+ **Flags:**
624
+ | Flag | Short | Description | Default |
625
+ |------|-------|-------------|---------|
626
+ | `--all` | `-a` | Push all worlds in the worlds directory | false |
627
+
628
+ **What it does:**
629
+ 1. Creates the world in cloud if it doesn't exist
630
+ 2. Uploads `config.ts` metadata
631
+ 3. Uploads `world.tar.gz` tarball
632
+
633
+ **Examples:**
634
+ ```bash
635
+ # Push single world
636
+ kradle world push my-world
637
+
638
+ # Push multiple worlds
639
+ kradle world push my-world another-world
640
+
641
+ # Push all local worlds
642
+ kradle world push --all
643
+ ```
644
+
645
+ ---
646
+
647
+ ### `kradle world pull [slug]`
648
+
649
+ Downloads a world from the cloud.
650
+
651
+ **Usage:**
652
+ ```bash
653
+ kradle world pull
654
+ kradle world pull <world-slug>
655
+ kradle world pull <username>:<world-slug>
656
+ kradle world pull <world-slug> --yes
657
+ ```
658
+
659
+ **Arguments:**
660
+ | Argument | Description | Required |
661
+ |----------|-------------|----------|
662
+ | `world-slug` | World slug to pull. If omitted, shows interactive selection. | No |
663
+
664
+ **Flags:**
665
+ | Flag | Short | Description | Default |
666
+ |------|-------|-------------|---------|
667
+ | `--yes` | `-y` | Skip confirmation prompts when overwriting local files | false |
668
+
669
+ **Interactive mode (no slug argument):**
670
+ - Shows list of all cloud worlds (your own + team-kradle)
671
+ - Marks worlds that exist locally with "(local)" indicator
672
+ - Select a world to pull
673
+
674
+ **What it does:**
675
+ 1. Downloads `world.tar.gz` from cloud
676
+ 2. Creates `config.ts` from cloud metadata
677
+ 3. Places files in `worlds/<short-slug>/` directory
678
+
679
+ **Examples:**
680
+ ```bash
681
+ # Interactive selection
682
+ kradle world pull
683
+
684
+ # Pull specific world
685
+ kradle world pull my-world
686
+
687
+ # Pull from another user
688
+ kradle world pull username:their-world
689
+
690
+ # Pull without confirmation
691
+ kradle world pull my-world --yes
692
+ ```
693
+
694
+ ---
695
+
696
+ ### `kradle world list`
697
+
698
+ Lists all worlds (local and cloud).
699
+
700
+ **Usage:**
701
+ ```bash
702
+ kradle world list
703
+ ```
704
+
705
+ **Output format:**
706
+ ```
707
+ Worlds:
708
+
709
+ Status Slug Name
710
+ ------------------------------------------------------------------------------------------
711
+ ✓ synced username:my-world My World
712
+ ☁ cloud only username:cloud-world Cloud World
713
+ ⊡ local only username:local-world -
714
+
715
+ Total: 3 worlds
716
+ ```
717
+
718
+ **Status indicators:**
719
+ - `✓ synced` - Exists both locally and in cloud
720
+ - `☁ cloud only` - Only in cloud, not local
721
+ - `⊡ local only` - Only local, not in cloud
722
+
723
+ **Example:**
724
+ ```bash
725
+ kradle world list
726
+ ```
727
+
728
+ ---
729
+
730
+ ### `kradle world delete <slug>`
731
+
732
+ Deletes a world locally, from the cloud, or both.
733
+
734
+ **Usage:**
735
+ ```bash
736
+ kradle world delete <world-slug>
737
+ kradle world delete <world-slug> --yes
738
+ ```
739
+
740
+ **Arguments:**
741
+ | Argument | Description | Required |
742
+ |----------|-------------|----------|
743
+ | `world-slug` | World slug to delete | Yes |
744
+
745
+ **Flags:**
746
+ | Flag | Short | Description | Default |
747
+ |------|-------|-------------|---------|
748
+ | `--yes` | `-y` | Skip confirmation prompts | false |
749
+
750
+ **Interactive prompts (unless `--yes`):**
751
+ 1. Delete local files? (y/n)
752
+ 2. Delete from cloud? (y/n)
753
+
754
+ **Examples:**
755
+ ```bash
756
+ # Interactive deletion
757
+ kradle world delete my-world
758
+
759
+ # Force delete without prompts
760
+ kradle world delete my-world --yes
761
+ ```
762
+
763
+ ---
764
+
562
765
  ## Agent Commands
563
766
 
564
767
  ### `kradle agent list`
@@ -725,6 +928,12 @@ kradle challenge build --all --visibility public
725
928
  | `kradle experiment run <name>` | Run/resume experiment |
726
929
  | `kradle experiment recordings <name>` | Download recordings |
727
930
  | `kradle experiment list` | List experiments |
931
+ | `kradle world import <path>` | Import Minecraft world folder |
932
+ | `kradle world push <slug...>` | Push world(s) to cloud |
933
+ | `kradle world push --all` | Push all worlds |
934
+ | `kradle world pull [slug]` | Pull world from cloud |
935
+ | `kradle world list` | List all worlds |
936
+ | `kradle world delete <slug>` | Delete world |
728
937
  | `kradle agent list` | List available agents |
729
938
  | `kradle ai-docs cli` | Output CLI reference for LLMs |
730
939
  | `kradle ai-docs api` | Output API reference for LLMs |