@gavana.ai/cli 0.2.1 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +38 -0
- package/README.md +35 -5
- package/guides/creative-canvas.md +16 -3
- package/guides/generated-assets.md +38 -5
- package/guides/getting-started.md +16 -1
- package/guides/paid-action-safety.md +2 -2
- package/guides/product-imports.md +45 -0
- package/guides/untrusted-content.md +51 -0
- package/guides/video-generation.md +52 -0
- package/guides/workflows.md +43 -0
- package/package.json +1 -1
- package/src/canvas-agent-guide.mjs +65 -13
- package/src/canvas-agent-validation.mjs +113 -17
- package/src/capabilities.mjs +1 -1
- package/src/client.mjs +16 -0
- package/src/commands.mjs +5 -5
- package/src/guide-sources.mjs +36 -4
- package/src/mcp-targets.mjs +1 -1
- package/src/runner.mjs +77 -26
- package/src/tools/definitions.mjs +2 -0
- package/src/tools/element_create.mjs +1 -1
- package/src/tools/element_update.mjs +2 -1
- package/src/tools/guide_get.mjs +1 -1
- package/src/tools/guide_search.mjs +1 -1
- package/src/tools/image_tool.mjs +10 -4
- package/src/tools/registry.mjs +72 -26
- package/src/tools/run_list.mjs +20 -0
- package/src/tools/schemas.mjs +38 -4
- package/src/tools/surface-names.mjs +120 -0
- package/src/version.mjs +1 -1
package/src/runner.mjs
CHANGED
|
@@ -12,7 +12,7 @@ import { GAVANA_CLI_GROUP_HELP, gavanaCommandActions, gavanaCommandGroupUsageLin
|
|
|
12
12
|
import { agentConfigFilePath, listAgentProfiles, readAgentConfig, readAgentConfigMetadata, removeAgentProfile, setActiveAgentProfile, writeAgentConfig } from "./config.mjs";
|
|
13
13
|
import { gavanaMcpClientDefinition } from "./mcp-targets.mjs";
|
|
14
14
|
|
|
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"]);
|
|
15
|
+
const BOOLEAN_OPTIONS = new Set(["help", "version", "pretty", "raw", "yes", "confirm", "rebase", "wait", "no-wait", "progress", "preflight", "token-stdin", "no-verify", "no-browser", "audio", "no-audio", "read-only"]);
|
|
16
16
|
const REPEATABLE_OPTIONS = new Set(["reference", "reference-role", "element", "input", "param", "target", "ratio", "approved", "field", "source-asset", "collection"]);
|
|
17
17
|
const MAX_REFERENCE_IMAGES = 16;
|
|
18
18
|
const MAX_VIDEO_REFERENCE_IMAGES = 9;
|
|
@@ -95,7 +95,7 @@ export async function runGavanaCli(argv = process.argv.slice(2), runtime = {}) {
|
|
|
95
95
|
}
|
|
96
96
|
if (!token && config.credentialState === "inaccessible") throw keychainInaccessibleError();
|
|
97
97
|
const client = createCanvasAgentClient({ token, baseUrl, surface: "cli", fetchImpl: runtime.fetchImpl });
|
|
98
|
-
return await runTrackedRemoteCommand(client,
|
|
98
|
+
return await runTrackedRemoteCommand(client, analyticsCommandName(group, action), runtime.analyticsEnabled ?? !runtime.fetchImpl, async () => {
|
|
99
99
|
if (group === "api") {
|
|
100
100
|
writeResult(stdout, await runApiCommand(client, action, positionals, parsed.options), parsed.options);
|
|
101
101
|
return 0;
|
|
@@ -156,6 +156,11 @@ async function runTrackedRemoteCommand(client, name, enabled, handler) {
|
|
|
156
156
|
});
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
function analyticsCommandName(group, action) {
|
|
160
|
+
if (group === "api") return `api.${/^(get|post|put|patch|delete)$/i.test(action || "") ? action.toLowerCase() : "get"}`;
|
|
161
|
+
return `${group}.${String(action || "default").toLowerCase()}`;
|
|
162
|
+
}
|
|
163
|
+
|
|
159
164
|
export const runCraftboardAgentCli = runGavanaCli;
|
|
160
165
|
|
|
161
166
|
function exitCodeForResult(group, action, result) {
|
|
@@ -504,11 +509,12 @@ async function executeCommand(client, group, action, args, options, runtime) {
|
|
|
504
509
|
}
|
|
505
510
|
|
|
506
511
|
if (group === "image" && (action === "generate" || action === "edit" || action === "variations")) {
|
|
512
|
+
if (options.wait === true && options["no-wait"] === true) throw usageError("--wait and --no-wait cannot be combined.");
|
|
507
513
|
const prepared = await imageInput(client, action, args, options, runtime);
|
|
508
|
-
const { __destination, ...input } = prepared;
|
|
509
|
-
const queued = await client.startImage(action, input);
|
|
510
|
-
|
|
511
|
-
if (
|
|
514
|
+
const { __destination, __preflight, ...input } = prepared;
|
|
515
|
+
const queued = __preflight ? await client.preflightImage(action, input) : await client.startImage(action, input);
|
|
516
|
+
if (__preflight) return { ...queued, destination: __destination };
|
|
517
|
+
if (options.wait !== true) return { ...queued, destination: __destination };
|
|
512
518
|
const result = await client.waitForRun(queued.run || queued.id, {
|
|
513
519
|
timeoutMs: secondsOption(options.timeout, 15 * 60) * 1000,
|
|
514
520
|
intervalMs: secondsOption(options.interval, 1.5, true) * 1000,
|
|
@@ -1196,6 +1202,7 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1196
1202
|
const targetNodeIds = supplied.targetNodeIds || repeatableStrings(options.target);
|
|
1197
1203
|
const singleTarget = supplied.targetNodeId || firstString(options["target-node"]);
|
|
1198
1204
|
const count = supplied.count !== undefined || options.count !== undefined ? (supplied.count ?? requiredNumber(options.count, "--count must be a number.")) : undefined;
|
|
1205
|
+
const preflight = supplied.preflight === true || options.preflight === true;
|
|
1199
1206
|
if (count !== undefined && (!Number.isInteger(Number(count)) || Number(count) < 1 || Number(count) > 4)) throw usageError("--count must be an integer between 1 and 4.");
|
|
1200
1207
|
const referenceInputs = imageReferenceInputs(supplied.references, options.reference, supplied.referenceRoles, options["reference-role"]);
|
|
1201
1208
|
const optionElements = repeatableStrings(options.element);
|
|
@@ -1204,14 +1211,58 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1204
1211
|
if (elements.length > 8) throw usageError("An image job can use up to 8 Elements.");
|
|
1205
1212
|
for (const entry of elements) {
|
|
1206
1213
|
const typed = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : null;
|
|
1207
|
-
if (typed && Object.keys(typed).some((key) =>
|
|
1214
|
+
if (typed && Object.keys(typed).some((key) => !["handle", "role", "influence", "applicationMode", "currentTurnUserModeOverride", "placements"].includes(key))) {
|
|
1215
|
+
throw usageError('An Element reference may contain only "handle", "role", "influence", "applicationMode", "currentTurnUserModeOverride", and "placements".');
|
|
1216
|
+
}
|
|
1208
1217
|
const handle = typeof entry === "string" ? entry.trim() : typeof typed?.handle === "string" ? typed.handle.trim() : "";
|
|
1209
1218
|
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
1219
|
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
1220
|
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.");
|
|
1221
|
+
if (typed?.applicationMode !== undefined && !["creative", "identity", "exact"].includes(typed.applicationMode)) throw usageError("Element applicationMode must be creative, identity, or exact.");
|
|
1222
|
+
if (typed?.currentTurnUserModeOverride !== undefined && typed.currentTurnUserModeOverride !== true) throw usageError("currentTurnUserModeOverride must be true when supplied.");
|
|
1223
|
+
if (typed?.placements !== undefined) {
|
|
1224
|
+
if (!Array.isArray(typed.placements) || !typed.placements.length || typed.placements.length > 16) throw usageError("Element placements must contain 1 through 16 normalized rectangles.");
|
|
1225
|
+
for (const placement of typed.placements) {
|
|
1226
|
+
if (!placement || typeof placement !== "object" || Array.isArray(placement) || Object.keys(placement).some((key) => !["x", "y", "width", "height"].includes(key))) throw usageError('Each Element placement must contain only "x", "y", "width", and "height".');
|
|
1227
|
+
const { x, y, width, height } = placement;
|
|
1228
|
+
if (![x, y, width, height].every((value) => typeof value === "number" && Number.isFinite(value)) || x < 0 || y < 0 || width <= 0 || height <= 0 || x + width > 1 || y + height > 1) {
|
|
1229
|
+
throw usageError("Element placements must stay within normalized output bounds.");
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1212
1233
|
}
|
|
1213
1234
|
const elementHandles = elements.map((entry) => (typeof entry === "string" ? entry.trim() : entry.handle.trim()));
|
|
1214
1235
|
if (new Set(elementHandles).size !== elementHandles.length) throw usageError("Element handles must be unique.");
|
|
1236
|
+
const source = supplied.source || firstString(options.source);
|
|
1237
|
+
const prompt = supplied.prompt || firstString(options.prompt);
|
|
1238
|
+
if (preflight) {
|
|
1239
|
+
if (destination === "agent-canvas" || destination === "new-canvas") throw usageError("--preflight requires an existing --destination canvas:<id>; it never creates a Canvas or target node.");
|
|
1240
|
+
const preflightTargets = [...targetNodeIds, ...(singleTarget ? [singleTarget] : [])];
|
|
1241
|
+
if (!preflightTargets.length) throw usageError("--preflight requires one or more explicit --target node:<id> handles.");
|
|
1242
|
+
if (referenceInputs.some((reference) => !/^(?:node:|asset:)/.test(reference.value)) || (source && !/^(?:node:|asset:)/.test(source))) {
|
|
1243
|
+
throw usageError("--preflight accepts only existing node: or asset: references; upload local images first.");
|
|
1244
|
+
}
|
|
1245
|
+
const canvasResult = await client.getCanvas(destination);
|
|
1246
|
+
const references = referenceInputs.map((reference) => ({ handle: reference.value, ...(reference.role ? { role: reference.role } : {}) }));
|
|
1247
|
+
return {
|
|
1248
|
+
canvasId: canvasResult.canvas.handle,
|
|
1249
|
+
baseRevision: supplied.baseRevision || firstString(options["base-revision"]) || canvasResult.canvas.revision,
|
|
1250
|
+
idempotencyKey,
|
|
1251
|
+
targetNodeIds: preflightTargets,
|
|
1252
|
+
...(prompt ? { prompt } : {}),
|
|
1253
|
+
...(supplied.promptNodeId || firstString(options["prompt-node"]) ? { promptNodeId: supplied.promptNodeId || firstString(options["prompt-node"]) } : {}),
|
|
1254
|
+
...(references.length ? { references: references.some((reference) => reference.role) ? references : references.map((reference) => reference.handle) } : {}),
|
|
1255
|
+
...(elements.length ? { elements } : {}),
|
|
1256
|
+
...(source ? { source } : {}),
|
|
1257
|
+
...(supplied.connectionId || firstString(options.connection) ? { connectionId: supplied.connectionId || firstString(options.connection) } : {}),
|
|
1258
|
+
...(supplied.model || firstString(options.model) ? { model: supplied.model || firstString(options.model) } : {}),
|
|
1259
|
+
...(supplied.size || firstString(options.size) ? { size: supplied.size || firstString(options.size) } : {}),
|
|
1260
|
+
...(supplied.quality || firstString(options.quality) ? { quality: supplied.quality || firstString(options.quality) } : {}),
|
|
1261
|
+
count: count ?? preflightTargets.length,
|
|
1262
|
+
__preflight: true,
|
|
1263
|
+
__destination: { requested: String(destination), canvasId: canvasResult.canvas.handle, targetNodeIds: preflightTargets },
|
|
1264
|
+
};
|
|
1265
|
+
}
|
|
1215
1266
|
const preparedReferences = await prepareReferenceInputs(referenceInputs.map((reference) => reference.value), runtime);
|
|
1216
1267
|
const canvasResult = await client.resolveCanvasDestination(destination, {
|
|
1217
1268
|
idempotencyKey,
|
|
@@ -1220,8 +1271,6 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1220
1271
|
const canvasId = canvasResult.canvas.handle;
|
|
1221
1272
|
const referenceHandles = await uploadLocalReferenceInputs(client, preparedReferences, canvasId);
|
|
1222
1273
|
const references = referenceHandles.map((handle, index) => ({ handle, ...(referenceInputs[index]?.role ? { role: referenceInputs[index].role } : {}) }));
|
|
1223
|
-
const source = supplied.source || firstString(options.source);
|
|
1224
|
-
const prompt = supplied.prompt || firstString(options.prompt);
|
|
1225
1274
|
const preparedDestination = await client.prepareImageDestination({
|
|
1226
1275
|
canvasResult,
|
|
1227
1276
|
destination,
|
|
@@ -1580,23 +1629,17 @@ function ensureUniqueVideoReferences(references) {
|
|
|
1580
1629
|
}
|
|
1581
1630
|
|
|
1582
1631
|
async function uploadLocalReferenceInputs(client, references, canvasReference) {
|
|
1583
|
-
const handles = [];
|
|
1584
1632
|
const uploads = new Map();
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
}
|
|
1594
|
-
|
|
1595
|
-
if (!uploads.has(fingerprint)) uploads.set(fingerprint, client.uploadAsset({ ...reference, canvasReference }));
|
|
1596
|
-
const uploaded = await uploads.get(fingerprint);
|
|
1597
|
-
handles.push(uploaded.asset.handle);
|
|
1598
|
-
}
|
|
1599
|
-
return handles;
|
|
1633
|
+
return Promise.all(
|
|
1634
|
+
references.map(async (reference) => {
|
|
1635
|
+
if (reference.kind === "handle") return reference.handle;
|
|
1636
|
+
if (reference.kind === "url") return reference.url;
|
|
1637
|
+
const fingerprint = crypto.createHash("sha256").update(reference.bytes).digest("base64url");
|
|
1638
|
+
if (!uploads.has(fingerprint)) uploads.set(fingerprint, client.uploadAsset({ ...reference, canvasReference }));
|
|
1639
|
+
const uploaded = await uploads.get(fingerprint);
|
|
1640
|
+
return uploaded.asset.handle;
|
|
1641
|
+
}),
|
|
1642
|
+
);
|
|
1600
1643
|
}
|
|
1601
1644
|
|
|
1602
1645
|
async function readMacClipboardImage() {
|
|
@@ -1938,6 +1981,7 @@ function elementInputFromOptions(options) {
|
|
|
1938
1981
|
type: firstString(options.type),
|
|
1939
1982
|
sourceAssetIds: repeatableStrings(options["source-asset"]),
|
|
1940
1983
|
...(firstString(options.guidelines) ? { guidelines: firstString(options.guidelines) } : {}),
|
|
1984
|
+
...(firstString(options["application-mode"]) ? { applicationMode: firstString(options["application-mode"]) } : {}),
|
|
1941
1985
|
...(repeatableStrings(options.collection).length ? { collectionIds: repeatableStrings(options.collection) } : {}),
|
|
1942
1986
|
};
|
|
1943
1987
|
}
|
|
@@ -2047,12 +2091,14 @@ Global options:
|
|
|
2047
2091
|
--connection VALUE Disambiguate a provider model id by connection
|
|
2048
2092
|
--reference VALUE Image reference; repeat for multiple visuals
|
|
2049
2093
|
--reference-role ROLE Optional role for the matching --reference: identity, construction, texture, fit, or style
|
|
2094
|
+
--preflight Compile an image reference plan without starting paid generation
|
|
2050
2095
|
--input VALUE Recipe KEY=VALUE or Action input; repeat as needed
|
|
2051
2096
|
--param NAME=VALUE Action parameter; repeat for multiple parameters
|
|
2052
2097
|
--webhook-url URL Send one signed callback when a Run finishes
|
|
2053
2098
|
--webhook-secret-env VAR Read the signing secret from VAR (default: GAVANA_WEBHOOK_SECRET)
|
|
2054
2099
|
--canvas-title VALUE Title used when --destination is new-canvas
|
|
2055
|
-
--
|
|
2100
|
+
--wait Wait for an image Run to finish instead of returning after queueing
|
|
2101
|
+
--no-wait Return immediately after queueing Recipe, video, or Action work (legacy image alias)
|
|
2056
2102
|
--progress Write Run or video Job state transitions to stderr
|
|
2057
2103
|
|
|
2058
2104
|
Video options:
|
|
@@ -2074,6 +2120,11 @@ ports treat @path as text; upload a visual first and pass its asset: handle when
|
|
|
2074
2120
|
that written port explicitly accepts an image reference. For image generation,
|
|
2075
2121
|
repeat --reference-role in the same order as --reference to preserve each
|
|
2076
2122
|
visual's identity, construction, texture, fit, or style role.
|
|
2123
|
+
Elements are version-pinned. creative permits visual reinterpretation; identity
|
|
2124
|
+
guides preservation but is not pixel-exact. exact Elements use JSON input with
|
|
2125
|
+
normalized placements and deterministic original-pixel composition. Use
|
|
2126
|
+
--preflight with an existing canvas and explicit --target handles to inspect the
|
|
2127
|
+
compiled reference plan before paid generation; it never creates a Canvas or target.
|
|
2077
2128
|
Image and Action commands create target nodes automatically when --target is omitted.
|
|
2078
2129
|
|
|
2079
2130
|
For history-safe token entry in zsh (macOS):
|
|
@@ -26,6 +26,7 @@ import { defineCampaignReview } from "./campaign_review.mjs";
|
|
|
26
26
|
import { defineCampaignStart } from "./campaign_start.mjs";
|
|
27
27
|
import { defineCanvasApplyBatch } from "./canvas_apply_batch.mjs";
|
|
28
28
|
import { defineCanvasCreate } from "./canvas_create.mjs";
|
|
29
|
+
import { defineRunList } from "./run_list.mjs";
|
|
29
30
|
import { defineCanvasGet } from "./canvas_get.mjs";
|
|
30
31
|
import { defineCanvasList } from "./canvas_list.mjs";
|
|
31
32
|
import { defineCanvasValidate } from "./canvas_validate.mjs";
|
|
@@ -91,6 +92,7 @@ export const GAVANA_TOOL_DEFINITIONS = Object.freeze({
|
|
|
91
92
|
campaign_start: defineCampaignStart,
|
|
92
93
|
canvas_apply_batch: defineCanvasApplyBatch,
|
|
93
94
|
canvas_create: defineCanvasCreate,
|
|
95
|
+
run_list: defineRunList,
|
|
94
96
|
canvas_get: defineCanvasGet,
|
|
95
97
|
canvas_list: defineCanvasList,
|
|
96
98
|
canvas_validate: defineCanvasValidate,
|
|
@@ -3,7 +3,7 @@ import { elementInput } from "./schemas.mjs";
|
|
|
3
3
|
export function defineElementCreate(client) {
|
|
4
4
|
return {
|
|
5
5
|
title: "Create a Gavana Element",
|
|
6
|
-
description: "Create a reusable visual Element
|
|
6
|
+
description: "Create a reusable visual Element. creative Elements inspire, identity Elements guide recognizable subjects, and exact Elements require one PNG source for deterministic composition.",
|
|
7
7
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
8
8
|
inputSchema: elementInput,
|
|
9
9
|
handler: (input) => client.createElement(input),
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
import { elementType, mutableElementReference, assetReference } from "./schemas.mjs";
|
|
2
|
+
import { elementApplicationMode, elementType, mutableElementReference, assetReference } from "./schemas.mjs";
|
|
3
3
|
|
|
4
4
|
export function defineElementUpdate(client) {
|
|
5
5
|
return {
|
|
@@ -13,6 +13,7 @@ export function defineElementUpdate(client) {
|
|
|
13
13
|
type: elementType,
|
|
14
14
|
sourceAssetIds: z.array(assetReference).max(8).default([]),
|
|
15
15
|
guidelines: z.string().max(2_000).optional(),
|
|
16
|
+
applicationMode: elementApplicationMode.optional(),
|
|
16
17
|
})
|
|
17
18
|
.strict()
|
|
18
19
|
.refine((value) => value.sourceAssetIds.length > 0 || Boolean(value.guidelines?.trim()), "Add at least one source asset or guideline."),
|
package/src/tools/guide_get.mjs
CHANGED
|
@@ -11,6 +11,6 @@ export function defineGuideGet(client) {
|
|
|
11
11
|
description: `Read one canonical Gavana Canvas Agent Guide topic. Start at ${GAVANA_CANVAS_GUIDE_INDEX_URI} or use guide_search to find an exact topic ID.`,
|
|
12
12
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
13
13
|
inputSchema: z.object({ guideId: z.string().min(1).max(600).describe("Guide topic ID or exact gavana:// guide URI returned by guide_search or resources/list.") }),
|
|
14
|
-
handler: ({ guideId }) => getCanvasGuide(guideId),
|
|
14
|
+
handler: ({ guideId }) => getCanvasGuide(guideId, "local"),
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -11,6 +11,6 @@ export function defineGuideSearch(client) {
|
|
|
11
11
|
description: `Search the canonical Gavana Canvas Agent Guide v${GAVANA_CANVAS_GUIDE_VERSION}. Use this before the first canvas mutation in a session or whenever an operation is unfamiliar.`,
|
|
12
12
|
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
13
13
|
inputSchema: z.object({ query: z.string().max(240).default(""), limit: z.number().int().min(1).max(10).default(10) }),
|
|
14
|
-
handler: ({ query, limit }) => searchCanvasGuides(query, limit),
|
|
14
|
+
handler: ({ query, limit }) => searchCanvasGuides(query, limit, "local"),
|
|
15
15
|
};
|
|
16
16
|
}
|
package/src/tools/image_tool.mjs
CHANGED
|
@@ -11,16 +11,21 @@ export function imageToolDefinition(operation, client) {
|
|
|
11
11
|
title: `${operation === "generate" ? "Generate" : operation === "edit" ? "Edit" : "Vary"} canvas images`,
|
|
12
12
|
description:
|
|
13
13
|
operation === "generate"
|
|
14
|
-
? "Queue image generation into existing target image nodes. Campaign work needs a visible reference-led concept before output; reuse existing Canvas node:/asset: handles for product identity, brand-world, and typography/layout references.
|
|
14
|
+
? "Queue image generation into existing target image nodes. Campaign work needs a visible reference-led concept before output; reuse existing Canvas node:/asset: handles for product identity, brand-world, and typography/layout references. identity Elements guide preservation but do not guarantee identical pixels. exact Elements require normalized placements and are deterministically composited from original source pixels. Set preflight true to inspect the compiled reference plan before paid generation. For several images set count (1-4): Gavana writes one image node per output. Never describe multiple panels, frames, or a collage in one prompt, and when you pass targetNodeIds pass exactly one target node per output."
|
|
15
15
|
: operation === "edit"
|
|
16
|
-
? "Queue an image edit using stable node: or asset: references. Reuse a Canvas image by its existing handle rather than re-uploading it.
|
|
17
|
-
: "Queue variations using an existing node: or asset: source.",
|
|
16
|
+
? "Queue an image edit using stable node: or asset: references. Reuse a Canvas image by its existing handle rather than re-uploading it. For a local image the user explicitly supplied, call asset_upload first. identity guidance is not pixel-exact; use an exact Element with placements for deterministic logo or wordmark composition. Set preflight true to inspect the compiled reference plan before paid generation. For several images set count (1-4): Gavana writes one image node per output. Never describe multiple panels, frames, or a collage in one prompt, and when you pass targetNodeIds pass exactly one target node per output."
|
|
17
|
+
: "Queue variations using an existing node: or asset: source. For several images set count (1-4): Gavana writes one image node per output. Never describe multiple panels, frames, or a collage in one prompt, and when you pass targetNodeIds pass exactly one target node per output.",
|
|
18
18
|
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: true },
|
|
19
19
|
inputSchema: imageSchema(),
|
|
20
20
|
markdown: true,
|
|
21
21
|
handler: async (input, extra) => {
|
|
22
22
|
validateImageInput(operation, input);
|
|
23
23
|
const destination = input.destination || input.canvasId;
|
|
24
|
+
if (input.preflight === true) {
|
|
25
|
+
const { destination: _destination, canvasId: _canvasId, canvasTitle: _canvasTitle, preflight: _preflight, wait: _wait, timeoutSeconds: _timeoutSeconds, ...preflightInput } = input;
|
|
26
|
+
const plan = await client.preflightImage(operation, { ...preflightInput, canvasId: destination });
|
|
27
|
+
return { ...plan, destination: { requested: destination, canvasId: destination, targetNodeIds: input.targetNodeIds } };
|
|
28
|
+
}
|
|
24
29
|
const prepared = await client.prepareImageDestination({
|
|
25
30
|
...input,
|
|
26
31
|
operation,
|
|
@@ -36,6 +41,7 @@ export function imageToolDefinition(operation, client) {
|
|
|
36
41
|
targetY: _targetY,
|
|
37
42
|
targetWidth: _targetWidth,
|
|
38
43
|
targetHeight: _targetHeight,
|
|
44
|
+
preflight: _preflight,
|
|
39
45
|
wait: _wait,
|
|
40
46
|
timeoutSeconds: _timeoutSeconds,
|
|
41
47
|
...jobInput
|
|
@@ -48,7 +54,7 @@ export function imageToolDefinition(operation, client) {
|
|
|
48
54
|
idempotencyKey: input.idempotencyKey,
|
|
49
55
|
});
|
|
50
56
|
const destinationResult = { requested: destination, canvasId: prepared.canvasId, targetNodeIds: prepared.targetNodeIds };
|
|
51
|
-
if (input.wait
|
|
57
|
+
if (input.wait !== true) return { ...queued, destination: destinationResult };
|
|
52
58
|
const result = await client.waitForRun(queued.run || queued.id, withProgress({ timeoutMs: (input.timeoutSeconds || 900) * 1000 }, extra));
|
|
53
59
|
return { ...result, destination: destinationResult };
|
|
54
60
|
},
|
package/src/tools/registry.mjs
CHANGED
|
@@ -144,7 +144,11 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
144
144
|
{
|
|
145
145
|
name: "canvas_create",
|
|
146
146
|
toolset: "canvas",
|
|
147
|
-
surfaces: ["local"],
|
|
147
|
+
surfaces: ["hosted","local"],
|
|
148
|
+
scopes: ["canvas:read","canvas:write"],
|
|
149
|
+
hostedOrder: 6,
|
|
150
|
+
hostedReadOnly: false,
|
|
151
|
+
hostedToolset: "canvas",
|
|
148
152
|
localOrder: 15,
|
|
149
153
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":false,"openWorldHint":false},
|
|
150
154
|
},
|
|
@@ -260,6 +264,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
260
264
|
scopes: ["canvas:read","job:manage"],
|
|
261
265
|
hostedOrder: 12,
|
|
262
266
|
hostedReadOnly: false,
|
|
267
|
+
hostedCategory: "read",
|
|
263
268
|
hostedToolset: "runs",
|
|
264
269
|
embedImage: true,
|
|
265
270
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
@@ -284,7 +289,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
284
289
|
toolset: "elements",
|
|
285
290
|
surfaces: ["hosted","local"],
|
|
286
291
|
scopes: ["element:read","element:write"],
|
|
287
|
-
hostedOrder:
|
|
292
|
+
hostedOrder: 18,
|
|
288
293
|
hostedReadOnly: false,
|
|
289
294
|
hostedToolset: "elements",
|
|
290
295
|
localOrder: 48,
|
|
@@ -296,7 +301,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
296
301
|
toolset: "elements",
|
|
297
302
|
surfaces: ["hosted","local"],
|
|
298
303
|
scopes: ["element:read","element:write"],
|
|
299
|
-
hostedOrder:
|
|
304
|
+
hostedOrder: 19,
|
|
300
305
|
hostedReadOnly: false,
|
|
301
306
|
hostedToolset: "elements",
|
|
302
307
|
localOrder: 50,
|
|
@@ -307,7 +312,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
307
312
|
toolset: "elements",
|
|
308
313
|
surfaces: ["hosted","local"],
|
|
309
314
|
scopes: ["element:read","element:write"],
|
|
310
|
-
hostedOrder:
|
|
315
|
+
hostedOrder: 20,
|
|
311
316
|
hostedReadOnly: false,
|
|
312
317
|
hostedToolset: "elements",
|
|
313
318
|
localOrder: 53,
|
|
@@ -320,7 +325,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
320
325
|
surfaces: ["hosted","local"],
|
|
321
326
|
scopes: ["element:read"],
|
|
322
327
|
readOnlySurface: true,
|
|
323
|
-
hostedOrder:
|
|
328
|
+
hostedOrder: 21,
|
|
324
329
|
hostedReadOnly: true,
|
|
325
330
|
hostedToolset: "elements",
|
|
326
331
|
localOrder: 49,
|
|
@@ -331,7 +336,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
331
336
|
toolset: "elements",
|
|
332
337
|
surfaces: ["hosted","local"],
|
|
333
338
|
scopes: ["element:read","element:write"],
|
|
334
|
-
hostedOrder:
|
|
339
|
+
hostedOrder: 22,
|
|
335
340
|
hostedReadOnly: false,
|
|
336
341
|
hostedToolset: "elements",
|
|
337
342
|
localOrder: 52,
|
|
@@ -342,7 +347,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
342
347
|
toolset: "elements",
|
|
343
348
|
surfaces: ["hosted","local"],
|
|
344
349
|
scopes: ["element:read","element:write"],
|
|
345
|
-
hostedOrder:
|
|
350
|
+
hostedOrder: 23,
|
|
346
351
|
hostedReadOnly: false,
|
|
347
352
|
hostedToolset: "elements",
|
|
348
353
|
localOrder: 45,
|
|
@@ -354,7 +359,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
354
359
|
surfaces: ["hosted","local"],
|
|
355
360
|
scopes: ["element:read"],
|
|
356
361
|
readOnlySurface: true,
|
|
357
|
-
hostedOrder:
|
|
362
|
+
hostedOrder: 24,
|
|
358
363
|
hostedReadOnly: true,
|
|
359
364
|
hostedToolset: "elements",
|
|
360
365
|
localOrder: 43,
|
|
@@ -366,7 +371,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
366
371
|
surfaces: ["hosted","local"],
|
|
367
372
|
scopes: ["element:read"],
|
|
368
373
|
readOnlySurface: true,
|
|
369
|
-
hostedOrder:
|
|
374
|
+
hostedOrder: 25,
|
|
370
375
|
hostedReadOnly: true,
|
|
371
376
|
hostedToolset: "elements",
|
|
372
377
|
localOrder: 44,
|
|
@@ -378,7 +383,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
378
383
|
surfaces: ["hosted","local"],
|
|
379
384
|
scopes: ["element:read"],
|
|
380
385
|
readOnlySurface: true,
|
|
381
|
-
hostedOrder:
|
|
386
|
+
hostedOrder: 26,
|
|
382
387
|
hostedReadOnly: true,
|
|
383
388
|
hostedToolset: "elements",
|
|
384
389
|
localOrder: 42,
|
|
@@ -389,7 +394,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
389
394
|
toolset: "elements",
|
|
390
395
|
surfaces: ["hosted","local"],
|
|
391
396
|
scopes: ["element:read","element:write"],
|
|
392
|
-
hostedOrder:
|
|
397
|
+
hostedOrder: 27,
|
|
393
398
|
hostedReadOnly: false,
|
|
394
399
|
hostedToolset: "elements",
|
|
395
400
|
localOrder: 47,
|
|
@@ -400,7 +405,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
400
405
|
toolset: "elements",
|
|
401
406
|
surfaces: ["hosted","local"],
|
|
402
407
|
scopes: ["element:read","element:write"],
|
|
403
|
-
hostedOrder:
|
|
408
|
+
hostedOrder: 28,
|
|
404
409
|
hostedReadOnly: false,
|
|
405
410
|
hostedToolset: "elements",
|
|
406
411
|
localOrder: 46,
|
|
@@ -411,7 +416,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
411
416
|
toolset: "elements",
|
|
412
417
|
surfaces: ["hosted","local"],
|
|
413
418
|
scopes: ["element:read","element:write"],
|
|
414
|
-
hostedOrder:
|
|
419
|
+
hostedOrder: 29,
|
|
415
420
|
hostedReadOnly: false,
|
|
416
421
|
hostedToolset: "elements",
|
|
417
422
|
localOrder: 51,
|
|
@@ -455,7 +460,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
455
460
|
hostedAlias: "generate_image_in_canvas",
|
|
456
461
|
scopes: ["canvas:read","canvas:write","asset:read","image:generate","job:manage"],
|
|
457
462
|
conditionalScopes: { elements: ["element:read"] },
|
|
458
|
-
hostedOrder:
|
|
463
|
+
hostedOrder: 14,
|
|
459
464
|
paid: true,
|
|
460
465
|
hostedReadOnly: false,
|
|
461
466
|
hostedToolset: "images",
|
|
@@ -551,6 +556,18 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
551
556
|
localOrder: 20,
|
|
552
557
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
553
558
|
},
|
|
559
|
+
{
|
|
560
|
+
name: "product_photoshoot_generate",
|
|
561
|
+
toolset: "images",
|
|
562
|
+
surfaces: ["hosted"],
|
|
563
|
+
hostedAlias: "generate_product_photoshoot_in_canvas",
|
|
564
|
+
scopes: ["canvas:read","canvas:write","asset:read","image:generate","job:manage"],
|
|
565
|
+
hostedOrder: 13,
|
|
566
|
+
paid: true,
|
|
567
|
+
hostedReadOnly: false,
|
|
568
|
+
hostedToolset: "images",
|
|
569
|
+
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":true},
|
|
570
|
+
},
|
|
554
571
|
{
|
|
555
572
|
name: "product_reference_pack_import",
|
|
556
573
|
toolset: "assets",
|
|
@@ -611,10 +628,30 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
611
628
|
{
|
|
612
629
|
name: "run_get",
|
|
613
630
|
toolset: "runs",
|
|
614
|
-
surfaces: ["local"],
|
|
631
|
+
surfaces: ["hosted","local"],
|
|
632
|
+
hostedAlias: "get_image_run",
|
|
633
|
+
scopes: ["job:manage"],
|
|
634
|
+
embedImage: true,
|
|
635
|
+
hostedOrder: 35,
|
|
636
|
+
hostedReadOnly: false,
|
|
637
|
+
hostedCategory: "read",
|
|
638
|
+
hostedToolset: "runs",
|
|
615
639
|
localOrder: 42,
|
|
616
640
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
617
641
|
},
|
|
642
|
+
{
|
|
643
|
+
name: "run_list",
|
|
644
|
+
toolset: "jobs",
|
|
645
|
+
surfaces: ["hosted","local"],
|
|
646
|
+
scopes: ["job:manage"],
|
|
647
|
+
hostedAlias: "list_image_runs",
|
|
648
|
+
hostedOrder: 36,
|
|
649
|
+
hostedReadOnly: false,
|
|
650
|
+
hostedToolset: "jobs",
|
|
651
|
+
localOrder: 41,
|
|
652
|
+
readOnlySurface: true,
|
|
653
|
+
annotations: {"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
654
|
+
},
|
|
618
655
|
{
|
|
619
656
|
name: "run_wait",
|
|
620
657
|
toolset: "runs",
|
|
@@ -627,8 +664,8 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
627
664
|
toolset: "videos",
|
|
628
665
|
surfaces: ["hosted","local"],
|
|
629
666
|
hostedAlias: "generate_video",
|
|
630
|
-
scopes: ["canvas:read","asset:read","video:generate","job:manage"],
|
|
631
|
-
hostedOrder:
|
|
667
|
+
scopes: ["canvas:read","canvas:write","asset:read","video:generate","job:manage"],
|
|
668
|
+
hostedOrder: 16,
|
|
632
669
|
paid: true,
|
|
633
670
|
hostedReadOnly: false,
|
|
634
671
|
hostedToolset: "videos",
|
|
@@ -641,8 +678,9 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
641
678
|
surfaces: ["hosted"],
|
|
642
679
|
hostedAlias: "get_video_job",
|
|
643
680
|
scopes: ["job:manage"],
|
|
644
|
-
hostedOrder:
|
|
681
|
+
hostedOrder: 17,
|
|
645
682
|
hostedReadOnly: false,
|
|
683
|
+
hostedCategory: "read",
|
|
646
684
|
hostedToolset: "runs",
|
|
647
685
|
annotations: {"readOnlyHint":false,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
648
686
|
},
|
|
@@ -652,8 +690,8 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
652
690
|
surfaces: ["hosted"],
|
|
653
691
|
hostedAlias: "find_video_models",
|
|
654
692
|
scopes: ["video:generate"],
|
|
655
|
-
hostedOrder:
|
|
656
|
-
hostedReadOnly:
|
|
693
|
+
hostedOrder: 15,
|
|
694
|
+
hostedReadOnly: false,
|
|
657
695
|
hostedToolset: "models",
|
|
658
696
|
annotations: {"readOnlyHint":true,"destructiveHint":false,"idempotentHint":true,"openWorldHint":false},
|
|
659
697
|
},
|
|
@@ -662,7 +700,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
662
700
|
toolset: "canvas",
|
|
663
701
|
surfaces: ["hosted","local"],
|
|
664
702
|
scopes: ["canvas:read","canvas:write"],
|
|
665
|
-
hostedOrder:
|
|
703
|
+
hostedOrder: 32,
|
|
666
704
|
hostedReadOnly: false,
|
|
667
705
|
hostedToolset: "canvas",
|
|
668
706
|
localOrder: 56,
|
|
@@ -674,7 +712,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
674
712
|
toolset: "canvas",
|
|
675
713
|
surfaces: ["hosted","local"],
|
|
676
714
|
scopes: ["canvas:read","canvas:write","asset:read","image:generate"],
|
|
677
|
-
hostedOrder:
|
|
715
|
+
hostedOrder: 33,
|
|
678
716
|
paid: true,
|
|
679
717
|
hostedReadOnly: false,
|
|
680
718
|
hostedToolset: "canvas",
|
|
@@ -688,7 +726,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
688
726
|
surfaces: ["hosted","local"],
|
|
689
727
|
scopes: ["canvas:read"],
|
|
690
728
|
readOnlySurface: true,
|
|
691
|
-
hostedOrder:
|
|
729
|
+
hostedOrder: 31,
|
|
692
730
|
hostedReadOnly: true,
|
|
693
731
|
hostedToolset: "canvas",
|
|
694
732
|
localOrder: 55,
|
|
@@ -700,7 +738,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
700
738
|
toolset: "canvas",
|
|
701
739
|
surfaces: ["hosted","local"],
|
|
702
740
|
scopes: ["canvas:read","canvas:write","asset:read"],
|
|
703
|
-
hostedOrder:
|
|
741
|
+
hostedOrder: 30,
|
|
704
742
|
hostedReadOnly: false,
|
|
705
743
|
hostedToolset: "canvas",
|
|
706
744
|
localOrder: 54,
|
|
@@ -712,7 +750,7 @@ export const GAVANA_TOOL_REGISTRY = Object.freeze([
|
|
|
712
750
|
toolset: "canvas",
|
|
713
751
|
surfaces: ["hosted","local"],
|
|
714
752
|
scopes: ["canvas:read","canvas:write","image:generate"],
|
|
715
|
-
hostedOrder:
|
|
753
|
+
hostedOrder: 34,
|
|
716
754
|
hostedReadOnly: false,
|
|
717
755
|
hostedToolset: "canvas",
|
|
718
756
|
localOrder: 58,
|
|
@@ -744,8 +782,16 @@ export function gavanaToolsForSurface(surface, { includeLegacyCampaign = false,
|
|
|
744
782
|
// each surface keeps the order it shipped with rather than falling out of the
|
|
745
783
|
// registry's alphabetical layout.
|
|
746
784
|
const key = surface === "hosted" ? "hostedOrder" : "localOrder";
|
|
785
|
+
// The two read-only flags are not the same question, and `run_list` is the
|
|
786
|
+
// first tool where they part. Local read-only serves whatever declares
|
|
787
|
+
// `readOnlyHint`, so a tool that only reads belongs there. Hosted read-only
|
|
788
|
+
// additionally has to grant the tool's OAuth scopes out of a read-only set
|
|
789
|
+
// that does not contain `job:manage`, so the same tool cannot be served
|
|
790
|
+
// there at all. Filtering both surfaces through the local flag advertised a
|
|
791
|
+
// hosted tool the hosted server would never serve.
|
|
792
|
+
const readOnlyKey = surface === "hosted" ? "hostedReadOnly" : "readOnlySurface";
|
|
747
793
|
return GAVANA_TOOL_REGISTRY.filter(
|
|
748
|
-
(tool) => tool.surfaces.includes(surface) && (includeLegacyCampaign || !tool.legacyCampaign) && (!readOnly || tool
|
|
794
|
+
(tool) => tool.surfaces.includes(surface) && (includeLegacyCampaign || !tool.legacyCampaign) && (!readOnly || tool[readOnlyKey]),
|
|
749
795
|
)
|
|
750
796
|
.slice()
|
|
751
797
|
.sort((left, right) => (left[key] ?? 0) - (right[key] ?? 0));
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Tool definition: run_list
|
|
2
|
+
//
|
|
3
|
+
// Registered through ./definitions.mjs. ./registry.mjs is the catalog that says
|
|
4
|
+
// this tool exists and on which surfaces; this file is what it does.
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
|
|
7
|
+
export function defineRunList(client) {
|
|
8
|
+
return {
|
|
9
|
+
title: "List Gavana image runs",
|
|
10
|
+
description:
|
|
11
|
+
"List image and Action runs this account has started, newest first, with their status and canvas. Use this to answer questions about work already in flight, to recover a run handle you no longer have, and to see which runs are holding the concurrent-job limit.",
|
|
12
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
13
|
+
inputSchema: z.object({
|
|
14
|
+
status: z.enum(["active", "all"]).default("active").describe("active returns only runs still queued or running; all includes finished and failed runs."),
|
|
15
|
+
canvasId: z.string().min(1).max(240).optional().describe("Restrict to runs targeting one canvas."),
|
|
16
|
+
limit: z.number().int().min(1).max(50).default(20),
|
|
17
|
+
}),
|
|
18
|
+
handler: (input) => client.listRuns(input),
|
|
19
|
+
};
|
|
20
|
+
}
|