@drawcall/design 0.1.8 → 0.2.0

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
@@ -1,6 +1,6 @@
1
1
  # @drawcall/design
2
2
 
3
- The Drawcall Design v1 API contract and its remote CLI.
3
+ The typed Drawcall Design v1 contract, client, and remote CLI.
4
4
 
5
5
  ```ts
6
6
  import { v1 } from "@drawcall/design";
@@ -9,17 +9,27 @@ const design = v1.createClient({ authToken: process.env.DRAWCALL_AUTH_TOKEN });
9
9
  const projects = await design.project.list();
10
10
  ```
11
11
 
12
+ Projects and frames have immutable 14-character lowercase IDs. A project is a hosted filesystem at `https://<project-id>.design.drawcallcontent.com/`; every frame owns `/<frame-id>/` within it.
13
+
12
14
  ```sh
13
15
  npx @drawcall/design login
14
- npx @drawcall/design skill
15
16
  npx @drawcall/design project create showroom
16
- npx @drawcall/design frame create showroom/chair --size 800x800 'import * as THREE from "three"; export const scene = new THREE.Group()'
17
- npx @drawcall/design frame create showroom/reference --size 800x800 --image ./reference.png
18
- npx @drawcall/design frame create showroom/mood --size 800x800 --image https://example.com/mood.webp
19
- npx @drawcall/design frame screenshot showroom/chair
17
+ # `project list` prints: <project-id> <name>
18
+
19
+ npx @drawcall/design -p r6z2n9k4x8m1qc frame create racecar \
20
+ --type glts --size 800x800
21
+ # `frame create` prints its frame ID and /<frame-id>/ path.
22
+
23
+ npx @drawcall/design -p r6z2n9k4x8m1qc write /a4z8m2q7v9kcde/index.glts \
24
+ 'export default class Racecar extends THREE.Group {}'
25
+ npx @drawcall/design -p r6z2n9k4x8m1qc ls /
26
+ npx @drawcall/design -p r6z2n9k4x8m1qc read /a4z8m2q7v9kcde/index.glts
27
+ npx @drawcall/design -p r6z2n9k4x8m1qc frame screenshot a4z8m2q7v9kcde
28
+
29
+ npx @drawcall/design -p r6z2n9k4x8m1qc frame create reference \
30
+ --type image --image ./reference.png
31
+ npx @drawcall/design -p r6z2n9k4x8m1qc frame create oak-chair \
32
+ --type market --asset oak-chair@1.2.3
20
33
  ```
21
34
 
22
- Source is always remote. Commands accept inline text or stdin; there is no checkout or local project
23
- linking. `frame source replace` accepts a `Record<path, code>` JSON object inline or on stdin.
24
- Agents should read `npx @drawcall/design skill` before creating or changing code-frame source. MCP
25
- clients get the same text from `get_skill`.
35
+ GLTS frames accept only `.glts` files. `index.glts` is optional: an absent entry renders an empty frame. Image and Market files are available through the same filesystem but are read-only. Read `npx @drawcall/design skill` (or MCP `get_skill`) before creating or changing a GLTS asset.
package/dist/cli.js CHANGED
@@ -5,18 +5,17 @@ import { clearConfig, getConfigPath, saveConfig } from "./config.js";
5
5
  import { getCliClient } from "./cli-client.js";
6
6
  import { readImageInput } from "./image.js";
7
7
  import { designSkill } from "./skill.generated.js";
8
- import { parseFrameSize, parseFrameTarget, parseScreenshotTarget, parseSourceFiles, } from "./target.js";
8
+ import { parseFrameSize } from "./target.js";
9
9
  import { createClient, DEFAULT_BASE_URL } from "./v1/client.js";
10
+ import { designIdSchema, marketAssetSchema } from "./v1/schemas.js";
10
11
  const AUTH_ISSUER_URL = "https://auth.drawcall.ai/api/auth";
11
12
  const DEVICE_CLIENT_ID = "drawcall-cli";
12
- function sourceInput(source) {
13
- return source === undefined ? {} : { source: { "/index.ts": source } };
14
- }
15
13
  const program = new Command()
16
14
  .name("design")
17
- .description("Design 3D scenes on Drawcall Design")
15
+ .description("Design 3D assets on Drawcall Design")
18
16
  .version(readPackageVersion())
19
- .addOption(new Option("--api <url>", "Design API URL").default(process.env.DESIGN_API_URL, "from DESIGN_API_URL / config / default"));
17
+ .addOption(new Option("--api <url>", "Design API URL").default(process.env.DESIGN_API_URL, "from DESIGN_API_URL / config / default"))
18
+ .addOption(new Option("-p, --project <id>", "Project ID"));
20
19
  program
21
20
  .command("skill")
22
21
  .description("Print the Drawcall Design skill")
@@ -53,7 +52,7 @@ project
53
52
  return;
54
53
  }
55
54
  for (const item of projects)
56
- console.log(`${item.name}\t${item.id}`);
55
+ console.log(`${item.id}\t${item.name}`);
57
56
  });
58
57
  project
59
58
  .command("create")
@@ -62,190 +61,163 @@ project
62
61
  .action(async (name, _options, command) => {
63
62
  const client = await clientFor(command);
64
63
  const created = await client.project.create({ name });
65
- console.log(`Created ${created.name}\t${created.id}`);
64
+ console.log(`Created ${created.id}\t${created.name}`);
66
65
  });
67
66
  project
68
67
  .command("delete")
69
68
  .description("Delete a project")
70
- .argument("<name>", "Project name")
69
+ .argument("<project-id>", "Project ID")
71
70
  .option("-y, --yes", "Confirm deletion", false)
72
- .action(async (name, options, command) => {
71
+ .action(async (projectId, options, command) => {
73
72
  requireConfirmation(options.yes);
73
+ const id = designIdSchema.parse(projectId);
74
74
  const client = await clientFor(command);
75
- await client.project.delete({ project: name });
76
- console.log(`Deleted ${name}.`);
75
+ await client.project.delete({ project: id });
76
+ console.log(`Deleted ${id}.`);
77
77
  });
78
78
  const frame = program.command("frame").description("Manage frames");
79
79
  frame
80
80
  .command("list")
81
- .description("List frames in a project")
82
- .argument("<project>", "Project name")
83
- .action(async (projectName, _options, command) => {
81
+ .description("List frames in the selected project")
82
+ .action(async (_options, command) => {
84
83
  const client = await clientFor(command);
85
- const frames = await client.frame.list({ project: projectName });
84
+ const frames = await client.frame.list({ project: projectId(command) });
86
85
  if (frames.length === 0) {
87
86
  console.log("No frames.");
88
87
  return;
89
88
  }
90
89
  for (const item of frames) {
91
- console.log(`${item.name}\t${item.kind}\t${item.width}x${item.height}\t${item.id}`);
90
+ console.log(`${item.id}\t${item.name}\t${item.type}\t${item.width}x${item.height}`);
92
91
  }
93
92
  });
94
93
  frame
95
94
  .command("create")
96
- .description("Create a code or image frame")
97
- .argument("<target>", "Project and frame as <project>/<frame>")
98
- .argument("[source]", "Inline /index.ts source, or - to read it from stdin")
99
- .requiredOption("-s, --size <width>x<height>", "Authoritative frame size")
100
- .option("--image <url-or-path>", "Create an image frame from a URL or file")
101
- .action(async (targetValue, sourceValue, options, command) => {
102
- const target = parseFrameTarget(targetValue);
103
- const size = parseFrameSize(options.size);
104
- if (options.image !== undefined && sourceValue !== undefined) {
105
- throw new Error("Source and --image cannot be used together");
106
- }
95
+ .description("Create a GLTS, image, or pinned Market frame")
96
+ .argument("<name>", "Frame name")
97
+ .requiredOption("-t, --type <type>", "glts, image, or market")
98
+ .option("-s, --size <width>x<height>", "GLTS viewport size")
99
+ .option("--image <url-or-path>", "Image URL or local PNG, JPEG, or WebP")
100
+ .option("--asset <name@version>", "Exact public Market asset version")
101
+ .action(async (name, options, command) => {
102
+ const project = projectId(command);
103
+ const input = await createFrameInput(project, name, options);
107
104
  const client = await clientFor(command);
108
- const created = options.image === undefined
109
- ? await client.frame.create({
110
- project: target.project,
111
- name: target.frame,
112
- kind: "code",
113
- ...size,
114
- ...sourceInput(await optionalIndexSource(sourceValue)),
115
- })
116
- : await client.frame.create({
117
- project: target.project,
118
- name: target.frame,
119
- kind: "image",
120
- image: await readImageInput(options.image),
121
- ...size,
122
- });
123
- console.log(`Created ${targetValue}\t${created.id}`);
105
+ const created = await client.frame.create(input);
106
+ console.log(`Created ${created.id}\t${created.name}\t${created.type}`);
107
+ console.log(`Path /${created.id}/`);
124
108
  });
125
109
  frame
126
110
  .command("rename")
127
111
  .description("Rename a frame")
128
- .argument("<target>", "Project and frame as <project>/<frame>")
112
+ .argument("<frame-id>", "Frame ID")
129
113
  .argument("<name>", "New frame name")
130
- .action(async (targetValue, name, _options, command) => {
114
+ .action(async (frameId, name, _options, command) => {
115
+ const id = designIdSchema.parse(frameId);
131
116
  const client = await clientFor(command);
132
117
  const renamed = await client.frame.rename({
133
- ...parseFrameTarget(targetValue),
118
+ project: projectId(command),
119
+ frame: id,
134
120
  name,
135
121
  });
136
- console.log(`Renamed ${targetValue} to ${renamed.name}.`);
122
+ console.log(`Renamed ${renamed.id}\t${renamed.name}.`);
137
123
  });
138
124
  frame
139
125
  .command("delete")
140
- .description("Delete one or more frames")
141
- .argument("<targets...>", "Frames as <project>/<frame>")
126
+ .description("Delete frames")
127
+ .argument("<frame-id...>", "Frame IDs")
142
128
  .option("-y, --yes", "Confirm deletion", false)
143
- .action(async (targets, options, command) => {
129
+ .action(async (frameIds, options, command) => {
144
130
  requireConfirmation(options.yes);
131
+ const project = projectId(command);
145
132
  const client = await clientFor(command);
146
- for (const targetValue of targets) {
147
- await client.frame.delete(parseFrameTarget(targetValue));
148
- console.log(`Deleted ${targetValue}.`);
133
+ for (const frameId of frameIds) {
134
+ const id = designIdSchema.parse(frameId);
135
+ await client.frame.delete({ project, frame: id });
136
+ console.log(`Deleted ${id}.`);
149
137
  }
150
138
  });
151
139
  frame
152
140
  .command("screenshot")
153
141
  .description("Render frames and print their screenshot URLs")
154
- .argument("<targets...>", "Projects or frames as <project> or <project>/<frame>")
155
- .action(async (targets, _options, command) => {
142
+ .argument("<frame-id...>", "Frame IDs")
143
+ .action(async (frameIds, _options, command) => {
144
+ const project = projectId(command);
156
145
  const client = await clientFor(command);
157
- for (const value of targets)
158
- await screenshotTarget(client, value);
146
+ for (const frameId of frameIds) {
147
+ const id = designIdSchema.parse(frameId);
148
+ const result = await client.frame.screenshot({ project, frame: id });
149
+ console.log(`${id}\t${result.url}`);
150
+ }
159
151
  });
160
- const source = frame.command("source").description("Manage code-frame source");
161
- source
162
- .command("list")
163
- .description("List source paths")
164
- .argument("<target>", "Project and frame as <project>/<frame>")
165
- .action(async (targetValue, _options, command) => {
152
+ program
153
+ .command("ls")
154
+ .description("List paths in the selected project filesystem")
155
+ .argument("[path]", "Absolute directory path", "/")
156
+ .action(async (path, _options, command) => {
166
157
  const client = await clientFor(command);
167
- const result = await client.frame.source.list(parseFrameTarget(targetValue));
168
- for (const path of result.paths)
169
- console.log(path);
158
+ const result = await client.filesystem.list({
159
+ project: projectId(command),
160
+ path,
161
+ });
162
+ for (const item of result.paths)
163
+ console.log(item);
170
164
  });
171
- source
165
+ program
172
166
  .command("read")
173
- .description("Read one source file")
174
- .argument("<target>", "Project and frame as <project>/<frame>")
175
- .argument("<path>", "Absolute source path")
176
- .action(async (targetValue, path, _options, command) => {
167
+ .description("Read a project file")
168
+ .argument("<path>", "Absolute project path")
169
+ .action(async (path, _options, command) => {
177
170
  const client = await clientFor(command);
178
- const result = await client.frame.source.read({
179
- ...parseFrameTarget(targetValue),
171
+ const file = await client.filesystem.read({
172
+ project: projectId(command),
180
173
  path,
181
174
  });
182
- process.stdout.write(result.code);
175
+ if (file.type === "text") {
176
+ process.stdout.write(file.text);
177
+ return;
178
+ }
179
+ console.log(file.url);
183
180
  });
184
- source
181
+ program
185
182
  .command("write")
186
- .description("Create or replace one source file")
187
- .argument("<target>", "Project and frame as <project>/<frame>")
188
- .argument("<path>", "Absolute source path")
189
- .argument("[code]", "Inline code; omit or use - to read stdin")
190
- .action(async (targetValue, path, codeValue, _options, command) => {
191
- const code = codeValue === undefined || codeValue === "-"
192
- ? await readStdin()
193
- : codeValue;
183
+ .description("Create or overwrite a text project file")
184
+ .argument("<path>", "Absolute project path")
185
+ .argument("[text]", "Inline text; omit or use - to read stdin")
186
+ .action(async (path, text, _options, command) => {
194
187
  const client = await clientFor(command);
195
- await client.frame.source.write({
196
- ...parseFrameTarget(targetValue),
188
+ await client.filesystem.write({
189
+ project: projectId(command),
197
190
  path,
198
- code,
191
+ text: text === undefined || text === "-" ? await readStdin() : text,
199
192
  });
200
193
  console.log(`Wrote ${path}.`);
201
194
  });
202
- source
195
+ program
203
196
  .command("edit")
204
- .description("Replace text that occurs exactly once in one source file")
205
- .argument("<target>", "Project and frame as <project>/<frame>")
206
- .argument("<path>", "Absolute source path")
197
+ .description("Replace text that occurs exactly once in a project file")
198
+ .argument("<path>", "Absolute project path")
207
199
  .argument("<old-text>", "Text that must occur exactly once")
208
200
  .argument("<new-text>", "Replacement text")
209
- .action(async (targetValue, path, oldText, newText, _options, command) => {
201
+ .action(async (path, oldText, newText, _options, command) => {
210
202
  const client = await clientFor(command);
211
- await client.frame.source.edit({
212
- ...parseFrameTarget(targetValue),
203
+ await client.filesystem.edit({
204
+ project: projectId(command),
213
205
  path,
214
206
  oldText,
215
207
  newText,
216
208
  });
217
209
  console.log(`Edited ${path}.`);
218
210
  });
219
- source
220
- .command("replace")
221
- .description("Replace all source files from a Record<path, code> JSON object")
222
- .argument("<target>", "Project and frame as <project>/<frame>")
223
- .argument("[files]", "Inline JSON, or omit/use - to read stdin")
224
- .action(async (targetValue, filesValue, _options, command) => {
225
- const input = filesValue === undefined || filesValue === "-"
226
- ? await readStdin()
227
- : filesValue;
228
- const client = await clientFor(command);
229
- await client.frame.source.replace({
230
- ...parseFrameTarget(targetValue),
231
- files: parseSourceFiles(input),
232
- });
233
- console.log("Replaced source.");
234
- });
235
- source
211
+ program
236
212
  .command("delete")
237
- .description("Delete exact source paths, or all source with a quoted *")
238
- .argument("<target>", "Project and frame as <project>/<frame>")
239
- .argument("<paths...>", "Absolute paths, or quoted *")
213
+ .description("Delete a project file")
214
+ .argument("<path>", "Absolute project path")
240
215
  .option("-y, --yes", "Confirm deletion", false)
241
- .action(async (targetValue, paths, options, command) => {
216
+ .action(async (path, options, command) => {
242
217
  requireConfirmation(options.yes);
243
218
  const client = await clientFor(command);
244
- await client.frame.source.delete({
245
- ...parseFrameTarget(targetValue),
246
- paths,
247
- });
248
- console.log("Deleted source.");
219
+ await client.filesystem.delete({ project: projectId(command), path });
220
+ console.log(`Deleted ${path}.`);
249
221
  });
250
222
  if (process.argv.length <= 2) {
251
223
  program.outputHelp();
@@ -256,6 +228,49 @@ else {
256
228
  process.exitCode = 1;
257
229
  });
258
230
  }
231
+ async function createFrameInput(project, name, options) {
232
+ if (options.type === "glts") {
233
+ if (options.image !== undefined || options.asset !== undefined) {
234
+ throw new Error("GLTS frames do not accept --image or --asset");
235
+ }
236
+ if (options.size === undefined) {
237
+ throw new Error("GLTS frames require --size <width>x<height>");
238
+ }
239
+ return {
240
+ project,
241
+ name,
242
+ type: "glts",
243
+ ...parseFrameSize(options.size),
244
+ };
245
+ }
246
+ if (options.type === "image") {
247
+ if (options.size !== undefined || options.asset !== undefined) {
248
+ throw new Error("Image frames do not accept --size or --asset");
249
+ }
250
+ if (options.image === undefined)
251
+ throw new Error("Image frames require --image");
252
+ return {
253
+ project,
254
+ name,
255
+ type: "image",
256
+ image: await readImageInput(options.image),
257
+ };
258
+ }
259
+ if (options.type === "market") {
260
+ if (options.size !== undefined || options.image !== undefined) {
261
+ throw new Error("Market frames do not accept --size or --image");
262
+ }
263
+ if (options.asset === undefined)
264
+ throw new Error("Market frames require --asset");
265
+ return {
266
+ project,
267
+ name,
268
+ type: "market",
269
+ asset: marketAssetSchema.parse(options.asset),
270
+ };
271
+ }
272
+ throw new Error("--type must be glts, image, or market");
273
+ }
259
274
  async function clientFor(command) {
260
275
  return getCliClient(apiOption(command));
261
276
  }
@@ -263,15 +278,17 @@ function apiOption(command) {
263
278
  const value = command.optsWithGlobals().api;
264
279
  return typeof value === "string" ? value : undefined;
265
280
  }
281
+ function projectId(command) {
282
+ const value = command.optsWithGlobals().project;
283
+ if (typeof value !== "string") {
284
+ throw new Error("Select a project with -p, --project <project-id>.");
285
+ }
286
+ return designIdSchema.parse(value);
287
+ }
266
288
  function requireConfirmation(confirmed) {
267
289
  if (!confirmed)
268
290
  throw new Error("Destructive commands require --yes.");
269
291
  }
270
- async function optionalIndexSource(value) {
271
- if (value === undefined)
272
- return undefined;
273
- return value === "-" ? readStdin() : value;
274
- }
275
292
  async function readStdin() {
276
293
  const chunks = [];
277
294
  for await (const chunk of process.stdin) {
@@ -279,22 +296,6 @@ async function readStdin() {
279
296
  }
280
297
  return Buffer.concat(chunks).toString("utf8");
281
298
  }
282
- async function screenshotTarget(client, value) {
283
- const target = parseScreenshotTarget(value);
284
- if (target.frame) {
285
- const result = await client.frame.screenshot(target);
286
- console.log(`${value}\t${result.url}`);
287
- return;
288
- }
289
- const frames = await client.frame.list({ project: target.project });
290
- for (const item of frames) {
291
- const result = await client.frame.screenshot({
292
- project: target.project,
293
- frame: item.name,
294
- });
295
- console.log(`${target.project}/${item.name}\t${result.url}`);
296
- }
297
- }
298
299
  function readPackageVersion() {
299
300
  const manifest = createRequire(import.meta.url)("../package.json");
300
301
  if (!manifest ||
package/dist/index.d.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  export * as v1 from "./v1/index.js";
2
2
  export { designSkill } from "./skill.generated.js";
3
- export { deleteSourceFiles, editSourceCode, editSourceFile, SourceEditError, SourceFileNotFoundError, } from "./source.js";
4
- export { parseFrameSize, parseFrameTarget, parseScreenshotTarget, parseSourceFiles, type FrameTarget, type ScreenshotTarget, } from "./target.js";
3
+ export { parseFrameSize } from "./target.js";
5
4
  export { acknowledgesInspection, inspectionCommandSchema, inspectionFrameMessageSchema, inspectionMessageTypes, projectSyncUrl, type InspectionAppliedMessage, type InspectionCommand, type InspectionFrameMessage, } from "./protocol.js";
6
- export type { CodeFrame, CreateCodeFrame, CreateFrame, CreateImageFrame, Frame, ImageFrame, Project, Screenshot, SourceFile, SourceFiles, SourceList, SourceMutation, User, } from "./v1/schemas.js";
5
+ export type { BinaryFile, CreateFrame, CreateGltsFrame, CreateImageFrame, CreateMarketFrame, DesignFile, FileList, FileMutation, Frame, GltsFrame, ImageFrame, MarketFrame, Project, Screenshot, TextFile, User, } from "./v1/schemas.js";
package/dist/index.js CHANGED
@@ -1,5 +1,4 @@
1
1
  export * as v1 from "./v1/index.js";
2
2
  export { designSkill } from "./skill.generated.js";
3
- export { deleteSourceFiles, editSourceCode, editSourceFile, SourceEditError, SourceFileNotFoundError, } from "./source.js";
4
- export { parseFrameSize, parseFrameTarget, parseScreenshotTarget, parseSourceFiles, } from "./target.js";
3
+ export { parseFrameSize } from "./target.js";
5
4
  export { acknowledgesInspection, inspectionCommandSchema, inspectionFrameMessageSchema, inspectionMessageTypes, projectSyncUrl, } from "./protocol.js";
@@ -1 +1 @@
1
- export declare const designSkill = "---\nname: drawcall-design\ndescription: Create, modify, inspect, and screenshot 3D objects and scenes in Drawcall Design using its MCP tools or @drawcall/design CLI. Use for Drawcall Design projects, frames, and code-frame source. Do not use for full games or applications.\n---\n\n# Drawcall Design\n\nUse the Drawcall Design MCP tools when available. Otherwise, use `npx @drawcall/design`. Drawcall Design is a remote, current-state canvas: inspect the existing project, frames, and source before changing them.\n\nA code frame stores source as a `Record<absolute path, code>`. A renderable frame has exactly one `/index.ts` or `/index.js`. That module must export `scene`, whose value is a `THREE.Object3D`, and may export `camera`, whose value is a `THREE.Camera`.\n\nSource runs as browser ESM. Use relative imports with explicit extensions for other frame files and normal package imports for browser-compatible packages. Top-level await is supported. In TypeScript files, use syntax that can be erased; avoid features that generate JavaScript.\n\nA code frame represents a designed state rather than a running application. Complete asynchronous setup before exporting the scene, and add any lighting or environment the object needs.\n\nCreate image frames from a public HTTP(S) URL through MCP. In the CLI, `frame create --image` accepts either an HTTP(S) URL or a local PNG, JPEG, or WebP file.\n\n```ts\nimport * as THREE from \"three\";\n\nconst root = new THREE.Scene();\n// Build the object or scene.\n\nexport const scene: THREE.Object3D = root;\n\nconst view = new THREE.PerspectiveCamera(35, 1, 0.1, 100);\nview.position.set(4, 3, 5);\nview.lookAt(0, 0, 0);\n\nexport const camera: THREE.Camera = view;\n```\n\nRead source before editing it. Prefer an exact edit for a small change, write one file when replacing that file, and replace the complete source only when the whole frame should change.\n\nAfter every meaningful visual change, take a frame screenshot and inspect it. Iterate until the rendered object, composition, and frame size satisfy the request.\n";
1
+ export declare const designSkill = "---\nname: drawcall-design\ndescription: Create, modify, inspect, and screenshot 3D assets and reference frames in Drawcall Design. Use for Drawcall Design projects, frames, the project filesystem, GLTS assets, or Drawcall Market frames. Do not use for full games or applications.\n---\n\n# Drawcall Design\n\nUse the Drawcall Design MCP tools when available. Otherwise use `npx @drawcall/design`. Design is a remote, current-state canvas: inspect the project and its frames before changing them. Use immutable IDs for every project and frame target; names are only labels.\n\n## Project filesystem\n\nA project is a hosted filesystem at `https://<project-id>.design.drawcallcontent.com/`. Each frame owns one top-level folder, `/<frame-id>/`. The CLI selects the project with `-p <project-id>`; its file paths are exactly the absolute paths inside that project, for example `/f7k3m9q2x8vd/index.glts`.\n\nCreate frames with an explicit type. GLTS frames require `--size`; image and Market frames derive their canvas size. A Market frame requires an exact public asset reference, `name@version`.\n\nRead a file before editing it. Use a narrow edit for one known change and write a complete file when replacing it. Only GLTS frame files may be created or deleted. Image and Market frame files are read-only.\n\n## GLTS assets\n\nA GLTS frame contains only `.glts` files. `index.glts` is its optional root asset; without it the frame renders empty. Every `.glts` file is a trusted TypeScript ESM module that default-exports a no-argument class derived from `THREE.Object3D`. Avoid top-level side effects because reload evaluates the module again. Implement `dispose()` when the asset exclusively owns disposable resources.\n\n```ts\nimport * as THREE from \"three\";\nimport Wheel from \"./parts/wheel.glts\";\n\nexport default class Racecar extends THREE.Group {\n constructor() {\n super();\n this.add(new Wheel());\n }\n}\n```\n\nUse relative `.glts` imports within a frame. Use a project-absolute path to import a GLTS asset from another frame:\n\n```ts\nimport Chassis from \"/other-frame-id/index.glts\";\n```\n\nFor a non-GLTS file from an image or Market frame, preserve the project filesystem URL through `import.meta.url`:\n\n```ts\nconst modelUrl = new URL(\"/market-frame-id/models/car.glb\", import.meta.url);\n```\n\nGLTS supports static `.glts`, `three`, Three addons, and bare npm imports. It does not support local helper `.ts` modules, dynamic imports, cyclic GLTS graphs, or cross-asset inheritance. Keep the asset self-contained and compose with nested GLTS assets.\n\nThe viewer uses the first camera found by depth-first traversal. If none exists, it autofits the asset. Put an authored camera in the scene only when its framing is intentional. Double-clicking a frame enters orbit from that resolved view; deselecting restores it.\n\nAfter every meaningful visual change, take and inspect a screenshot. Iterate until the asset, camera, composition, and requested frame size are right.\n";
@@ -1,2 +1,2 @@
1
1
  // Generated from skills/drawcall-design/SKILL.md.
2
- export const designSkill = '---\nname: drawcall-design\ndescription: Create, modify, inspect, and screenshot 3D objects and scenes in Drawcall Design using its MCP tools or @drawcall/design CLI. Use for Drawcall Design projects, frames, and code-frame source. Do not use for full games or applications.\n---\n\n# Drawcall Design\n\nUse the Drawcall Design MCP tools when available. Otherwise, use `npx @drawcall/design`. Drawcall Design is a remote, current-state canvas: inspect the existing project, frames, and source before changing them.\n\nA code frame stores source as a `Record<absolute path, code>`. A renderable frame has exactly one `/index.ts` or `/index.js`. That module must export `scene`, whose value is a `THREE.Object3D`, and may export `camera`, whose value is a `THREE.Camera`.\n\nSource runs as browser ESM. Use relative imports with explicit extensions for other frame files and normal package imports for browser-compatible packages. Top-level await is supported. In TypeScript files, use syntax that can be erased; avoid features that generate JavaScript.\n\nA code frame represents a designed state rather than a running application. Complete asynchronous setup before exporting the scene, and add any lighting or environment the object needs.\n\nCreate image frames from a public HTTP(S) URL through MCP. In the CLI, `frame create --image` accepts either an HTTP(S) URL or a local PNG, JPEG, or WebP file.\n\n```ts\nimport * as THREE from "three";\n\nconst root = new THREE.Scene();\n// Build the object or scene.\n\nexport const scene: THREE.Object3D = root;\n\nconst view = new THREE.PerspectiveCamera(35, 1, 0.1, 100);\nview.position.set(4, 3, 5);\nview.lookAt(0, 0, 0);\n\nexport const camera: THREE.Camera = view;\n```\n\nRead source before editing it. Prefer an exact edit for a small change, write one file when replacing that file, and replace the complete source only when the whole frame should change.\n\nAfter every meaningful visual change, take a frame screenshot and inspect it. Iterate until the rendered object, composition, and frame size satisfy the request.\n';
2
+ export const designSkill = '---\nname: drawcall-design\ndescription: Create, modify, inspect, and screenshot 3D assets and reference frames in Drawcall Design. Use for Drawcall Design projects, frames, the project filesystem, GLTS assets, or Drawcall Market frames. Do not use for full games or applications.\n---\n\n# Drawcall Design\n\nUse the Drawcall Design MCP tools when available. Otherwise use `npx @drawcall/design`. Design is a remote, current-state canvas: inspect the project and its frames before changing them. Use immutable IDs for every project and frame target; names are only labels.\n\n## Project filesystem\n\nA project is a hosted filesystem at `https://<project-id>.design.drawcallcontent.com/`. Each frame owns one top-level folder, `/<frame-id>/`. The CLI selects the project with `-p <project-id>`; its file paths are exactly the absolute paths inside that project, for example `/f7k3m9q2x8vd/index.glts`.\n\nCreate frames with an explicit type. GLTS frames require `--size`; image and Market frames derive their canvas size. A Market frame requires an exact public asset reference, `name@version`.\n\nRead a file before editing it. Use a narrow edit for one known change and write a complete file when replacing it. Only GLTS frame files may be created or deleted. Image and Market frame files are read-only.\n\n## GLTS assets\n\nA GLTS frame contains only `.glts` files. `index.glts` is its optional root asset; without it the frame renders empty. Every `.glts` file is a trusted TypeScript ESM module that default-exports a no-argument class derived from `THREE.Object3D`. Avoid top-level side effects because reload evaluates the module again. Implement `dispose()` when the asset exclusively owns disposable resources.\n\n```ts\nimport * as THREE from "three";\nimport Wheel from "./parts/wheel.glts";\n\nexport default class Racecar extends THREE.Group {\n constructor() {\n super();\n this.add(new Wheel());\n }\n}\n```\n\nUse relative `.glts` imports within a frame. Use a project-absolute path to import a GLTS asset from another frame:\n\n```ts\nimport Chassis from "/other-frame-id/index.glts";\n```\n\nFor a non-GLTS file from an image or Market frame, preserve the project filesystem URL through `import.meta.url`:\n\n```ts\nconst modelUrl = new URL("/market-frame-id/models/car.glb", import.meta.url);\n```\n\nGLTS supports static `.glts`, `three`, Three addons, and bare npm imports. It does not support local helper `.ts` modules, dynamic imports, cyclic GLTS graphs, or cross-asset inheritance. Keep the asset self-contained and compose with nested GLTS assets.\n\nThe viewer uses the first camera found by depth-first traversal. If none exists, it autofits the asset. Put an authored camera in the scene only when its framing is intentional. Double-clicking a frame enters orbit from that resolved view; deselecting restores it.\n\nAfter every meaningful visual change, take and inspect a screenshot. Iterate until the asset, camera, composition, and requested frame size are right.\n';
package/dist/target.d.ts CHANGED
@@ -1,18 +1,4 @@
1
- export interface FrameTarget {
2
- project: string;
3
- frame: string;
4
- }
5
- export type ScreenshotTarget = {
6
- project: string;
7
- frame?: never;
8
- } | {
9
- project: string;
10
- frame: string;
11
- };
12
- export declare function parseFrameTarget(value: string): FrameTarget;
13
- export declare function parseScreenshotTarget(value: string): ScreenshotTarget;
14
1
  export declare function parseFrameSize(value: string): {
15
2
  width: number;
16
3
  height: number;
17
4
  };
18
- export declare function parseSourceFiles(value: string): Record<string, string>;
package/dist/target.js CHANGED
@@ -1,22 +1,3 @@
1
- import { z } from "zod";
2
- import { frameNameSchema, projectNameSchema, sourceFilesSchema, } from "./v1/schemas.js";
3
- export function parseFrameTarget(value) {
4
- const segments = value.split("/");
5
- const project = segments[0];
6
- const frame = segments[1];
7
- if (segments.length !== 2 || project === undefined || frame === undefined) {
8
- throw new Error(`Expected <project>/<frame>, received ${JSON.stringify(value)}`);
9
- }
10
- return {
11
- project: projectNameSchema.parse(project),
12
- frame: frameNameSchema.parse(frame),
13
- };
14
- }
15
- export function parseScreenshotTarget(value) {
16
- if (value.includes("/"))
17
- return parseFrameTarget(value);
18
- return { project: projectNameSchema.parse(value) };
19
- }
20
1
  export function parseFrameSize(value) {
21
2
  const match = /^(\d+)[x×](\d+)$/i.exec(value);
22
3
  const widthText = match?.[1];
@@ -34,7 +15,3 @@ export function parseFrameSize(value) {
34
15
  }
35
16
  return { width, height };
36
17
  }
37
- export function parseSourceFiles(value) {
38
- const parsed = JSON.parse(value);
39
- return sourceFilesSchema.parse(parsed);
40
- }