@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 +30 -0
- package/dist/browser.d.ts +9 -0
- package/dist/browser.js +8 -0
- package/dist/cli-client.js +2 -1
- package/dist/cli.js +2 -323
- package/dist/command.d.ts +7 -0
- package/dist/command.js +311 -0
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/index.d.ts +2 -8
- package/dist/index.js +2 -7
- package/dist/mcp/client.d.ts +3 -0
- package/dist/mcp/client.js +38 -0
- package/dist/mcp/file.d.ts +2 -0
- package/dist/mcp/file.js +60 -0
- package/dist/mcp/frame.d.ts +2 -0
- package/dist/mcp/frame.js +90 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.js +2 -0
- package/dist/mcp/project.d.ts +2 -0
- package/dist/mcp/project.js +39 -0
- package/dist/mcp/register.d.ts +2 -0
- package/dist/mcp/register.js +13 -0
- package/dist/mcp/result.d.ts +5 -0
- package/dist/mcp/result.js +18 -0
- package/dist/mcp/schema.d.ts +33 -0
- package/dist/mcp/schema.js +108 -0
- package/dist/mcp/skill.d.ts +2 -0
- package/dist/mcp/skill.js +14 -0
- package/dist/mcp/types.d.ts +54 -0
- package/dist/mcp/types.js +1 -0
- package/dist/project-state.d.ts +6 -6
- package/dist/v1/contract.d.ts +13 -13
- package/dist/v1/schemas.d.ts +8 -8
- package/package.json +13 -9
package/dist/command.js
ADDED
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { createRequire } from "node:module";
|
|
2
|
+
import { getConfigPath as getAuthConfigPath, runDeviceLogin, saveAuthToken, signOut, } from "@drawcall/auth";
|
|
3
|
+
import { Command, Option } from "commander";
|
|
4
|
+
import { clearConfig, 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
|
+
export function createDesignCommand(options = {}) {
|
|
12
|
+
const program = new Command()
|
|
13
|
+
.name(options.name ?? "design")
|
|
14
|
+
.description("Design 3D assets on Drawcall Design")
|
|
15
|
+
.version(options.version ?? readPackageVersion())
|
|
16
|
+
.addOption(new Option("--api <url>", "Design API URL").default(process.env.DESIGN_API_URL, "from DESIGN_API_URL / config / default"))
|
|
17
|
+
.addOption(new Option("-p, --project <id>", "Project ID"));
|
|
18
|
+
program
|
|
19
|
+
.command("skill")
|
|
20
|
+
.description("Print the Drawcall Design skill")
|
|
21
|
+
.action(() => {
|
|
22
|
+
process.stdout.write(cliDesignSkill);
|
|
23
|
+
});
|
|
24
|
+
if (options.includeAuthCommands ?? true)
|
|
25
|
+
addAuthCommands(program);
|
|
26
|
+
const project = program.command("project").description("Manage projects");
|
|
27
|
+
project
|
|
28
|
+
.command("list")
|
|
29
|
+
.description("List projects")
|
|
30
|
+
.action(async (_options, command) => {
|
|
31
|
+
const client = await clientFor(command);
|
|
32
|
+
const projects = await client.project.list();
|
|
33
|
+
if (projects.length === 0) {
|
|
34
|
+
console.log("No projects.");
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
for (const item of projects)
|
|
38
|
+
console.log(`${item.id}\t${item.name}`);
|
|
39
|
+
});
|
|
40
|
+
project
|
|
41
|
+
.command("create")
|
|
42
|
+
.description("Create a project")
|
|
43
|
+
.argument("<name>", "Project name")
|
|
44
|
+
.action(async (name, _options, command) => {
|
|
45
|
+
const client = await clientFor(command);
|
|
46
|
+
const created = await client.project.create({ name });
|
|
47
|
+
console.log(`Created ${created.id}\t${created.name}`);
|
|
48
|
+
});
|
|
49
|
+
project
|
|
50
|
+
.command("delete")
|
|
51
|
+
.description("Delete a project")
|
|
52
|
+
.argument("<project-id>", "Project ID")
|
|
53
|
+
.option("-y, --yes", "Confirm deletion", false)
|
|
54
|
+
.action(async (projectId, options, command) => {
|
|
55
|
+
requireConfirmation(options.yes);
|
|
56
|
+
const id = designIdSchema.parse(projectId);
|
|
57
|
+
const client = await clientFor(command);
|
|
58
|
+
await client.project.delete({ project: id });
|
|
59
|
+
console.log(`Deleted ${id}.`);
|
|
60
|
+
});
|
|
61
|
+
const frame = program.command("frame").description("Manage frames");
|
|
62
|
+
frame
|
|
63
|
+
.command("list")
|
|
64
|
+
.description("List frames in the selected project")
|
|
65
|
+
.action(async (_options, command) => {
|
|
66
|
+
const client = await clientFor(command);
|
|
67
|
+
const frames = await client.frame.list({ project: projectId(command) });
|
|
68
|
+
if (frames.length === 0) {
|
|
69
|
+
console.log("No frames.");
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const item of frames) {
|
|
73
|
+
console.log(`${item.id}\t${item.name}\t${item.type}\t${item.width}x${item.height}`);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
frame
|
|
77
|
+
.command("create")
|
|
78
|
+
.description("Create a GLTS, image, or pinned Market frame")
|
|
79
|
+
.argument("<name>", "Frame name")
|
|
80
|
+
.requiredOption("-t, --type <type>", "glts, image, or market")
|
|
81
|
+
.option("-s, --size <width>x<height>", "GLTS viewport size")
|
|
82
|
+
.option("--image <url-or-path>", "Image URL or local PNG, JPEG, or WebP")
|
|
83
|
+
.option("--asset <name@version>", "Exact public Market asset version")
|
|
84
|
+
.action(async (name, options, command) => {
|
|
85
|
+
const project = projectId(command);
|
|
86
|
+
const input = await createFrameInput(project, name, options);
|
|
87
|
+
const client = await clientFor(command);
|
|
88
|
+
const created = await client.frame.create(input);
|
|
89
|
+
console.log(`Created ${created.id}\t${created.name}\t${created.type}`);
|
|
90
|
+
console.log(`Path /${created.id}/`);
|
|
91
|
+
});
|
|
92
|
+
frame
|
|
93
|
+
.command("rename")
|
|
94
|
+
.description("Rename a frame")
|
|
95
|
+
.argument("<frame-id>", "Frame ID")
|
|
96
|
+
.argument("<name>", "New frame name")
|
|
97
|
+
.action(async (frameId, name, _options, command) => {
|
|
98
|
+
const id = designIdSchema.parse(frameId);
|
|
99
|
+
const client = await clientFor(command);
|
|
100
|
+
const renamed = await client.frame.rename({
|
|
101
|
+
project: projectId(command),
|
|
102
|
+
frame: id,
|
|
103
|
+
name,
|
|
104
|
+
});
|
|
105
|
+
console.log(`Renamed ${renamed.id}\t${renamed.name}.`);
|
|
106
|
+
});
|
|
107
|
+
frame
|
|
108
|
+
.command("delete")
|
|
109
|
+
.description("Delete frames")
|
|
110
|
+
.argument("<frame-id...>", "Frame IDs")
|
|
111
|
+
.option("-y, --yes", "Confirm deletion", false)
|
|
112
|
+
.action(async (frameIds, options, command) => {
|
|
113
|
+
requireConfirmation(options.yes);
|
|
114
|
+
const project = projectId(command);
|
|
115
|
+
const client = await clientFor(command);
|
|
116
|
+
for (const frameId of frameIds) {
|
|
117
|
+
const id = designIdSchema.parse(frameId);
|
|
118
|
+
await client.frame.delete({ project, frame: id });
|
|
119
|
+
console.log(`Deleted ${id}.`);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
frame
|
|
123
|
+
.command("screenshot")
|
|
124
|
+
.description("Render frames and print their screenshot URLs")
|
|
125
|
+
.argument("<frame-id...>", "Frame IDs")
|
|
126
|
+
.action(async (frameIds, _options, command) => {
|
|
127
|
+
const project = projectId(command);
|
|
128
|
+
const client = await clientFor(command);
|
|
129
|
+
for (const frameId of frameIds) {
|
|
130
|
+
const id = designIdSchema.parse(frameId);
|
|
131
|
+
const result = await client.frame.screenshot({ project, frame: id });
|
|
132
|
+
console.log(`${id}\t${result.url}`);
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
program
|
|
136
|
+
.command("ls")
|
|
137
|
+
.description("List paths in the selected project filesystem")
|
|
138
|
+
.argument("[path]", "Absolute directory path", "/")
|
|
139
|
+
.action(async (path, _options, command) => {
|
|
140
|
+
const client = await clientFor(command);
|
|
141
|
+
const result = await client.filesystem.list({
|
|
142
|
+
project: projectId(command),
|
|
143
|
+
path,
|
|
144
|
+
});
|
|
145
|
+
for (const item of result.paths)
|
|
146
|
+
console.log(item);
|
|
147
|
+
});
|
|
148
|
+
program
|
|
149
|
+
.command("read")
|
|
150
|
+
.description("Read a project file")
|
|
151
|
+
.argument("<path>", "Absolute project path")
|
|
152
|
+
.action(async (path, _options, command) => {
|
|
153
|
+
const client = await clientFor(command);
|
|
154
|
+
const file = await client.filesystem.read({
|
|
155
|
+
project: projectId(command),
|
|
156
|
+
path,
|
|
157
|
+
});
|
|
158
|
+
if (file.type === "text") {
|
|
159
|
+
process.stdout.write(file.text);
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
console.log(file.url);
|
|
163
|
+
});
|
|
164
|
+
program
|
|
165
|
+
.command("write")
|
|
166
|
+
.description("Create or overwrite a text project file")
|
|
167
|
+
.argument("<path>", "Absolute project path")
|
|
168
|
+
.argument("[text]", "Inline text; omit or use - to read stdin")
|
|
169
|
+
.action(async (path, text, _options, command) => {
|
|
170
|
+
const client = await clientFor(command);
|
|
171
|
+
await client.filesystem.write({
|
|
172
|
+
project: projectId(command),
|
|
173
|
+
path,
|
|
174
|
+
text: text === undefined || text === "-" ? await readStdin() : text,
|
|
175
|
+
});
|
|
176
|
+
console.log(`Wrote ${path}.`);
|
|
177
|
+
});
|
|
178
|
+
program
|
|
179
|
+
.command("edit")
|
|
180
|
+
.description("Replace text that occurs exactly once in a project file")
|
|
181
|
+
.argument("<path>", "Absolute project path")
|
|
182
|
+
.argument("<old-text>", "Text that must occur exactly once")
|
|
183
|
+
.argument("<new-text>", "Replacement text")
|
|
184
|
+
.action(async (path, oldText, newText, _options, command) => {
|
|
185
|
+
const client = await clientFor(command);
|
|
186
|
+
await client.filesystem.edit({
|
|
187
|
+
project: projectId(command),
|
|
188
|
+
path,
|
|
189
|
+
oldText,
|
|
190
|
+
newText,
|
|
191
|
+
});
|
|
192
|
+
console.log(`Edited ${path}.`);
|
|
193
|
+
});
|
|
194
|
+
program
|
|
195
|
+
.command("delete")
|
|
196
|
+
.description("Delete a project file")
|
|
197
|
+
.argument("<path>", "Absolute project path")
|
|
198
|
+
.option("-y, --yes", "Confirm deletion", false)
|
|
199
|
+
.action(async (path, options, command) => {
|
|
200
|
+
requireConfirmation(options.yes);
|
|
201
|
+
const client = await clientFor(command);
|
|
202
|
+
await client.filesystem.delete({ project: projectId(command), path });
|
|
203
|
+
console.log(`Deleted ${path}.`);
|
|
204
|
+
});
|
|
205
|
+
return program;
|
|
206
|
+
}
|
|
207
|
+
function addAuthCommands(program) {
|
|
208
|
+
program
|
|
209
|
+
.command("login")
|
|
210
|
+
.description("Sign in with your Drawcall account")
|
|
211
|
+
.action(async (_options, command) => {
|
|
212
|
+
const baseUrlOverride = apiOption(command);
|
|
213
|
+
const baseUrl = baseUrlOverride ?? DEFAULT_BASE_URL;
|
|
214
|
+
const token = await runDeviceLogin();
|
|
215
|
+
const client = createClient({ baseUrl, authToken: token });
|
|
216
|
+
const user = await client.user.me();
|
|
217
|
+
if (baseUrlOverride)
|
|
218
|
+
await saveConfig({ baseUrl });
|
|
219
|
+
else
|
|
220
|
+
await clearConfig();
|
|
221
|
+
await saveAuthToken(token);
|
|
222
|
+
console.log(`Signed in as ${user.email}. Credentials saved to ${getAuthConfigPath()}.`);
|
|
223
|
+
});
|
|
224
|
+
program
|
|
225
|
+
.command("logout")
|
|
226
|
+
.description("Sign out")
|
|
227
|
+
.action(async () => {
|
|
228
|
+
const authExisted = await signOut();
|
|
229
|
+
const configExisted = await clearConfig();
|
|
230
|
+
console.log(authExisted || configExisted ? "Signed out." : "Already signed out.");
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
async function createFrameInput(project, name, options) {
|
|
234
|
+
if (options.type === "glts") {
|
|
235
|
+
if (options.image !== undefined || options.asset !== undefined) {
|
|
236
|
+
throw new Error("GLTS frames do not accept --image or --asset");
|
|
237
|
+
}
|
|
238
|
+
if (options.size === undefined) {
|
|
239
|
+
throw new Error("GLTS frames require --size <width>x<height>");
|
|
240
|
+
}
|
|
241
|
+
return {
|
|
242
|
+
project,
|
|
243
|
+
name,
|
|
244
|
+
type: "glts",
|
|
245
|
+
...parseFrameSize(options.size),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
if (options.type === "image") {
|
|
249
|
+
if (options.size !== undefined || options.asset !== undefined) {
|
|
250
|
+
throw new Error("Image frames do not accept --size or --asset");
|
|
251
|
+
}
|
|
252
|
+
if (options.image === undefined)
|
|
253
|
+
throw new Error("Image frames require --image");
|
|
254
|
+
return {
|
|
255
|
+
project,
|
|
256
|
+
name,
|
|
257
|
+
type: "image",
|
|
258
|
+
image: await readImageInput(options.image),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
if (options.type === "market") {
|
|
262
|
+
if (options.size !== undefined || options.image !== undefined) {
|
|
263
|
+
throw new Error("Market frames do not accept --size or --image");
|
|
264
|
+
}
|
|
265
|
+
if (options.asset === undefined)
|
|
266
|
+
throw new Error("Market frames require --asset");
|
|
267
|
+
return {
|
|
268
|
+
project,
|
|
269
|
+
name,
|
|
270
|
+
type: "market",
|
|
271
|
+
asset: marketAssetSchema.parse(options.asset),
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
throw new Error("--type must be glts, image, or market");
|
|
275
|
+
}
|
|
276
|
+
async function clientFor(command) {
|
|
277
|
+
return getCliClient(apiOption(command));
|
|
278
|
+
}
|
|
279
|
+
function apiOption(command) {
|
|
280
|
+
const value = command.optsWithGlobals().api;
|
|
281
|
+
return typeof value === "string" ? value : undefined;
|
|
282
|
+
}
|
|
283
|
+
function projectId(command) {
|
|
284
|
+
const value = command.optsWithGlobals().project;
|
|
285
|
+
if (typeof value !== "string") {
|
|
286
|
+
throw new Error("Select a project with -p, --project <project-id>.");
|
|
287
|
+
}
|
|
288
|
+
return designIdSchema.parse(value);
|
|
289
|
+
}
|
|
290
|
+
function requireConfirmation(confirmed) {
|
|
291
|
+
if (!confirmed)
|
|
292
|
+
throw new Error("Destructive commands require --yes.");
|
|
293
|
+
}
|
|
294
|
+
async function readStdin() {
|
|
295
|
+
const chunks = [];
|
|
296
|
+
for await (const chunk of process.stdin) {
|
|
297
|
+
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
298
|
+
}
|
|
299
|
+
return Buffer.concat(chunks).toString("utf8");
|
|
300
|
+
}
|
|
301
|
+
function readPackageVersion() {
|
|
302
|
+
const manifest = createRequire(import.meta.url)("../package.json");
|
|
303
|
+
if (!manifest ||
|
|
304
|
+
typeof manifest !== "object" ||
|
|
305
|
+
Array.isArray(manifest) ||
|
|
306
|
+
!("version" in manifest) ||
|
|
307
|
+
typeof manifest.version !== "string") {
|
|
308
|
+
throw new Error("@drawcall/design package.json is missing a valid version");
|
|
309
|
+
}
|
|
310
|
+
return manifest.version;
|
|
311
|
+
}
|
package/dist/config.d.ts
CHANGED
package/dist/config.js
CHANGED
|
@@ -3,7 +3,7 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { z } from "zod";
|
|
5
5
|
const configSchema = z.object({
|
|
6
|
-
authToken: z.string().min(1),
|
|
6
|
+
authToken: z.string().min(1).optional(),
|
|
7
7
|
baseUrl: z.string().url().optional(),
|
|
8
8
|
});
|
|
9
9
|
function configDirectory() {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
export *
|
|
2
|
-
export {
|
|
3
|
-
export { frameCreationCapabilities, type FrameCreationCapability, type FrameCreationCapabilityId, type FrameCreationInput, } from "./v1/capabilities.js";
|
|
4
|
-
export { cameraPoseSchema, IMAGE_GENERATION_MODEL, IMAGE_GENERATION_PROVIDER, } from "./v1/schemas.js";
|
|
5
|
-
export { parseFrameSize } from "./target.js";
|
|
6
|
-
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";
|
|
7
|
-
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";
|
|
8
|
-
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";
|
|
1
|
+
export * from "./browser.js";
|
|
2
|
+
export { createDesignCommand, type DesignCommandOptions } from "./command.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,2 @@
|
|
|
1
|
-
export *
|
|
2
|
-
export {
|
|
3
|
-
export { frameCreationCapabilities, } from "./v1/capabilities.js";
|
|
4
|
-
export { cameraPoseSchema, IMAGE_GENERATION_MODEL, IMAGE_GENERATION_PROVIDER, } from "./v1/schemas.js";
|
|
5
|
-
export { parseFrameSize } from "./target.js";
|
|
6
|
-
export { projectFrameLayoutSchema, projectFrameRecordSchema, projectGltsFrameRecordSchema, projectImageFrameRecordSchema, projectMarketFileSchema, projectMarketFrameRecordSchema, projectPresenceClientMessageSchema, projectPresenceServerMessageSchema, projectStateSchema, } from "./project-state.js";
|
|
7
|
-
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
|
+
export * from "./browser.js";
|
|
2
|
+
export { createDesignCommand } from "./command.js";
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export function createDesignMcpOperations(client, fetcher = globalThis.fetch) {
|
|
2
|
+
return {
|
|
3
|
+
listProjects: () => client.project.list(),
|
|
4
|
+
createProject: (name) => client.project.create({ name }),
|
|
5
|
+
deleteProject: (project) => client.project.delete({ project }),
|
|
6
|
+
listFrames: (project) => client.frame.list({ project }),
|
|
7
|
+
createFrame: (input) => client.frame.create(input),
|
|
8
|
+
generateImage: (input) => client.frame.generateImage(input),
|
|
9
|
+
renameFrame: (project, frame, name) => client.frame.rename({ project, frame, name }),
|
|
10
|
+
deleteFrame: (project, frame) => client.frame.delete({ project, frame }),
|
|
11
|
+
listFiles: (project, path) => client.filesystem.list({
|
|
12
|
+
project,
|
|
13
|
+
...(path === undefined ? {} : { path }),
|
|
14
|
+
}),
|
|
15
|
+
readFile: (project, path) => client.filesystem.read({ project, path }),
|
|
16
|
+
writeFile: (project, path, text) => client.filesystem.write({ project, path, text }),
|
|
17
|
+
editFile: (project, path, oldText, newText) => client.filesystem.edit({ project, path, oldText, newText }),
|
|
18
|
+
deleteFile: (project, path) => client.filesystem.delete({ project, path }),
|
|
19
|
+
screenshot: async (project, frame) => {
|
|
20
|
+
const screenshot = await client.frame.screenshot({ project, frame });
|
|
21
|
+
const response = await fetcher(screenshot.url);
|
|
22
|
+
if (!response.ok) {
|
|
23
|
+
throw new Error(`Could not read screenshot ${screenshot.url}: ${response.status} ${response.statusText}`);
|
|
24
|
+
}
|
|
25
|
+
const mediaType = response.headers
|
|
26
|
+
.get("content-type")
|
|
27
|
+
?.split(";", 1)[0]
|
|
28
|
+
?.trim();
|
|
29
|
+
if (mediaType !== screenshot.mediaType) {
|
|
30
|
+
throw new Error(`Screenshot ${screenshot.url} returned ${mediaType ?? "no media type"}; expected ${screenshot.mediaType}`);
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
...screenshot,
|
|
34
|
+
png: new Uint8Array(await response.arrayBuffer()),
|
|
35
|
+
};
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
package/dist/mcp/file.js
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { fileTextSchema } from "../v1/schemas.js";
|
|
2
|
+
import { fileResult, result } from "./result.js";
|
|
3
|
+
import { directoryPathSchema, fileInput, projectInput } from "./schema.js";
|
|
4
|
+
export function registerFileTools(server, design, run) {
|
|
5
|
+
server.registerTool("list_files", {
|
|
6
|
+
description: "Return canonical project-absolute paths. Omit path to list the entire project; otherwise use /<frame-id> without a trailing slash. Pass returned file paths unchanged to file tools.",
|
|
7
|
+
inputSchema: projectInput.extend({
|
|
8
|
+
path: directoryPathSchema.optional(),
|
|
9
|
+
}),
|
|
10
|
+
annotations: {
|
|
11
|
+
readOnlyHint: true,
|
|
12
|
+
destructiveHint: false,
|
|
13
|
+
openWorldHint: false,
|
|
14
|
+
idempotentHint: true,
|
|
15
|
+
},
|
|
16
|
+
}, async ({ project, path }) => result(await run("list_files", () => design.listFiles(project, path))));
|
|
17
|
+
server.registerTool("read_file", {
|
|
18
|
+
description: "Read one canonical project-absolute path returned by list_files. The path includes the frame ID; this tool does not accept a frame argument. Binary files return their hosted URL and media type.",
|
|
19
|
+
inputSchema: fileInput,
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: true,
|
|
22
|
+
destructiveHint: false,
|
|
23
|
+
openWorldHint: false,
|
|
24
|
+
idempotentHint: true,
|
|
25
|
+
},
|
|
26
|
+
}, async ({ project, path }) => {
|
|
27
|
+
const file = await run("read_file", () => design.readFile(project, path));
|
|
28
|
+
return file.type === "text" ? fileResult(file) : result(file);
|
|
29
|
+
});
|
|
30
|
+
server.registerTool("write_file", {
|
|
31
|
+
description: "Create or replace one .glts file at a canonical project-absolute path. The path includes the frame ID; this tool does not accept a frame argument.",
|
|
32
|
+
inputSchema: fileInput.extend({ text: fileTextSchema }),
|
|
33
|
+
annotations: {
|
|
34
|
+
readOnlyHint: false,
|
|
35
|
+
destructiveHint: true,
|
|
36
|
+
openWorldHint: true,
|
|
37
|
+
},
|
|
38
|
+
}, async ({ project, path, text }) => result(await run("write_file", () => design.writeFile(project, path, text))));
|
|
39
|
+
server.registerTool("edit_file", {
|
|
40
|
+
description: "Replace oldText when it occurs exactly once at a canonical project-absolute .glts path. The path includes the frame ID; this tool does not accept a frame argument.",
|
|
41
|
+
inputSchema: fileInput.extend({
|
|
42
|
+
oldText: fileTextSchema.min(1),
|
|
43
|
+
newText: fileTextSchema,
|
|
44
|
+
}),
|
|
45
|
+
annotations: {
|
|
46
|
+
readOnlyHint: false,
|
|
47
|
+
destructiveHint: true,
|
|
48
|
+
openWorldHint: true,
|
|
49
|
+
},
|
|
50
|
+
}, async ({ project, path, oldText, newText }) => result(await run("edit_file", () => design.editFile(project, path, oldText, newText))));
|
|
51
|
+
server.registerTool("delete_file", {
|
|
52
|
+
description: "Permanently delete one .glts file at a canonical project-absolute path. The path includes the frame ID; this tool does not accept a frame argument.",
|
|
53
|
+
inputSchema: fileInput,
|
|
54
|
+
annotations: {
|
|
55
|
+
readOnlyHint: false,
|
|
56
|
+
destructiveHint: true,
|
|
57
|
+
openWorldHint: true,
|
|
58
|
+
},
|
|
59
|
+
}, async ({ project, path }) => result(await run("delete_file", () => design.deleteFile(project, path))));
|
|
60
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { frameNameSchema, generateImageFrameSchema } from "../v1/schemas.js";
|
|
2
|
+
import { base64, result } from "./result.js";
|
|
3
|
+
import { createFrameSchema, frameCreationInput, frameInput, projectInput, } from "./schema.js";
|
|
4
|
+
export function registerFrameTools(server, design, run) {
|
|
5
|
+
server.registerTool("list_frames", {
|
|
6
|
+
description: "List frames. Use their immutable ids for frame commands and filesystem paths.",
|
|
7
|
+
inputSchema: projectInput,
|
|
8
|
+
annotations: {
|
|
9
|
+
readOnlyHint: true,
|
|
10
|
+
destructiveHint: false,
|
|
11
|
+
openWorldHint: false,
|
|
12
|
+
idempotentHint: true,
|
|
13
|
+
},
|
|
14
|
+
}, async ({ project }) => result({
|
|
15
|
+
frames: await run("list_frames", () => design.listFrames(project)),
|
|
16
|
+
}));
|
|
17
|
+
server.registerTool("create_frame", {
|
|
18
|
+
description: "Create a frame in a Drawcall Design project. Use type=glts for reusable 3D objects and scenes, then write its source with write_file. Use type=image only for an existing public image URL and type=market only for an exact public Market asset.",
|
|
19
|
+
inputSchema: createFrameSchema,
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: false,
|
|
22
|
+
destructiveHint: false,
|
|
23
|
+
openWorldHint: true,
|
|
24
|
+
idempotentHint: false,
|
|
25
|
+
},
|
|
26
|
+
}, async (input) => result({
|
|
27
|
+
frame: await run("create_frame", () => design.createFrame(frameCreationInput(input))),
|
|
28
|
+
}));
|
|
29
|
+
server.registerTool("generate_image", {
|
|
30
|
+
description: "Generate a 2D image or reference frame from text and up to four same-project image frame references. Use only when the user asks for a 2D image, picture, concept, or reference; do not use for 3D objects, scenes, or reusable GLTS assets. For edit, target is reference 0. New frames are the default; result=replace explicitly replaces the target image while preserving its frame ID, name, and layout.",
|
|
31
|
+
inputSchema: generateImageFrameSchema,
|
|
32
|
+
annotations: {
|
|
33
|
+
readOnlyHint: false,
|
|
34
|
+
destructiveHint: true,
|
|
35
|
+
openWorldHint: true,
|
|
36
|
+
idempotentHint: false,
|
|
37
|
+
},
|
|
38
|
+
}, async (input) => result({
|
|
39
|
+
frame: await run("generate_image", () => design.generateImage(input)),
|
|
40
|
+
}));
|
|
41
|
+
server.registerTool("rename_frame", {
|
|
42
|
+
description: "Rename a frame. Its immutable frame id does not change.",
|
|
43
|
+
inputSchema: frameInput.extend({ name: frameNameSchema }),
|
|
44
|
+
annotations: {
|
|
45
|
+
readOnlyHint: false,
|
|
46
|
+
destructiveHint: false,
|
|
47
|
+
openWorldHint: false,
|
|
48
|
+
},
|
|
49
|
+
}, async ({ project, frame, name }) => result({
|
|
50
|
+
frame: await run("rename_frame", () => design.renameFrame(project, frame, name)),
|
|
51
|
+
}));
|
|
52
|
+
server.registerTool("delete_frame", {
|
|
53
|
+
description: "Permanently delete a frame.",
|
|
54
|
+
inputSchema: frameInput,
|
|
55
|
+
annotations: {
|
|
56
|
+
readOnlyHint: false,
|
|
57
|
+
destructiveHint: true,
|
|
58
|
+
openWorldHint: true,
|
|
59
|
+
idempotentHint: false,
|
|
60
|
+
},
|
|
61
|
+
}, async ({ project, frame }) => result(await run("delete_frame", () => design.deleteFrame(project, frame))));
|
|
62
|
+
server.registerTool("get_frame_screenshot", {
|
|
63
|
+
description: "Render a DPR-1 PNG screenshot of a frame.",
|
|
64
|
+
inputSchema: frameInput,
|
|
65
|
+
annotations: {
|
|
66
|
+
readOnlyHint: true,
|
|
67
|
+
destructiveHint: false,
|
|
68
|
+
openWorldHint: false,
|
|
69
|
+
idempotentHint: true,
|
|
70
|
+
},
|
|
71
|
+
}, async ({ project, frame }) => {
|
|
72
|
+
const screenshot = await run("get_frame_screenshot", () => design.screenshot(project, frame));
|
|
73
|
+
const structuredContent = {
|
|
74
|
+
url: screenshot.url,
|
|
75
|
+
width: screenshot.width,
|
|
76
|
+
height: screenshot.height,
|
|
77
|
+
mediaType: screenshot.mediaType,
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
content: [
|
|
81
|
+
{
|
|
82
|
+
type: "image",
|
|
83
|
+
data: base64(screenshot.png),
|
|
84
|
+
mimeType: screenshot.mediaType,
|
|
85
|
+
},
|
|
86
|
+
],
|
|
87
|
+
structuredContent,
|
|
88
|
+
};
|
|
89
|
+
});
|
|
90
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { projectNameSchema } from "../v1/schemas.js";
|
|
3
|
+
import { result } from "./result.js";
|
|
4
|
+
import { projectInput } from "./schema.js";
|
|
5
|
+
export function registerProjectTools(server, design, run) {
|
|
6
|
+
server.registerTool("list_projects", {
|
|
7
|
+
description: "List projects available to the current Drawcall account.",
|
|
8
|
+
annotations: {
|
|
9
|
+
readOnlyHint: true,
|
|
10
|
+
destructiveHint: false,
|
|
11
|
+
openWorldHint: false,
|
|
12
|
+
idempotentHint: true,
|
|
13
|
+
},
|
|
14
|
+
}, async () => result({
|
|
15
|
+
projects: await run("list_projects", () => design.listProjects()),
|
|
16
|
+
}));
|
|
17
|
+
server.registerTool("create_project", {
|
|
18
|
+
description: "Create a project. Use its returned immutable id in every later call.",
|
|
19
|
+
inputSchema: z.object({ name: projectNameSchema }).strict(),
|
|
20
|
+
annotations: {
|
|
21
|
+
readOnlyHint: false,
|
|
22
|
+
destructiveHint: false,
|
|
23
|
+
openWorldHint: false,
|
|
24
|
+
idempotentHint: false,
|
|
25
|
+
},
|
|
26
|
+
}, async ({ name }) => result({
|
|
27
|
+
project: await run("create_project", () => design.createProject(name)),
|
|
28
|
+
}));
|
|
29
|
+
server.registerTool("delete_project", {
|
|
30
|
+
description: "Permanently delete a project and its frames.",
|
|
31
|
+
inputSchema: projectInput,
|
|
32
|
+
annotations: {
|
|
33
|
+
readOnlyHint: false,
|
|
34
|
+
destructiveHint: true,
|
|
35
|
+
openWorldHint: true,
|
|
36
|
+
idempotentHint: false,
|
|
37
|
+
},
|
|
38
|
+
}, async ({ project }) => result(await run("delete_project", () => design.deleteProject(project))));
|
|
39
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { registerFileTools } from "./file.js";
|
|
2
|
+
import { registerFrameTools } from "./frame.js";
|
|
3
|
+
import { registerProjectTools } from "./project.js";
|
|
4
|
+
import { registerSkillTool } from "./skill.js";
|
|
5
|
+
export function registerDesignTools(server, design, run = runOperation) {
|
|
6
|
+
registerSkillTool(server);
|
|
7
|
+
registerProjectTools(server, design, run);
|
|
8
|
+
registerFrameTools(server, design, run);
|
|
9
|
+
registerFileTools(server, design, run);
|
|
10
|
+
}
|
|
11
|
+
function runOperation(_name, operation) {
|
|
12
|
+
return operation();
|
|
13
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TextFile } from "../v1/schemas.js";
|
|
2
|
+
import type { DesignMcpToolResult } from "./types.js";
|
|
3
|
+
export declare function result<Structured extends Record<string, unknown>>(structuredContent: Structured): DesignMcpToolResult;
|
|
4
|
+
export declare function fileResult(file: TextFile): DesignMcpToolResult;
|
|
5
|
+
export declare function base64(bytes: Uint8Array): string;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function result(structuredContent) {
|
|
2
|
+
return {
|
|
3
|
+
content: [{ type: "text", text: JSON.stringify(structuredContent) }],
|
|
4
|
+
structuredContent,
|
|
5
|
+
};
|
|
6
|
+
}
|
|
7
|
+
export function fileResult(file) {
|
|
8
|
+
return {
|
|
9
|
+
content: [{ type: "text", text: file.text }],
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export function base64(bytes) {
|
|
13
|
+
let binary = "";
|
|
14
|
+
for (let offset = 0; offset < bytes.length; offset += 32_768) {
|
|
15
|
+
binary += String.fromCharCode(...bytes.subarray(offset, offset + 32_768));
|
|
16
|
+
}
|
|
17
|
+
return btoa(binary);
|
|
18
|
+
}
|