@dcl-regenesislabs/opendcl 0.2.1-26238928766.commit-28648d7 → 0.2.1

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 (45) hide show
  1. package/README.md +3 -5
  2. package/context/sdk7-cheat-sheet.md +0 -4
  3. package/dist/index.js +12 -0
  4. package/dist/index.js.map +1 -1
  5. package/extensions/dcl-init.ts +6 -58
  6. package/extensions/dcl-setup-ollama.ts +312 -0
  7. package/package.json +4 -3
  8. package/prompts/system.md +41 -71
  9. package/skills/add-3d-models/SKILL.md +70 -120
  10. package/skills/add-interactivity/SKILL.md +2 -74
  11. package/skills/advanced-input/SKILL.md +1 -34
  12. package/skills/advanced-rendering/SKILL.md +9 -82
  13. package/skills/animations-tweens/SKILL.md +98 -203
  14. package/skills/audio-video/SKILL.md +83 -184
  15. package/skills/build-ui/SKILL.md +2 -25
  16. package/skills/camera-control/SKILL.md +7 -78
  17. package/skills/create-scene/SKILL.md +13 -56
  18. package/skills/deploy-scene/SKILL.md +0 -12
  19. package/skills/deploy-worlds/SKILL.md +0 -35
  20. package/skills/game-design/SKILL.md +2 -1
  21. package/skills/lighting-environment/SKILL.md +56 -103
  22. package/skills/multiplayer-sync/SKILL.md +2 -31
  23. package/skills/nft-blockchain/SKILL.md +40 -45
  24. package/skills/optimize-scene/SKILL.md +2 -7
  25. package/skills/player-avatar/SKILL.md +7 -133
  26. package/skills/scene-runtime/SKILL.md +5 -9
  27. package/skills/visual-feedback/SKILL.md +0 -1
  28. package/skills/audio-analysis/SKILL.md +0 -164
  29. package/skills/editor-gizmo/.gitignore +0 -11
  30. package/skills/editor-gizmo/SKILL.md +0 -222
  31. package/skills/editor-gizmo/src/__editor/camera.ts +0 -277
  32. package/skills/editor-gizmo/src/__editor/discovery.ts +0 -186
  33. package/skills/editor-gizmo/src/__editor/drag.ts +0 -265
  34. package/skills/editor-gizmo/src/__editor/gizmo.ts +0 -496
  35. package/skills/editor-gizmo/src/__editor/history.ts +0 -72
  36. package/skills/editor-gizmo/src/__editor/index.ts +0 -137
  37. package/skills/editor-gizmo/src/__editor/input.ts +0 -55
  38. package/skills/editor-gizmo/src/__editor/math-utils.ts +0 -114
  39. package/skills/editor-gizmo/src/__editor/persistence.ts +0 -113
  40. package/skills/editor-gizmo/src/__editor/selection.ts +0 -157
  41. package/skills/editor-gizmo/src/__editor/state.ts +0 -117
  42. package/skills/editor-gizmo/src/__editor/ui.tsx +0 -697
  43. package/skills/npcs/SKILL.md +0 -180
  44. package/skills/particle-system/SKILL.md +0 -222
  45. package/skills/player-physics/SKILL.md +0 -93
package/README.md CHANGED
@@ -26,7 +26,7 @@ The result: **more creators building more scenes, faster.**
26
26
  ## Features
27
27
 
28
28
  - **Branded header** — on startup, displays a block-character "Decentraland" ASCII art banner with version and working directory. Falls back to a compact text header on narrow terminals
29
- - **Multi-provider LLM support** — works with Claude, OpenAI, Google, OpenRouter, and more
29
+ - **Multi-provider LLM support** — works with Claude, OpenAI, Google, Ollama (free/local), OpenRouter, and more
30
30
  - **Scene-aware** — automatically detects your project's `scene.json`, SDK version, and entry points
31
31
  - **20 built-in skills** — scaffolding, 3D models, interactivity, UI, animations, multiplayer, authoritative server, audio/video, deployment (Genesis City & Worlds), optimization, camera control, lighting, player/avatar, NFT/blockchain, advanced rendering, advanced input, scene runtime, visual feedback, game design
32
32
  - **Integrated commands** — `/init` to scaffold, `/preview` to launch the dev server, `/tasks` to manage running processes, `/review` to audit code
@@ -99,7 +99,6 @@ This uses the open [skills](https://github.com/vercel-labs/skills) CLI to copy S
99
99
  | `/deploy` | Deploy the scene to Genesis City or a World (auto-detects from scene.json) |
100
100
  | `/tasks` | Interactively manage running background processes (preview server, etc.) |
101
101
  | `/review` | Review scene code for quality, performance, and SDK7 best practices |
102
- | `/save-editor` | Apply pending visual editor changes to scene source code |
103
102
  | `/explain <concept>` | Explain a Decentraland SDK7 concept (e.g., `/explain tweens`) |
104
103
 
105
104
  The agent also has a `screenshot` tool it can call automatically to see the running preview. On first use it asks for your permission to open a headless browser. The browser stays open for the entire session — no repeated logins.
@@ -130,7 +129,6 @@ OpenDCL loads domain-specific skills on demand based on what you're asking:
130
129
  | `scene-runtime` | Async tasks, fetch, timers, realm info, restricted actions, testing |
131
130
  | `visual-feedback` | Use the screenshot tool to see your scene, verify changes, iterate visually |
132
131
  | `game-design` | Plan game architecture, scene limits, state management, MVP planning |
133
- | `editor-gizmo` | Use a visual in-scene editor with translate/rotate gizmos |
134
132
 
135
133
  ## How It Works
136
134
 
@@ -152,7 +150,7 @@ opendcl/
152
150
  │ └── scene-context.ts # Scene detection & context formatting
153
151
  ├── extensions/
154
152
  │ ├── dcl-context.ts # Auto-detect scene, inject metadata
155
- │ ├── dcl-init.ts # /init command + editor setup
153
+ │ ├── dcl-init.ts # /init command
156
154
  │ ├── dcl-preview.ts # /preview command
157
155
  │ ├── dcl-deploy.ts # /deploy command
158
156
  │ ├── dcl-setup.ts # /setup command (cloud API provider config)
@@ -161,7 +159,6 @@ opendcl/
161
159
  │ ├── dcl-validate.ts # Post-write TypeScript validation
162
160
  │ ├── dcl-screenshot.ts # screenshot tool (headless Chrome, persistent browser)
163
161
  │ ├── dcl-tasks.ts # /tasks command (process manager)
164
- │ ├── dcl-editor-save.ts # /save-editor command (apply visual editor changes)
165
162
  │ ├── process-registry.ts # Shared background process registry
166
163
  │ └── permissions/ # Permission gate for dangerous operations
167
164
  ├── skills/ # 20 SKILL.md files (domain expertise)
@@ -234,6 +231,7 @@ OpenDCL supports any provider compatible with pi-coding-agent:
234
231
  | Anthropic (Claude) | `ANTHROPIC_API_KEY` | Best quality |
235
232
  | OpenAI | `OPENAI_API_KEY` | GPT-4o, o1, etc. |
236
233
  | Google | `GOOGLE_API_KEY` | Gemini models |
234
+ | Ollama | — | Free, runs locally |
237
235
  | OpenRouter | `OPENROUTER_API_KEY` | Access to many models |
238
236
 
239
237
  Set the environment variable or enter the key on first run. Switch models anytime with `Ctrl+P`.
@@ -34,10 +34,6 @@ removeEntityWithChildren(engine, entity)
34
34
 
35
35
  // Components — CRUD
36
36
  Transform.create(entity, { position: Vector3.create(8, 1, 8) })
37
- // ⚠️ NEVER pass undefined values in Transform fields — the SDK serializer crashes
38
- // reading .x on undefined. If a field is optional, omit the key entirely:
39
- // BAD: Transform.create(e, { position, rotation }) // rotation may be undefined
40
- // GOOD: Transform.create(e, rotation ? { position, rotation } : { position })
41
37
  const t = Transform.get(entity) // read-only, throws if missing
42
38
  const t = Transform.getMutable(entity) // mutable reference
43
39
  const t = Transform.getOrNull(entity) // read-only, returns null if missing
package/dist/index.js CHANGED
@@ -67,6 +67,10 @@ const extensions = headless
67
67
  "dcl-asset-path.ts",
68
68
  "dcl-screenshot.ts",
69
69
  ];
70
+ // Conditionally load dcl-setup-ollama (hidden by default, enable with ENABLE_OLLAMA_SETUP=true)
71
+ if (!headless && process.env.ENABLE_OLLAMA_SETUP === "true") {
72
+ extensions.push("dcl-setup-ollama.ts");
73
+ }
70
74
  for (const ext of extensions) {
71
75
  args.push("-e", join(extDir, ext));
72
76
  }
@@ -86,6 +90,14 @@ if (!isDev()) {
86
90
  }
87
91
  // InteractiveMode patches — only needed for terminal UI, skip in headless mode
88
92
  if (!headless) {
93
+ // Suppress pi's generic "No models available" warning — our dcl-setup-ollama
94
+ // extension shows a more helpful message that mentions /setup-ollama.
95
+ const _showWarning = InteractiveMode.prototype.showWarning;
96
+ InteractiveMode.prototype.showWarning = function (msg) {
97
+ if (msg.startsWith("No models available"))
98
+ return;
99
+ _showWarning.call(this, msg);
100
+ };
89
101
  // Suppress pi's "What's New" changelog notification on startup — it shows pi's
90
102
  // own version/changes, which confuses OpenDCL users.
91
103
  InteractiveMode.prototype.getChangelogForDisplay = function () {
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -3,40 +3,24 @@
3
3
  *
4
4
  * Registers the `init` tool (LLM-callable) and `/init` command that scaffolds
5
5
  * a new Decentraland scene project using `npx @dcl/sdk-commands init`.
6
- * After scaffolding, triggers the editor-gizmo skill to set up the visual editor.
7
- * On session start, prompts the user to enable the editor if a scene exists but lacks it.
8
6
  */
9
7
 
10
8
  import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
11
9
  import { Type } from "@sinclair/typebox";
12
10
  import { join } from "node:path";
13
11
  import { readFile, writeFile } from "node:fs/promises";
14
- import { fileExists, findSceneRoot } from "./scene-utils.js";
15
-
16
- function triggerEditorSkill(pi: { sendMessage(msg: unknown, opts?: unknown): void }) {
17
- pi.sendMessage(
18
- {
19
- customType: "editor-setup",
20
- content: "Scene initialized (dependencies not yet installed). Now set up the visual editor by following the editor-gizmo skill.",
21
- display: true,
22
- },
23
- { triggerTurn: true }
24
- );
25
- }
12
+ import { fileExists } from "./scene-utils.js";
26
13
 
27
14
  async function initScene(
28
15
  cwd: string,
29
- pi: {
30
- exec(cmd: string, args: string[], opts?: unknown): Promise<{ code: number; stdout: string; stderr: string }>;
31
- sendMessage(msg: unknown, opts?: unknown): void;
32
- }
16
+ pi: { exec(cmd: string, args: string[], opts?: unknown): Promise<{ code: number; stdout: string; stderr: string }> }
33
17
  ): Promise<{ message: string; isError?: boolean }> {
34
18
  if (await fileExists(join(cwd, "scene.json"))) {
35
19
  return { message: "A scene.json already exists in this directory. Aborting to prevent overwriting.", isError: true };
36
20
  }
37
21
 
38
22
  try {
39
- const result = await pi.exec("npx", ["@dcl/sdk-commands", "init", "--yes", "--skip-install"], {
23
+ const result = await pi.exec("npx", ["@dcl/sdk-commands", "init", "--yes"], {
40
24
  cwd,
41
25
  timeout: 180000,
42
26
  });
@@ -52,23 +36,7 @@ async function initScene(
52
36
  } catch {
53
37
  // Non-fatal: scene was still initialized successfully
54
38
  }
55
-
56
- // Append OpenDCL build artifacts to .gitignore so they don't get committed.
57
- // main.crdt is generated from main-entities.ts at bundle time.
58
- try {
59
- const ignorePath = join(cwd, ".gitignore");
60
- let existing = "";
61
- try { existing = await readFile(ignorePath, "utf-8"); } catch {}
62
- if (!existing.split(/\r?\n/).includes("main.crdt")) {
63
- const sep = existing.length > 0 && !existing.endsWith("\n") ? "\n" : "";
64
- await writeFile(ignorePath, existing + sep + "main.crdt\n");
65
- }
66
- } catch {
67
- // Non-fatal: scene still works without .gitignore updates
68
- }
69
-
70
- triggerEditorSkill(pi);
71
- return { message: "Scene initialized! Setting up visual editor..." };
39
+ return { message: "Scene initialized and dependencies installed! Use the preview tool to start." };
72
40
  } else {
73
41
  return { message: `Init failed (exit code ${result.code}): ${result.stderr || result.stdout}`, isError: true };
74
42
  }
@@ -82,7 +50,7 @@ const extension: ExtensionFactory = (pi) => {
82
50
  name: "init",
83
51
  label: "Init Scene",
84
52
  description:
85
- "Initialize a new Decentraland SDK7 scene with visual editor. Scaffolds scene.json, package.json, tsconfig.json, src/index.ts, and the __editor/ directory. Use when user wants to create or start a new scene.",
53
+ "Initialize a new Decentraland SDK7 scene. Scaffolds scene.json, package.json, tsconfig.json, and src/index.ts. Use when user wants to create or start a new scene.",
86
54
  parameters: Type.Object({}),
87
55
  async execute(_id, _params, _signal, _onUpdate, ctx) {
88
56
  const result = await initScene(ctx.cwd, pi);
@@ -92,7 +60,7 @@ const extension: ExtensionFactory = (pi) => {
92
60
  });
93
61
 
94
62
  pi.registerCommand("init", {
95
- description: "Initialize a new Decentraland scene project with visual editor",
63
+ description: "Initialize a new Decentraland scene project in the current directory",
96
64
  handler: async (_args, ctx) => {
97
65
  ctx.ui.notify("Initializing new Decentraland scene...", "info");
98
66
  const result = await initScene(ctx.cwd, pi);
@@ -100,26 +68,6 @@ const extension: ExtensionFactory = (pi) => {
100
68
  if (!result.isError) await ctx.reload();
101
69
  },
102
70
  });
103
-
104
- // Prompt on session start if scene exists but editor is not installed
105
- let editorPromptShown = false;
106
- pi.on("before_agent_start", async (_event, ctx) => {
107
- if (editorPromptShown) return;
108
- editorPromptShown = true;
109
-
110
- const sceneRoot = await findSceneRoot(ctx.cwd);
111
- if (!sceneRoot) return;
112
-
113
- if (await fileExists(join(sceneRoot, "src", "__editor", "state.ts"))) return;
114
-
115
- const enable = await ctx.ui.confirm(
116
- "Visual Editor",
117
- "Enable the visual editor for this scene?"
118
- );
119
- if (!enable) return;
120
-
121
- triggerEditorSkill(pi);
122
- });
123
71
  };
124
72
 
125
73
  export default extension;
@@ -0,0 +1,312 @@
1
+ /**
2
+ * DCL Setup Ollama Extension
3
+ *
4
+ * Registers the /setup-ollama command that walks users through
5
+ * model selection and configuration for a free local LLM setup.
6
+ * On session start, nudges users who have no provider configured.
7
+ */
8
+
9
+ import type { ExtensionFactory } from "@mariozechner/pi-coding-agent";
10
+ import { readFile, writeFile, mkdir } from "node:fs/promises";
11
+ import { dirname, join } from "node:path";
12
+ import { homedir } from "node:os";
13
+ import { spawn } from "node:child_process";
14
+
15
+ interface OllamaProvider {
16
+ baseUrl?: string;
17
+ api?: string;
18
+ apiKey?: string;
19
+ models?: { id: string }[];
20
+ }
21
+
22
+ const API_KEY_ENV_VARS = [
23
+ "ANTHROPIC_API_KEY",
24
+ "OPENAI_API_KEY",
25
+ "GOOGLE_API_KEY",
26
+ "OPENROUTER_API_KEY",
27
+ ];
28
+
29
+ export const OLLAMA_MODELS = [
30
+ { id: "qwen2.5-coder:32b", label: "qwen2.5-coder:32b (Recommended — best coding benchmarks, ~18GB)" },
31
+ { id: "qwen3-coder:30b", label: "qwen3-coder:30b (Latest Alibaba coder, 256K context, ~19GB)" },
32
+ { id: "devstral:24b", label: "devstral:24b (Mistral coding agent model, ~14GB)" },
33
+ { id: "glm-4.7-flash", label: "glm-4.7-flash (Reasoning + code generation, ~25GB)" },
34
+ ];
35
+
36
+ async function readJsonFile<T>(path: string, fallback: T): Promise<T> {
37
+ try {
38
+ const content = await readFile(path, "utf-8");
39
+ return JSON.parse(content);
40
+ } catch {
41
+ return fallback;
42
+ }
43
+ }
44
+
45
+ async function writeJsonFile(path: string, data: unknown): Promise<void> {
46
+ await mkdir(dirname(path), { recursive: true });
47
+ await writeFile(path, JSON.stringify(data, null, 2) + "\n", "utf-8");
48
+ }
49
+
50
+ export async function writeModelsConfig(modelsPath: string, modelId: string): Promise<void> {
51
+ const config = await readJsonFile<{ providers?: Record<string, unknown> }>(modelsPath, {});
52
+ config.providers ??= {};
53
+
54
+ const ollama = config.providers.ollama as OllamaProvider | undefined;
55
+ if (ollama) {
56
+ ollama.models ??= [];
57
+ if (!ollama.models.some((m) => m.id === modelId)) {
58
+ ollama.models.push({ id: modelId });
59
+ }
60
+ } else {
61
+ config.providers.ollama = {
62
+ baseUrl: "http://localhost:11434/v1",
63
+ api: "openai-completions",
64
+ apiKey: "ollama",
65
+ models: [{ id: modelId }],
66
+ };
67
+ }
68
+
69
+ await writeJsonFile(modelsPath, config);
70
+ }
71
+
72
+ export async function setDefaultModel(settingsPath: string, provider: string, modelId: string): Promise<void> {
73
+ const settings = await readJsonFile<Record<string, unknown>>(settingsPath, {});
74
+
75
+ settings.defaultProvider = provider;
76
+ settings.defaultModel = modelId;
77
+
78
+ await writeJsonFile(settingsPath, settings);
79
+ }
80
+
81
+ export async function isProviderConfigured(): Promise<boolean> {
82
+ if (API_KEY_ENV_VARS.some((v) => process.env[v])) {
83
+ return true;
84
+ }
85
+
86
+ const configDir = join(homedir(), ".opendcl", "agent");
87
+
88
+ const modelsConfig = await readJsonFile<{ providers?: Record<string, unknown> }>(join(configDir, "models.json"), {});
89
+ if (modelsConfig.providers != null && Object.keys(modelsConfig.providers).length > 0) {
90
+ return true;
91
+ }
92
+
93
+ const authConfig = await readJsonFile<Record<string, unknown>>(join(configDir, "auth.json"), {});
94
+ return Object.keys(authConfig).length > 0;
95
+ }
96
+
97
+ export function parseOllamaList(output: string): string[] {
98
+ const lines = output.split(/\r?\n/).filter((l) => l.trim());
99
+ // Skip header line (starts with "NAME")
100
+ const dataLines = lines.filter((l) => !l.startsWith("NAME"));
101
+ return dataLines
102
+ .map((l) => l.split(/\s+/)[0])
103
+ .filter(Boolean)
104
+ .map((name) => name.replace(/:latest$/, ""));
105
+ }
106
+
107
+ export async function removeOllamaModel(
108
+ modelsPath: string,
109
+ settingsPath: string,
110
+ modelId: string,
111
+ ): Promise<void> {
112
+ const config = await readJsonFile<{ providers?: Record<string, unknown> }>(modelsPath, {});
113
+ const ollama = config.providers?.ollama as OllamaProvider | undefined;
114
+ if (ollama?.models) {
115
+ ollama.models = ollama.models.filter((m) => m.id !== modelId);
116
+ if (ollama.models.length === 0) {
117
+ delete config.providers!.ollama;
118
+ }
119
+ await writeJsonFile(modelsPath, config);
120
+ }
121
+
122
+ const settings = await readJsonFile<Record<string, unknown>>(settingsPath, {});
123
+ if (settings.defaultProvider === "ollama" && settings.defaultModel === modelId) {
124
+ delete settings.defaultProvider;
125
+ delete settings.defaultModel;
126
+ await writeJsonFile(settingsPath, settings);
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Run ollama commands through the user's login shell so the full PATH is used.
132
+ * (pi.exec without shell won't find binaries added by installers to profile files.)
133
+ */
134
+ function ollamaExec(pi: Parameters<ExtensionFactory>[0], args: string, timeout = 10000) {
135
+ const shell = process.env.SHELL || "/bin/sh";
136
+ return pi.exec(shell, ["-lc", `ollama ${args}`], { timeout }).catch(() => null);
137
+ }
138
+
139
+ function stripAnsi(text: string): string {
140
+ return text.replace(/\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, "");
141
+ }
142
+
143
+ /**
144
+ * Extract a clean, human-readable progress line from raw ollama pull output.
145
+ * Ollama outputs ANSI-heavy terminal UI — this strips escape codes and
146
+ * picks the most informative line (percentage progress > phase labels).
147
+ */
148
+ export function extractPullProgress(raw: string): string | null {
149
+ const clean = stripAnsi(raw);
150
+ const lines = clean.split(/\r\n|\r|\n/).map((l) => l.trim()).filter(Boolean);
151
+ // Prefer the line with download percentage (e.g. "pulling abc123: 45% 8 GB/18 GB 12 MB/s")
152
+ const progress = lines.findLast((l) => /\d+%/.test(l));
153
+ if (progress) return progress;
154
+ // Fall back to phase lines like "pulling manifest", "verifying sha256 digest"
155
+ const phase = lines.findLast((l) => /^(pulling|verifying|writing|success)/.test(l));
156
+ return phase ?? null;
157
+ }
158
+
159
+ /**
160
+ * Pull a model with streaming progress via spawn.
161
+ * Throttles notifications to avoid UI spam (one update every 2 seconds max).
162
+ */
163
+ export function ollamaPull(
164
+ modelId: string,
165
+ onProgress: (line: string) => void,
166
+ ): Promise<{ success: boolean; error?: string }> {
167
+ return new Promise((resolve) => {
168
+ const shell = process.env.SHELL || "/bin/sh";
169
+ const child = spawn(shell, ["-lc", `ollama pull ${modelId}`], {
170
+ stdio: "pipe",
171
+ });
172
+
173
+ let lastNotify = 0;
174
+ let lastLine = "";
175
+ let errorOutput = "";
176
+
177
+ function handleData(data: Buffer): void {
178
+ const text = data.toString();
179
+ errorOutput += text;
180
+ const line = extractPullProgress(text);
181
+ if (!line) return;
182
+ lastLine = line;
183
+ const now = Date.now();
184
+ if (now - lastNotify >= 2000) {
185
+ lastNotify = now;
186
+ onProgress(line);
187
+ }
188
+ }
189
+
190
+ child.stdout?.on("data", handleData);
191
+ child.stderr?.on("data", handleData);
192
+
193
+ child.on("error", (err) => {
194
+ resolve({ success: false, error: err.message });
195
+ });
196
+
197
+ child.on("exit", (code) => {
198
+ if (lastLine) onProgress(lastLine);
199
+ if (code === 0) {
200
+ resolve({ success: true });
201
+ } else {
202
+ resolve({ success: false, error: stripAnsi(errorOutput).slice(-500) });
203
+ }
204
+ });
205
+ });
206
+ }
207
+
208
+ const extension: ExtensionFactory = (pi) => {
209
+ pi.on("session_start", async (_event, ctx) => {
210
+ if (!(await isProviderConfigured())) {
211
+ ctx.ui.notify(
212
+ "Get started by running /setup to configure a cloud provider",
213
+ "warning",
214
+ );
215
+ return;
216
+ }
217
+
218
+ const configDir = join(homedir(), ".opendcl", "agent");
219
+ const settingsPath = join(configDir, "settings.json");
220
+ const settings = await readJsonFile<Record<string, unknown>>(settingsPath, {});
221
+ if (settings.defaultProvider !== "ollama") return;
222
+
223
+ const defaultModel = settings.defaultModel as string | undefined;
224
+ if (!defaultModel) return;
225
+
226
+ const listResult = await ollamaExec(pi, "list");
227
+ if (!listResult || listResult.code !== 0) return;
228
+
229
+ const installed = parseOllamaList(listResult.stdout || "");
230
+ if (installed.includes(defaultModel)) return;
231
+
232
+ const modelsPath = join(configDir, "models.json");
233
+ await removeOllamaModel(modelsPath, settingsPath, defaultModel);
234
+ ctx.ui.notify(
235
+ `Model '${defaultModel}' is no longer installed in Ollama. Run /setup-ollama to configure a new model.`,
236
+ "warning",
237
+ );
238
+ });
239
+
240
+ pi.registerCommand("setup-ollama", {
241
+ description: "Configure Ollama as your free local LLM provider",
242
+ handler: async (_args, ctx) => {
243
+ const configDir = join(homedir(), ".opendcl", "agent");
244
+
245
+ const versionResult = await ollamaExec(pi, "--version");
246
+ if (!versionResult || versionResult.code !== 0) {
247
+ ctx.ui.notify("Ollama is not installed. Download it from https://ollama.com then run /setup-ollama again.", "warning");
248
+ return;
249
+ }
250
+
251
+ const listResult = await ollamaExec(pi, "list");
252
+ if (!listResult || listResult.code !== 0) {
253
+ ctx.ui.notify("Ollama is installed but not running. Start it with 'ollama serve', then run /setup-ollama again.", "warning");
254
+ return;
255
+ }
256
+
257
+ const installed = parseOllamaList(listResult.stdout || "");
258
+ const theme = ctx.ui.theme;
259
+ const sorted = [...OLLAMA_MODELS].sort((a, b) => {
260
+ const aInstalled = installed.includes(a.id) ? 0 : 1;
261
+ const bInstalled = installed.includes(b.id) ? 0 : 1;
262
+ return aInstalled - bInstalled;
263
+ });
264
+ const labels = sorted.map((m) =>
265
+ installed.includes(m.id)
266
+ ? `${m.label} ${theme.fg("success", "● ready")}`
267
+ : `${m.label} ${theme.fg("dim", "○ needs download")}`,
268
+ );
269
+
270
+ const selected = await ctx.ui.select("Which model do you want to use?", labels);
271
+ if (!selected) {
272
+ ctx.ui.notify("Setup cancelled.", "info");
273
+ return;
274
+ }
275
+
276
+ const model = sorted.find((m) => selected.startsWith(m.label));
277
+ if (!model) {
278
+ ctx.ui.notify("Invalid selection.", "error");
279
+ return;
280
+ }
281
+
282
+ const alreadyInstalled = installed.includes(model.id);
283
+ if (!alreadyInstalled) {
284
+ ctx.ui.setStatus("pull", `Pulling ${model.id}...`);
285
+ const pullResult = await ollamaPull(model.id, (line) => {
286
+ ctx.ui.setStatus("pull", line);
287
+ });
288
+ ctx.ui.setStatus("pull", undefined);
289
+ if (!pullResult.success) {
290
+ ctx.ui.notify(`Failed to pull model: ${pullResult.error || "unknown error"}`, "error");
291
+ return;
292
+ }
293
+ }
294
+ ctx.ui.notify("Model ready.", "info");
295
+
296
+ const modelsPath = join(configDir, "models.json");
297
+ await writeModelsConfig(modelsPath, model.id);
298
+
299
+ const settingsPath = join(configDir, "settings.json");
300
+ await setDefaultModel(settingsPath, "ollama", model.id);
301
+
302
+ ctx.ui.notify(
303
+ `Ollama configured: ${model.id}. Reloading...`,
304
+ "info",
305
+ );
306
+ await ctx.reload();
307
+ return;
308
+ },
309
+ });
310
+ };
311
+
312
+ export default extension;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dcl-regenesislabs/opendcl",
3
- "version": "0.2.1-26238928766.commit-28648d7",
3
+ "version": "0.2.1",
4
4
  "description": "AI coding assistant for Decentraland SDK7 scene development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -48,7 +48,8 @@
48
48
  "url": "git+https://github.com/dcl-regenesislabs/opendcl.git"
49
49
  },
50
50
  "dependencies": {
51
- "@mariozechner/pi-coding-agent": "^0.62.0",
51
+ "@mariozechner/pi-coding-agent": "^0.73.1",
52
+ "@sinclair/typebox": "0.34.48",
52
53
  "playwright-core": "^1.58.2"
53
54
  },
54
55
  "devDependencies": {
@@ -71,5 +72,5 @@
71
72
  "prompts/",
72
73
  "context/"
73
74
  ],
74
- "commit": "28648d736e3cd0f11630168013ee7ca6075abb45"
75
+ "commit": "96d9b973a1059d26e9b7b972add8b62f4ea08dd3"
75
76
  }