@gavana.ai/cli 0.2.0 → 0.2.2
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/CHANGELOG.md +19 -0
- package/README.md +42 -2
- package/guides/creative-canvas.md +52 -0
- package/guides/generated-assets.md +7 -2
- package/guides/paid-action-safety.md +1 -1
- package/guides/sections-layout.md +3 -3
- package/guides/validation-recovery.md +9 -2
- package/package.json +1 -1
- package/src/canvas-agent-guide.mjs +3 -3
- package/src/canvas-agent-validation.mjs +30 -15
- package/src/capabilities.mjs +3 -1
- package/src/client.mjs +252 -0
- package/src/commands.mjs +25 -3
- package/src/config.mjs +50 -17
- package/src/guide-sources.mjs +12 -4
- package/src/mcp-targets.mjs +72 -0
- package/src/runner.mjs +205 -58
- package/src/tools/campaign_plan.mjs +2 -2
- package/src/tools/campaign_review.mjs +2 -2
- package/src/tools/campaign_start.mjs +2 -2
- package/src/tools/definitions.mjs +34 -0
- package/src/tools/element_archive.mjs +12 -0
- package/src/tools/element_collection_create.mjs +11 -0
- package/src/tools/element_collection_delete.mjs +12 -0
- package/src/tools/element_collection_list.mjs +13 -0
- package/src/tools/element_collection_update.mjs +12 -0
- package/src/tools/element_create.mjs +11 -0
- package/src/tools/element_get.mjs +12 -0
- package/src/tools/element_history.mjs +13 -0
- package/src/tools/element_list.mjs +13 -0
- package/src/tools/element_restore.mjs +12 -0
- package/src/tools/element_update.mjs +21 -0
- package/src/tools/element_update_collections.mjs +12 -0
- package/src/tools/image_tool.mjs +3 -3
- package/src/tools/registry.mjs +202 -0
- package/src/tools/schemas.mjs +63 -1
- package/src/tools/work_continue.mjs +42 -0
- package/src/tools/work_execute.mjs +12 -0
- package/src/tools/work_get.mjs +12 -0
- package/src/tools/work_prepare.mjs +21 -0
- package/src/tools/work_refresh.mjs +12 -0
- package/src/version.mjs +5 -7
package/src/runner.mjs
CHANGED
|
@@ -3,15 +3,17 @@ import { execFile as execFileCallback } from "node:child_process";
|
|
|
3
3
|
import fs from "node:fs/promises";
|
|
4
4
|
import http from "node:http";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { createInterface } from "node:readline/promises";
|
|
6
7
|
import { promisify } from "node:util";
|
|
7
8
|
|
|
8
|
-
import { CanvasAgentApiError, createCanvasAgentClient, markdownForResult } from "./client.mjs";
|
|
9
|
+
import { CanvasAgentApiError, createCanvasAgentClient, markdownForResult, parseElementHandle } from "./client.mjs";
|
|
9
10
|
import { GAVANA_CLI_VERSION, gavanaCapabilitySummary } from "./capabilities.mjs";
|
|
10
11
|
import { GAVANA_CLI_GROUP_HELP, gavanaCommandActions, gavanaCommandGroupUsageLines, gavanaCommandGroups, gavanaCommandIsKnown, gavanaCommandUsageLines } from "./commands.mjs";
|
|
11
12
|
import { agentConfigFilePath, listAgentProfiles, readAgentConfig, readAgentConfigMetadata, removeAgentProfile, setActiveAgentProfile, writeAgentConfig } from "./config.mjs";
|
|
13
|
+
import { gavanaMcpClientDefinition } from "./mcp-targets.mjs";
|
|
12
14
|
|
|
13
|
-
const BOOLEAN_OPTIONS = new Set(["help", "version", "pretty", "raw", "yes", "wait", "no-wait", "progress", "token-stdin", "no-verify", "no-browser", "audio", "no-audio", "read-only"]);
|
|
14
|
-
const REPEATABLE_OPTIONS = new Set(["reference", "reference-role", "input", "param", "target", "ratio", "approved", "field"]);
|
|
15
|
+
const BOOLEAN_OPTIONS = new Set(["help", "version", "pretty", "raw", "yes", "confirm", "rebase", "wait", "no-wait", "progress", "token-stdin", "no-verify", "no-browser", "audio", "no-audio", "read-only"]);
|
|
16
|
+
const REPEATABLE_OPTIONS = new Set(["reference", "reference-role", "element", "input", "param", "target", "ratio", "approved", "field", "source-asset", "collection"]);
|
|
15
17
|
const MAX_REFERENCE_IMAGES = 16;
|
|
16
18
|
const MAX_VIDEO_REFERENCE_IMAGES = 9;
|
|
17
19
|
const MAX_REFERENCE_IMAGE_BYTES = 50 * 1024 * 1024;
|
|
@@ -66,7 +68,7 @@ export async function runGavanaCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
66
68
|
return 0;
|
|
67
69
|
}
|
|
68
70
|
if (group === "config") {
|
|
69
|
-
const result = await runConfigCommand(action, positionals, parsed.options, env);
|
|
71
|
+
const result = await runConfigCommand(action, positionals, parsed.options, env, runtime);
|
|
70
72
|
writeResult(stdout, result, parsed.options);
|
|
71
73
|
return 0;
|
|
72
74
|
}
|
|
@@ -78,7 +80,7 @@ export async function runGavanaCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
78
80
|
|
|
79
81
|
const selectedProfile = firstString(parsed.options.profile) || env.GAVANA_PROFILE || env.CRAFTBOARD_PROFILE || "";
|
|
80
82
|
const profileEnv = selectedProfile ? { ...env, GAVANA_PROFILE: selectedProfile } : env;
|
|
81
|
-
const config = group === "mcp" ? await readAgentConfigMetadata(profileEnv) : await readAgentConfig(profileEnv);
|
|
83
|
+
const config = group === "mcp" ? await readAgentConfigMetadata(profileEnv) : await readAgentConfig(profileEnv, keychainDependencies(runtime));
|
|
82
84
|
const token = firstString(parsed.options.token) || env.GAVANA_AGENT_TOKEN || env.CRAFTBOARD_AGENT_TOKEN || config.token;
|
|
83
85
|
const baseUrl = firstString(parsed.options["base-url"]) || env.GAVANA_BASE_URL || env.CRAFTBOARD_BASE_URL || config.baseUrl || "https://app.gavana.ai";
|
|
84
86
|
if (group === "mcp") {
|
|
@@ -91,6 +93,7 @@ export async function runGavanaCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
91
93
|
writeResult(stdout, result, parsed.options);
|
|
92
94
|
return result.ok ? 0 : 1;
|
|
93
95
|
}
|
|
96
|
+
if (!token && config.credentialState === "inaccessible") throw keychainInaccessibleError();
|
|
94
97
|
const client = createCanvasAgentClient({ token, baseUrl, surface: "cli", fetchImpl: runtime.fetchImpl });
|
|
95
98
|
return await runTrackedRemoteCommand(client, `${group}.${String(action || "default").toLowerCase()}`, runtime.analyticsEnabled ?? !runtime.fetchImpl, async () => {
|
|
96
99
|
if (group === "api") {
|
|
@@ -101,6 +104,9 @@ export async function runGavanaCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
101
104
|
env,
|
|
102
105
|
stderr,
|
|
103
106
|
readClipboardImage: runtime.readClipboardImage,
|
|
107
|
+
confirm: runtime.confirm,
|
|
108
|
+
stdin: runtime.stdin || process.stdin,
|
|
109
|
+
isInteractive: runtime.isInteractive ?? Boolean((runtime.stdin || process.stdin).isTTY && stderr.isTTY),
|
|
104
110
|
legacyCampaignCommandsEnabled:
|
|
105
111
|
runtime.legacyCampaignCommandsEnabled ??
|
|
106
112
|
(env.GAVANA_ENABLE_LEGACY_CAMPAIGN_COMMANDS === "true" ||
|
|
@@ -159,7 +165,8 @@ function exitCodeForResult(group, action, result) {
|
|
|
159
165
|
const waitedJob = group === "job" && action === "wait";
|
|
160
166
|
const waitedRun = group === "run" && action === "wait";
|
|
161
167
|
const waitedRecipe = group === "recipe" && action === "run";
|
|
162
|
-
|
|
168
|
+
const terminalWorkFailure = group === "work" && action === "execute" && ["FAILED", "PARTIALLY_FAILED", "CANCELED"].includes(String(result?.status || "").toUpperCase());
|
|
169
|
+
if (((waitedImage || waitedVideo || waitedAction || waitedJob || waitedRun || waitedRecipe) && (result?.status === "failed" || result?.status === "canceled" || result?.status === "expired")) || terminalWorkFailure) return 9;
|
|
163
170
|
return 0;
|
|
164
171
|
}
|
|
165
172
|
|
|
@@ -206,6 +213,35 @@ async function executeCommand(client, group, action, args, options, runtime) {
|
|
|
206
213
|
}
|
|
207
214
|
}
|
|
208
215
|
|
|
216
|
+
if (group === "work") {
|
|
217
|
+
if (action === "prepare") {
|
|
218
|
+
const supplied = await optionalJsonInput(options);
|
|
219
|
+
return client.prepareWork(
|
|
220
|
+
supplied || {
|
|
221
|
+
request: firstString(options.request) || args.join(" ") || requiredArg(args, 0, "work prepare requires --request."),
|
|
222
|
+
...(repeatableStrings(options.reference).length ? { references: repeatableStrings(options.reference) } : {}),
|
|
223
|
+
...(firstString(options.canvas) ? { canvasId: firstString(options.canvas) } : {}),
|
|
224
|
+
idempotencyKey: firstString(options["idempotency-key"]),
|
|
225
|
+
},
|
|
226
|
+
);
|
|
227
|
+
}
|
|
228
|
+
if (action === "get") return client.getWork(firstString(options.work) || requiredArg(args, 0, "work get requires work:<id>."));
|
|
229
|
+
if (action === "refresh") return client.refreshWork(firstString(options.work) || requiredArg(args, 0, "work refresh requires work:<id>."));
|
|
230
|
+
if (action === "continue") {
|
|
231
|
+
const supplied = await optionalJsonInput(options);
|
|
232
|
+
const workId = firstString(options.work) || supplied?.workId || requiredArg(args, 0, "work continue requires work:<id>.");
|
|
233
|
+
const input = supplied ? omitKeys(supplied, ["workId"]) : workContinuationInput(options);
|
|
234
|
+
return client.continueWork(workId, input);
|
|
235
|
+
}
|
|
236
|
+
if (action === "execute") {
|
|
237
|
+
requireWorkConfirmation(options);
|
|
238
|
+
const supplied = await optionalJsonInput(options);
|
|
239
|
+
const workId = firstString(options.work) || supplied?.workId || requiredArg(args, 0, "work execute requires work:<id>.");
|
|
240
|
+
const input = supplied ? omitKeys(supplied, ["workId"]) : { confirm: true, idempotencyKey: firstString(options["idempotency-key"]) };
|
|
241
|
+
return client.executeWork(workId, input);
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
209
245
|
if (group === "campaign") {
|
|
210
246
|
if (action === "plan") {
|
|
211
247
|
const supplied = await optionalJsonInput(options);
|
|
@@ -379,6 +415,45 @@ async function executeCommand(client, group, action, args, options, runtime) {
|
|
|
379
415
|
}
|
|
380
416
|
}
|
|
381
417
|
|
|
418
|
+
if (group === "element") {
|
|
419
|
+
if (action === "list") {
|
|
420
|
+
const state = firstString(options.state);
|
|
421
|
+
if (state && state !== "active" && state !== "archived") throw usageError("--state must be active or archived.");
|
|
422
|
+
return client.listElements({ query: firstString(options.query) || args.join(" "), ...(state ? { state } : {}) }, paginationOptions(options, 100));
|
|
423
|
+
}
|
|
424
|
+
if (action === "get") {
|
|
425
|
+
const elementId = requiredArg(args, 0, "element get requires element:<id>@v<n>.");
|
|
426
|
+
if (!parseElementHandle(elementId).version) throw usageError("element get requires an exact element:<id>@v<n> revision handle.");
|
|
427
|
+
return client.getElement(elementId);
|
|
428
|
+
}
|
|
429
|
+
if (action === "history") return client.listElementHistory(requiredArg(args, 0, "element history requires element:<id>."), paginationOptions(options, 100));
|
|
430
|
+
if (action === "create" || action === "update") {
|
|
431
|
+
const supplied = await optionalJsonInput(options);
|
|
432
|
+
const input = supplied || elementInputFromOptions(options);
|
|
433
|
+
if (action === "create") return client.createElement(input);
|
|
434
|
+
return client.updateElement(requiredArg(args, 0, "element update requires element:<id>."), input);
|
|
435
|
+
}
|
|
436
|
+
if (action === "collections") {
|
|
437
|
+
const supplied = await optionalJsonInput(options);
|
|
438
|
+
const collectionIds = supplied?.collectionIds === undefined ? repeatableStrings(options.collection) : supplied.collectionIds;
|
|
439
|
+
return client.updateElementCollections(requiredArg(args, 0, "element collections requires element:<id>."), collectionIds);
|
|
440
|
+
}
|
|
441
|
+
if (action === "archive") {
|
|
442
|
+
const elementId = requiredArg(args, 0, "element archive requires element:<id>.");
|
|
443
|
+
await requireDestructiveConfirmation(options, runtime, `Archive ${elementId}? It will remain restorable.`, "element archive is destructive. Re-run with --yes in non-interactive mode.");
|
|
444
|
+
return client.archiveElement(elementId);
|
|
445
|
+
}
|
|
446
|
+
if (action === "restore") return client.restoreElement(requiredArg(args, 0, "element restore requires element:<id>."));
|
|
447
|
+
if (action === "collection-list") return client.listElementCollections(paginationOptions(options, 100));
|
|
448
|
+
if (action === "collection-create") return client.createElementCollection({ name: firstString(options.name) || args.join(" ") });
|
|
449
|
+
if (action === "collection-rename") return client.updateElementCollection(requiredArg(args, 0, "element collection-rename requires element-collection:<id>."), { name: firstString(options.name) || requiredArg(args, 1, "element collection-rename requires --name.") });
|
|
450
|
+
if (action === "collection-delete") {
|
|
451
|
+
const collectionId = requiredArg(args, 0, "element collection-delete requires element-collection:<id>.");
|
|
452
|
+
await requireDestructiveConfirmation(options, runtime, `Delete ${collectionId}? This removes the collection, not its Elements.`, "element collection-delete is destructive. Re-run with --yes in non-interactive mode.");
|
|
453
|
+
return client.deleteElementCollection(collectionId);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
382
457
|
if (group === "ai-connection" || group === "provider") {
|
|
383
458
|
if (action === "list") return client.listConnections(paginationOptions(options, 100));
|
|
384
459
|
}
|
|
@@ -429,11 +504,11 @@ async function executeCommand(client, group, action, args, options, runtime) {
|
|
|
429
504
|
}
|
|
430
505
|
|
|
431
506
|
if (group === "image" && (action === "generate" || action === "edit" || action === "variations")) {
|
|
507
|
+
if (options.wait === true && options["no-wait"] === true) throw usageError("--wait and --no-wait cannot be combined.");
|
|
432
508
|
const prepared = await imageInput(client, action, args, options, runtime);
|
|
433
509
|
const { __destination, ...input } = prepared;
|
|
434
510
|
const queued = await client.startImage(action, input);
|
|
435
|
-
|
|
436
|
-
if (!shouldWait) return { ...queued, destination: __destination };
|
|
511
|
+
if (options.wait !== true) return { ...queued, destination: __destination };
|
|
437
512
|
const result = await client.waitForRun(queued.run || queued.id, {
|
|
438
513
|
timeoutMs: secondsOption(options.timeout, 15 * 60) * 1000,
|
|
439
514
|
intervalMs: secondsOption(options.interval, 1.5, true) * 1000,
|
|
@@ -551,7 +626,7 @@ async function runAuthCommand(action, _args, options, env, runtime) {
|
|
|
551
626
|
if (action === "login") {
|
|
552
627
|
const profile = firstString(options.profile) || env.GAVANA_PROFILE || env.CRAFTBOARD_PROFILE || "";
|
|
553
628
|
const profileEnv = profile ? { ...env, GAVANA_PROFILE: profile } : env;
|
|
554
|
-
const current = await readAgentConfig(profileEnv);
|
|
629
|
+
const current = await readAgentConfig(profileEnv, keychainDependencies(runtime));
|
|
555
630
|
let token = firstString(options.token) || env.GAVANA_AGENT_TOKEN || env.CRAFTBOARD_AGENT_TOKEN || (options["token-stdin"] === true ? (await readAllStdin()).trim() : "");
|
|
556
631
|
const baseUrl = createCanvasAgentClient({
|
|
557
632
|
token: "configuration-validation",
|
|
@@ -593,10 +668,21 @@ async function runAuthCommand(action, _args, options, env, runtime) {
|
|
|
593
668
|
if (action === "status") {
|
|
594
669
|
const profile = firstString(options.profile) || env.GAVANA_PROFILE || env.CRAFTBOARD_PROFILE || "";
|
|
595
670
|
const profileEnv = profile ? { ...env, GAVANA_PROFILE: profile } : env;
|
|
596
|
-
const config = await readAgentConfig(profileEnv);
|
|
671
|
+
const config = await readAgentConfig(profileEnv, keychainDependencies(runtime));
|
|
597
672
|
const token = firstString(options.token) || env.GAVANA_AGENT_TOKEN || env.CRAFTBOARD_AGENT_TOKEN || config.token;
|
|
598
673
|
const baseUrl = firstString(options["base-url"]) || env.GAVANA_BASE_URL || env.CRAFTBOARD_BASE_URL || config.baseUrl;
|
|
599
|
-
if (!token || !baseUrl)
|
|
674
|
+
if (!token || !baseUrl) {
|
|
675
|
+
return {
|
|
676
|
+
authenticated: false,
|
|
677
|
+
configured: false,
|
|
678
|
+
profile: config.profile,
|
|
679
|
+
baseUrl: baseUrl || "",
|
|
680
|
+
credentialStore: config.credentialStore || "",
|
|
681
|
+
credentialState: config.credentialState,
|
|
682
|
+
...(config.credentialState === "inaccessible" ? { detail: keychainInaccessibleError().message } : {}),
|
|
683
|
+
configPath: agentConfigFilePath(env),
|
|
684
|
+
};
|
|
685
|
+
}
|
|
600
686
|
const client = createCanvasAgentClient({ token, baseUrl, surface: "cli", fetchImpl });
|
|
601
687
|
const verification = await verifyAuthentication(client);
|
|
602
688
|
let canvasCount = verification.canvasCount;
|
|
@@ -612,9 +698,11 @@ async function runAuthCommand(action, _args, options, env, runtime) {
|
|
|
612
698
|
return {
|
|
613
699
|
...verification,
|
|
614
700
|
profile: config.profile,
|
|
615
|
-
configured: Boolean(config.
|
|
701
|
+
configured: Boolean(config.credentialState === "available" && config.baseUrl),
|
|
616
702
|
baseUrl: client.baseUrl,
|
|
617
703
|
token: maskToken(token),
|
|
704
|
+
credentialStore: config.credentialStore || "",
|
|
705
|
+
credentialState: config.credentialState,
|
|
618
706
|
...(canvasCount === undefined ? {} : { canvasCount }),
|
|
619
707
|
configPath: agentConfigFilePath(env),
|
|
620
708
|
};
|
|
@@ -622,7 +710,7 @@ async function runAuthCommand(action, _args, options, env, runtime) {
|
|
|
622
710
|
if (action === "logout") {
|
|
623
711
|
const profile = firstString(options.profile) || env.GAVANA_PROFILE || env.CRAFTBOARD_PROFILE || "";
|
|
624
712
|
const profileEnv = profile ? { ...env, GAVANA_PROFILE: profile } : env;
|
|
625
|
-
const config = await readAgentConfig(profileEnv);
|
|
713
|
+
const config = await readAgentConfig(profileEnv, keychainDependencies(runtime));
|
|
626
714
|
if (config.authMode === "oauth" && config.token && config.baseUrl) {
|
|
627
715
|
await revokeBrowserOAuthToken(config.baseUrl, config.token, fetchImpl || globalThis.fetch);
|
|
628
716
|
}
|
|
@@ -653,7 +741,7 @@ export async function performBrowserOAuthLogin({ baseUrl, readOnly = false, noBr
|
|
|
653
741
|
const state = crypto.randomBytes(24).toString("base64url");
|
|
654
742
|
const callback = runtime.createOAuthCallbackServer ? await runtime.createOAuthCallbackServer(state) : await createOAuthCallbackServer(state);
|
|
655
743
|
try {
|
|
656
|
-
const scopes = readOnly ? "canvas:read asset:read" : "canvas:read canvas:write asset:read image:generate video:generate job:manage";
|
|
744
|
+
const scopes = readOnly ? "canvas:read asset:read element:read" : "canvas:read canvas:write asset:read element:read element:write image:generate video:generate job:manage";
|
|
657
745
|
const registrationResponse = await fetchImpl(`${origin}/oauth/register`, {
|
|
658
746
|
method: "POST",
|
|
659
747
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
@@ -757,20 +845,31 @@ async function openBrowser(url) {
|
|
|
757
845
|
return execFile("xdg-open", [url]);
|
|
758
846
|
}
|
|
759
847
|
|
|
760
|
-
async function runConfigCommand(action, args, options, env) {
|
|
761
|
-
if (action === "list" || action === "profiles" || !action)
|
|
848
|
+
async function runConfigCommand(action, args, options, env, runtime = {}) {
|
|
849
|
+
if (action === "list" || action === "profiles" || !action) {
|
|
850
|
+
const result = await listAgentProfiles(env, keychainDependencies(runtime));
|
|
851
|
+
return {
|
|
852
|
+
...result,
|
|
853
|
+
profiles: result.profiles.map((profile) =>
|
|
854
|
+
profile.credentialState === "inaccessible" ? { ...profile, detail: keychainInaccessibleError().message } : profile,
|
|
855
|
+
),
|
|
856
|
+
};
|
|
857
|
+
}
|
|
762
858
|
if (action === "use") {
|
|
763
859
|
const profile = firstString(options.profile) || requiredArg(args, 0, "config use requires a profile name.");
|
|
764
860
|
return { active: true, ...(await setActiveAgentProfile(profile, env)) };
|
|
765
861
|
}
|
|
766
862
|
if (action === "get") {
|
|
767
863
|
const profile = firstString(options.profile) || args[0] || "";
|
|
768
|
-
const config = await readAgentConfig(profile ? { ...env, GAVANA_PROFILE: profile } : env);
|
|
864
|
+
const config = await readAgentConfig(profile ? { ...env, GAVANA_PROFILE: profile } : env, keychainDependencies(runtime));
|
|
769
865
|
return {
|
|
770
866
|
profile: config.profile,
|
|
771
867
|
baseUrl: config.baseUrl || "",
|
|
772
|
-
configured: Boolean(config.
|
|
868
|
+
configured: Boolean(config.credentialState === "available" && config.baseUrl),
|
|
773
869
|
token: config.token ? maskToken(config.token) : "",
|
|
870
|
+
credentialStore: config.credentialStore || "",
|
|
871
|
+
credentialState: config.credentialState,
|
|
872
|
+
...(config.credentialState === "inaccessible" ? { detail: keychainInaccessibleError().message } : {}),
|
|
774
873
|
configPath: agentConfigFilePath(env),
|
|
775
874
|
};
|
|
776
875
|
}
|
|
@@ -778,17 +877,18 @@ async function runConfigCommand(action, args, options, env) {
|
|
|
778
877
|
}
|
|
779
878
|
|
|
780
879
|
async function runDoctor(client, config, env, baseUrl, credentialSource = "") {
|
|
880
|
+
const credentialInaccessible = config.credentialState === "inaccessible" && !credentialSource;
|
|
781
881
|
const checks = [
|
|
782
882
|
{ name: "node", ok: Number(process.versions.node.split(".")[0]) >= 20, detail: process.version },
|
|
783
883
|
{
|
|
784
884
|
name: "credentials",
|
|
785
885
|
ok: Boolean(client),
|
|
786
|
-
detail: credentialSource || (config.token && config.baseUrl ? agentConfigFilePath(env) : client ? "Environment or command-line override" : "Run gavana auth login or set GAVANA_AGENT_TOKEN."),
|
|
886
|
+
detail: credentialInaccessible ? keychainInaccessibleError().message : credentialSource || (config.token && config.baseUrl ? agentConfigFilePath(env) : client ? "Environment or command-line override" : "Run gavana auth login or set GAVANA_AGENT_TOKEN."),
|
|
787
887
|
},
|
|
788
888
|
{ name: "base_url", ok: /^https:\/\//.test(baseUrl) || /^http:\/\/(?:127\.0\.0\.1|localhost|\[::1\])(?::\d+)?(?:\/|$)/.test(baseUrl), detail: baseUrl },
|
|
789
889
|
];
|
|
790
890
|
if (!client) {
|
|
791
|
-
checks.push({ name: "authentication", ok: false, detail: "No token is configured. Run gavana auth login." });
|
|
891
|
+
checks.push({ name: "authentication", ok: false, detail: credentialInaccessible ? keychainInaccessibleError().message : "No token is configured. Run gavana auth login." });
|
|
792
892
|
return { ok: false, profile: config.profile, checks };
|
|
793
893
|
}
|
|
794
894
|
try {
|
|
@@ -801,6 +901,14 @@ async function runDoctor(client, config, env, baseUrl, credentialSource = "") {
|
|
|
801
901
|
return { ok: checks.every((check) => check.ok), profile: config.profile, checks };
|
|
802
902
|
}
|
|
803
903
|
|
|
904
|
+
function keychainDependencies(runtime) {
|
|
905
|
+
return runtime.readKeychainCredential ? { readKeychainCredential: runtime.readKeychainCredential } : {};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
function keychainInaccessibleError() {
|
|
909
|
+
return new CanvasAgentApiError("The configured macOS Keychain credential is inaccessible to this process. Run this command from a Keychain-authorized terminal or provide GAVANA_AGENT_TOKEN for this invocation; do not log in again unless the credential is actually missing.", { code: "keychain_inaccessible" });
|
|
910
|
+
}
|
|
911
|
+
|
|
804
912
|
async function runApiCommand(client, methodOrPath, args, options) {
|
|
805
913
|
const knownMethod = /^(get|post|put|patch|delete)$/i.test(methodOrPath || "");
|
|
806
914
|
const method = knownMethod ? methodOrPath.toUpperCase() : "GET";
|
|
@@ -823,34 +931,16 @@ async function runMcpCommand(action, args, options, baseUrl, runtime) {
|
|
|
823
931
|
if (action !== "install" && action !== "config") throw usageError("mcp supports install and config.");
|
|
824
932
|
const clientName = (firstString(options.client) || args[0] || "codex").toLowerCase();
|
|
825
933
|
if (action === "install" && clientName !== "codex" && clientName !== "claude") {
|
|
826
|
-
throw usageError("mcp install supports codex and claude. Use mcp config for cursor, chatgpt, or local clients.");
|
|
934
|
+
throw usageError("mcp install supports codex and claude. Use mcp config for cursor, vscode, chatgpt, or local clients.");
|
|
827
935
|
}
|
|
828
|
-
const
|
|
829
|
-
|
|
936
|
+
const definition = gavanaMcpClientDefinition(clientName, baseUrl, options["read-only"] === true);
|
|
937
|
+
if (!definition) throw usageError("mcp client must be codex, claude, cursor, vscode, chatgpt, or local.");
|
|
830
938
|
if (action === "config" || !definition.command) return definition;
|
|
831
939
|
const execute = runtime.execFile || execFile;
|
|
832
940
|
await execute(definition.command, definition.args);
|
|
833
941
|
return { ...definition, installed: true };
|
|
834
942
|
}
|
|
835
943
|
|
|
836
|
-
function mcpClientDefinition(clientName, endpoint, readOnly = false) {
|
|
837
|
-
if (clientName === "codex") return { client: "codex", transport: "streamable-http", endpoint, command: "codex", args: ["mcp", "add", "gavana", "--url", endpoint] };
|
|
838
|
-
if (clientName === "claude") return { client: "claude", transport: "streamable-http", endpoint, command: "claude", args: ["mcp", "add", "--transport", "http", "gavana", endpoint] };
|
|
839
|
-
if (clientName === "cursor") return { client: "cursor", transport: "streamable-http", endpoint, config: { mcpServers: { gavana: { url: endpoint } } } };
|
|
840
|
-
if (clientName === "chatgpt") return { client: "chatgpt", transport: "streamable-http", endpoint, instructions: "Add the endpoint as a custom MCP connector, then complete Gavana OAuth in the browser." };
|
|
841
|
-
if (clientName === "stdio" || clientName === "local") {
|
|
842
|
-
return {
|
|
843
|
-
client: "local",
|
|
844
|
-
transport: "stdio",
|
|
845
|
-
command: "npx",
|
|
846
|
-
args: ["-y", "@gavana.ai/mcp@0.2.0"],
|
|
847
|
-
env: readOnly ? { GAVANA_MCP_READ_ONLY: "true" } : {},
|
|
848
|
-
credentialSource: "Reads the active Gavana CLI profile or inherited GAVANA_BASE_URL and GAVANA_AGENT_TOKEN environment variables.",
|
|
849
|
-
};
|
|
850
|
-
}
|
|
851
|
-
throw usageError("mcp client must be codex, claude, cursor, chatgpt, or local.");
|
|
852
|
-
}
|
|
853
|
-
|
|
854
944
|
function completionScript(shell) {
|
|
855
945
|
// Generated from the command table, so a new command is completable the moment
|
|
856
946
|
// it is declared. This list used to be a third hand-maintained copy of the
|
|
@@ -1108,6 +1198,20 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1108
1198
|
const count = supplied.count !== undefined || options.count !== undefined ? (supplied.count ?? requiredNumber(options.count, "--count must be a number.")) : undefined;
|
|
1109
1199
|
if (count !== undefined && (!Number.isInteger(Number(count)) || Number(count) < 1 || Number(count) > 4)) throw usageError("--count must be an integer between 1 and 4.");
|
|
1110
1200
|
const referenceInputs = imageReferenceInputs(supplied.references, options.reference, supplied.referenceRoles, options["reference-role"]);
|
|
1201
|
+
const optionElements = repeatableStrings(options.element);
|
|
1202
|
+
if (supplied.elements !== undefined && optionElements.length) throw usageError("Set Elements in JSON or with --element, not both.");
|
|
1203
|
+
const elements = supplied.elements === undefined ? optionElements : Array.isArray(supplied.elements) ? supplied.elements : (() => { throw usageError("elements must be an array."); })();
|
|
1204
|
+
if (elements.length > 8) throw usageError("An image job can use up to 8 Elements.");
|
|
1205
|
+
for (const entry of elements) {
|
|
1206
|
+
const typed = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : null;
|
|
1207
|
+
if (typed && Object.keys(typed).some((key) => key !== "handle" && key !== "role" && key !== "influence")) throw usageError('An Element reference may contain only "handle", "role", and "influence".');
|
|
1208
|
+
const handle = typeof entry === "string" ? entry.trim() : typeof typed?.handle === "string" ? typed.handle.trim() : "";
|
|
1209
|
+
if (!/^element:[A-Za-z0-9_-]{1,160}@v[1-9][0-9]*$/.test(handle)) throw usageError("Each --element must be a version-pinned element:<id>@v<n> handle.");
|
|
1210
|
+
if (typed?.role !== undefined && !["identity", "construction", "texture", "fit", "style"].includes(typed.role)) throw usageError("Element roles must be identity, construction, texture, fit, or style.");
|
|
1211
|
+
if (typed?.influence !== undefined && (typeof typed.influence !== "number" || !Number.isFinite(typed.influence) || typed.influence < 0 || typed.influence > 1)) throw usageError("Element influence must be a number from 0 through 1.");
|
|
1212
|
+
}
|
|
1213
|
+
const elementHandles = elements.map((entry) => (typeof entry === "string" ? entry.trim() : entry.handle.trim()));
|
|
1214
|
+
if (new Set(elementHandles).size !== elementHandles.length) throw usageError("Element handles must be unique.");
|
|
1111
1215
|
const preparedReferences = await prepareReferenceInputs(referenceInputs.map((reference) => reference.value), runtime);
|
|
1112
1216
|
const canvasResult = await client.resolveCanvasDestination(destination, {
|
|
1113
1217
|
idempotencyKey,
|
|
@@ -1145,6 +1249,7 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1145
1249
|
targetY: _targetY,
|
|
1146
1250
|
targetWidth: _targetWidth,
|
|
1147
1251
|
targetHeight: _targetHeight,
|
|
1252
|
+
elements: _elements,
|
|
1148
1253
|
...jobSupplied
|
|
1149
1254
|
} = supplied;
|
|
1150
1255
|
return {
|
|
@@ -1156,6 +1261,7 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1156
1261
|
...(prompt ? { prompt } : {}),
|
|
1157
1262
|
...(supplied.promptNodeId || firstString(options["prompt-node"]) ? { promptNodeId: supplied.promptNodeId || firstString(options["prompt-node"]) } : {}),
|
|
1158
1263
|
...(references.length ? { references: references.some((reference) => reference.role) ? references : referenceHandles } : {}),
|
|
1264
|
+
...(elements.length ? { elements } : {}),
|
|
1159
1265
|
...(source ? { source } : {}),
|
|
1160
1266
|
...(supplied.connectionId || firstString(options.connection) ? { connectionId: supplied.connectionId || firstString(options.connection) } : {}),
|
|
1161
1267
|
...(supplied.model || firstString(options.model) ? { model: supplied.model || firstString(options.model) } : {}),
|
|
@@ -1474,23 +1580,17 @@ function ensureUniqueVideoReferences(references) {
|
|
|
1474
1580
|
}
|
|
1475
1581
|
|
|
1476
1582
|
async function uploadLocalReferenceInputs(client, references, canvasReference) {
|
|
1477
|
-
const handles = [];
|
|
1478
1583
|
const uploads = new Map();
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
if (!uploads.has(fingerprint)) uploads.set(fingerprint, client.uploadAsset({ ...reference, canvasReference }));
|
|
1490
|
-
const uploaded = await uploads.get(fingerprint);
|
|
1491
|
-
handles.push(uploaded.asset.handle);
|
|
1492
|
-
}
|
|
1493
|
-
return handles;
|
|
1584
|
+
return Promise.all(
|
|
1585
|
+
references.map(async (reference) => {
|
|
1586
|
+
if (reference.kind === "handle") return reference.handle;
|
|
1587
|
+
if (reference.kind === "url") return reference.url;
|
|
1588
|
+
const fingerprint = crypto.createHash("sha256").update(reference.bytes).digest("base64url");
|
|
1589
|
+
if (!uploads.has(fingerprint)) uploads.set(fingerprint, client.uploadAsset({ ...reference, canvasReference }));
|
|
1590
|
+
const uploaded = await uploads.get(fingerprint);
|
|
1591
|
+
return uploaded.asset.handle;
|
|
1592
|
+
}),
|
|
1593
|
+
);
|
|
1494
1594
|
}
|
|
1495
1595
|
|
|
1496
1596
|
async function readMacClipboardImage() {
|
|
@@ -1763,6 +1863,7 @@ function exitCodeForError(error) {
|
|
|
1763
1863
|
if (
|
|
1764
1864
|
error.code === "usage" ||
|
|
1765
1865
|
error.code === "configuration" ||
|
|
1866
|
+
error.code === "keychain_inaccessible" ||
|
|
1766
1867
|
error.code === "validation" ||
|
|
1767
1868
|
error.code === "input_validation_error" ||
|
|
1768
1869
|
error.code === "invalid_json" ||
|
|
@@ -1786,10 +1887,55 @@ function requiredArg(args, index, message) {
|
|
|
1786
1887
|
return value;
|
|
1787
1888
|
}
|
|
1788
1889
|
|
|
1890
|
+
function workContinuationInput(options) {
|
|
1891
|
+
const action = firstString(options.action);
|
|
1892
|
+
const idempotencyKey = firstString(options["idempotency-key"]);
|
|
1893
|
+
if (action === "answer") return { action, answer: firstString(options.answer), idempotencyKey };
|
|
1894
|
+
if (action === "select_direction") return { action, directionId: firstString(options["direction-id"]), idempotencyKey };
|
|
1895
|
+
if (action === "adjust") return { action, adjustment: firstString(options.adjustment), idempotencyKey };
|
|
1896
|
+
if (action === "acknowledge_canvas") return { action, idempotencyKey };
|
|
1897
|
+
if (options.rebase === true) return { rebase: true, idempotencyKey };
|
|
1898
|
+
throw usageError("work continue requires --action answer, select_direction, adjust, acknowledge_canvas, or --rebase.");
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
function omitKeys(value, keys) {
|
|
1902
|
+
return Object.fromEntries(Object.entries(value).filter(([key]) => !keys.includes(key)));
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1789
1905
|
function requireConfirmation(options, message) {
|
|
1790
1906
|
if (options.yes !== true) throw usageError(message);
|
|
1791
1907
|
}
|
|
1792
1908
|
|
|
1909
|
+
function requireWorkConfirmation(options) {
|
|
1910
|
+
if (options.confirm !== true) throw usageError("work execute starts paid work. Re-run with --confirm.");
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
async function requireDestructiveConfirmation(options, runtime, prompt, nonInteractiveMessage) {
|
|
1914
|
+
if (options.yes === true) return;
|
|
1915
|
+
if (!runtime.isInteractive) throw usageError(nonInteractiveMessage);
|
|
1916
|
+
const confirmed = typeof runtime.confirm === "function" ? await runtime.confirm(prompt) : await promptForConfirmation(runtime.stdin, runtime.stderr, prompt);
|
|
1917
|
+
if (!confirmed) throw usageError("Destructive operation cancelled.");
|
|
1918
|
+
}
|
|
1919
|
+
|
|
1920
|
+
async function promptForConfirmation(input, output, prompt) {
|
|
1921
|
+
const terminal = createInterface({ input, output, terminal: true });
|
|
1922
|
+
try {
|
|
1923
|
+
return /^(y|yes)$/i.test((await terminal.question(`${prompt} [y/N] `)).trim());
|
|
1924
|
+
} finally {
|
|
1925
|
+
terminal.close();
|
|
1926
|
+
}
|
|
1927
|
+
}
|
|
1928
|
+
|
|
1929
|
+
function elementInputFromOptions(options) {
|
|
1930
|
+
return {
|
|
1931
|
+
name: firstString(options.name),
|
|
1932
|
+
type: firstString(options.type),
|
|
1933
|
+
sourceAssetIds: repeatableStrings(options["source-asset"]),
|
|
1934
|
+
...(firstString(options.guidelines) ? { guidelines: firstString(options.guidelines) } : {}),
|
|
1935
|
+
...(repeatableStrings(options.collection).length ? { collectionIds: repeatableStrings(options.collection) } : {}),
|
|
1936
|
+
};
|
|
1937
|
+
}
|
|
1938
|
+
|
|
1793
1939
|
function parseJsonOption(value, message) {
|
|
1794
1940
|
const text = firstString(value);
|
|
1795
1941
|
return text ? parseJson(text, message) : null;
|
|
@@ -1900,7 +2046,8 @@ Global options:
|
|
|
1900
2046
|
--webhook-url URL Send one signed callback when a Run finishes
|
|
1901
2047
|
--webhook-secret-env VAR Read the signing secret from VAR (default: GAVANA_WEBHOOK_SECRET)
|
|
1902
2048
|
--canvas-title VALUE Title used when --destination is new-canvas
|
|
1903
|
-
--
|
|
2049
|
+
--wait Wait for an image Run to finish instead of returning after queueing
|
|
2050
|
+
--no-wait Return immediately after queueing Recipe, video, or Action work (legacy image alias)
|
|
1904
2051
|
--progress Write Run or video Job state transitions to stderr
|
|
1905
2052
|
|
|
1906
2053
|
Video options:
|
|
@@ -7,8 +7,8 @@ import { campaignAspectRatio, campaignIdempotencyKey, campaignProductReference,
|
|
|
7
7
|
|
|
8
8
|
export function defineCampaignPlan(client) {
|
|
9
9
|
return {
|
|
10
|
-
title: "
|
|
11
|
-
description: "Compatibility operation for an existing campaign integration. Do not use it for new work; use editable
|
|
10
|
+
title: "Retired campaign plan (migration only)",
|
|
11
|
+
description: "Compatibility operation for an existing historical campaign integration. The server rejects mutations unless its explicit migration flag is enabled. Do not use it for new creative work; use editable Canvas Sections, reference nodes, and explicit image jobs instead.",
|
|
12
12
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
13
13
|
inputSchema: z.object({
|
|
14
14
|
canvasId: exactCanvasReference,
|
|
@@ -7,8 +7,8 @@ import { approvedOutputReferences, campaignIdempotencyKey, runReference } from "
|
|
|
7
7
|
|
|
8
8
|
export function defineCampaignReview(client) {
|
|
9
9
|
return {
|
|
10
|
-
title: "
|
|
11
|
-
description: "Compatibility operation for an existing campaign record.
|
|
10
|
+
title: "Retired campaign review (migration only)",
|
|
11
|
+
description: "Compatibility operation for an existing historical campaign record. This old approval gate is not for new creative work; keep new directions editable and spatially organized on the Canvas.",
|
|
12
12
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
13
13
|
inputSchema: z.object({
|
|
14
14
|
runId: runReference,
|
|
@@ -7,8 +7,8 @@ import { campaignIdempotencyKey, campaignReference } from "./schemas.mjs";
|
|
|
7
7
|
|
|
8
8
|
export function defineCampaignStart(client) {
|
|
9
9
|
return {
|
|
10
|
-
title: "
|
|
11
|
-
description: "
|
|
10
|
+
title: "Retired campaign start (migration only)",
|
|
11
|
+
description: "Starts an existing historical campaign record only when the server's explicit compatibility migration flag is enabled. Its old review gate is not a creative workflow; use editable Canvas Sections, reference nodes, and explicit image jobs for new work.",
|
|
12
12
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
13
13
|
inputSchema: z.object({
|
|
14
14
|
campaignId: campaignReference,
|
|
@@ -31,6 +31,18 @@ import { defineCanvasList } from "./canvas_list.mjs";
|
|
|
31
31
|
import { defineCanvasValidate } from "./canvas_validate.mjs";
|
|
32
32
|
import { defineConnectionCreate } from "./connection_create.mjs";
|
|
33
33
|
import { defineConnectionDelete } from "./connection_delete.mjs";
|
|
34
|
+
import { defineElementArchive } from "./element_archive.mjs";
|
|
35
|
+
import { defineElementCollectionCreate } from "./element_collection_create.mjs";
|
|
36
|
+
import { defineElementCollectionDelete } from "./element_collection_delete.mjs";
|
|
37
|
+
import { defineElementCollectionList } from "./element_collection_list.mjs";
|
|
38
|
+
import { defineElementCollectionUpdate } from "./element_collection_update.mjs";
|
|
39
|
+
import { defineElementCreate } from "./element_create.mjs";
|
|
40
|
+
import { defineElementGet } from "./element_get.mjs";
|
|
41
|
+
import { defineElementHistory } from "./element_history.mjs";
|
|
42
|
+
import { defineElementList } from "./element_list.mjs";
|
|
43
|
+
import { defineElementRestore } from "./element_restore.mjs";
|
|
44
|
+
import { defineElementUpdate } from "./element_update.mjs";
|
|
45
|
+
import { defineElementUpdateCollections } from "./element_update_collections.mjs";
|
|
34
46
|
import { defineGuideGet } from "./guide_get.mjs";
|
|
35
47
|
import { defineGuideSearch } from "./guide_search.mjs";
|
|
36
48
|
import { defineJobCancel } from "./job_cancel.mjs";
|
|
@@ -53,6 +65,11 @@ import { defineRunCancel } from "./run_cancel.mjs";
|
|
|
53
65
|
import { defineRunGet } from "./run_get.mjs";
|
|
54
66
|
import { defineRunWait } from "./run_wait.mjs";
|
|
55
67
|
import { defineVideoGenerate } from "./video_generate.mjs";
|
|
68
|
+
import { defineWorkContinue } from "./work_continue.mjs";
|
|
69
|
+
import { defineWorkExecute } from "./work_execute.mjs";
|
|
70
|
+
import { defineWorkGet } from "./work_get.mjs";
|
|
71
|
+
import { defineWorkPrepare } from "./work_prepare.mjs";
|
|
72
|
+
import { defineWorkRefresh } from "./work_refresh.mjs";
|
|
56
73
|
|
|
57
74
|
/** @type {Record<string, (client: unknown) => object>} */
|
|
58
75
|
export const GAVANA_TOOL_DEFINITIONS = Object.freeze({
|
|
@@ -79,6 +96,18 @@ export const GAVANA_TOOL_DEFINITIONS = Object.freeze({
|
|
|
79
96
|
canvas_validate: defineCanvasValidate,
|
|
80
97
|
connection_create: defineConnectionCreate,
|
|
81
98
|
connection_delete: defineConnectionDelete,
|
|
99
|
+
element_archive: defineElementArchive,
|
|
100
|
+
element_collection_create: defineElementCollectionCreate,
|
|
101
|
+
element_collection_delete: defineElementCollectionDelete,
|
|
102
|
+
element_collection_list: defineElementCollectionList,
|
|
103
|
+
element_collection_update: defineElementCollectionUpdate,
|
|
104
|
+
element_create: defineElementCreate,
|
|
105
|
+
element_get: defineElementGet,
|
|
106
|
+
element_history: defineElementHistory,
|
|
107
|
+
element_list: defineElementList,
|
|
108
|
+
element_restore: defineElementRestore,
|
|
109
|
+
element_update: defineElementUpdate,
|
|
110
|
+
element_update_collections: defineElementUpdateCollections,
|
|
82
111
|
guide_get: defineGuideGet,
|
|
83
112
|
guide_search: defineGuideSearch,
|
|
84
113
|
job_cancel: defineJobCancel,
|
|
@@ -101,6 +130,11 @@ export const GAVANA_TOOL_DEFINITIONS = Object.freeze({
|
|
|
101
130
|
run_get: defineRunGet,
|
|
102
131
|
run_wait: defineRunWait,
|
|
103
132
|
video_generate: defineVideoGenerate,
|
|
133
|
+
work_continue: defineWorkContinue,
|
|
134
|
+
work_execute: defineWorkExecute,
|
|
135
|
+
work_get: defineWorkGet,
|
|
136
|
+
work_prepare: defineWorkPrepare,
|
|
137
|
+
work_refresh: defineWorkRefresh,
|
|
104
138
|
});
|
|
105
139
|
|
|
106
140
|
/** Build every local tool config for a client, in registry order. */
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { mutableElementReference } from "./schemas.mjs";
|
|
3
|
+
|
|
4
|
+
export function defineElementArchive(client) {
|
|
5
|
+
return {
|
|
6
|
+
title: "Archive a Gavana Element",
|
|
7
|
+
description: "Archive an Element. First read and show the affected Element to the user; it stays recoverable and confirm must be true only after explicit approval.",
|
|
8
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
9
|
+
inputSchema: z.object({ elementId: mutableElementReference, confirm: z.literal(true).describe("Must be true after the user confirms archival.") }),
|
|
10
|
+
handler: ({ elementId }) => client.archiveElement(elementId),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
|
|
3
|
+
export function defineElementCollectionCreate(client) {
|
|
4
|
+
return {
|
|
5
|
+
title: "Create an Element collection",
|
|
6
|
+
description: "Create a user-owned collection for organizing Elements.",
|
|
7
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
8
|
+
inputSchema: z.object({ name: z.string().min(1).max(120) }),
|
|
9
|
+
handler: (input) => client.createElementCollection(input),
|
|
10
|
+
};
|
|
11
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { elementCollectionReference } from "./schemas.mjs";
|
|
3
|
+
|
|
4
|
+
export function defineElementCollectionDelete(client) {
|
|
5
|
+
return {
|
|
6
|
+
title: "Delete an Element collection",
|
|
7
|
+
description: "Delete a collection without deleting its Elements. First show the affected collection to the user; confirm must be true only after explicit approval.",
|
|
8
|
+
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: false },
|
|
9
|
+
inputSchema: z.object({ collectionId: elementCollectionReference, confirm: z.literal(true).describe("Must be true after the user confirms deletion.") }),
|
|
10
|
+
handler: ({ collectionId }) => client.deleteElementCollection(collectionId),
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { listOptions } from "./helpers.mjs";
|
|
3
|
+
import { listCursor, listLimit } from "./schemas.mjs";
|
|
4
|
+
|
|
5
|
+
export function defineElementCollectionList(client) {
|
|
6
|
+
return {
|
|
7
|
+
title: "List Element collections",
|
|
8
|
+
description: "List the user's Element collections.",
|
|
9
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
10
|
+
inputSchema: z.object({ limit: listLimit(100), cursor: listCursor }),
|
|
11
|
+
handler: ({ limit, cursor }) => client.listElementCollections(listOptions(limit, cursor)),
|
|
12
|
+
};
|
|
13
|
+
}
|