@agentuity/cli 2.0.13 → 2.0.15

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.
Files changed (101) hide show
  1. package/dist/agent-detection.d.ts.map +1 -1
  2. package/dist/agent-detection.js +4 -6
  3. package/dist/agent-detection.js.map +1 -1
  4. package/dist/ai-help.js +10 -10
  5. package/dist/ai-help.js.map +1 -1
  6. package/dist/cmd/ai/capabilities/show.d.ts.map +1 -1
  7. package/dist/cmd/ai/capabilities/show.js +6 -0
  8. package/dist/cmd/ai/capabilities/show.js.map +1 -1
  9. package/dist/cmd/ai/intro.d.ts.map +1 -1
  10. package/dist/cmd/ai/intro.js +1 -0
  11. package/dist/cmd/ai/intro.js.map +1 -1
  12. package/dist/cmd/cloud/aigateway/complete.d.ts +7 -0
  13. package/dist/cmd/cloud/aigateway/complete.d.ts.map +1 -0
  14. package/dist/cmd/cloud/aigateway/complete.js +386 -0
  15. package/dist/cmd/cloud/aigateway/complete.js.map +1 -0
  16. package/dist/cmd/cloud/aigateway/index.d.ts +3 -0
  17. package/dist/cmd/cloud/aigateway/index.d.ts.map +1 -0
  18. package/dist/cmd/cloud/aigateway/index.js +20 -0
  19. package/dist/cmd/cloud/aigateway/index.js.map +1 -0
  20. package/dist/cmd/cloud/aigateway/model-cache.d.ts +4 -0
  21. package/dist/cmd/cloud/aigateway/model-cache.d.ts.map +1 -0
  22. package/dist/cmd/cloud/aigateway/model-cache.js +72 -0
  23. package/dist/cmd/cloud/aigateway/model-cache.js.map +1 -0
  24. package/dist/cmd/cloud/aigateway/models.d.ts +2 -0
  25. package/dist/cmd/cloud/aigateway/models.d.ts.map +1 -0
  26. package/dist/cmd/cloud/aigateway/models.js +193 -0
  27. package/dist/cmd/cloud/aigateway/models.js.map +1 -0
  28. package/dist/cmd/cloud/aigateway/util.d.ts +20 -0
  29. package/dist/cmd/cloud/aigateway/util.d.ts.map +1 -0
  30. package/dist/cmd/cloud/aigateway/util.js +58 -0
  31. package/dist/cmd/cloud/aigateway/util.js.map +1 -0
  32. package/dist/cmd/cloud/index.d.ts.map +1 -1
  33. package/dist/cmd/cloud/index.js +2 -0
  34. package/dist/cmd/cloud/index.js.map +1 -1
  35. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
  36. package/dist/cmd/cloud/sandbox/create.js +46 -4
  37. package/dist/cmd/cloud/sandbox/create.js.map +1 -1
  38. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -1
  39. package/dist/cmd/cloud/sandbox/exec.js +4 -3
  40. package/dist/cmd/cloud/sandbox/exec.js.map +1 -1
  41. package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -1
  42. package/dist/cmd/cloud/sandbox/run.js +9 -5
  43. package/dist/cmd/cloud/sandbox/run.js.map +1 -1
  44. package/dist/cmd/coder/skill/create.d.ts +2 -0
  45. package/dist/cmd/coder/skill/create.d.ts.map +1 -0
  46. package/dist/cmd/coder/skill/create.js +104 -0
  47. package/dist/cmd/coder/skill/create.js.map +1 -0
  48. package/dist/cmd/coder/skill/index.d.ts.map +1 -1
  49. package/dist/cmd/coder/skill/index.js +12 -1
  50. package/dist/cmd/coder/skill/index.js.map +1 -1
  51. package/dist/cmd/coder/start.d.ts.map +1 -1
  52. package/dist/cmd/coder/start.js +1 -0
  53. package/dist/cmd/coder/start.js.map +1 -1
  54. package/dist/cmd/coder/workspace/common.d.ts +22 -2
  55. package/dist/cmd/coder/workspace/common.d.ts.map +1 -1
  56. package/dist/cmd/coder/workspace/common.js +38 -2
  57. package/dist/cmd/coder/workspace/common.js.map +1 -1
  58. package/dist/cmd/coder/workspace/create.d.ts.map +1 -1
  59. package/dist/cmd/coder/workspace/create.js +34 -2
  60. package/dist/cmd/coder/workspace/create.js.map +1 -1
  61. package/dist/cmd/coder/workspace/update.d.ts.map +1 -1
  62. package/dist/cmd/coder/workspace/update.js +33 -1
  63. package/dist/cmd/coder/workspace/update.js.map +1 -1
  64. package/dist/cmd/dev/download.d.ts +8 -0
  65. package/dist/cmd/dev/download.d.ts.map +1 -1
  66. package/dist/cmd/dev/download.js +27 -1
  67. package/dist/cmd/dev/download.js.map +1 -1
  68. package/dist/cmd/dev/index.d.ts.map +1 -1
  69. package/dist/cmd/dev/index.js +18 -7
  70. package/dist/cmd/dev/index.js.map +1 -1
  71. package/dist/config.d.ts.map +1 -1
  72. package/dist/config.js +3 -0
  73. package/dist/config.js.map +1 -1
  74. package/dist/types.d.ts +3 -2
  75. package/dist/types.d.ts.map +1 -1
  76. package/dist/types.js +1 -0
  77. package/dist/types.js.map +1 -1
  78. package/package.json +7 -7
  79. package/src/agent-detection.ts +4 -6
  80. package/src/ai-help.ts +10 -10
  81. package/src/cmd/ai/capabilities/show.ts +6 -0
  82. package/src/cmd/ai/intro.ts +1 -0
  83. package/src/cmd/cloud/aigateway/complete.ts +461 -0
  84. package/src/cmd/cloud/aigateway/index.ts +21 -0
  85. package/src/cmd/cloud/aigateway/model-cache.ts +89 -0
  86. package/src/cmd/cloud/aigateway/models.ts +219 -0
  87. package/src/cmd/cloud/aigateway/util.ts +86 -0
  88. package/src/cmd/cloud/index.ts +2 -0
  89. package/src/cmd/cloud/sandbox/create.ts +57 -4
  90. package/src/cmd/cloud/sandbox/exec.ts +4 -3
  91. package/src/cmd/cloud/sandbox/run.ts +9 -5
  92. package/src/cmd/coder/skill/create.ts +122 -0
  93. package/src/cmd/coder/skill/index.ts +14 -1
  94. package/src/cmd/coder/start.ts +1 -0
  95. package/src/cmd/coder/workspace/common.ts +46 -2
  96. package/src/cmd/coder/workspace/create.ts +34 -1
  97. package/src/cmd/coder/workspace/update.ts +33 -0
  98. package/src/cmd/dev/download.ts +32 -1
  99. package/src/cmd/dev/index.ts +24 -8
  100. package/src/config.ts +3 -0
  101. package/src/types.ts +1 -0
@@ -2,16 +2,32 @@ import {
2
2
  type CoderCreateWorkspaceRequest,
3
3
  type CoderUpdateWorkspaceRequest,
4
4
  type CoderWorkspaceDetail,
5
+ type CoderWorkspaceSystemPromptMode,
5
6
  } from '@agentuity/core/coder';
6
7
  import { StructuredError } from '@agentuity/core';
7
8
  import * as tui from '../../../tui';
8
9
 
9
10
  export const EMPTY_WORKSPACE_ERROR =
10
- 'A workspace needs at least one repo, dependency, setup script, saved skill, skill bucket, or agent';
11
+ 'A workspace needs at least one repo, dependency, setup script, system prompt, saved skill, skill bucket, or agent';
11
12
  export const SetupScriptValidationError = StructuredError('SetupScriptValidationError')<{
12
13
  message: string;
13
14
  path?: string;
14
15
  }>();
16
+ export const SystemPromptValidationError = StructuredError('SystemPromptValidationError')<{
17
+ message: string;
18
+ path?: string;
19
+ }>();
20
+
21
+ export function normalizeSystemPromptMode(
22
+ value?: string
23
+ ): CoderWorkspaceSystemPromptMode | undefined {
24
+ if (value === undefined) return undefined;
25
+ const normalized = value.trim().toLowerCase();
26
+ if (normalized === 'append' || normalized === 'overwrite') return normalized;
27
+ throw new SystemPromptValidationError({
28
+ message: 'Use --system-prompt-mode append or --system-prompt-mode overwrite.',
29
+ });
30
+ }
15
31
 
16
32
  export function parseCommaList(value?: string): string[] {
17
33
  return value
@@ -46,11 +62,36 @@ export async function readSetupScript(input: {
46
62
  }
47
63
  }
48
64
 
65
+ export async function readSystemPrompt(input: {
66
+ systemPrompt?: string;
67
+ systemPromptFile?: string;
68
+ }): Promise<string | undefined> {
69
+ if (input.systemPrompt !== undefined && input.systemPromptFile) {
70
+ throw new SystemPromptValidationError({
71
+ message: 'Use either --system-prompt or --system-prompt-file, not both.',
72
+ });
73
+ }
74
+ if (input.systemPrompt !== undefined) return input.systemPrompt;
75
+ if (!input.systemPromptFile) return undefined;
76
+ try {
77
+ return await Bun.file(input.systemPromptFile).text();
78
+ } catch (error) {
79
+ throw new SystemPromptValidationError({
80
+ message: `Failed to read system prompt file "${input.systemPromptFile}": ${
81
+ error instanceof Error ? error.message : String(error)
82
+ }`,
83
+ path: input.systemPromptFile,
84
+ cause: error,
85
+ });
86
+ }
87
+ }
88
+
49
89
  export function hasWorkspaceSelections(input: CoderCreateWorkspaceRequest): boolean {
50
90
  return (
51
91
  (input.repos?.length ?? 0) > 0 ||
52
92
  (input.dependencies?.length ?? 0) > 0 ||
53
93
  Boolean(input.setupScript?.trim()) ||
94
+ Boolean(input.systemPrompt?.trim()) ||
54
95
  (input.savedSkillIds?.length ?? 0) > 0 ||
55
96
  (input.skillBucketIds?.length ?? 0) > 0 ||
56
97
  (input.enabledAgents?.length ?? 0) > 0
@@ -67,7 +108,7 @@ export function formatWorkspaceValidationMessage(issues: Array<{ message: string
67
108
  return 'Invalid workspace configuration';
68
109
  }
69
110
  if (messages.includes(EMPTY_WORKSPACE_ERROR)) {
70
- return `${EMPTY_WORKSPACE_ERROR}. Use --repo, --dependency, --setup-script, or --enabled-agents.`;
111
+ return `${EMPTY_WORKSPACE_ERROR}. Use --repo, --dependency, --setup-script, --system-prompt, or --enabled-agents.`;
71
112
  }
72
113
  return messages.join('; ');
73
114
  }
@@ -94,6 +135,9 @@ export function printWorkspaceSummary(workspace: CoderWorkspaceDetail): void {
94
135
  if (workspace.setupScript) {
95
136
  tui.output(' Setup: configured');
96
137
  }
138
+ if (workspace.systemPrompt) {
139
+ tui.output(` Prompt: configured (${workspace.systemPromptMode})`);
140
+ }
97
141
  if (workspace.snapshot?.status) {
98
142
  tui.output(` Snapshot: ${workspace.snapshot.status}`);
99
143
  }
@@ -14,8 +14,10 @@ import {
14
14
  EMPTY_WORKSPACE_ERROR,
15
15
  formatWorkspaceValidationMessage,
16
16
  hasWorkspaceSelections,
17
+ normalizeSystemPromptMode,
17
18
  parseCommaList,
18
19
  printWorkspaceSummary,
20
+ readSystemPrompt,
19
21
  readSetupScript,
20
22
  } from './common';
21
23
 
@@ -38,6 +40,12 @@ export const createWorkspaceSubcommand = createSubcommand({
38
40
  ),
39
41
  description: 'Create an org-scoped workspace with dependencies and a setup script',
40
42
  },
43
+ {
44
+ command: getCommand(
45
+ 'coder workspace create "My Workspace" --system-prompt-file ./WORKSPACE_PROMPT.md --system-prompt-mode overwrite'
46
+ ),
47
+ description: 'Create a workspace with Lead system prompt instructions',
48
+ },
41
49
  {
42
50
  command: getCommand('coder workspace create "My Workspace" --enabled-agents code-review'),
43
51
  description: 'Create a workspace with an agent roster',
@@ -71,6 +79,18 @@ export const createWorkspaceSubcommand = createSubcommand({
71
79
  .string()
72
80
  .optional()
73
81
  .describe('Path to a shell script to run while preparing workspace snapshots'),
82
+ systemPrompt: z
83
+ .string()
84
+ .optional()
85
+ .describe('Inline Lead system prompt to apply to sessions created from this workspace'),
86
+ systemPromptFile: z
87
+ .string()
88
+ .optional()
89
+ .describe('Path to a file containing the workspace Lead system prompt'),
90
+ systemPromptMode: z
91
+ .string()
92
+ .optional()
93
+ .describe('How to apply the system prompt: append or overwrite'),
74
94
  enabledAgents: z
75
95
  .string()
76
96
  .optional()
@@ -116,12 +136,25 @@ export const createWorkspaceSubcommand = createSubcommand({
116
136
  tui.fatal(`Failed to read setup script: ${msg}`, ErrorCode.VALIDATION_FAILED);
117
137
  return;
118
138
  }
139
+ try {
140
+ const systemPrompt = await readSystemPrompt({
141
+ systemPrompt: opts?.systemPrompt,
142
+ systemPromptFile: opts?.systemPromptFile,
143
+ });
144
+ if (systemPrompt !== undefined) body.systemPrompt = systemPrompt;
145
+ const systemPromptMode = normalizeSystemPromptMode(opts?.systemPromptMode);
146
+ if (systemPromptMode !== undefined) body.systemPromptMode = systemPromptMode;
147
+ } catch (err) {
148
+ const msg = err instanceof Error ? err.message : String(err);
149
+ tui.fatal(`Failed to read system prompt: ${msg}`, ErrorCode.VALIDATION_FAILED);
150
+ return;
151
+ }
119
152
  if (opts?.enabledAgents) {
120
153
  body.enabledAgents = parseCommaList(opts.enabledAgents);
121
154
  }
122
155
  if (!hasWorkspaceSelections(body)) {
123
156
  tui.fatal(
124
- `Failed to create workspace: ${EMPTY_WORKSPACE_ERROR}. Use --repo, --dependency, --setup-script, or --enabled-agents.`,
157
+ `Failed to create workspace: ${EMPTY_WORKSPACE_ERROR}. Use --repo, --dependency, --setup-script, --system-prompt, or --enabled-agents.`,
125
158
  ErrorCode.VALIDATION_FAILED
126
159
  );
127
160
  }
@@ -13,8 +13,10 @@ import { resolveGitHubRepo } from '../resolve-repo';
13
13
  import {
14
14
  formatWorkspaceValidationMessage,
15
15
  hasWorkspaceUpdate,
16
+ normalizeSystemPromptMode,
16
17
  parseCommaList,
17
18
  printWorkspaceSummary,
19
+ readSystemPrompt,
18
20
  readSetupScript,
19
21
  } from './common';
20
22
 
@@ -33,6 +35,12 @@ export const updateWorkspaceSubcommand = createSubcommand({
33
35
  command: getCommand('coder workspace update ws_abc123 --setup-script-file ./setup.sh'),
34
36
  description: 'Update the workspace setup script',
35
37
  },
38
+ {
39
+ command: getCommand(
40
+ 'coder workspace update ws_abc123 --system-prompt-file ./WORKSPACE_PROMPT.md --system-prompt-mode append'
41
+ ),
42
+ description: 'Update the workspace Lead system prompt',
43
+ },
36
44
  ],
37
45
  schema: {
38
46
  args: z.object({
@@ -57,6 +65,18 @@ export const updateWorkspaceSubcommand = createSubcommand({
57
65
  .string()
58
66
  .optional()
59
67
  .describe('Path to a shell script to run while preparing workspace snapshots'),
68
+ systemPrompt: z
69
+ .string()
70
+ .optional()
71
+ .describe('Inline Lead system prompt to apply to sessions created from this workspace'),
72
+ systemPromptFile: z
73
+ .string()
74
+ .optional()
75
+ .describe('Path to a file containing the workspace Lead system prompt'),
76
+ systemPromptMode: z
77
+ .string()
78
+ .optional()
79
+ .describe('How to apply the system prompt: append or overwrite'),
60
80
  enabledAgents: z
61
81
  .string()
62
82
  .optional()
@@ -102,6 +122,19 @@ export const updateWorkspaceSubcommand = createSubcommand({
102
122
  tui.fatal(`Failed to read setup script: ${msg}`, ErrorCode.VALIDATION_FAILED);
103
123
  return;
104
124
  }
125
+ try {
126
+ const systemPrompt = await readSystemPrompt({
127
+ systemPrompt: opts?.systemPrompt,
128
+ systemPromptFile: opts?.systemPromptFile,
129
+ });
130
+ if (systemPrompt !== undefined) body.systemPrompt = systemPrompt;
131
+ const systemPromptMode = normalizeSystemPromptMode(opts?.systemPromptMode);
132
+ if (systemPromptMode !== undefined) body.systemPromptMode = systemPromptMode;
133
+ } catch (err) {
134
+ const msg = err instanceof Error ? err.message : String(err);
135
+ tui.fatal(`Failed to read system prompt: ${msg}`, ErrorCode.VALIDATION_FAILED);
136
+ return;
137
+ }
105
138
  if (opts?.enabledAgents) {
106
139
  body.enabledAgents = parseCommaList(opts.enabledAgents);
107
140
  }
@@ -1,5 +1,5 @@
1
1
  import { randomUUID } from 'node:crypto';
2
- import { mkdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { existsSync, mkdirSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
3
3
  import { tmpdir, platform } from 'node:os';
4
4
  import { join, dirname } from 'node:path';
5
5
  import * as tar from 'tar';
@@ -11,6 +11,37 @@ interface GravityClient {
11
11
  version: string;
12
12
  }
13
13
 
14
+ /**
15
+ * Remove previously downloaded gravity version directories after a newer
16
+ * version has started successfully.
17
+ *
18
+ * Safety guard: only removes sibling directories that contain a gravity
19
+ * binary, leaving any unrelated files/folders untouched.
20
+ */
21
+ export function sweepOldGravityVersions(gravityDir: string, currentVersion: string): string[] {
22
+ if (!existsSync(gravityDir)) {
23
+ return [];
24
+ }
25
+
26
+ const removed: string[] = [];
27
+ for (const entry of readdirSync(gravityDir, { withFileTypes: true })) {
28
+ if (!entry.isDirectory() || entry.name === currentVersion) {
29
+ continue;
30
+ }
31
+
32
+ const candidateDir = join(gravityDir, entry.name);
33
+ const candidateBinary = join(candidateDir, 'gravity');
34
+ if (!existsSync(candidateBinary)) {
35
+ continue;
36
+ }
37
+
38
+ rmSync(candidateDir, { recursive: true, force: true });
39
+ removed.push(candidateDir);
40
+ }
41
+
42
+ return removed;
43
+ }
44
+
14
45
  const GravityVersionError = StructuredError('GravityVersionError')<{
15
46
  status: number;
16
47
  statusText: string;
@@ -9,7 +9,7 @@ import * as tui from '../../tui';
9
9
  import { getCommand } from '../../command-prefix';
10
10
  import { generateEndpoint, type DevmodeResponse } from './api';
11
11
  import { APIClient, getAPIBaseURL, getAppBaseURL, getGravityDevModeURL } from '../../api';
12
- import { download } from './download';
12
+ import { download, sweepOldGravityVersions } from './download';
13
13
  import { createDevmodeSyncService } from './sync';
14
14
  import { getDevmodeDeploymentId } from '../build/ids';
15
15
  import { getDefaultConfigDir, saveConfig, loadProjectSDKKey, getAuth } from '../../config';
@@ -411,6 +411,7 @@ export const command = createCommand({
411
411
  let devmode: DevmodeResponse | undefined;
412
412
  let gravityBin: string | undefined;
413
413
  let gravityURL: string | undefined;
414
+ let gravitySweepTarget: { gravityDir: string; version: string } | null = null;
414
415
  let appURL: string | undefined;
415
416
  let savedPrivateKey: string | undefined = config?.devmode?.privateKey
416
417
  ? Buffer.from(config.devmode.privateKey, 'base64').toString('utf-8')
@@ -465,8 +466,12 @@ export const command = createCommand({
465
466
  }
466
467
 
467
468
  if (mustCheck) {
469
+ const previousGravityVersion = config?.gravity?.version;
468
470
  const res = await download(gravityDir);
469
471
  gravityBin = res.filename;
472
+ if (previousGravityVersion !== res.version) {
473
+ gravitySweepTarget = { gravityDir, version: res.version };
474
+ }
470
475
  const _config = { ...config } as Config;
471
476
  _config.gravity = {
472
477
  checked: Date.now(),
@@ -494,10 +499,6 @@ export const command = createCommand({
494
499
 
495
500
  // Calculate URLs for banner
496
501
  const padding = 12;
497
- const workbenchUrl =
498
- auth && project?.projectId
499
- ? `${getAppBaseURL(config)}/w/${project.projectId}`
500
- : `http://127.0.0.1:${opts.port}${workbench.config?.route ?? '/workbench'}`;
501
502
 
502
503
  const devmodebody =
503
504
  tui.muted(tui.padRight('Local:', padding)) +
@@ -506,9 +507,6 @@ export const command = createCommand({
506
507
  tui.muted(tui.padRight('Public:', padding)) +
507
508
  (devmode?.hostname ? tui.link(`https://${devmode.hostname}`) : tui.warn('Disabled')) +
508
509
  '\n' +
509
- tui.muted(tui.padRight('Workbench:', padding)) +
510
- (workbench.hasWorkbench ? tui.link(workbenchUrl) : tui.warn('Disabled')) +
511
- '\n' +
512
510
  tui.muted(tui.padRight('Dashboard:', padding)) +
513
511
  (appURL ? tui.link(appURL) : tui.warn('Disabled')) +
514
512
  '\n' +
@@ -1133,6 +1131,24 @@ export const command = createCommand({
1133
1131
  sendHeartbeat();
1134
1132
  gravityHeartbeatInterval = setInterval(sendHeartbeat, 5000);
1135
1133
  }
1134
+
1135
+ if (gravitySweepTarget) {
1136
+ try {
1137
+ const removed = sweepOldGravityVersions(
1138
+ gravitySweepTarget.gravityDir,
1139
+ gravitySweepTarget.version
1140
+ );
1141
+ logger.debug(
1142
+ 'Swept %d old gravity version director%s after successful startup',
1143
+ removed.length,
1144
+ removed.length === 1 ? 'y' : 'ies'
1145
+ );
1146
+ } catch (error) {
1147
+ logger.warn('Failed to sweep old gravity versions: %s', error);
1148
+ } finally {
1149
+ gravitySweepTarget = null;
1150
+ }
1151
+ }
1136
1152
  } else if (trimmed) {
1137
1153
  logger.debug('[gravity] %s', trimmed);
1138
1154
  }
package/src/config.ts CHANGED
@@ -226,6 +226,9 @@ export async function loadConfig(
226
226
  if (process.env.AGENTUITY_VECTOR_URL) {
227
227
  overrides.vector_url = process.env.AGENTUITY_VECTOR_URL;
228
228
  }
229
+ if (process.env.AGENTUITY_AIGATEWAY_URL) {
230
+ overrides.aigateway_url = process.env.AGENTUITY_AIGATEWAY_URL;
231
+ }
229
232
  if (process.env.AGENTUITY_STREAM_URL) {
230
233
  overrides.stream_url = process.env.AGENTUITY_STREAM_URL;
231
234
  }
package/src/types.ts CHANGED
@@ -41,6 +41,7 @@ export const ConfigSchema = zod.object({
41
41
  kv_url: zod.url().optional().describe('Override keyvalue URL'),
42
42
  sandbox_url: zod.url().optional().describe('Override sandbox URL'),
43
43
  vector_url: zod.url().optional().describe('Override vector store URL'),
44
+ aigateway_url: zod.url().optional().describe('Override AI Gateway URL'),
44
45
  catalyst_url: zod.url().optional().describe('Override catalyst URL'),
45
46
  ion_url: zod.url().optional().describe('Override ion URL'),
46
47
  gravity_url: zod.url().optional().describe('Override gravity URL'),