@gavana.ai/cli 0.2.2 → 0.3.1
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 +42 -0
- package/README.md +27 -3
- package/guides/creative-canvas.md +16 -3
- package/guides/generated-assets.md +46 -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 +3 -3
- package/src/config.mjs +20 -2
- package/src/guide-sources.mjs +36 -4
- package/src/mcp-targets.mjs +1 -1
- package/src/runner.mjs +72 -10
- 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 +9 -3
- package/src/tools/registry.mjs +72 -26
- package/src/tools/run_list.mjs +20 -0
- package/src/tools/schemas.mjs +37 -3
- package/src/tools/surface-names.mjs +120 -0
- package/src/version.mjs +1 -1
package/src/runner.mjs
CHANGED
|
@@ -9,10 +9,10 @@ import { promisify } from "node:util";
|
|
|
9
9
|
import { CanvasAgentApiError, createCanvasAgentClient, markdownForResult, parseElementHandle } from "./client.mjs";
|
|
10
10
|
import { GAVANA_CLI_VERSION, gavanaCapabilitySummary } from "./capabilities.mjs";
|
|
11
11
|
import { GAVANA_CLI_GROUP_HELP, gavanaCommandActions, gavanaCommandGroupUsageLines, gavanaCommandGroups, gavanaCommandIsKnown, gavanaCommandUsageLines } from "./commands.mjs";
|
|
12
|
-
import { agentConfigFilePath, listAgentProfiles, readAgentConfig, readAgentConfigMetadata, removeAgentProfile, setActiveAgentProfile, writeAgentConfig } from "./config.mjs";
|
|
12
|
+
import { agentConfigFilePath, listAgentProfiles, readAgentConfig, readAgentConfigMetadata, readOAuthInstallId, 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) {
|
|
@@ -506,8 +511,9 @@ async function executeCommand(client, group, action, args, options, runtime) {
|
|
|
506
511
|
if (group === "image" && (action === "generate" || action === "edit" || action === "variations")) {
|
|
507
512
|
if (options.wait === true && options["no-wait"] === true) throw usageError("--wait and --no-wait cannot be combined.");
|
|
508
513
|
const prepared = await imageInput(client, action, args, options, runtime);
|
|
509
|
-
const { __destination, ...input } = prepared;
|
|
510
|
-
const queued = await client.startImage(action, input);
|
|
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 };
|
|
511
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,
|
|
@@ -637,7 +643,7 @@ async function runAuthCommand(action, _args, options, env, runtime) {
|
|
|
637
643
|
if (!token) {
|
|
638
644
|
const login = runtime.oauthLogin
|
|
639
645
|
? await runtime.oauthLogin({ baseUrl, readOnly: options["read-only"] === true, profile: profile || current.profile })
|
|
640
|
-
: await performBrowserOAuthLogin({ baseUrl, readOnly: options["read-only"] === true, noBrowser: options["no-browser"] === true, runtime });
|
|
646
|
+
: await performBrowserOAuthLogin({ baseUrl, readOnly: options["read-only"] === true, noBrowser: options["no-browser"] === true, env, runtime });
|
|
641
647
|
token = login.accessToken;
|
|
642
648
|
loginMethod = "browser";
|
|
643
649
|
}
|
|
@@ -732,7 +738,7 @@ async function revokeBrowserOAuthToken(baseUrl, token, fetchImpl) {
|
|
|
732
738
|
}
|
|
733
739
|
}
|
|
734
740
|
|
|
735
|
-
export async function performBrowserOAuthLogin({ baseUrl, readOnly = false, noBrowser = false, runtime = {} }) {
|
|
741
|
+
export async function performBrowserOAuthLogin({ baseUrl, readOnly = false, noBrowser = false, env = process.env, runtime = {} }) {
|
|
736
742
|
const fetchImpl = runtime.fetchImpl || globalThis.fetch;
|
|
737
743
|
if (typeof fetchImpl !== "function") throw usageError("This runtime does not provide fetch for browser login.");
|
|
738
744
|
const origin = new URL(createCanvasAgentClient({ token: "configuration-validation", baseUrl, fetchImpl }).baseUrl).origin;
|
|
@@ -742,11 +748,17 @@ export async function performBrowserOAuthLogin({ baseUrl, readOnly = false, noBr
|
|
|
742
748
|
const callback = runtime.createOAuthCallbackServer ? await runtime.createOAuthCallbackServer(state) : await createOAuthCallbackServer(state);
|
|
743
749
|
try {
|
|
744
750
|
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";
|
|
751
|
+
// Registering under a stable install id is what makes this login a
|
|
752
|
+
// reconnect: Gavana returns the client id this machine already had and
|
|
753
|
+
// replaces the delegation it issued last time, instead of stacking a
|
|
754
|
+
// new one behind a client it has never seen.
|
|
755
|
+
const softwareId = runtime.installId || (await readOAuthInstallId(env));
|
|
745
756
|
const registrationResponse = await fetchImpl(`${origin}/oauth/register`, {
|
|
746
757
|
method: "POST",
|
|
747
758
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
748
759
|
body: JSON.stringify({
|
|
749
760
|
client_name: "Gavana CLI",
|
|
761
|
+
software_id: softwareId,
|
|
750
762
|
redirect_uris: [callback.redirectUri],
|
|
751
763
|
token_endpoint_auth_method: "none",
|
|
752
764
|
grant_types: ["authorization_code"],
|
|
@@ -1196,6 +1208,7 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1196
1208
|
const targetNodeIds = supplied.targetNodeIds || repeatableStrings(options.target);
|
|
1197
1209
|
const singleTarget = supplied.targetNodeId || firstString(options["target-node"]);
|
|
1198
1210
|
const count = supplied.count !== undefined || options.count !== undefined ? (supplied.count ?? requiredNumber(options.count, "--count must be a number.")) : undefined;
|
|
1211
|
+
const preflight = supplied.preflight === true || options.preflight === true;
|
|
1199
1212
|
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
1213
|
const referenceInputs = imageReferenceInputs(supplied.references, options.reference, supplied.referenceRoles, options["reference-role"]);
|
|
1201
1214
|
const optionElements = repeatableStrings(options.element);
|
|
@@ -1204,14 +1217,58 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1204
1217
|
if (elements.length > 8) throw usageError("An image job can use up to 8 Elements.");
|
|
1205
1218
|
for (const entry of elements) {
|
|
1206
1219
|
const typed = entry && typeof entry === "object" && !Array.isArray(entry) ? entry : null;
|
|
1207
|
-
if (typed && Object.keys(typed).some((key) =>
|
|
1220
|
+
if (typed && Object.keys(typed).some((key) => !["handle", "role", "influence", "applicationMode", "currentTurnUserModeOverride", "placements"].includes(key))) {
|
|
1221
|
+
throw usageError('An Element reference may contain only "handle", "role", "influence", "applicationMode", "currentTurnUserModeOverride", and "placements".');
|
|
1222
|
+
}
|
|
1208
1223
|
const handle = typeof entry === "string" ? entry.trim() : typeof typed?.handle === "string" ? typed.handle.trim() : "";
|
|
1209
1224
|
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
1225
|
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
1226
|
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.");
|
|
1227
|
+
if (typed?.applicationMode !== undefined && !["creative", "identity", "exact"].includes(typed.applicationMode)) throw usageError("Element applicationMode must be creative, identity, or exact.");
|
|
1228
|
+
if (typed?.currentTurnUserModeOverride !== undefined && typed.currentTurnUserModeOverride !== true) throw usageError("currentTurnUserModeOverride must be true when supplied.");
|
|
1229
|
+
if (typed?.placements !== undefined) {
|
|
1230
|
+
if (!Array.isArray(typed.placements) || !typed.placements.length || typed.placements.length > 16) throw usageError("Element placements must contain 1 through 16 normalized rectangles.");
|
|
1231
|
+
for (const placement of typed.placements) {
|
|
1232
|
+
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".');
|
|
1233
|
+
const { x, y, width, height } = placement;
|
|
1234
|
+
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) {
|
|
1235
|
+
throw usageError("Element placements must stay within normalized output bounds.");
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1212
1239
|
}
|
|
1213
1240
|
const elementHandles = elements.map((entry) => (typeof entry === "string" ? entry.trim() : entry.handle.trim()));
|
|
1214
1241
|
if (new Set(elementHandles).size !== elementHandles.length) throw usageError("Element handles must be unique.");
|
|
1242
|
+
const source = supplied.source || firstString(options.source);
|
|
1243
|
+
const prompt = supplied.prompt || firstString(options.prompt);
|
|
1244
|
+
if (preflight) {
|
|
1245
|
+
if (destination === "agent-canvas" || destination === "new-canvas") throw usageError("--preflight requires an existing --destination canvas:<id>; it never creates a Canvas or target node.");
|
|
1246
|
+
const preflightTargets = [...targetNodeIds, ...(singleTarget ? [singleTarget] : [])];
|
|
1247
|
+
if (!preflightTargets.length) throw usageError("--preflight requires one or more explicit --target node:<id> handles.");
|
|
1248
|
+
if (referenceInputs.some((reference) => !/^(?:node:|asset:)/.test(reference.value)) || (source && !/^(?:node:|asset:)/.test(source))) {
|
|
1249
|
+
throw usageError("--preflight accepts only existing node: or asset: references; upload local images first.");
|
|
1250
|
+
}
|
|
1251
|
+
const canvasResult = await client.getCanvas(destination);
|
|
1252
|
+
const references = referenceInputs.map((reference) => ({ handle: reference.value, ...(reference.role ? { role: reference.role } : {}) }));
|
|
1253
|
+
return {
|
|
1254
|
+
canvasId: canvasResult.canvas.handle,
|
|
1255
|
+
baseRevision: supplied.baseRevision || firstString(options["base-revision"]) || canvasResult.canvas.revision,
|
|
1256
|
+
idempotencyKey,
|
|
1257
|
+
targetNodeIds: preflightTargets,
|
|
1258
|
+
...(prompt ? { prompt } : {}),
|
|
1259
|
+
...(supplied.promptNodeId || firstString(options["prompt-node"]) ? { promptNodeId: supplied.promptNodeId || firstString(options["prompt-node"]) } : {}),
|
|
1260
|
+
...(references.length ? { references: references.some((reference) => reference.role) ? references : references.map((reference) => reference.handle) } : {}),
|
|
1261
|
+
...(elements.length ? { elements } : {}),
|
|
1262
|
+
...(source ? { source } : {}),
|
|
1263
|
+
...(supplied.connectionId || firstString(options.connection) ? { connectionId: supplied.connectionId || firstString(options.connection) } : {}),
|
|
1264
|
+
...(supplied.model || firstString(options.model) ? { model: supplied.model || firstString(options.model) } : {}),
|
|
1265
|
+
...(supplied.size || firstString(options.size) ? { size: supplied.size || firstString(options.size) } : {}),
|
|
1266
|
+
...(supplied.quality || firstString(options.quality) ? { quality: supplied.quality || firstString(options.quality) } : {}),
|
|
1267
|
+
count: count ?? preflightTargets.length,
|
|
1268
|
+
__preflight: true,
|
|
1269
|
+
__destination: { requested: String(destination), canvasId: canvasResult.canvas.handle, targetNodeIds: preflightTargets },
|
|
1270
|
+
};
|
|
1271
|
+
}
|
|
1215
1272
|
const preparedReferences = await prepareReferenceInputs(referenceInputs.map((reference) => reference.value), runtime);
|
|
1216
1273
|
const canvasResult = await client.resolveCanvasDestination(destination, {
|
|
1217
1274
|
idempotencyKey,
|
|
@@ -1220,8 +1277,6 @@ async function imageInput(client, operation, args, options, runtime) {
|
|
|
1220
1277
|
const canvasId = canvasResult.canvas.handle;
|
|
1221
1278
|
const referenceHandles = await uploadLocalReferenceInputs(client, preparedReferences, canvasId);
|
|
1222
1279
|
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
1280
|
const preparedDestination = await client.prepareImageDestination({
|
|
1226
1281
|
canvasResult,
|
|
1227
1282
|
destination,
|
|
@@ -1932,6 +1987,7 @@ function elementInputFromOptions(options) {
|
|
|
1932
1987
|
type: firstString(options.type),
|
|
1933
1988
|
sourceAssetIds: repeatableStrings(options["source-asset"]),
|
|
1934
1989
|
...(firstString(options.guidelines) ? { guidelines: firstString(options.guidelines) } : {}),
|
|
1990
|
+
...(firstString(options["application-mode"]) ? { applicationMode: firstString(options["application-mode"]) } : {}),
|
|
1935
1991
|
...(repeatableStrings(options.collection).length ? { collectionIds: repeatableStrings(options.collection) } : {}),
|
|
1936
1992
|
};
|
|
1937
1993
|
}
|
|
@@ -2041,6 +2097,7 @@ Global options:
|
|
|
2041
2097
|
--connection VALUE Disambiguate a provider model id by connection
|
|
2042
2098
|
--reference VALUE Image reference; repeat for multiple visuals
|
|
2043
2099
|
--reference-role ROLE Optional role for the matching --reference: identity, construction, texture, fit, or style
|
|
2100
|
+
--preflight Compile an image reference plan without starting paid generation
|
|
2044
2101
|
--input VALUE Recipe KEY=VALUE or Action input; repeat as needed
|
|
2045
2102
|
--param NAME=VALUE Action parameter; repeat for multiple parameters
|
|
2046
2103
|
--webhook-url URL Send one signed callback when a Run finishes
|
|
@@ -2069,6 +2126,11 @@ ports treat @path as text; upload a visual first and pass its asset: handle when
|
|
|
2069
2126
|
that written port explicitly accepts an image reference. For image generation,
|
|
2070
2127
|
repeat --reference-role in the same order as --reference to preserve each
|
|
2071
2128
|
visual's identity, construction, texture, fit, or style role.
|
|
2129
|
+
Elements are version-pinned. creative permits visual reinterpretation; identity
|
|
2130
|
+
guides preservation but is not pixel-exact. exact Elements use JSON input with
|
|
2131
|
+
normalized placements and deterministic original-pixel composition. Use
|
|
2132
|
+
--preflight with an existing canvas and explicit --target handles to inspect the
|
|
2133
|
+
compiled reference plan before paid generation; it never creates a Canvas or target.
|
|
2072
2134
|
Image and Action commands create target nodes automatically when --target is omitted.
|
|
2073
2135
|
|
|
2074
2136
|
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
|
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
|
+
}
|