@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.
- package/README.md +234 -0
- package/context/components-reference.md +113 -0
- package/context/open-source-3d-assets.md +705 -0
- package/context/sdk7-complete-reference.md +3684 -0
- package/context/sdk7-examples.md +1709 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +76 -0
- package/dist/index.js.map +1 -0
- package/dist/scene-context.d.ts +97 -0
- package/dist/scene-context.d.ts.map +1 -0
- package/dist/scene-context.js +203 -0
- package/dist/scene-context.js.map +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +4 -0
- package/dist/utils.js.map +1 -0
- package/extensions/dcl-context.ts +123 -0
- package/extensions/dcl-deploy.ts +89 -0
- package/extensions/dcl-header.ts +162 -0
- package/extensions/dcl-init.ts +62 -0
- package/extensions/dcl-preview.ts +144 -0
- package/extensions/dcl-setup-ollama.ts +312 -0
- package/extensions/dcl-setup.ts +96 -0
- package/extensions/dcl-status.ts +88 -0
- package/extensions/dcl-tasks.ts +102 -0
- package/extensions/dcl-update-check.ts +79 -0
- package/extensions/dcl-validate.ts +80 -0
- package/extensions/plan-mode/index.ts +340 -0
- package/extensions/plan-mode/utils.ts +168 -0
- package/extensions/process-registry.ts +25 -0
- package/extensions/scene-utils.ts +31 -0
- package/package.json +65 -0
- package/prompts/explain.md +16 -0
- package/prompts/review.md +19 -0
- package/prompts/system.md +126 -0
- package/skills/add-3d-models/SKILL.md +115 -0
- package/skills/add-interactivity/SKILL.md +176 -0
- package/skills/advanced-input/SKILL.md +238 -0
- package/skills/advanced-rendering/SKILL.md +235 -0
- package/skills/animations-tweens/SKILL.md +173 -0
- package/skills/audio-video/SKILL.md +167 -0
- package/skills/authoritative-server/SKILL.md +329 -0
- package/skills/build-ui/SKILL.md +231 -0
- package/skills/camera-control/SKILL.md +199 -0
- package/skills/create-scene/SKILL.md +67 -0
- package/skills/deploy-scene/SKILL.md +106 -0
- package/skills/deploy-worlds/SKILL.md +107 -0
- package/skills/lighting-environment/SKILL.md +216 -0
- package/skills/multiplayer-sync/SKILL.md +132 -0
- package/skills/nft-blockchain/SKILL.md +246 -0
- package/skills/optimize-scene/SKILL.md +160 -0
- package/skills/player-avatar/SKILL.md +239 -0
- package/skills/smart-items/SKILL.md +181 -0
package/dist/index.d.ts
ADDED
|
@@ -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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"}
|
package/dist/utils.d.ts
ADDED
|
@@ -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 @@
|
|
|
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;
|