@dcl-regenesislabs/opendcl 0.1.0-22234509684.commit-63dfd19

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.
Files changed (54) hide show
  1. package/README.md +234 -0
  2. package/context/components-reference.md +113 -0
  3. package/context/open-source-3d-assets.md +705 -0
  4. package/context/sdk7-complete-reference.md +3684 -0
  5. package/context/sdk7-examples.md +1709 -0
  6. package/dist/index.d.ts +8 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +76 -0
  9. package/dist/index.js.map +1 -0
  10. package/dist/scene-context.d.ts +97 -0
  11. package/dist/scene-context.d.ts.map +1 -0
  12. package/dist/scene-context.js +203 -0
  13. package/dist/scene-context.js.map +1 -0
  14. package/dist/utils.d.ts +2 -0
  15. package/dist/utils.d.ts.map +1 -0
  16. package/dist/utils.js +4 -0
  17. package/dist/utils.js.map +1 -0
  18. package/extensions/dcl-context.ts +123 -0
  19. package/extensions/dcl-deploy.ts +89 -0
  20. package/extensions/dcl-header.ts +162 -0
  21. package/extensions/dcl-init.ts +62 -0
  22. package/extensions/dcl-preview.ts +144 -0
  23. package/extensions/dcl-setup-ollama.ts +312 -0
  24. package/extensions/dcl-setup.ts +96 -0
  25. package/extensions/dcl-status.ts +88 -0
  26. package/extensions/dcl-tasks.ts +102 -0
  27. package/extensions/dcl-update-check.ts +79 -0
  28. package/extensions/dcl-validate.ts +80 -0
  29. package/extensions/plan-mode/index.ts +340 -0
  30. package/extensions/plan-mode/utils.ts +168 -0
  31. package/extensions/process-registry.ts +25 -0
  32. package/extensions/scene-utils.ts +31 -0
  33. package/package.json +65 -0
  34. package/prompts/explain.md +16 -0
  35. package/prompts/review.md +19 -0
  36. package/prompts/system.md +126 -0
  37. package/skills/add-3d-models/SKILL.md +115 -0
  38. package/skills/add-interactivity/SKILL.md +176 -0
  39. package/skills/advanced-input/SKILL.md +238 -0
  40. package/skills/advanced-rendering/SKILL.md +235 -0
  41. package/skills/animations-tweens/SKILL.md +173 -0
  42. package/skills/audio-video/SKILL.md +167 -0
  43. package/skills/authoritative-server/SKILL.md +329 -0
  44. package/skills/build-ui/SKILL.md +231 -0
  45. package/skills/camera-control/SKILL.md +199 -0
  46. package/skills/create-scene/SKILL.md +67 -0
  47. package/skills/deploy-scene/SKILL.md +106 -0
  48. package/skills/deploy-worlds/SKILL.md +107 -0
  49. package/skills/lighting-environment/SKILL.md +216 -0
  50. package/skills/multiplayer-sync/SKILL.md +132 -0
  51. package/skills/nft-blockchain/SKILL.md +246 -0
  52. package/skills/optimize-scene/SKILL.md +160 -0
  53. package/skills/player-avatar/SKILL.md +239 -0
  54. package/skills/smart-items/SKILL.md +181 -0
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenDCL — AI coding assistant for Decentraland SDK7 scene development.
4
+ *
5
+ * Wraps pi-coding-agent with Decentraland-specific system prompt, skills, and extensions.
6
+ */
7
+ export {};
8
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;GAIG"}
package/dist/index.js ADDED
@@ -0,0 +1,76 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * OpenDCL — AI coding assistant for Decentraland SDK7 scene development.
4
+ *
5
+ * Wraps pi-coding-agent with Decentraland-specific system prompt, skills, and extensions.
6
+ */
7
+ import { main, InteractiveMode } from "@mariozechner/pi-coding-agent";
8
+ import { isDev } from "./utils.js";
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
10
+ import { homedir } from "node:os";
11
+ import { fileURLToPath } from "node:url";
12
+ import { dirname, join } from "node:path";
13
+ const __filename = fileURLToPath(import.meta.url);
14
+ const __dirname = dirname(__filename);
15
+ const packageDir = join(__dirname, "..");
16
+ // Use ~/.opendcl/agent/ for settings, sessions, etc. (separate from pi's ~/.pi/agent/)
17
+ const agentDir = join(homedir(), ".opendcl", "agent");
18
+ if (!process.env.PI_CODING_AGENT_DIR) {
19
+ process.env.PI_CODING_AGENT_DIR = agentDir;
20
+ }
21
+ // Ensure default settings exist (hide thinking blocks for a cleaner UI)
22
+ const settingsPath = join(agentDir, "settings.json");
23
+ if (!existsSync(settingsPath)) {
24
+ mkdirSync(agentDir, { recursive: true });
25
+ writeFileSync(settingsPath, JSON.stringify({ hideThinkingBlock: true }, null, 2) + "\n");
26
+ }
27
+ // Build args: start with user's CLI args
28
+ const args = process.argv.slice(2);
29
+ // Inject DCL system prompt (read from prompts/system.md, strip YAML frontmatter)
30
+ if (!args.includes("--system-prompt")) {
31
+ const raw = readFileSync(join(packageDir, "prompts/system.md"), "utf-8");
32
+ const systemPrompt = raw.replace(/^---\n[\s\S]*?\n---\n/, "").trim();
33
+ args.push("--system-prompt", systemPrompt);
34
+ }
35
+ // Load our extensions
36
+ const extDir = join(packageDir, "extensions");
37
+ const extensions = [
38
+ "dcl-context.ts",
39
+ "dcl-preview.ts",
40
+ "dcl-init.ts",
41
+ "dcl-deploy.ts",
42
+ "dcl-setup.ts",
43
+ "dcl-setup-ollama.ts",
44
+ "dcl-validate.ts",
45
+ "dcl-header.ts",
46
+ "dcl-update-check.ts",
47
+ "dcl-status.ts",
48
+ "dcl-tasks.ts",
49
+ ];
50
+ for (const ext of extensions) {
51
+ args.push("-e", join(extDir, ext));
52
+ }
53
+ args.push("-e", join(extDir, "plan-mode/index.ts"));
54
+ // Load all skill directories
55
+ args.push("--skill", join(packageDir, "skills"));
56
+ // Load prompt templates (review, explain — NOT system.md since that's the system prompt)
57
+ args.push("--prompt-template", join(packageDir, "prompts/review.md"));
58
+ args.push("--prompt-template", join(packageDir, "prompts/explain.md"));
59
+ // Suppress pi's built-in update notification in npm installs (it tells users to
60
+ // install pi directly). In local dev (ENV=dev) we keep it visible.
61
+ if (!isDev()) {
62
+ process.env.PI_SKIP_VERSION_CHECK = "1";
63
+ }
64
+ // Suppress pi's generic "No models available" warning — our dcl-setup-ollama
65
+ // extension shows a more helpful message that mentions /setup-ollama.
66
+ const _showWarning = InteractiveMode.prototype.showWarning;
67
+ InteractiveMode.prototype.showWarning = function (msg) {
68
+ if (msg.startsWith("No models available"))
69
+ return;
70
+ _showWarning.call(this, msg);
71
+ };
72
+ main(args).catch((err) => {
73
+ console.error("OpenDCL fatal error:", err);
74
+ process.exit(1);
75
+ });
76
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,97 @@
1
+ /**
2
+ * Scene context detection for Decentraland projects.
3
+ *
4
+ * Detects scene.json, package.json, and entry point files to provide
5
+ * contextual information to the AI agent about the current project.
6
+ */
7
+ export interface SceneJson {
8
+ ecs7?: boolean;
9
+ runtimeVersion?: string;
10
+ display?: {
11
+ title?: string;
12
+ description?: string;
13
+ navmapThumbnail?: string;
14
+ favicon?: string;
15
+ };
16
+ scene?: {
17
+ parcels?: string[];
18
+ base?: string;
19
+ };
20
+ main?: string;
21
+ spawnPoints?: Array<{
22
+ name?: string;
23
+ default?: boolean;
24
+ position?: {
25
+ x: number | number[];
26
+ y: number | number[];
27
+ z: number | number[];
28
+ };
29
+ cameraTarget?: {
30
+ x: number;
31
+ y: number;
32
+ z: number;
33
+ };
34
+ }>;
35
+ requiredPermissions?: string[];
36
+ worldConfiguration?: {
37
+ name?: string;
38
+ [key: string]: unknown;
39
+ };
40
+ [key: string]: unknown;
41
+ }
42
+ export interface SceneContext {
43
+ /** Whether a valid scene.json was found */
44
+ hasScene: boolean;
45
+ /** Absolute path to the scene root directory */
46
+ sceneRoot?: string;
47
+ /** Parsed scene.json content */
48
+ sceneJson?: SceneJson;
49
+ /** Scene display title */
50
+ title?: string;
51
+ /** Scene description */
52
+ description?: string;
53
+ /** Scene parcels list */
54
+ parcels?: string[];
55
+ /** Base parcel */
56
+ base?: string;
57
+ /** Scene size in meters (width x depth) */
58
+ sizeMeters?: {
59
+ width: number;
60
+ depth: number;
61
+ };
62
+ /** Number of parcels */
63
+ parcelCount?: number;
64
+ /** Main entry point from scene.json */
65
+ main?: string;
66
+ /** Detected source entry point file (e.g., src/index.ts) */
67
+ entryPoint?: string;
68
+ /** @dcl/sdk version from package.json */
69
+ sdkVersion?: string;
70
+ /** Whether node_modules exists */
71
+ needsInstall?: boolean;
72
+ /** Whether this is a World (vs Genesis City) deployment */
73
+ isWorld?: boolean;
74
+ /** World name if applicable */
75
+ worldName?: string;
76
+ /** Parse error if scene.json was malformed */
77
+ parseError?: string;
78
+ /** Whether this appears to be an SDK6 (legacy) scene */
79
+ isLegacySdk6?: boolean;
80
+ }
81
+ /**
82
+ * Calculate scene dimensions from parcels.
83
+ * Each parcel is 16x16 meters.
84
+ */
85
+ export declare function calculateSceneSize(parcels: string[]): {
86
+ width: number;
87
+ depth: number;
88
+ };
89
+ /**
90
+ * Detect and return the scene context for the given directory.
91
+ */
92
+ export declare function detectSceneContext(cwd: string): Promise<SceneContext>;
93
+ /**
94
+ * Format the scene context as a string for injection into the system prompt.
95
+ */
96
+ export declare function formatSceneContext(ctx: SceneContext): string;
97
+ //# sourceMappingURL=scene-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-context.d.ts","sourceRoot":"","sources":["../src/scene-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Scene context detection for Decentraland projects.
3
+ *
4
+ * Detects scene.json, package.json, and entry point files to provide
5
+ * contextual information to the AI agent about the current project.
6
+ */
7
+ import { readFile, access } from "node:fs/promises";
8
+ import { join, dirname, resolve } from "node:path";
9
+ /**
10
+ * Calculate scene dimensions from parcels.
11
+ * Each parcel is 16x16 meters.
12
+ */
13
+ export function calculateSceneSize(parcels) {
14
+ if (parcels.length === 0)
15
+ return { width: 16, depth: 16 };
16
+ const coords = parcels.map((p) => {
17
+ const [x, z] = p.split(",").map(Number);
18
+ return { x, z };
19
+ });
20
+ const xs = coords.map((c) => c.x);
21
+ const zs = coords.map((c) => c.z);
22
+ const minX = Math.min(...xs);
23
+ const maxX = Math.max(...xs);
24
+ const minZ = Math.min(...zs);
25
+ const maxZ = Math.max(...zs);
26
+ return {
27
+ width: (maxX - minX + 1) * 16,
28
+ depth: (maxZ - minZ + 1) * 16,
29
+ };
30
+ }
31
+ async function fileExists(path) {
32
+ try {
33
+ await access(path);
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
40
+ async function readJsonFile(path) {
41
+ try {
42
+ let content = await readFile(path, "utf-8");
43
+ // Handle UTF-8 BOM
44
+ if (content.charCodeAt(0) === 0xfeff) {
45
+ content = content.slice(1);
46
+ }
47
+ return JSON.parse(content);
48
+ }
49
+ catch {
50
+ return null;
51
+ }
52
+ }
53
+ /**
54
+ * Walk up from the given directory to find scene.json.
55
+ */
56
+ async function findSceneRoot(startDir) {
57
+ let current = resolve(startDir);
58
+ // Walk up at most 10 levels
59
+ for (let i = 0; i < 10; i++) {
60
+ if (await fileExists(join(current, "scene.json"))) {
61
+ return current;
62
+ }
63
+ const parent = dirname(current);
64
+ if (parent === current)
65
+ break; // filesystem root
66
+ current = parent;
67
+ }
68
+ return null;
69
+ }
70
+ /**
71
+ * Find the entry point TypeScript file for the scene.
72
+ */
73
+ async function findEntryPoint(sceneRoot, main) {
74
+ // Common entry point locations
75
+ const candidates = [
76
+ "src/index.ts",
77
+ "src/game.ts",
78
+ "src/index.tsx",
79
+ "src/game.tsx",
80
+ ];
81
+ // If main is specified, try to derive the TS source from it
82
+ if (main) {
83
+ const tsPath = main.replace(/^bin\//, "src/").replace(/\.js$/, ".ts");
84
+ if (!candidates.includes(tsPath)) {
85
+ candidates.unshift(tsPath);
86
+ }
87
+ }
88
+ for (const candidate of candidates) {
89
+ if (await fileExists(join(sceneRoot, candidate))) {
90
+ return candidate;
91
+ }
92
+ }
93
+ return undefined;
94
+ }
95
+ /**
96
+ * Detect and return the scene context for the given directory.
97
+ */
98
+ export async function detectSceneContext(cwd) {
99
+ const sceneRoot = await findSceneRoot(cwd);
100
+ if (!sceneRoot) {
101
+ return { hasScene: false };
102
+ }
103
+ // Parse scene.json
104
+ let sceneJson;
105
+ try {
106
+ const content = await readFile(join(sceneRoot, "scene.json"), "utf-8");
107
+ const cleaned = content.charCodeAt(0) === 0xfeff ? content.slice(1) : content;
108
+ sceneJson = JSON.parse(cleaned);
109
+ }
110
+ catch (e) {
111
+ return {
112
+ hasScene: false,
113
+ sceneRoot,
114
+ parseError: `Failed to parse scene.json: ${e instanceof Error ? e.message : String(e)}`,
115
+ };
116
+ }
117
+ // Check for legacy SDK6
118
+ const isLegacySdk6 = sceneJson.ecs7 !== true && sceneJson.runtimeVersion !== "7";
119
+ // Parse package.json for SDK version
120
+ const pkgJson = await readJsonFile(join(sceneRoot, "package.json"));
121
+ const sdkVersion = pkgJson?.dependencies?.["@dcl/sdk"] ??
122
+ pkgJson?.devDependencies?.["@dcl/sdk"] ??
123
+ undefined;
124
+ // Check node_modules
125
+ const hasNodeModules = await fileExists(join(sceneRoot, "node_modules"));
126
+ // Find entry point
127
+ const entryPoint = await findEntryPoint(sceneRoot, sceneJson.main);
128
+ // Calculate scene size
129
+ const parcels = sceneJson.scene?.parcels ?? [];
130
+ const sizeMeters = calculateSceneSize(parcels);
131
+ // Check for World configuration
132
+ const isWorld = !!sceneJson.worldConfiguration;
133
+ const worldName = sceneJson.worldConfiguration?.name;
134
+ return {
135
+ hasScene: true,
136
+ sceneRoot,
137
+ sceneJson,
138
+ title: sceneJson.display?.title,
139
+ description: sceneJson.display?.description,
140
+ parcels,
141
+ base: sceneJson.scene?.base,
142
+ sizeMeters,
143
+ parcelCount: parcels.length,
144
+ main: sceneJson.main,
145
+ entryPoint,
146
+ sdkVersion,
147
+ needsInstall: !hasNodeModules,
148
+ isWorld,
149
+ worldName,
150
+ isLegacySdk6,
151
+ };
152
+ }
153
+ /**
154
+ * Format the scene context as a string for injection into the system prompt.
155
+ */
156
+ export function formatSceneContext(ctx) {
157
+ if (!ctx.hasScene) {
158
+ if (ctx.parseError) {
159
+ return `## Current Project Status
160
+ **Error**: ${ctx.parseError}
161
+ The scene.json file exists but could not be parsed. Help the user fix it.`;
162
+ }
163
+ return `## Current Project Status
164
+ **No Decentraland scene detected** in the current directory.
165
+ You are in an empty folder. **You must run \`/init\` first** to scaffold the project with the official SDK template before writing any scene code. Never manually create scene.json, package.json, or tsconfig.json — \`/init\` generates the correct versions.
166
+ After \`/init\`, customize scene.json and src/index.ts based on what the user wants to build.`;
167
+ }
168
+ if (ctx.isLegacySdk6) {
169
+ return `## Current Project Status
170
+ **Legacy SDK6 scene detected.** This scene uses the older SDK6 format.
171
+ OpenDCL supports SDK7 only. Suggest the user migrate to SDK7 or create a new SDK7 scene.
172
+ Migration guide: https://docs.decentraland.org/creator/sdk7/sdk7-migration-guide/`;
173
+ }
174
+ const lines = ["## Current Project"];
175
+ if (ctx.title)
176
+ lines.push(`- **Title**: ${ctx.title}`);
177
+ if (ctx.description)
178
+ lines.push(`- **Description**: ${ctx.description}`);
179
+ if (ctx.sceneRoot)
180
+ lines.push(`- **Root**: ${ctx.sceneRoot}`);
181
+ if (ctx.sdkVersion)
182
+ lines.push(`- **SDK Version**: @dcl/sdk@${ctx.sdkVersion}`);
183
+ if (ctx.entryPoint)
184
+ lines.push(`- **Entry Point**: ${ctx.entryPoint}`);
185
+ if (ctx.main)
186
+ lines.push(`- **Main (compiled)**: ${ctx.main}`);
187
+ if (ctx.parcels && ctx.parcels.length > 0) {
188
+ lines.push(`- **Parcels**: ${ctx.parcels.join(", ")} (${ctx.parcelCount} parcel${ctx.parcelCount !== 1 ? "s" : ""})`);
189
+ if (ctx.base)
190
+ lines.push(`- **Base Parcel**: ${ctx.base}`);
191
+ if (ctx.sizeMeters)
192
+ lines.push(`- **Scene Size**: ${ctx.sizeMeters.width}m x ${ctx.sizeMeters.depth}m`);
193
+ }
194
+ if (ctx.isWorld) {
195
+ lines.push(`- **Deployment**: Decentraland World${ctx.worldName ? ` (${ctx.worldName})` : ""}`);
196
+ }
197
+ if (ctx.needsInstall) {
198
+ lines.push("");
199
+ lines.push("**Warning**: `node_modules/` not found. The user needs to run `npm install` before building.");
200
+ }
201
+ return lines.join("\n");
202
+ }
203
+ //# sourceMappingURL=scene-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scene-context.js","sourceRoot":"","sources":["../src/scene-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export declare function isDev(): boolean;
2
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,wBAAgB,KAAK,IAAI,OAAO,CAE/B"}
package/dist/utils.js ADDED
@@ -0,0 +1,4 @@
1
+ export function isDev() {
2
+ return process.env.ENV === "dev";
3
+ }
4
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,KAAK;IACnB,OAAO,OAAO,CAAC,GAAG,CAAC,GAAG,KAAK,KAAK,CAAC;AACnC,CAAC"}
@@ -0,0 +1,123 @@
1
+ /**
2
+ * DCL Context Extension
3
+ *
4
+ * Auto-detects Decentraland scene projects and injects project metadata
5
+ * into the system prompt so the agent has context about the current scene.
6
+ */
7
+
8
+ import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
9
+ import { readFile } from "node:fs/promises";
10
+ import { join } from "node:path";
11
+ import { fileExists, findSceneRoot } from "./scene-utils.js";
12
+
13
+ interface SceneJson {
14
+ ecs7?: boolean;
15
+ runtimeVersion?: string;
16
+ display?: { title?: string; description?: string };
17
+ scene?: { parcels?: string[]; base?: string };
18
+ main?: string;
19
+ worldConfiguration?: { name?: string; [key: string]: unknown };
20
+ [key: string]: unknown;
21
+ }
22
+
23
+ function calculateSceneSize(parcels: string[]): { width: number; depth: number } {
24
+ if (parcels.length === 0) return { width: 16, depth: 16 };
25
+ const coords = parcels.map((p) => {
26
+ const [x, z] = p.split(",").map(Number);
27
+ return { x, z };
28
+ });
29
+ const xs = coords.map((c) => c.x);
30
+ const zs = coords.map((c) => c.z);
31
+ return {
32
+ width: (Math.max(...xs) - Math.min(...xs) + 1) * 16,
33
+ depth: (Math.max(...zs) - Math.min(...zs) + 1) * 16,
34
+ };
35
+ }
36
+
37
+ const extension: ExtensionFactory = (pi) => {
38
+ pi.on("before_agent_start", async (event, ctx) => {
39
+ const cwd = ctx.cwd;
40
+ const sceneRoot = await findSceneRoot(cwd);
41
+
42
+ let sceneContext = "";
43
+
44
+ if (!sceneRoot) {
45
+ sceneContext = `\n## Current Project Status
46
+ **No Decentraland scene detected** in the current directory.
47
+ You are in an empty folder. **You must run \`/init\` first** to scaffold the project with the official SDK template before writing any scene code. Never manually create scene.json, package.json, or tsconfig.json — \`/init\` generates the correct versions.
48
+ After \`/init\`, customize scene.json and src/index.ts based on what the user wants to build.\n`;
49
+ } else {
50
+ try {
51
+ let content = await readFile(join(sceneRoot, "scene.json"), "utf-8");
52
+ if (content.charCodeAt(0) === 0xfeff) content = content.slice(1);
53
+ const sceneJson: SceneJson = JSON.parse(content);
54
+
55
+ // Check for legacy SDK6
56
+ if (sceneJson.ecs7 !== true && sceneJson.runtimeVersion !== "7") {
57
+ sceneContext = `\n## Current Project Status
58
+ **Legacy SDK6 scene detected.** This scene uses the older SDK6 format.
59
+ OpenDCL supports SDK7 only. Suggest the user migrate to SDK7.
60
+ Migration guide: https://docs.decentraland.org/creator/sdk7/sdk7-migration-guide/\n`;
61
+ } else {
62
+ const lines: string[] = ["\n## Current Project"];
63
+
64
+ if (sceneJson.display?.title) lines.push(`- **Title**: ${sceneJson.display.title}`);
65
+ if (sceneJson.display?.description) lines.push(`- **Description**: ${sceneJson.display.description}`);
66
+ lines.push(`- **Root**: ${sceneRoot}`);
67
+
68
+ // SDK version from package.json
69
+ try {
70
+ const pkgContent = await readFile(join(sceneRoot, "package.json"), "utf-8");
71
+ const pkg = JSON.parse(pkgContent);
72
+ const sdkVersion = pkg.dependencies?.["@dcl/sdk"] ?? pkg.devDependencies?.["@dcl/sdk"];
73
+ if (sdkVersion) lines.push(`- **SDK Version**: @dcl/sdk@${sdkVersion}`);
74
+ } catch {
75
+ // No package.json
76
+ }
77
+
78
+ // Entry point
79
+ for (const candidate of ["src/index.ts", "src/game.ts", "src/index.tsx"]) {
80
+ if (await fileExists(join(sceneRoot, candidate))) {
81
+ lines.push(`- **Entry Point**: ${candidate}`);
82
+ break;
83
+ }
84
+ }
85
+
86
+ if (sceneJson.main) lines.push(`- **Main (compiled)**: ${sceneJson.main}`);
87
+
88
+ const parcels = sceneJson.scene?.parcels ?? [];
89
+ if (parcels.length > 0) {
90
+ lines.push(`- **Parcels**: ${parcels.join(", ")} (${parcels.length} parcel${parcels.length !== 1 ? "s" : ""})`);
91
+ if (sceneJson.scene?.base) lines.push(`- **Base Parcel**: ${sceneJson.scene.base}`);
92
+ const size = calculateSceneSize(parcels);
93
+ lines.push(`- **Scene Size**: ${size.width}m x ${size.depth}m`);
94
+ }
95
+
96
+ if (sceneJson.worldConfiguration) {
97
+ const worldName = sceneJson.worldConfiguration.name;
98
+ lines.push(`- **Deployment**: Decentraland World${worldName ? ` (${worldName})` : ""}`);
99
+ }
100
+
101
+ // Check node_modules
102
+ if (!(await fileExists(join(sceneRoot, "node_modules")))) {
103
+ lines.push("");
104
+ lines.push("**Warning**: `node_modules/` not found. The user needs to run `npm install` before building.");
105
+ }
106
+
107
+ sceneContext = lines.join("\n") + "\n";
108
+ }
109
+ } catch (e) {
110
+ sceneContext = `\n## Current Project Status
111
+ **Error**: Failed to parse scene.json: ${e instanceof Error ? e.message : String(e)}
112
+ The scene.json file exists but could not be parsed. Help the user fix it.\n`;
113
+ }
114
+ }
115
+
116
+ // Inject scene context into the system prompt
117
+ return {
118
+ systemPrompt: event.systemPrompt + sceneContext,
119
+ };
120
+ });
121
+ };
122
+
123
+ export default extension;
@@ -0,0 +1,89 @@
1
+ /**
2
+ * DCL Deploy Extension
3
+ *
4
+ * Registers the `deploy` tool (LLM-callable) and `/deploy` command that deploys
5
+ * a Decentraland scene using `npx @dcl/sdk-commands deploy`. Supports both
6
+ * Genesis City and Worlds deployment.
7
+ */
8
+
9
+ import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
10
+ import { Type } from "@sinclair/typebox";
11
+ import { readFile } from "node:fs/promises";
12
+ import { join } from "node:path";
13
+ import { fileExists, findSceneRoot } from "./scene-utils.js";
14
+
15
+ const WORLDS_CONTENT_SERVER = "https://worlds-content-server.decentraland.org";
16
+
17
+ async function hasWorldConfiguration(sceneRoot: string): Promise<boolean> {
18
+ try {
19
+ let content = await readFile(join(sceneRoot, "scene.json"), "utf-8");
20
+ if (content.charCodeAt(0) === 0xfeff) content = content.slice(1);
21
+ const sceneJson = JSON.parse(content);
22
+ return Boolean(sceneJson.worldConfiguration?.name);
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+
28
+ async function deployScene(
29
+ cwd: string,
30
+ pi: { exec(cmd: string, args: string[], opts?: unknown): Promise<{ code: number; stdout: string; stderr: string }> }
31
+ ): Promise<{ message: string; isError?: boolean }> {
32
+ const sceneRoot = await findSceneRoot(cwd);
33
+
34
+ if (!sceneRoot) {
35
+ return { message: "No scene.json found. Create a scene first with /init.", isError: true };
36
+ }
37
+
38
+ if (!(await fileExists(join(sceneRoot, "node_modules")))) {
39
+ return { message: "node_modules not found. Run 'npm install' first.", isError: true };
40
+ }
41
+
42
+ const isWorldDeploy = await hasWorldConfiguration(sceneRoot);
43
+ const deployArgs = ["@dcl/sdk-commands", "deploy"];
44
+ if (isWorldDeploy) {
45
+ deployArgs.push("--target-content", WORLDS_CONTENT_SERVER);
46
+ }
47
+
48
+ const targetLabel = isWorldDeploy ? "World" : "Genesis City";
49
+
50
+ try {
51
+ const result = await pi.exec("npx", deployArgs, {
52
+ cwd: sceneRoot,
53
+ timeout: 120000,
54
+ });
55
+
56
+ if (result.code === 0) {
57
+ return { message: `Scene deployed to ${targetLabel} successfully!` };
58
+ } else {
59
+ return { message: `Deploy failed (exit code ${result.code}): ${result.stderr || result.stdout}`, isError: true };
60
+ }
61
+ } catch (err) {
62
+ return { message: `Failed to deploy scene: ${err instanceof Error ? err.message : String(err)}`, isError: true };
63
+ }
64
+ }
65
+
66
+ const extension: ExtensionFactory = (pi) => {
67
+ pi.registerTool({
68
+ name: "deploy",
69
+ label: "Deploy Scene",
70
+ description:
71
+ "Deploy the Decentraland scene. Auto-detects Genesis City vs World from scene.json worldConfiguration. Use when user wants to deploy, publish, or go live.",
72
+ parameters: Type.Object({}),
73
+ async execute(_id, _params, _signal, _onUpdate, ctx) {
74
+ const result = await deployScene(ctx.cwd, pi);
75
+ return { content: [{ type: "text" as const, text: result.message }], details: undefined };
76
+ },
77
+ });
78
+
79
+ pi.registerCommand("deploy", {
80
+ description: "Deploy the scene to Genesis City or a World",
81
+ handler: async (_args, ctx) => {
82
+ ctx.ui.notify("Deploying scene...", "info");
83
+ const result = await deployScene(ctx.cwd, pi);
84
+ ctx.ui.notify(result.message, result.isError ? "error" : "info");
85
+ },
86
+ });
87
+ };
88
+
89
+ export default extension;