@drawcall/design 0.5.8 → 0.5.11

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
@@ -32,4 +32,34 @@ npx @drawcall/design -p r6z2n9k4x8m1qc frame create oak-chair \
32
32
  --type market --asset oak-chair@1.2.3
33
33
  ```
34
34
 
35
+ Design credentials are managed by `@drawcall/auth` and shared with other Drawcall CLIs. Existing
36
+ credentials in `drawcall-design/config.json` migrate automatically.
37
+
38
+ The Commander implementation is available for aggregate CLIs:
39
+
40
+ ```ts
41
+ import { createDesignCommand } from "@drawcall/design";
42
+
43
+ program.addCommand(createDesignCommand({ includeAuthCommands: false }));
44
+ ```
45
+
46
+ The MCP tools can be mounted into a server owned by another package:
47
+
48
+ ```ts
49
+ import {
50
+ createDesignMcpOperations,
51
+ registerDesignTools,
52
+ v1,
53
+ } from "@drawcall/design";
54
+ import { McpServer } from "@modelcontextprotocol/server";
55
+
56
+ const client = v1.createClient({ authToken: process.env.DRAWCALL_AUTH_TOKEN });
57
+ const server = new McpServer({ name: "mcp.drawcall.ai", version: "1.0.0" });
58
+
59
+ registerDesignTools(server, createDesignMcpOperations(client));
60
+ ```
61
+
62
+ The host owns the server identity, authentication, and transport. The MCP SDK
63
+ is an optional peer dependency and is required only when mounting these tools.
64
+
35
65
  GLTS frames accept only `.glts` files. An absent `index.glts` still renders an intentionally empty frame. For authored scenes, work top-down: write `index.glts`, reference planned child assets, then implement those children progressively. A missing imported `.glts` path renders a labeled glowing placeholder without creating a stored file; writing the real file replaces it on revalidation. 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.
@@ -0,0 +1,9 @@
1
+ export * as v1 from "./v1/index.js";
2
+ export { createDesignMcpOperations, registerDesignTools, type DesignMcpOperationRunner, type DesignMcpOperations, type DesignMcpServer, type DesignMcpScreenshot, type DesignMcpToolResult, } from "./mcp/index.js";
3
+ export { cliDesignSkill, mcpDesignSkill } from "./skill.generated.js";
4
+ export { frameCreationCapabilities, type FrameCreationCapability, type FrameCreationCapabilityId, type FrameCreationInput, } from "./v1/capabilities.js";
5
+ export { cameraPoseSchema, IMAGE_GENERATION_MODEL, IMAGE_GENERATION_PROVIDER, } from "./v1/schemas.js";
6
+ export { parseFrameSize } from "./target.js";
7
+ export { projectFrameLayoutSchema, projectFrameRecordSchema, projectGltsFrameRecordSchema, projectImageFrameRecordSchema, projectMarketFileSchema, projectMarketFrameRecordSchema, projectPresenceClientMessageSchema, projectPresenceServerMessageSchema, projectStateSchema, type ProjectFrameLayout, type ProjectFrameRecord, type ProjectGltsFrameRecord, type ProjectImageFrameRecord, type ProjectMarketFile, type ProjectMarketFrameRecord, type ProjectPresenceClientMessage, type ProjectPresenceServerMessage, type ProjectState, } from "./project-state.js";
8
+ export { cameraCaptureCommandSchema, cameraCaptureResultSchema, cameraMessageTypes, cameraSetCommandSchema, gltsExportCancelCommandSchema, gltsExportCancelledResultSchema, gltsExportCommandSchema, gltsExportFrameMessageSchema, gltsExportMessageTypes, gltsExportParentMessageSchema, gltsExportResultSchema, gltsRuntimeExportFormatSchema, immersiveCommandSchema, immersiveFrameMessageSchema, immersiveMessageTypes, immersiveModeSchema, inspectionCommandSchema, inspectionFrameMessageSchema, inspectionMessageTypes, projectSyncUrl, sourceFileChangeSchema, sourceFileMessageTypes, sourceFileReloadResultSchema, sourceFilesChangedMessageSchema, type GltsExportCancelCommand, type GltsExportCancelledResult, type GltsExportCommand, type GltsExportFrameMessage, type GltsExportParentMessage, type GltsExportResult, type GltsRuntimeExportFormat, type CameraCaptureCommand, type CameraSetCommand, type CameraCaptureFailure, type CameraCaptureResult, type InspectionAppliedMessage, type InspectionCommand, type InspectionFrameMessage, type ImmersiveCommand, type ImmersiveFrameMessage, type ImmersiveMode, type SourceFileChange, type SourceFileReloadResult, type SourceFilesChangedMessage, } from "./protocol.js";
9
+ export type { BinaryFile, CameraPose, CreateFrame, CreateProjectFromBrief, CreateGltsFrame, CreateImageFrame, CreateMarketFrame, GenerateImageFrame, DesignFile, FileList, FileMutation, FrameExport, Frame, GltsFrame, GltsFrameExport, ImageFrame, ImageFrameExport, ImageGenerationOperation, ImageGenerationProvenance, ImageGenerationResult, MarketFrame, MarketFrameExport, Project, ProjectDerivationStatus, Screenshot, TextFile, User, } from "./v1/schemas.js";
@@ -0,0 +1,8 @@
1
+ export * as v1 from "./v1/index.js";
2
+ export { createDesignMcpOperations, registerDesignTools, } from "./mcp/index.js";
3
+ export { cliDesignSkill, mcpDesignSkill } from "./skill.generated.js";
4
+ export { frameCreationCapabilities, } from "./v1/capabilities.js";
5
+ export { cameraPoseSchema, IMAGE_GENERATION_MODEL, IMAGE_GENERATION_PROVIDER, } from "./v1/schemas.js";
6
+ export { parseFrameSize } from "./target.js";
7
+ export { projectFrameLayoutSchema, projectFrameRecordSchema, projectGltsFrameRecordSchema, projectImageFrameRecordSchema, projectMarketFileSchema, projectMarketFrameRecordSchema, projectPresenceClientMessageSchema, projectPresenceServerMessageSchema, projectStateSchema, } from "./project-state.js";
8
+ export { cameraCaptureCommandSchema, cameraCaptureResultSchema, cameraMessageTypes, cameraSetCommandSchema, gltsExportCancelCommandSchema, gltsExportCancelledResultSchema, gltsExportCommandSchema, gltsExportFrameMessageSchema, gltsExportMessageTypes, gltsExportParentMessageSchema, gltsExportResultSchema, gltsRuntimeExportFormatSchema, immersiveCommandSchema, immersiveFrameMessageSchema, immersiveMessageTypes, immersiveModeSchema, inspectionCommandSchema, inspectionFrameMessageSchema, inspectionMessageTypes, projectSyncUrl, sourceFileChangeSchema, sourceFileMessageTypes, sourceFileReloadResultSchema, sourceFilesChangedMessageSchema, } from "./protocol.js";
@@ -1,3 +1,4 @@
1
+ import { getAuthToken } from "@drawcall/auth";
1
2
  import { loadConfig } from "./config.js";
2
3
  import { createClient, DEFAULT_BASE_URL, } from "./v1/client.js";
3
4
  export class NotLoggedInError extends Error {
@@ -7,8 +8,8 @@ export class NotLoggedInError extends Error {
7
8
  }
8
9
  }
9
10
  export async function getCliClient(baseUrlOverride) {
11
+ const authToken = await getAuthToken();
10
12
  const config = await loadConfig();
11
- const authToken = process.env.DRAWCALL_AUTH_TOKEN ?? config?.authToken;
12
13
  if (!authToken)
13
14
  throw new NotLoggedInError();
14
15
  const baseUrl = baseUrlOverride ??
package/dist/cli.js CHANGED
@@ -1,224 +1,6 @@
1
1
  #!/usr/bin/env node
2
- import { createRequire } from "node:module";
3
- import { Command, Option } from "commander";
4
- import { clearConfig, getConfigPath, saveConfig } from "./config.js";
5
- import { getCliClient } from "./cli-client.js";
6
- import { readImageInput } from "./image.js";
7
- import { cliDesignSkill } from "./skill.generated.js";
8
- import { parseFrameSize } from "./target.js";
9
- import { createClient, DEFAULT_BASE_URL } from "./v1/client.js";
10
- import { designIdSchema, marketAssetSchema } from "./v1/schemas.js";
11
- const AUTH_ISSUER_URL = "https://auth.drawcall.ai/api/auth";
12
- const DEVICE_CLIENT_ID = "drawcall-cli";
13
- const program = new Command()
14
- .name("design")
15
- .description("Design 3D assets on Drawcall Design")
16
- .version(readPackageVersion())
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"));
19
- program
20
- .command("skill")
21
- .description("Print the Drawcall Design skill")
22
- .action(() => {
23
- process.stdout.write(cliDesignSkill);
24
- });
25
- program
26
- .command("login")
27
- .description("Sign in with your Drawcall account")
28
- .action(async (_options, command) => {
29
- const baseUrlOverride = apiOption(command);
30
- const baseUrl = baseUrlOverride ?? DEFAULT_BASE_URL;
31
- const token = await runDeviceLogin();
32
- const client = createClient({ baseUrl, authToken: token });
33
- const user = await client.user.me();
34
- await saveConfig(baseUrlOverride ? { authToken: token, baseUrl } : { authToken: token });
35
- console.log(`Signed in as ${user.email}. Credentials saved to ${getConfigPath()}.`);
36
- });
37
- program
38
- .command("logout")
39
- .description("Sign out")
40
- .action(async () => {
41
- console.log((await clearConfig()) ? "Signed out." : "Already signed out.");
42
- });
43
- const project = program.command("project").description("Manage projects");
44
- project
45
- .command("list")
46
- .description("List projects")
47
- .action(async (_options, command) => {
48
- const client = await clientFor(command);
49
- const projects = await client.project.list();
50
- if (projects.length === 0) {
51
- console.log("No projects.");
52
- return;
53
- }
54
- for (const item of projects)
55
- console.log(`${item.id}\t${item.name}`);
56
- });
57
- project
58
- .command("create")
59
- .description("Create a project")
60
- .argument("<name>", "Project name")
61
- .action(async (name, _options, command) => {
62
- const client = await clientFor(command);
63
- const created = await client.project.create({ name });
64
- console.log(`Created ${created.id}\t${created.name}`);
65
- });
66
- project
67
- .command("delete")
68
- .description("Delete a project")
69
- .argument("<project-id>", "Project ID")
70
- .option("-y, --yes", "Confirm deletion", false)
71
- .action(async (projectId, options, command) => {
72
- requireConfirmation(options.yes);
73
- const id = designIdSchema.parse(projectId);
74
- const client = await clientFor(command);
75
- await client.project.delete({ project: id });
76
- console.log(`Deleted ${id}.`);
77
- });
78
- const frame = program.command("frame").description("Manage frames");
79
- frame
80
- .command("list")
81
- .description("List frames in the selected project")
82
- .action(async (_options, command) => {
83
- const client = await clientFor(command);
84
- const frames = await client.frame.list({ project: projectId(command) });
85
- if (frames.length === 0) {
86
- console.log("No frames.");
87
- return;
88
- }
89
- for (const item of frames) {
90
- console.log(`${item.id}\t${item.name}\t${item.type}\t${item.width}x${item.height}`);
91
- }
92
- });
93
- frame
94
- .command("create")
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);
104
- const client = await clientFor(command);
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}/`);
108
- });
109
- frame
110
- .command("rename")
111
- .description("Rename a frame")
112
- .argument("<frame-id>", "Frame ID")
113
- .argument("<name>", "New frame name")
114
- .action(async (frameId, name, _options, command) => {
115
- const id = designIdSchema.parse(frameId);
116
- const client = await clientFor(command);
117
- const renamed = await client.frame.rename({
118
- project: projectId(command),
119
- frame: id,
120
- name,
121
- });
122
- console.log(`Renamed ${renamed.id}\t${renamed.name}.`);
123
- });
124
- frame
125
- .command("delete")
126
- .description("Delete frames")
127
- .argument("<frame-id...>", "Frame IDs")
128
- .option("-y, --yes", "Confirm deletion", false)
129
- .action(async (frameIds, options, command) => {
130
- requireConfirmation(options.yes);
131
- const project = projectId(command);
132
- const client = await clientFor(command);
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}.`);
137
- }
138
- });
139
- frame
140
- .command("screenshot")
141
- .description("Render frames and print their screenshot URLs")
142
- .argument("<frame-id...>", "Frame IDs")
143
- .action(async (frameIds, _options, command) => {
144
- const project = projectId(command);
145
- const client = await clientFor(command);
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
- }
151
- });
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) => {
157
- const client = await clientFor(command);
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);
164
- });
165
- program
166
- .command("read")
167
- .description("Read a project file")
168
- .argument("<path>", "Absolute project path")
169
- .action(async (path, _options, command) => {
170
- const client = await clientFor(command);
171
- const file = await client.filesystem.read({
172
- project: projectId(command),
173
- path,
174
- });
175
- if (file.type === "text") {
176
- process.stdout.write(file.text);
177
- return;
178
- }
179
- console.log(file.url);
180
- });
181
- program
182
- .command("write")
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) => {
187
- const client = await clientFor(command);
188
- await client.filesystem.write({
189
- project: projectId(command),
190
- path,
191
- text: text === undefined || text === "-" ? await readStdin() : text,
192
- });
193
- console.log(`Wrote ${path}.`);
194
- });
195
- program
196
- .command("edit")
197
- .description("Replace text that occurs exactly once in a project file")
198
- .argument("<path>", "Absolute project path")
199
- .argument("<old-text>", "Text that must occur exactly once")
200
- .argument("<new-text>", "Replacement text")
201
- .action(async (path, oldText, newText, _options, command) => {
202
- const client = await clientFor(command);
203
- await client.filesystem.edit({
204
- project: projectId(command),
205
- path,
206
- oldText,
207
- newText,
208
- });
209
- console.log(`Edited ${path}.`);
210
- });
211
- program
212
- .command("delete")
213
- .description("Delete a project file")
214
- .argument("<path>", "Absolute project path")
215
- .option("-y, --yes", "Confirm deletion", false)
216
- .action(async (path, options, command) => {
217
- requireConfirmation(options.yes);
218
- const client = await clientFor(command);
219
- await client.filesystem.delete({ project: projectId(command), path });
220
- console.log(`Deleted ${path}.`);
221
- });
2
+ import { createDesignCommand } from "./command.js";
3
+ const program = createDesignCommand();
222
4
  if (process.argv.length <= 2) {
223
5
  program.outputHelp();
224
6
  }
@@ -228,106 +10,3 @@ else {
228
10
  process.exitCode = 1;
229
11
  });
230
12
  }
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
- }
274
- async function clientFor(command) {
275
- return getCliClient(apiOption(command));
276
- }
277
- function apiOption(command) {
278
- const value = command.optsWithGlobals().api;
279
- return typeof value === "string" ? value : undefined;
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
- }
288
- function requireConfirmation(confirmed) {
289
- if (!confirmed)
290
- throw new Error("Destructive commands require --yes.");
291
- }
292
- async function readStdin() {
293
- const chunks = [];
294
- for await (const chunk of process.stdin) {
295
- chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
296
- }
297
- return Buffer.concat(chunks).toString("utf8");
298
- }
299
- function readPackageVersion() {
300
- const manifest = createRequire(import.meta.url)("../package.json");
301
- if (!manifest ||
302
- typeof manifest !== "object" ||
303
- Array.isArray(manifest) ||
304
- !("version" in manifest) ||
305
- typeof manifest.version !== "string") {
306
- throw new Error("@drawcall/design package.json is missing a valid version");
307
- }
308
- return manifest.version;
309
- }
310
- async function runDeviceLogin() {
311
- const [{ default: open }, oauth] = await Promise.all([
312
- import("open"),
313
- import("openid-client"),
314
- ]);
315
- const configuration = await oauth.discovery(new URL(AUTH_ISSUER_URL), DEVICE_CLIENT_ID, undefined, oauth.None());
316
- const authorization = await oauth.initiateDeviceAuthorization(configuration, {
317
- scope: "design",
318
- });
319
- const verificationUrl = authorization.verification_uri_complete ?? authorization.verification_uri;
320
- console.log(`Open ${verificationUrl}`);
321
- console.log(`Code: ${authorization.user_code}`);
322
- try {
323
- await open(verificationUrl);
324
- }
325
- catch (error) {
326
- const message = error instanceof Error ? error.message : String(error);
327
- console.error(`Could not open a browser automatically: ${message}`);
328
- }
329
- const tokens = await oauth.pollDeviceAuthorizationGrant(configuration, authorization);
330
- if (!tokens.access_token)
331
- throw new Error("Device login completed without an access token.");
332
- return tokens.access_token;
333
- }
@@ -0,0 +1,7 @@
1
+ import { Command } from "commander";
2
+ export interface DesignCommandOptions {
3
+ name?: string;
4
+ version?: string;
5
+ includeAuthCommands?: boolean;
6
+ }
7
+ export declare function createDesignCommand(options?: DesignCommandOptions): Command;