@agentuity/cli 0.1.6 → 0.1.8

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/cli.d.ts.map +1 -1
  2. package/dist/cli.js +64 -4
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
  5. package/dist/cmd/build/vite/agent-discovery.js +159 -16
  6. package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
  7. package/dist/cmd/build/vite/docs-generator.d.ts.map +1 -1
  8. package/dist/cmd/build/vite/docs-generator.js +8 -4
  9. package/dist/cmd/build/vite/docs-generator.js.map +1 -1
  10. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  11. package/dist/cmd/build/vite/registry-generator.js +27 -4
  12. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  13. package/dist/cmd/build/vite/server-bundler.d.ts.map +1 -1
  14. package/dist/cmd/build/vite/server-bundler.js +3 -1
  15. package/dist/cmd/build/vite/server-bundler.js.map +1 -1
  16. package/dist/cmd/build/vite/vite-asset-server-config.d.ts.map +1 -1
  17. package/dist/cmd/build/vite/vite-asset-server-config.js +5 -2
  18. package/dist/cmd/build/vite/vite-asset-server-config.js.map +1 -1
  19. package/dist/cmd/canary/index.d.ts.map +1 -0
  20. package/dist/cmd/canary/index.js +186 -0
  21. package/dist/cmd/canary/index.js.map +1 -0
  22. package/dist/cmd/cloud/deploy.js +1 -1
  23. package/dist/cmd/cloud/deploy.js.map +1 -1
  24. package/dist/cmd/git/account/add.js +1 -1
  25. package/dist/cmd/git/account/add.js.map +1 -1
  26. package/dist/cmd/git/account/list.js +1 -1
  27. package/dist/cmd/git/account/list.js.map +1 -1
  28. package/dist/cmd/git/account/remove.js +1 -1
  29. package/dist/cmd/git/account/remove.js.map +1 -1
  30. package/dist/cmd/git/api.d.ts.map +1 -0
  31. package/dist/cmd/git/api.js.map +1 -0
  32. package/dist/cmd/git/link.js +1 -1
  33. package/dist/cmd/git/link.js.map +1 -1
  34. package/dist/cmd/git/list.js +1 -1
  35. package/dist/cmd/git/list.js.map +1 -1
  36. package/dist/cmd/git/status.js +1 -1
  37. package/dist/cmd/git/status.js.map +1 -1
  38. package/dist/cmd/git/unlink.js +1 -1
  39. package/dist/cmd/git/unlink.js.map +1 -1
  40. package/dist/cmd/index.js +1 -1
  41. package/dist/cmd/index.js.map +1 -1
  42. package/dist/cmd/project/auth/generate.js.map +1 -1
  43. package/dist/cmd/project/auth/init.js.map +1 -1
  44. package/dist/cmd/project/auth/shared.d.ts.map +1 -1
  45. package/dist/cmd/project/auth/shared.js +1 -2
  46. package/dist/cmd/project/auth/shared.js.map +1 -1
  47. package/dist/cmd/project/template-flow.js.map +1 -1
  48. package/dist/types.d.ts +3 -0
  49. package/dist/types.d.ts.map +1 -1
  50. package/dist/types.js.map +1 -1
  51. package/dist/utils/dependency-checker.d.ts.map +1 -1
  52. package/dist/utils/dependency-checker.js +5 -1
  53. package/dist/utils/dependency-checker.js.map +1 -1
  54. package/package.json +6 -6
  55. package/src/cli.ts +73 -4
  56. package/src/cmd/build/vite/agent-discovery.ts +209 -16
  57. package/src/cmd/build/vite/docs-generator.ts +8 -4
  58. package/src/cmd/build/vite/registry-generator.ts +29 -4
  59. package/src/cmd/build/vite/server-bundler.ts +4 -5
  60. package/src/cmd/build/vite/vite-asset-server-config.ts +5 -2
  61. package/src/cmd/canary/index.ts +214 -0
  62. package/src/cmd/cloud/deploy.ts +1 -1
  63. package/src/cmd/git/account/add.ts +1 -1
  64. package/src/cmd/git/account/list.ts +1 -1
  65. package/src/cmd/git/account/remove.ts +1 -1
  66. package/src/cmd/git/link.ts +1 -1
  67. package/src/cmd/git/list.ts +1 -1
  68. package/src/cmd/git/status.ts +1 -1
  69. package/src/cmd/git/unlink.ts +1 -1
  70. package/src/cmd/index.ts +1 -1
  71. package/src/cmd/project/auth/generate.ts +4 -4
  72. package/src/cmd/project/auth/init.ts +2 -2
  73. package/src/cmd/project/auth/shared.ts +3 -4
  74. package/src/cmd/project/template-flow.ts +3 -3
  75. package/src/types.ts +3 -0
  76. package/src/utils/dependency-checker.ts +6 -2
  77. package/dist/cmd/integration/api.d.ts.map +0 -1
  78. package/dist/cmd/integration/api.js.map +0 -1
  79. package/dist/cmd/integration/github/connect.d.ts +0 -2
  80. package/dist/cmd/integration/github/connect.d.ts.map +0 -1
  81. package/dist/cmd/integration/github/connect.js +0 -197
  82. package/dist/cmd/integration/github/connect.js.map +0 -1
  83. package/dist/cmd/integration/github/disconnect.d.ts +0 -2
  84. package/dist/cmd/integration/github/disconnect.d.ts.map +0 -1
  85. package/dist/cmd/integration/github/disconnect.js +0 -121
  86. package/dist/cmd/integration/github/disconnect.js.map +0 -1
  87. package/dist/cmd/integration/github/index.d.ts +0 -2
  88. package/dist/cmd/integration/github/index.d.ts.map +0 -1
  89. package/dist/cmd/integration/github/index.js +0 -21
  90. package/dist/cmd/integration/github/index.js.map +0 -1
  91. package/dist/cmd/integration/index.d.ts.map +0 -1
  92. package/dist/cmd/integration/index.js +0 -16
  93. package/dist/cmd/integration/index.js.map +0 -1
  94. package/src/cmd/integration/github/connect.ts +0 -242
  95. package/src/cmd/integration/github/disconnect.ts +0 -149
  96. package/src/cmd/integration/github/index.ts +0 -21
  97. package/src/cmd/integration/index.ts +0 -16
  98. /package/dist/cmd/{integration → canary}/index.d.ts +0 -0
  99. /package/dist/cmd/{integration → git}/api.d.ts +0 -0
  100. /package/dist/cmd/{integration → git}/api.js +0 -0
  101. /package/src/cmd/{integration → git}/api.ts +0 -0
@@ -46,11 +46,146 @@ interface ASTCallExpression extends ASTNode {
46
46
  callee: ASTNode;
47
47
  }
48
48
 
49
+ interface ASTMemberExpression extends ASTNode {
50
+ object: ASTNode;
51
+ property: ASTNode;
52
+ computed?: boolean;
53
+ }
54
+
49
55
  interface ASTVariableDeclarator extends ASTNode {
50
56
  id: ASTNode;
51
57
  init?: ASTNode;
52
58
  }
53
59
 
60
+ interface ASTVariableDeclaration extends ASTNode {
61
+ declarations: ASTVariableDeclarator[];
62
+ }
63
+
64
+ interface ASTExportNamedDeclaration extends ASTNode {
65
+ declaration?: ASTNode;
66
+ }
67
+
68
+ interface ASTProgram {
69
+ type: string;
70
+ body: ASTNode[];
71
+ }
72
+
73
+ /**
74
+ * Type for identifier resolver function
75
+ */
76
+ type IdentifierResolver = (name: string) => ASTNode | undefined;
77
+
78
+ /**
79
+ * Build a file-local identifier resolver that maps top-level variable names
80
+ * to their initializer AST nodes. This allows resolving schema variable references.
81
+ */
82
+ function buildIdentifierResolver(program: ASTProgram): IdentifierResolver {
83
+ const initMap = new Map<string, ASTNode>();
84
+
85
+ for (const node of program.body) {
86
+ // const x = ... or let x = ... or var x = ...
87
+ if (node.type === 'VariableDeclaration') {
88
+ const decl = node as unknown as ASTVariableDeclaration;
89
+ for (const d of decl.declarations) {
90
+ if (d.id.type === 'Identifier' && d.init) {
91
+ const id = d.id as ASTNodeIdentifier;
92
+ initMap.set(id.name, d.init);
93
+ }
94
+ }
95
+ }
96
+
97
+ // export const x = ...
98
+ if (node.type === 'ExportNamedDeclaration') {
99
+ const exp = node as unknown as ASTExportNamedDeclaration;
100
+ if (exp.declaration && exp.declaration.type === 'VariableDeclaration') {
101
+ const decl = exp.declaration as unknown as ASTVariableDeclaration;
102
+ for (const d of decl.declarations) {
103
+ if (d.id.type === 'Identifier' && d.init) {
104
+ const id = d.id as ASTNodeIdentifier;
105
+ initMap.set(id.name, d.init);
106
+ }
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ return (name: string) => initMap.get(name);
113
+ }
114
+
115
+ /**
116
+ * Get the property name from an AST node (Identifier or Literal).
117
+ */
118
+ function getPropertyName(node: ASTNode): string | undefined {
119
+ if (node.type === 'Identifier') {
120
+ return (node as ASTNodeIdentifier).name;
121
+ }
122
+ if (node.type === 'Literal') {
123
+ const lit = node as ASTLiteral;
124
+ return typeof lit.value === 'string' ? lit.value : undefined;
125
+ }
126
+ return undefined;
127
+ }
128
+
129
+ /**
130
+ * Resolve an expression by following identifier references and member access chains.
131
+ * Applies a recursion limit to prevent infinite loops from cyclic references.
132
+ *
133
+ * Supported patterns:
134
+ * - Identifiers: `AgentInput` -> resolves to variable definition
135
+ * - Member access: `configs.agent1.schema` -> traverses object literals
136
+ */
137
+ function resolveExpression(
138
+ node: ASTNode,
139
+ resolveIdentifier: IdentifierResolver,
140
+ depth = 0
141
+ ): ASTNode {
142
+ if (!node) return node;
143
+ if (depth > 8) return node; // Prevent cycles / deep alias chains
144
+
145
+ // Follow identifiers to their definitions
146
+ if (node.type === 'Identifier') {
147
+ const id = node as ASTNodeIdentifier;
148
+ const resolved = resolveIdentifier(id.name);
149
+ if (resolved) {
150
+ return resolveExpression(resolved, resolveIdentifier, depth + 1);
151
+ }
152
+ }
153
+
154
+ // Follow member expressions: configs.agent1.schema, baseSchemas.shared
155
+ if (node.type === 'MemberExpression') {
156
+ const memberExpr = node as unknown as ASTMemberExpression;
157
+
158
+ // Skip computed properties like configs[agentName]
159
+ if (memberExpr.computed) return node;
160
+
161
+ const propName = getPropertyName(memberExpr.property);
162
+ if (!propName) return node;
163
+
164
+ // First resolve the object side (e.g., configs -> { agent1: {...} })
165
+ const resolvedObj = resolveExpression(memberExpr.object, resolveIdentifier, depth + 1);
166
+
167
+ // If we got an object literal, look up the property
168
+ if (resolvedObj.type === 'ObjectExpression') {
169
+ const obj = resolvedObj as ASTObjectExpression;
170
+ for (const prop of obj.properties) {
171
+ // Skip spread elements
172
+ if (!prop || !('key' in prop) || !prop.key) continue;
173
+
174
+ const keyName = getPropertyName(prop.key);
175
+ if (keyName === propName && prop.value) {
176
+ // Recurse into the property value
177
+ return resolveExpression(prop.value as ASTNode, resolveIdentifier, depth + 1);
178
+ }
179
+ }
180
+ }
181
+
182
+ // Couldn't resolve - return original node
183
+ return node;
184
+ }
185
+
186
+ return node;
187
+ }
188
+
54
189
  export interface AgentMetadata {
55
190
  filename: string;
56
191
  name: string;
@@ -117,9 +252,43 @@ function generateStableEvalId(projectId: string, agentId: string, name: string):
117
252
  }
118
253
 
119
254
  /**
120
- * Extract schema code from createAgent call arguments
255
+ * Check if a property key matches a given name.
256
+ * Handles both Identifier keys (schema) and Literal keys ('schema').
121
257
  */
122
- function extractSchemaCode(callargexp: ASTObjectExpression): {
258
+ function isKeyNamed(prop: ASTPropertyNode, name: 'schema' | 'input' | 'output'): boolean {
259
+ if (!prop || !prop.key) return false;
260
+
261
+ if (prop.key.type === 'Identifier') {
262
+ return (prop.key as ASTNodeIdentifier).name === name;
263
+ }
264
+ if (prop.key.type === 'Literal') {
265
+ const lit = prop.key as unknown as ASTLiteral;
266
+ return typeof lit.value === 'string' && lit.value === name;
267
+ }
268
+ return false;
269
+ }
270
+
271
+ /**
272
+ * Extract schema code from createAgent call arguments.
273
+ * Resolves variable references to their actual definitions when possible.
274
+ *
275
+ * Supported patterns:
276
+ * - Inline schema: `schema: { input: s.object({...}), output: s.object({...}) }`
277
+ * - Variable reference: `schema: { input: AgentInput, output: AgentOutput }`
278
+ * - Schema object variable: `schema: schemaVar` where schemaVar is a top-level const
279
+ * - Shorthand: `schema: { input, output }` where input/output are top-level consts
280
+ *
281
+ * Unsupported patterns (returns empty or partial result):
282
+ * - Config alias: `createAgent('x', configVar)` - config must be inline object
283
+ * - Schema from member access: `schema: configs.agent1.schema`
284
+ * - Schema from function call: `schema: getSchema()`
285
+ * - Destructured variables: `const { schema } = config`
286
+ * - Cross-file imports (falls back to identifier name)
287
+ */
288
+ function extractSchemaCode(
289
+ callargexp: ASTObjectExpression,
290
+ resolveIdentifier: IdentifierResolver
291
+ ): {
123
292
  inputSchemaCode?: string;
124
293
  outputSchemaCode?: string;
125
294
  } {
@@ -127,9 +296,16 @@ function extractSchemaCode(callargexp: ASTObjectExpression): {
127
296
 
128
297
  // Find the schema property
129
298
  for (const prop of callargexp.properties) {
130
- if (prop.key.type === 'Identifier' && prop.key.name === 'schema') {
131
- if (prop.value.type === 'ObjectExpression') {
132
- schemaObj = prop.value as ASTObjectExpression;
299
+ // Skip spread elements or any non-Property nodes
300
+ if (!prop || !('key' in prop) || !prop.key) continue;
301
+
302
+ if (isKeyNamed(prop, 'schema')) {
303
+ // Resolve the schema value if it's an identifier (e.g., schema: schemaVar)
304
+ let valueNode = prop.value as ASTNode;
305
+ valueNode = resolveExpression(valueNode, resolveIdentifier);
306
+
307
+ if (valueNode.type === 'ObjectExpression') {
308
+ schemaObj = valueNode as ASTObjectExpression;
133
309
  break;
134
310
  }
135
311
  }
@@ -144,12 +320,17 @@ function extractSchemaCode(callargexp: ASTObjectExpression): {
144
320
 
145
321
  // Extract input and output schema code
146
322
  for (const prop of schemaObj.properties) {
147
- if (prop.key.type === 'Identifier') {
148
- if (prop.key.name === 'input' && prop.value) {
149
- inputSchemaCode = formatSchemaCode(generate(prop.value));
150
- } else if (prop.key.name === 'output' && prop.value) {
151
- outputSchemaCode = formatSchemaCode(generate(prop.value));
152
- }
323
+ // Skip spread elements or any non-Property nodes
324
+ if (!prop || !('key' in prop) || !prop.key) continue;
325
+
326
+ if (isKeyNamed(prop, 'input') && prop.value) {
327
+ // Resolve variable reference if the value is an identifier
328
+ const resolvedValue = resolveExpression(prop.value as ASTNode, resolveIdentifier);
329
+ inputSchemaCode = formatSchemaCode(generate(resolvedValue));
330
+ } else if (isKeyNamed(prop, 'output') && prop.value) {
331
+ // Resolve variable reference if the value is an identifier
332
+ const resolvedValue = resolveExpression(prop.value as ASTNode, resolveIdentifier);
333
+ outputSchemaCode = formatSchemaCode(generate(resolvedValue));
153
334
  }
154
335
  }
155
336
 
@@ -179,13 +360,19 @@ function extractAgentMetadata(
179
360
  projectId: string,
180
361
  deploymentId: string
181
362
  ): AgentMetadata | null {
182
- const ast = acornLoose.parse(code, { ecmaVersion: 'latest', sourceType: 'module' });
363
+ const ast = acornLoose.parse(code, {
364
+ ecmaVersion: 'latest',
365
+ sourceType: 'module',
366
+ }) as ASTProgram;
367
+
368
+ // Build identifier resolver for resolving schema variable references
369
+ const resolveIdentifier = buildIdentifierResolver(ast);
183
370
 
184
371
  // Calculate file version (hash of contents)
185
372
  const version = hash(code);
186
373
 
187
374
  // Find createAgent calls
188
- for (const node of (ast as { body: ASTNode[] }).body) {
375
+ for (const node of ast.body) {
189
376
  if (node.type === 'ExportDefaultDeclaration') {
190
377
  const declaration = (node as unknown as { declaration: ASTNode }).declaration;
191
378
 
@@ -204,8 +391,11 @@ function extractAgentMetadata(
204
391
  // Second arg is config object
205
392
  const callargexp = callExpr.arguments[1] as ASTObjectExpression;
206
393
 
207
- // Extract schemas
208
- const { inputSchemaCode, outputSchemaCode } = extractSchemaCode(callargexp);
394
+ // Extract schemas (with variable resolution)
395
+ const { inputSchemaCode, outputSchemaCode } = extractSchemaCode(
396
+ callargexp,
397
+ resolveIdentifier
398
+ );
209
399
 
210
400
  // Extract description from either direct property or metadata object
211
401
  let description: string | undefined;
@@ -262,7 +452,10 @@ function extractAgentMetadata(
262
452
  const name = String(nameArg.value);
263
453
 
264
454
  const callargexp = callExpr.arguments[1] as ASTObjectExpression;
265
- const { inputSchemaCode, outputSchemaCode } = extractSchemaCode(callargexp);
455
+ const { inputSchemaCode, outputSchemaCode } = extractSchemaCode(
456
+ callargexp,
457
+ resolveIdentifier
458
+ );
266
459
 
267
460
  let description: string | undefined;
268
461
  for (const prop of callargexp.properties) {
@@ -20,8 +20,10 @@ This directory contains auto-generated TypeScript files created by the Agentuity
20
20
  - \`registry.ts\` - Agent registry from \`src/agent/**\`
21
21
  - \`routes.ts\` - Route registry from \`src/api/**\`
22
22
  - \`app.ts\` - Application entry point
23
- - \`state.ts\` - App state types (when setup() exists)
24
- - \`router.ts\` - Runtime wrapper (when setup() exists)
23
+ - \`analytics-config.ts\` - Web analytics configuration from \`agentuity.json\`
24
+ - \`webanalytics.ts\` - Web analytics injection and route registration
25
+ - \`state.ts\` - App state type (only generated when \`setup()\` returns state in \`app.ts\`)
26
+ - \`router.ts\` - Runtime wrapper with type augmentation (only generated when \`setup()\` returns state in \`app.ts\`)
25
27
 
26
28
  ## For Developers
27
29
 
@@ -50,8 +52,10 @@ const AGENTS_MD_CONTENT = `# AI Agent Instructions
50
52
  - \`registry.ts\` - Built from agent discovery in \`src/agent/\`
51
53
  - \`routes.ts\` - Built from route discovery in \`src/api/\`
52
54
  - \`app.ts\` - Entry point assembled from project configuration
53
- - \`state.ts\` - App state type from \`setup()\` return value
54
- - \`router.ts\` - Runtime wrapper with type augmentation
55
+ - \`analytics-config.ts\` - Web analytics configuration from \`agentuity.json\`
56
+ - \`webanalytics.ts\` - Web analytics injection and route registration
57
+ - \`state.ts\` - App state type (only generated when \`setup()\` returns state in \`app.ts\`)
58
+ - \`router.ts\` - Runtime wrapper with type augmentation (only generated when \`setup()\` returns state)
55
59
 
56
60
  These files are regenerated on every \`bun run build\` or \`bun run dev\`.
57
61
  `;
@@ -935,11 +935,15 @@ ${routeSchemaTypes}
935
935
  * Route Definitions
936
936
  *
937
937
  * Type-safe route registry for all API routes, WebSocket connections, and SSE endpoints.
938
- * Used by @agentuity/react for client-side type-safe routing.
938
+ * Used by @agentuity/react and @agentuity/frontend for client-side type-safe routing.
939
939
  *
940
940
  * @remarks
941
941
  * This module augmentation is auto-generated from your route files during build.
942
942
  * Individual route Input/Output types are exported above for direct usage.
943
+ *
944
+ * The augmentation targets @agentuity/frontend (the canonical source of registry types).
945
+ * Since @agentuity/react re-exports these types, the augmentation is visible when
946
+ * importing from either package.
943
947
  */
944
948
  ${
945
949
  shouldEmitFrontendClient
@@ -956,7 +960,7 @@ ${rpcRegistryType}
956
960
  `
957
961
  : ''
958
962
  }
959
- declare module '@agentuity/react' {
963
+ declare module '@agentuity/frontend' {
960
964
  \t/**
961
965
  \t * API Route Registry
962
966
  \t *
@@ -988,13 +992,34 @@ ${sseRouteEntries}
988
992
  \t * RPC Route Registry
989
993
  \t *
990
994
  \t * Nested structure for RPC-style client access (e.g., client.hello.post())
991
- \t * Used by createClient() from @agentuity/core for type-safe RPC calls.
995
+ \t * Used by createClient() from @agentuity/frontend for type-safe RPC calls.
992
996
  \t */
993
997
  \texport interface RPCRouteRegistry {
994
998
  ${rpcRegistryType}
995
999
  \t}
996
1000
  }
997
-
1001
+ ${
1002
+ hasReactDependency
1003
+ ? `
1004
+ // Backward compatibility: also augment @agentuity/react for older versions
1005
+ // that define RouteRegistry locally instead of re-exporting from @agentuity/frontend
1006
+ declare module '@agentuity/react' {
1007
+ \texport interface RouteRegistry {
1008
+ ${apiRouteEntries}
1009
+ \t}
1010
+ \texport interface WebSocketRouteRegistry {
1011
+ ${websocketRouteEntries}
1012
+ \t}
1013
+ \texport interface SSERouteRegistry {
1014
+ ${sseRouteEntries}
1015
+ \t}
1016
+ \texport interface RPCRouteRegistry {
1017
+ ${rpcRegistryType}
1018
+ \t}
1019
+ }
1020
+ `
1021
+ : ''
1022
+ }
998
1023
  /**
999
1024
  * Runtime metadata for RPC routes.
1000
1025
  * Contains route type information for client routing decisions.
@@ -66,7 +66,9 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
66
66
 
67
67
  // Build tool externals: packages that should be external but NOT installed
68
68
  // These are devDependencies that may exist in node_modules but aren't needed at runtime
69
- const buildToolExternals = ['@babel/*', 'lightningcss', '@vitejs/*', 'vite', 'esbuild'];
69
+ // NOTE: @babel/* is NOT externalized because some runtime deps (e.g., puppeteer → cosmiconfig → parse-json)
70
+ // require @babel/code-frame at runtime. Babel packages are pure JS and bundle fine.
71
+ const buildToolExternals = ['lightningcss', '@vitejs/*', 'vite', 'esbuild'];
70
72
 
71
73
  // Load custom externals and define from agentuity.config.ts if it exists
72
74
  const customExternals: string[] = [];
@@ -194,10 +196,7 @@ export async function installExternalsAndBuild(options: ServerBundleOptions): Pr
194
196
  // Use npm with --force for cross-platform installs since Bun's --target flag
195
197
  // doesn't correctly handle optional dependencies for other platforms
196
198
  const allPackagesToInstall = [...externalInstalls, ...platformOptionalDeps];
197
- logger.debug(
198
- 'Installing with npm (cross-platform): %s',
199
- allPackagesToInstall.join(', ')
200
- );
199
+ logger.debug('Installing with npm (cross-platform): %s', allPackagesToInstall.join(', '));
201
200
 
202
201
  const proc = Bun.spawn(
203
202
  [
@@ -70,9 +70,12 @@ export async function generateAssetServerConfig(
70
70
  dedupe: ['react', 'react-dom', 'react/jsx-runtime', 'react/jsx-dev-runtime'],
71
71
  },
72
72
 
73
- // Pre-bundle @agentuity/workbench to avoid React preamble issues with pre-built JSX
73
+ // Pre-bundle dependencies to avoid React preamble issues with pre-built JSX
74
+ // Only include @agentuity/workbench if workbench is enabled
74
75
  optimizeDeps: {
75
- include: ['@agentuity/workbench', '@agentuity/core', '@agentuity/react'],
76
+ include: workbenchPath
77
+ ? ['@agentuity/workbench', '@agentuity/core', '@agentuity/react']
78
+ : ['@agentuity/core', '@agentuity/react'],
76
79
  },
77
80
 
78
81
  // Only allow frontend env vars (server uses process.env)
@@ -0,0 +1,214 @@
1
+ import { createCommand } from '../../types';
2
+ import { getPlatformInfo } from '../upgrade';
3
+ import { downloadWithProgress } from '../../download';
4
+ import { z } from 'zod';
5
+ import { $ } from 'bun';
6
+ import { join } from 'node:path';
7
+ import { homedir } from 'node:os';
8
+ import { readdir, rm, mkdir, stat } from 'node:fs/promises';
9
+ import { createHash } from 'node:crypto';
10
+ import * as tui from '../../tui';
11
+
12
+ const CANARY_CACHE_DIR = join(homedir(), '.agentuity', 'canary');
13
+ const CANARY_BASE_URL = 'https://agentuity-sdk-objects.t3.storage.dev/binary';
14
+ const CACHE_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
15
+
16
+ const CanaryArgsSchema = z.object({
17
+ args: z
18
+ .array(z.string())
19
+ .describe('Version/URL followed by commands to run (e.g., 0.1.6-abc1234 deploy --force)'),
20
+ });
21
+
22
+ const CanaryResponseSchema = z.object({
23
+ executed: z.boolean().describe('Whether the canary was executed'),
24
+ version: z.string().describe('The canary version'),
25
+ message: z.string().describe('Status message'),
26
+ });
27
+
28
+ function isUrl(str: string): boolean {
29
+ return str.startsWith('http://') || str.startsWith('https://');
30
+ }
31
+
32
+ function getBinaryFilename(platform: { os: string; arch: string }): string {
33
+ return `agentuity-${platform.os}-${platform.arch}.gz`;
34
+ }
35
+
36
+ function getCachePath(version: string): string {
37
+ return join(CANARY_CACHE_DIR, version, 'agentuity');
38
+ }
39
+
40
+ function hashUrl(url: string): string {
41
+ return createHash('sha256').update(url).digest('hex').slice(0, 12);
42
+ }
43
+
44
+ async function cleanupOldCanaries(): Promise<void> {
45
+ try {
46
+ await mkdir(CANARY_CACHE_DIR, { recursive: true });
47
+ const entries = await readdir(CANARY_CACHE_DIR);
48
+ const now = Date.now();
49
+
50
+ for (const entry of entries) {
51
+ const entryPath = join(CANARY_CACHE_DIR, entry);
52
+ try {
53
+ const stats = await stat(entryPath);
54
+ if (now - stats.mtimeMs > CACHE_MAX_AGE_MS) {
55
+ await rm(entryPath, { recursive: true, force: true });
56
+ }
57
+ } catch {
58
+ // Ignore errors for individual entries
59
+ }
60
+ }
61
+ } catch {
62
+ // Ignore cleanup errors
63
+ }
64
+ }
65
+
66
+ async function downloadCanary(url: string, destPath: string): Promise<void> {
67
+ const destDir = join(destPath, '..');
68
+ await mkdir(destDir, { recursive: true });
69
+
70
+ const gzPath = `${destPath}.gz`;
71
+
72
+ const stream = await downloadWithProgress({
73
+ url,
74
+ message: 'Downloading canary...',
75
+ });
76
+
77
+ const writer = Bun.file(gzPath).writer();
78
+ for await (const chunk of stream) {
79
+ writer.write(chunk);
80
+ }
81
+ await writer.end();
82
+
83
+ if (!(await Bun.file(gzPath).exists())) {
84
+ throw new Error('Download failed - file not created');
85
+ }
86
+
87
+ try {
88
+ await $`gunzip ${gzPath}`.quiet();
89
+ } catch (error) {
90
+ if (await Bun.file(gzPath).exists()) {
91
+ await $`rm ${gzPath}`.quiet();
92
+ }
93
+ throw new Error(
94
+ `Decompression failed: ${error instanceof Error ? error.message : 'Unknown error'}`
95
+ );
96
+ }
97
+
98
+ if (!(await Bun.file(destPath).exists())) {
99
+ throw new Error('Decompression failed - file not found');
100
+ }
101
+
102
+ await $`chmod 755 ${destPath}`.quiet();
103
+ }
104
+
105
+ export const command = createCommand({
106
+ name: 'canary',
107
+ description: 'Run a canary version of the CLI',
108
+ hidden: true,
109
+ skipUpgradeCheck: true,
110
+ passThroughArgs: true,
111
+ schema: {
112
+ args: CanaryArgsSchema,
113
+ response: CanaryResponseSchema,
114
+ },
115
+
116
+ async handler(ctx) {
117
+ const { args } = ctx;
118
+
119
+ // Get raw args from process.argv to capture ALL args after 'canary <version>'
120
+ // This ensures we forward everything including flags like --json, --force, etc.
121
+ const argv = process.argv;
122
+ const canaryIndex = argv.indexOf('canary');
123
+
124
+ if (args.args.length === 0) {
125
+ tui.error('Usage: agentuity canary <version|url> [commands...]');
126
+ tui.newline();
127
+ tui.info('Examples:');
128
+ tui.info(' agentuity canary 0.1.6-abc1234');
129
+ tui.info(' agentuity canary 0.1.6-abc1234 deploy --log-level trace');
130
+ tui.info(
131
+ ' agentuity canary https://agentuity-sdk-objects.t3.storage.dev/binary/0.1.6-abc1234/agentuity-darwin-arm64.gz'
132
+ );
133
+ return {
134
+ executed: false,
135
+ version: '',
136
+ message: 'No target specified',
137
+ };
138
+ }
139
+
140
+ // Get target from parsed args, but get forward args from raw argv
141
+ // This captures ALL args after the version including any flags
142
+ const target = args.args[0];
143
+ const targetIndex = canaryIndex >= 0 ? argv.indexOf(target, canaryIndex) : -1;
144
+ const forwardArgs = targetIndex >= 0 ? argv.slice(targetIndex + 1) : args.args.slice(1);
145
+
146
+ // Clean up old canaries in background
147
+ cleanupOldCanaries().catch(() => {});
148
+
149
+ const platform = getPlatformInfo();
150
+ let version: string;
151
+ let downloadUrl: string;
152
+ let cachePath: string;
153
+
154
+ if (isUrl(target)) {
155
+ // Extract version from URL, or create a unique hash for custom URLs
156
+ const match = target.match(/\/binary\/([^/]+)\//);
157
+ version = match ? match[1] : `custom-${hashUrl(target)}`;
158
+ downloadUrl = target;
159
+ cachePath = getCachePath(version);
160
+ } else {
161
+ // Treat as version string
162
+ version = target;
163
+ const filename = getBinaryFilename(platform);
164
+ downloadUrl = `${CANARY_BASE_URL}/${version}/${filename}`;
165
+ cachePath = getCachePath(version);
166
+ }
167
+
168
+ // Check cache
169
+ if (await Bun.file(cachePath).exists()) {
170
+ tui.info(`Using cached canary ${version}`);
171
+ } else {
172
+ tui.info(`Downloading canary ${version}...`);
173
+ try {
174
+ await downloadCanary(downloadUrl, cachePath);
175
+ tui.success(`Downloaded canary ${version}`);
176
+ } catch (error) {
177
+ tui.error(
178
+ `Failed to download canary: ${error instanceof Error ? error.message : 'Unknown error'}`
179
+ );
180
+ return {
181
+ executed: false,
182
+ version,
183
+ message: `Failed to download: ${error instanceof Error ? error.message : 'Unknown error'}`,
184
+ };
185
+ }
186
+ }
187
+
188
+ // Update access time
189
+ try {
190
+ await $`touch -a -m ${cachePath}`.quiet();
191
+ } catch {
192
+ // Ignore touch errors
193
+ }
194
+
195
+ tui.newline();
196
+ tui.info(`Running canary ${version}...`);
197
+ tui.newline();
198
+
199
+ // Execute the canary binary with forwarded args
200
+ // Skip version check in the canary binary to avoid upgrade prompts
201
+ const proc = Bun.spawn([cachePath, ...forwardArgs], {
202
+ stdin: 'inherit',
203
+ stdout: 'inherit',
204
+ stderr: 'inherit',
205
+ env: {
206
+ ...process.env,
207
+ AGENTUITY_SKIP_VERSION_CHECK: '1',
208
+ },
209
+ });
210
+
211
+ const exitCode = await proc.exited;
212
+ process.exit(exitCode);
213
+ },
214
+ });
@@ -14,7 +14,7 @@ import {
14
14
  loadProjectSDKKey,
15
15
  updateProjectConfig,
16
16
  } from '../../config';
17
- import { getProjectGithubStatus } from '../integration/api';
17
+ import { getProjectGithubStatus } from '../git/api';
18
18
  import { runGitLink } from '../git/link';
19
19
  import {
20
20
  runSteps,
@@ -15,7 +15,7 @@ import {
15
15
  getGithubIntegrationStatus,
16
16
  getExistingGithubIntegrations,
17
17
  copyGithubIntegration,
18
- } from '../../integration/api';
18
+ } from '../api';
19
19
 
20
20
  export interface RunGitAccountConnectOptions {
21
21
  apiClient: APIClient;
@@ -3,7 +3,7 @@ import * as tui from '../../../tui';
3
3
  import { getCommand } from '../../../command-prefix';
4
4
  import { ErrorCode } from '../../../errors';
5
5
  import { listOrganizations } from '@agentuity/server';
6
- import { getGithubIntegrationStatus } from '../../integration/api';
6
+ import { getGithubIntegrationStatus } from '../api';
7
7
  import { z } from 'zod';
8
8
 
9
9
  const ListResponseSchema = z.array(
@@ -9,7 +9,7 @@ import {
9
9
  getGithubIntegrationStatus,
10
10
  disconnectGithubIntegration,
11
11
  type GithubIntegration,
12
- } from '../../integration/api';
12
+ } from '../api';
13
13
 
14
14
  const RemoveOptionsSchema = z.object({
15
15
  org: z.string().optional().describe('Organization ID'),
@@ -10,7 +10,7 @@ import {
10
10
  linkProjectToRepo,
11
11
  getProjectGithubStatus,
12
12
  type GithubRepo,
13
- } from '../integration/api';
13
+ } from './api';
14
14
  import type { APIClient } from '../../api';
15
15
  import type { Logger } from '@agentuity/core';
16
16
  import { runGitAccountConnect } from './account/add';
@@ -3,7 +3,7 @@ import * as tui from '../../tui';
3
3
  import { getCommand } from '../../command-prefix';
4
4
  import enquirer from 'enquirer';
5
5
  import { z } from 'zod';
6
- import { getGithubIntegrationStatus, listGithubRepos } from '../integration/api';
6
+ import { getGithubIntegrationStatus, listGithubRepos } from './api';
7
7
  import { ErrorCode } from '../../errors';
8
8
  import { listOrganizations } from '@agentuity/server';
9
9