@gavana.ai/cli 0.3.0 → 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 +14 -0
- package/guides/generated-assets.md +8 -0
- package/package.json +1 -1
- package/src/canvas-agent-guide.mjs +1 -1
- package/src/config.mjs +20 -2
- package/src/guide-sources.mjs +1 -1
- package/src/runner.mjs +9 -3
- package/src/version.mjs +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Changelog — @gavana.ai/cli
|
|
2
2
|
|
|
3
|
+
## 0.3.1
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
|
|
7
|
+
- **`auth login` no longer stacks a new delegation on every login.** The CLI
|
|
8
|
+
registered a fresh OAuth client each time — a new client id against a new
|
|
9
|
+
loopback port — so Gavana could not tell one login from the next, and the
|
|
10
|
+
delegations it minted never expire. They accumulated until the account hit
|
|
11
|
+
its token cap, at which point the exchange failed and the CLI answered with
|
|
12
|
+
another login that failed the same way. The CLI now registers under a stable
|
|
13
|
+
per-installation id (`installId` in the agent config), so Gavana returns the
|
|
14
|
+
client id this machine already had and replaces the delegation it holds.
|
|
15
|
+
Other machines are untouched: each install keeps its own id.
|
|
16
|
+
|
|
3
17
|
## 0.3.0
|
|
4
18
|
|
|
5
19
|
### Added
|
|
@@ -33,6 +33,14 @@ order: 6
|
|
|
33
33
|
proves more than one variant, pass the one the user named rather than letting
|
|
34
34
|
the import pick. If no durable references can be imported, say exact product
|
|
35
35
|
fidelity is unverified.
|
|
36
|
+
- When the user supplied the product image themselves and there is no product
|
|
37
|
+
page, call `product_photoshoot_generate` with `references` set to the durable
|
|
38
|
+
`node:` handles already on the canvas, one direction object per requested
|
|
39
|
+
output. It verifies those nodes before any paid work and stops when one is
|
|
40
|
+
not a durable image. Save an asset to the canvas first: a workflow input
|
|
41
|
+
binds to a node, not to an `asset:` handle. This is the route for several
|
|
42
|
+
distinct directions from one uploaded photo; one image call with `count > 1`
|
|
43
|
+
is not.
|
|
36
44
|
{{/hosted}}{{#local}}- This surface has no product-page importer. Ask the user for the images
|
|
37
45
|
themselves, bring them in through the supported asset flow, and pass the
|
|
38
46
|
resulting handles as references. Do not fetch the page and describe it in a
|
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@ import { renderForSurface } from "./tools/surface-names.mjs";
|
|
|
8
8
|
export const GAVANA_CANVAS_GUIDE_VERSION = "1.6.1";
|
|
9
9
|
export const GAVANA_CANVAS_GUIDE_INDEX_URI = `gavana://guides/canvas/v${GAVANA_CANVAS_GUIDE_VERSION.split(".")[0]}/index`;
|
|
10
10
|
export const GAVANA_CANVAS_GUIDE_WORKFLOW_INSTRUCTION =
|
|
11
|
-
"For the first canvas task in a session, and before any unfamiliar canvas operation, inspect the relevant guide through MCP resources or call guide_search then guide_get. Before changing an existing canvas, call canvas_get. For spatial, multi-node, or destructive work, call canvas_validate with the proposed operations, apply one revision-safe atomic batch, then call canvas_validate again. Campaign work must read creative-canvas and use a reference-led concept before image output. {{#local}}Before image or video generation, call model_list for the required capability and use its exact model: handle; a bare model name does not select a saved connection. If no matching model is returned, report that the agent account cannot access the connection instead of asking the user to add a key again.{{/local}}{{#hosted}}Before video generation, call video_model_find for the required capability and use its exact model: handle; a bare model name does not select a saved connection. If no matching model is returned, report that the agent account cannot access the connection instead of asking the user to add a key again. Image generation has no model-discovery tool here: omit model unless the user named an exact handle.{{/hosted}} A product page URL is source input, never prompt prose. {{#hosted}}When asked to generate one or more exact-product directions from a product page, call product_photoshoot_generate once with one explicit direction object per requested output; it imports verified references before any paid generation and stops if none are available. Use product_reference_pack_import only for a standalone import with no generation. When the page proves more than one variant, pass the variantSelector the user named.{{/hosted}}{{#local}}This surface has no product-page importer: ask for the images themselves and bring them in through the supported asset flow rather than describing the page in a prompt.{{/local}} When a standalone image fallback uses visual references, forward every exact node:/asset: handle (and each known role) in the image request; never reduce that work to a prompt-only generation. Image tools return durable queued progress by default; report it immediately and {{#local}}call run_wait{{/local}}{{#hosted}}set wait true{{/hosted}} only when the current user explicitly needs the completed asset in the same interaction. Otherwise run_get is authoritative; use the exact Canvas URL for navigation, but remember Canvas activity may lag. After an image or workflow finishes, read completionReview. Never claim Done while completionReview.doneClaimAllowed is false, delivery is pending, failed, or non-durable, or blocking findings remain. Render campaign compositions for visual inspection. Treat legacy warnings as review items, not permission to rewrite unrelated work. For N separate images, set count on one image call and never ask one prompt for N panels, frames, or a collage.";
|
|
11
|
+
"For the first canvas task in a session, and before any unfamiliar canvas operation, inspect the relevant guide through MCP resources or call guide_search then guide_get. Before changing an existing canvas, call canvas_get. For spatial, multi-node, or destructive work, call canvas_validate with the proposed operations, apply one revision-safe atomic batch, then call canvas_validate again. Campaign work must read creative-canvas and use a reference-led concept before image output. {{#local}}Before image or video generation, call model_list for the required capability and use its exact model: handle; a bare model name does not select a saved connection. If no matching model is returned, report that the agent account cannot access the connection instead of asking the user to add a key again.{{/local}}{{#hosted}}Before video generation, call video_model_find for the required capability and use its exact model: handle; a bare model name does not select a saved connection. If no matching model is returned, report that the agent account cannot access the connection instead of asking the user to add a key again. Image generation has no model-discovery tool here: omit model unless the user named an exact handle.{{/hosted}} A product page URL is source input, never prompt prose. {{#hosted}}When asked to generate one or more exact-product directions from a product page, call product_photoshoot_generate once with one explicit direction object per requested output; it imports verified references before any paid generation and stops if none are available. Use product_reference_pack_import only for a standalone import with no generation. When the page proves more than one variant, pass the variantSelector the user named. When the product image is already on the canvas and there is no product page, call the same tool with references set to its durable node: handles and one direction object per requested output; it verifies those nodes before any paid generation. Several distinct directions are never one image call with count > 1.{{/hosted}}{{#local}}This surface has no product-page importer: ask for the images themselves and bring them in through the supported asset flow rather than describing the page in a prompt.{{/local}} When a standalone image fallback uses visual references, forward every exact node:/asset: handle (and each known role) in the image request; never reduce that work to a prompt-only generation. Image tools return durable queued progress by default; report it immediately and {{#local}}call run_wait{{/local}}{{#hosted}}set wait true{{/hosted}} only when the current user explicitly needs the completed asset in the same interaction. Otherwise run_get is authoritative; use the exact Canvas URL for navigation, but remember Canvas activity may lag. After an image or workflow finishes, read completionReview. Never claim Done while completionReview.doneClaimAllowed is false, delivery is pending, failed, or non-durable, or blocking findings remain. Render campaign compositions for visual inspection. Treat legacy warnings as review items, not permission to rewrite unrelated work. For N separate images, set count on one image call and never ask one prompt for N panels, frames, or a collage.";
|
|
12
12
|
export const GAVANA_CANVAS_GUIDE_READ_ONLY_INSTRUCTION =
|
|
13
13
|
"For the first canvas task in a session, and before any unfamiliar canvas review, inspect the relevant guide through MCP resources or call guide_search then guide_get. Call canvas_get before reasoning about an existing canvas, and use canvas_validate to audit its current graph. Treat validation warnings as review items and never claim to mutate the canvas.";
|
|
14
14
|
|
package/src/config.mjs
CHANGED
|
@@ -2,10 +2,12 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { execFile as execFileCallback } from "node:child_process";
|
|
5
|
+
import { randomBytes } from "node:crypto";
|
|
5
6
|
import { promisify } from "node:util";
|
|
6
7
|
|
|
7
8
|
const execFile = promisify(execFileCallback);
|
|
8
9
|
const KEYCHAIN_SERVICE = "ai.gavana.cli";
|
|
10
|
+
const INSTALL_ID_PATTERN = /^[A-Za-z0-9_-]{16,64}$/;
|
|
9
11
|
|
|
10
12
|
export async function readAgentConfig(env = process.env, dependencies = {}) {
|
|
11
13
|
const store = await readAgentConfigStore(env);
|
|
@@ -68,6 +70,7 @@ export async function writeAgentConfig(config, env = process.env) {
|
|
|
68
70
|
if (credentialStore === "macos-keychain") delete profileConfig.token;
|
|
69
71
|
const next = {
|
|
70
72
|
version: 1,
|
|
73
|
+
...(current.installId ? { installId: current.installId } : {}),
|
|
71
74
|
activeProfile: profile,
|
|
72
75
|
profiles: { ...(current.profiles || {}), [profile]: profileConfig },
|
|
73
76
|
};
|
|
@@ -90,6 +93,20 @@ export async function writeAgentConfigStore(config, env = process.env) {
|
|
|
90
93
|
return filePath;
|
|
91
94
|
}
|
|
92
95
|
|
|
96
|
+
/**
|
|
97
|
+
* The id this installation registers its OAuth client under. It is not a
|
|
98
|
+
* credential: it exists so a repeat `auth login` is recognised as the same
|
|
99
|
+
* install and replaces the delegation it already holds, instead of leaving a
|
|
100
|
+
* new one behind on every login.
|
|
101
|
+
*/
|
|
102
|
+
export async function readOAuthInstallId(env = process.env) {
|
|
103
|
+
const store = await readAgentConfigStore(env);
|
|
104
|
+
if (store.installId) return store.installId;
|
|
105
|
+
const installId = randomBytes(24).toString("base64url");
|
|
106
|
+
await writeAgentConfigStore({ ...store, installId }, env);
|
|
107
|
+
return installId;
|
|
108
|
+
}
|
|
109
|
+
|
|
93
110
|
export async function removeAgentConfig(env = process.env) {
|
|
94
111
|
const filePaths = agentConfigFileCandidates(env);
|
|
95
112
|
await Promise.all(filePaths.map((filePath) => fs.rm(filePath, { force: true })));
|
|
@@ -164,14 +181,15 @@ function agentConfigFileCandidates(env) {
|
|
|
164
181
|
|
|
165
182
|
function normalizeConfigStore(value) {
|
|
166
183
|
if (!value || typeof value !== "object" || Array.isArray(value)) return { version: 1, activeProfile: "default", profiles: {} };
|
|
184
|
+
const install = typeof value.installId === "string" && INSTALL_ID_PATTERN.test(value.installId) ? { installId: value.installId } : {};
|
|
167
185
|
if (value.profiles && typeof value.profiles === "object" && !Array.isArray(value.profiles)) {
|
|
168
|
-
return { version: 1, activeProfile: cleanProfileName(value.activeProfile || "default"), profiles: value.profiles };
|
|
186
|
+
return { version: 1, ...install, activeProfile: cleanProfileName(value.activeProfile || "default"), profiles: value.profiles };
|
|
169
187
|
}
|
|
170
188
|
const legacy = {
|
|
171
189
|
...(typeof value.baseUrl === "string" && value.baseUrl ? { baseUrl: value.baseUrl } : {}),
|
|
172
190
|
...(typeof value.token === "string" && value.token ? { token: value.token } : {}),
|
|
173
191
|
};
|
|
174
|
-
return { version: 1, activeProfile: "default", profiles: Object.keys(legacy).length ? { default: legacy } : {} };
|
|
192
|
+
return { version: 1, ...install, activeProfile: "default", profiles: Object.keys(legacy).length ? { default: legacy } : {} };
|
|
175
193
|
}
|
|
176
194
|
|
|
177
195
|
function selectedProfileName(store, env) {
|
package/src/guide-sources.mjs
CHANGED
|
@@ -49,7 +49,7 @@ export const GAVANA_CANVAS_GUIDE_SOURCES = Object.freeze([
|
|
|
49
49
|
description: "Prepare media nodes, start only explicit generation, and preserve output lineage.",
|
|
50
50
|
keywords: Object.freeze(["generated","generation","image","video","output","asset","durable","lineage","placeholder","job"]),
|
|
51
51
|
order: 6,
|
|
52
|
-
markdown: "\n## Before generation\n\n- Read the destination canvas and relevant source nodes.\n- Use exact source `node:` or `asset:` handles.\n{{#local}}- Before image or video generation, call `model_list` for the required capability and pass its exact `model:` handle. A bare model name does not select a saved connection. If no matching model is returned, report that the agent account cannot access that connection; do not ask the user to add a key again.\n{{/local}}{{#hosted}}- Before video generation, call `video_model_find` for the required capability and pass its exact `model:` handle. A bare model name does not select a saved connection. If no matching model is returned, report that the agent account cannot access that connection; do not ask the user to add a key again.\n- Image generation has no model-discovery tool on this surface. Omit `model` and Gavana uses the account's configured default; pass `model` only when the user named an exact handle. Do not go looking for a tool that lists image models here.\n{{/hosted}}- For a standalone image request, pass every visual source in `references`.\n Use `{ \"handle\": \"node:...\", \"role\": \"identity\" }` when its\n responsibility is known; valid roles are `identity`, `construction`,\n `texture`, `fit`, and `style`. Do not flatten multi-reference work\n into prompt prose or omit a source during fallback.\n- Reuse existing Canvas `node:` or `asset:` handles directly. Do not download\n and re-upload a generated Canvas image merely to use it as the next\n generation's reference. State whether a style reference establishes the\n brand-world or typography/layout direction in the prompt.\n- A product page URL is source input, not prompt prose. Never paste it into a\n generation prompt, and never describe prompt-only output as the exact product.\n{{#hosted}}- When asked to generate one or more exact-product directions from a product\n page, call `product_photoshoot_generate` once. Supply one explicit direction\n object per requested output; the tool imports durable gallery references\n first and runs each direction as its own reference-guided image job. Use\n `product_reference_pack_import` only when the user wants a standalone import\n with no generation. Both take an optional `variantSelector`; when the page\n proves more than one variant, pass the one the user named rather than letting\n the import pick. If no durable references can be imported, say exact product\n fidelity is unverified.\n{{/hosted}}{{#local}}- This surface has no product-page importer. Ask the user for the images\n themselves, bring them in through the supported asset flow, and pass the\n resulting handles as references. Do not fetch the page and describe it in a\n prompt: that produces a generic product, not theirs.\n{{/local}}- Create an empty image or video target only through supported operations. Do not write media bytes, storage keys, or arbitrary output URLs into metadata.\n- Connect prompts, products, references, Lists, and frame inputs to their target with the correct direction and mode.\n\n## Paid execution\n\nGeneration is allowed only after explicit current-turn user intent. Start one run with one caller-stable idempotency key. When the user asks for N separate images, pass `count: N` (1-4) on one image call: Gavana reserves one image node per output and writes one image into each. Never encode N as a prompt describing N panels, frames, or a collage — that returns one composite image in one node. When you pass `targetNodeIds`, pass exactly one target node per output; a mismatch is rejected. For a multi-direction product photoshoot, never encode the directions as one shared prompt with `count > 1`; the product photoshoot tool owns one `count: 1` child per direction under one durable Recipe Run. Image tools return durable queued progress by default; report that progress immediately. {{#local}}Do not automatically call `run_wait`; call it only when the current user explicitly needs the completed asset in this same interaction.{{/local}}{{#hosted}}Do not block on the result: leave `wait` false and let the call return its queued run. Set `wait` true only when the current user explicitly needs the completed asset in this same interaction.{{/hosted}} Otherwise, use the returned exact Canvas URL for navigation, but treat `run_get` as the authoritative status; Canvas activity is a presentation surface and may lag. Do not start another run while one is pending. A terminal failure must be reported without automatic retry.\n\n## Sizes, formats, and limits\n\nThere is no size discovery tool. What holds today:\n\n- `size` accepts an exact `WIDTHxHEIGHT` or an aspect shorthand like `4:5`.\n gpt-image models render `1024x1024`, `1024x1536`, and `1536x1024`; anything\n else resolves to the closest of those. The run's `settings` reports both\n `requestedSize` and `effectiveSize`, and a resolved request also returns a\n `parameterAdjustments` entry — read them before telling the person what they\n got, and never assume the size you asked for is the size that was made.\n- Imports accept PNG, JPEG, WebP, and GIF up to 50 MB. A larger or undecodable\n source is refused without writing anything; that refusal will not succeed on\n retry, so shrink the image or link a smaller rendition instead.\n\n## Completion\n\n`run_get` is what you call while a run is going, and it answers about state, not pixels. Its response always links the finished image; {{#hosted}}pass `includeImage: true` {{/hosted}}{{#local}}read the linked asset {{/local}}only when a person is about to look at the picture, because inlining one costs a couple of megabytes on every read. A run that is still going also dates its own answer: `timing.observedAt` is when the record was read, `timing.elapsedMs` runs off the wall clock, and `timing.stale` with `timing.staleNote` means nothing has written to the run in a long while — the work may already be finished, so read the destination canvas before starting anything new.\n\nDo not claim a generated image is durable until the result returns a target `node:`, durable `asset:`, and the final canvas read shows server-owned media fields. `run_get` is the authoritative status when a user asks whether a run is done; a queued Canvas activity row is not evidence that the provider never started. A video Job may return a protected download without materializing a native video node; report exactly what the server returned and do not invent durability.\n\nKeep generated output spatially near its input stage and connected to its source, prompt, List, or workflow. After finalization, run `canvas_validate` and read `completionReview`: it reports overlap, full-frame Section containment, reference lineage, and delivery state. Do not claim Done while it says `doneClaimAllowed: false`, including when delivery is pending, failed, or non-durable. Render the Canvas for visual inspection when the request includes a campaign, poster, banner, or multi-direction composition. Reference provenance is not a user workflow state and never requires a Keep action. Never create another paid provider call automatically.\n",
|
|
52
|
+
markdown: "\n## Before generation\n\n- Read the destination canvas and relevant source nodes.\n- Use exact source `node:` or `asset:` handles.\n{{#local}}- Before image or video generation, call `model_list` for the required capability and pass its exact `model:` handle. A bare model name does not select a saved connection. If no matching model is returned, report that the agent account cannot access that connection; do not ask the user to add a key again.\n{{/local}}{{#hosted}}- Before video generation, call `video_model_find` for the required capability and pass its exact `model:` handle. A bare model name does not select a saved connection. If no matching model is returned, report that the agent account cannot access that connection; do not ask the user to add a key again.\n- Image generation has no model-discovery tool on this surface. Omit `model` and Gavana uses the account's configured default; pass `model` only when the user named an exact handle. Do not go looking for a tool that lists image models here.\n{{/hosted}}- For a standalone image request, pass every visual source in `references`.\n Use `{ \"handle\": \"node:...\", \"role\": \"identity\" }` when its\n responsibility is known; valid roles are `identity`, `construction`,\n `texture`, `fit`, and `style`. Do not flatten multi-reference work\n into prompt prose or omit a source during fallback.\n- Reuse existing Canvas `node:` or `asset:` handles directly. Do not download\n and re-upload a generated Canvas image merely to use it as the next\n generation's reference. State whether a style reference establishes the\n brand-world or typography/layout direction in the prompt.\n- A product page URL is source input, not prompt prose. Never paste it into a\n generation prompt, and never describe prompt-only output as the exact product.\n{{#hosted}}- When asked to generate one or more exact-product directions from a product\n page, call `product_photoshoot_generate` once. Supply one explicit direction\n object per requested output; the tool imports durable gallery references\n first and runs each direction as its own reference-guided image job. Use\n `product_reference_pack_import` only when the user wants a standalone import\n with no generation. Both take an optional `variantSelector`; when the page\n proves more than one variant, pass the one the user named rather than letting\n the import pick. If no durable references can be imported, say exact product\n fidelity is unverified.\n- When the user supplied the product image themselves and there is no product\n page, call `product_photoshoot_generate` with `references` set to the durable\n `node:` handles already on the canvas, one direction object per requested\n output. It verifies those nodes before any paid work and stops when one is\n not a durable image. Save an asset to the canvas first: a workflow input\n binds to a node, not to an `asset:` handle. This is the route for several\n distinct directions from one uploaded photo; one image call with `count > 1`\n is not.\n{{/hosted}}{{#local}}- This surface has no product-page importer. Ask the user for the images\n themselves, bring them in through the supported asset flow, and pass the\n resulting handles as references. Do not fetch the page and describe it in a\n prompt: that produces a generic product, not theirs.\n{{/local}}- Create an empty image or video target only through supported operations. Do not write media bytes, storage keys, or arbitrary output URLs into metadata.\n- Connect prompts, products, references, Lists, and frame inputs to their target with the correct direction and mode.\n\n## Paid execution\n\nGeneration is allowed only after explicit current-turn user intent. Start one run with one caller-stable idempotency key. When the user asks for N separate images, pass `count: N` (1-4) on one image call: Gavana reserves one image node per output and writes one image into each. Never encode N as a prompt describing N panels, frames, or a collage — that returns one composite image in one node. When you pass `targetNodeIds`, pass exactly one target node per output; a mismatch is rejected. For a multi-direction product photoshoot, never encode the directions as one shared prompt with `count > 1`; the product photoshoot tool owns one `count: 1` child per direction under one durable Recipe Run. Image tools return durable queued progress by default; report that progress immediately. {{#local}}Do not automatically call `run_wait`; call it only when the current user explicitly needs the completed asset in this same interaction.{{/local}}{{#hosted}}Do not block on the result: leave `wait` false and let the call return its queued run. Set `wait` true only when the current user explicitly needs the completed asset in this same interaction.{{/hosted}} Otherwise, use the returned exact Canvas URL for navigation, but treat `run_get` as the authoritative status; Canvas activity is a presentation surface and may lag. Do not start another run while one is pending. A terminal failure must be reported without automatic retry.\n\n## Sizes, formats, and limits\n\nThere is no size discovery tool. What holds today:\n\n- `size` accepts an exact `WIDTHxHEIGHT` or an aspect shorthand like `4:5`.\n gpt-image models render `1024x1024`, `1024x1536`, and `1536x1024`; anything\n else resolves to the closest of those. The run's `settings` reports both\n `requestedSize` and `effectiveSize`, and a resolved request also returns a\n `parameterAdjustments` entry — read them before telling the person what they\n got, and never assume the size you asked for is the size that was made.\n- Imports accept PNG, JPEG, WebP, and GIF up to 50 MB. A larger or undecodable\n source is refused without writing anything; that refusal will not succeed on\n retry, so shrink the image or link a smaller rendition instead.\n\n## Completion\n\n`run_get` is what you call while a run is going, and it answers about state, not pixels. Its response always links the finished image; {{#hosted}}pass `includeImage: true` {{/hosted}}{{#local}}read the linked asset {{/local}}only when a person is about to look at the picture, because inlining one costs a couple of megabytes on every read. A run that is still going also dates its own answer: `timing.observedAt` is when the record was read, `timing.elapsedMs` runs off the wall clock, and `timing.stale` with `timing.staleNote` means nothing has written to the run in a long while — the work may already be finished, so read the destination canvas before starting anything new.\n\nDo not claim a generated image is durable until the result returns a target `node:`, durable `asset:`, and the final canvas read shows server-owned media fields. `run_get` is the authoritative status when a user asks whether a run is done; a queued Canvas activity row is not evidence that the provider never started. A video Job may return a protected download without materializing a native video node; report exactly what the server returned and do not invent durability.\n\nKeep generated output spatially near its input stage and connected to its source, prompt, List, or workflow. After finalization, run `canvas_validate` and read `completionReview`: it reports overlap, full-frame Section containment, reference lineage, and delivery state. Do not claim Done while it says `doneClaimAllowed: false`, including when delivery is pending, failed, or non-durable. Render the Canvas for visual inspection when the request includes a campaign, poster, banner, or multi-direction composition. Reference provenance is not a user workflow state and never requires a Keep action. Never create another paid provider call automatically.\n",
|
|
53
53
|
}),
|
|
54
54
|
Object.freeze({
|
|
55
55
|
id: "existing-canvases",
|
package/src/runner.mjs
CHANGED
|
@@ -9,7 +9,7 @@ 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
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"]);
|
|
@@ -643,7 +643,7 @@ async function runAuthCommand(action, _args, options, env, runtime) {
|
|
|
643
643
|
if (!token) {
|
|
644
644
|
const login = runtime.oauthLogin
|
|
645
645
|
? await runtime.oauthLogin({ baseUrl, readOnly: options["read-only"] === true, profile: profile || current.profile })
|
|
646
|
-
: 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 });
|
|
647
647
|
token = login.accessToken;
|
|
648
648
|
loginMethod = "browser";
|
|
649
649
|
}
|
|
@@ -738,7 +738,7 @@ async function revokeBrowserOAuthToken(baseUrl, token, fetchImpl) {
|
|
|
738
738
|
}
|
|
739
739
|
}
|
|
740
740
|
|
|
741
|
-
export async function performBrowserOAuthLogin({ baseUrl, readOnly = false, noBrowser = false, runtime = {} }) {
|
|
741
|
+
export async function performBrowserOAuthLogin({ baseUrl, readOnly = false, noBrowser = false, env = process.env, runtime = {} }) {
|
|
742
742
|
const fetchImpl = runtime.fetchImpl || globalThis.fetch;
|
|
743
743
|
if (typeof fetchImpl !== "function") throw usageError("This runtime does not provide fetch for browser login.");
|
|
744
744
|
const origin = new URL(createCanvasAgentClient({ token: "configuration-validation", baseUrl, fetchImpl }).baseUrl).origin;
|
|
@@ -748,11 +748,17 @@ export async function performBrowserOAuthLogin({ baseUrl, readOnly = false, noBr
|
|
|
748
748
|
const callback = runtime.createOAuthCallbackServer ? await runtime.createOAuthCallbackServer(state) : await createOAuthCallbackServer(state);
|
|
749
749
|
try {
|
|
750
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));
|
|
751
756
|
const registrationResponse = await fetchImpl(`${origin}/oauth/register`, {
|
|
752
757
|
method: "POST",
|
|
753
758
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
754
759
|
body: JSON.stringify({
|
|
755
760
|
client_name: "Gavana CLI",
|
|
761
|
+
software_id: softwareId,
|
|
756
762
|
redirect_uris: [callback.redirectUri],
|
|
757
763
|
token_endpoint_auth_method: "none",
|
|
758
764
|
grant_types: ["authorization_code"],
|
package/src/version.mjs
CHANGED