@agentuity/cli 0.0.104 → 0.0.106

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 (214) hide show
  1. package/bin/cli.ts +6 -3
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +93 -21
  4. package/dist/cli.js.map +1 -1
  5. package/dist/cmd/ai/prompt/version.d.ts +1 -0
  6. package/dist/cmd/ai/prompt/version.d.ts.map +1 -1
  7. package/dist/cmd/ai/prompt/version.js +3 -2
  8. package/dist/cmd/ai/prompt/version.js.map +1 -1
  9. package/dist/cmd/build/ast.d.ts.map +1 -1
  10. package/dist/cmd/build/ast.js +179 -37
  11. package/dist/cmd/build/ast.js.map +1 -1
  12. package/dist/cmd/build/entry-generator.d.ts.map +1 -1
  13. package/dist/cmd/build/entry-generator.js +24 -14
  14. package/dist/cmd/build/entry-generator.js.map +1 -1
  15. package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
  16. package/dist/cmd/build/vite/registry-generator.js +8 -9
  17. package/dist/cmd/build/vite/registry-generator.js.map +1 -1
  18. package/dist/cmd/build/vite/vite-asset-server.d.ts.map +1 -1
  19. package/dist/cmd/build/vite/vite-asset-server.js.map +1 -1
  20. package/dist/cmd/cloud/db/create.d.ts.map +1 -1
  21. package/dist/cmd/cloud/db/create.js +11 -2
  22. package/dist/cmd/cloud/db/create.js.map +1 -1
  23. package/dist/cmd/cloud/db/delete.d.ts.map +1 -1
  24. package/dist/cmd/cloud/db/delete.js +13 -2
  25. package/dist/cmd/cloud/db/delete.js.map +1 -1
  26. package/dist/cmd/cloud/deploy.js +3 -3
  27. package/dist/cmd/cloud/deploy.js.map +1 -1
  28. package/dist/cmd/cloud/env/delete.js +1 -1
  29. package/dist/cmd/cloud/env/delete.js.map +1 -1
  30. package/dist/cmd/cloud/env/import.js +4 -4
  31. package/dist/cmd/cloud/env/import.js.map +1 -1
  32. package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
  33. package/dist/cmd/cloud/env/pull.js +7 -9
  34. package/dist/cmd/cloud/env/pull.js.map +1 -1
  35. package/dist/cmd/cloud/env/push.js +2 -2
  36. package/dist/cmd/cloud/env/push.js.map +1 -1
  37. package/dist/cmd/cloud/env/set.js +3 -3
  38. package/dist/cmd/cloud/env/set.js.map +1 -1
  39. package/dist/cmd/cloud/index.d.ts.map +1 -1
  40. package/dist/cmd/cloud/index.js +2 -0
  41. package/dist/cmd/cloud/index.js.map +1 -1
  42. package/dist/cmd/cloud/sandbox/cp.d.ts +3 -0
  43. package/dist/cmd/cloud/sandbox/cp.d.ts.map +1 -0
  44. package/dist/cmd/cloud/sandbox/cp.js +334 -0
  45. package/dist/cmd/cloud/sandbox/cp.js.map +1 -0
  46. package/dist/cmd/cloud/sandbox/create.d.ts +3 -0
  47. package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -0
  48. package/dist/cmd/cloud/sandbox/create.js +105 -0
  49. package/dist/cmd/cloud/sandbox/create.js.map +1 -0
  50. package/dist/cmd/cloud/sandbox/delete.d.ts +3 -0
  51. package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -0
  52. package/dist/cmd/cloud/sandbox/delete.js +72 -0
  53. package/dist/cmd/cloud/sandbox/delete.js.map +1 -0
  54. package/dist/cmd/cloud/sandbox/exec.d.ts +3 -0
  55. package/dist/cmd/cloud/sandbox/exec.d.ts.map +1 -0
  56. package/dist/cmd/cloud/sandbox/exec.js +211 -0
  57. package/dist/cmd/cloud/sandbox/exec.js.map +1 -0
  58. package/dist/cmd/cloud/sandbox/execution/get.d.ts +3 -0
  59. package/dist/cmd/cloud/sandbox/execution/get.d.ts.map +1 -0
  60. package/dist/cmd/cloud/sandbox/execution/get.js +96 -0
  61. package/dist/cmd/cloud/sandbox/execution/get.js.map +1 -0
  62. package/dist/cmd/cloud/sandbox/execution/index.d.ts +3 -0
  63. package/dist/cmd/cloud/sandbox/execution/index.d.ts.map +1 -0
  64. package/dist/cmd/cloud/sandbox/execution/index.js +24 -0
  65. package/dist/cmd/cloud/sandbox/execution/index.js.map +1 -0
  66. package/dist/cmd/cloud/sandbox/execution/list.d.ts +3 -0
  67. package/dist/cmd/cloud/sandbox/execution/list.d.ts.map +1 -0
  68. package/dist/cmd/cloud/sandbox/execution/list.js +100 -0
  69. package/dist/cmd/cloud/sandbox/execution/list.js.map +1 -0
  70. package/dist/cmd/cloud/sandbox/get.d.ts +3 -0
  71. package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -0
  72. package/dist/cmd/cloud/sandbox/get.js +95 -0
  73. package/dist/cmd/cloud/sandbox/get.js.map +1 -0
  74. package/dist/cmd/cloud/sandbox/index.d.ts +3 -0
  75. package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -0
  76. package/dist/cmd/cloud/sandbox/index.js +45 -0
  77. package/dist/cmd/cloud/sandbox/index.js.map +1 -0
  78. package/dist/cmd/cloud/sandbox/list.d.ts +3 -0
  79. package/dist/cmd/cloud/sandbox/list.d.ts.map +1 -0
  80. package/dist/cmd/cloud/sandbox/list.js +120 -0
  81. package/dist/cmd/cloud/sandbox/list.js.map +1 -0
  82. package/dist/cmd/cloud/sandbox/run.d.ts +3 -0
  83. package/dist/cmd/cloud/sandbox/run.d.ts.map +1 -0
  84. package/dist/cmd/cloud/sandbox/run.js +152 -0
  85. package/dist/cmd/cloud/sandbox/run.js.map +1 -0
  86. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts +3 -0
  87. package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -0
  88. package/dist/cmd/cloud/sandbox/snapshot/create.js +65 -0
  89. package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -0
  90. package/dist/cmd/cloud/sandbox/snapshot/delete.d.ts +3 -0
  91. package/dist/cmd/cloud/sandbox/snapshot/delete.d.ts.map +1 -0
  92. package/dist/cmd/cloud/sandbox/snapshot/delete.js +66 -0
  93. package/dist/cmd/cloud/sandbox/snapshot/delete.js.map +1 -0
  94. package/dist/cmd/cloud/sandbox/snapshot/get.d.ts +3 -0
  95. package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -0
  96. package/dist/cmd/cloud/sandbox/snapshot/get.js +154 -0
  97. package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -0
  98. package/dist/cmd/cloud/sandbox/snapshot/index.d.ts +3 -0
  99. package/dist/cmd/cloud/sandbox/snapshot/index.d.ts.map +1 -0
  100. package/dist/cmd/cloud/sandbox/snapshot/index.js +27 -0
  101. package/dist/cmd/cloud/sandbox/snapshot/index.js.map +1 -0
  102. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts +3 -0
  103. package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -0
  104. package/dist/cmd/cloud/sandbox/snapshot/list.js +83 -0
  105. package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -0
  106. package/dist/cmd/cloud/sandbox/snapshot/tag.d.ts +3 -0
  107. package/dist/cmd/cloud/sandbox/snapshot/tag.d.ts.map +1 -0
  108. package/dist/cmd/cloud/sandbox/snapshot/tag.js +63 -0
  109. package/dist/cmd/cloud/sandbox/snapshot/tag.js.map +1 -0
  110. package/dist/cmd/cloud/sandbox/util.d.ts +15 -0
  111. package/dist/cmd/cloud/sandbox/util.d.ts.map +1 -0
  112. package/dist/cmd/cloud/sandbox/util.js +50 -0
  113. package/dist/cmd/cloud/sandbox/util.js.map +1 -0
  114. package/dist/cmd/cloud/secret/delete.d.ts.map +1 -1
  115. package/dist/cmd/cloud/secret/delete.js +3 -3
  116. package/dist/cmd/cloud/secret/delete.js.map +1 -1
  117. package/dist/cmd/cloud/secret/import.js +6 -6
  118. package/dist/cmd/cloud/secret/import.js.map +1 -1
  119. package/dist/cmd/cloud/secret/index.d.ts.map +1 -1
  120. package/dist/cmd/cloud/secret/index.js +1 -0
  121. package/dist/cmd/cloud/secret/index.js.map +1 -1
  122. package/dist/cmd/cloud/secret/pull.d.ts.map +1 -1
  123. package/dist/cmd/cloud/secret/pull.js +7 -9
  124. package/dist/cmd/cloud/secret/pull.js.map +1 -1
  125. package/dist/cmd/cloud/secret/push.js +3 -3
  126. package/dist/cmd/cloud/secret/push.js.map +1 -1
  127. package/dist/cmd/cloud/secret/set.d.ts.map +1 -1
  128. package/dist/cmd/cloud/secret/set.js +3 -3
  129. package/dist/cmd/cloud/secret/set.js.map +1 -1
  130. package/dist/cmd/cloud/storage/create.d.ts.map +1 -1
  131. package/dist/cmd/cloud/storage/create.js +13 -2
  132. package/dist/cmd/cloud/storage/create.js.map +1 -1
  133. package/dist/cmd/cloud/storage/delete.d.ts.map +1 -1
  134. package/dist/cmd/cloud/storage/delete.js +13 -2
  135. package/dist/cmd/cloud/storage/delete.js.map +1 -1
  136. package/dist/cmd/cloud/stream/list.d.ts.map +1 -1
  137. package/dist/cmd/cloud/stream/list.js +2 -13
  138. package/dist/cmd/cloud/stream/list.js.map +1 -1
  139. package/dist/cmd/dev/index.d.ts.map +1 -1
  140. package/dist/cmd/dev/index.js +14 -1
  141. package/dist/cmd/dev/index.js.map +1 -1
  142. package/dist/cmd/profile/create.d.ts.map +1 -1
  143. package/dist/cmd/profile/create.js +1 -0
  144. package/dist/cmd/profile/create.js.map +1 -1
  145. package/dist/cmd/project/template-flow.d.ts.map +1 -1
  146. package/dist/cmd/project/template-flow.js +27 -10
  147. package/dist/cmd/project/template-flow.js.map +1 -1
  148. package/dist/config.d.ts +0 -2
  149. package/dist/config.d.ts.map +1 -1
  150. package/dist/config.js +3 -0
  151. package/dist/config.js.map +1 -1
  152. package/dist/env-util.d.ts +16 -8
  153. package/dist/env-util.d.ts.map +1 -1
  154. package/dist/env-util.js +46 -18
  155. package/dist/env-util.js.map +1 -1
  156. package/dist/tui.d.ts +20 -3
  157. package/dist/tui.d.ts.map +1 -1
  158. package/dist/tui.js +82 -23
  159. package/dist/tui.js.map +1 -1
  160. package/dist/types.d.ts +18 -4
  161. package/dist/types.d.ts.map +1 -1
  162. package/dist/types.js +1 -0
  163. package/dist/types.js.map +1 -1
  164. package/package.json +4 -4
  165. package/src/cli.ts +99 -21
  166. package/src/cmd/ai/prompt/api.md +26 -21
  167. package/src/cmd/ai/prompt/version.ts +3 -2
  168. package/src/cmd/build/ast.ts +214 -37
  169. package/src/cmd/build/entry-generator.ts +24 -14
  170. package/src/cmd/build/vite/registry-generator.ts +8 -11
  171. package/src/cmd/build/vite/vite-asset-server.ts +3 -1
  172. package/src/cmd/cloud/db/create.ts +13 -2
  173. package/src/cmd/cloud/db/delete.ts +15 -2
  174. package/src/cmd/cloud/deploy.ts +3 -3
  175. package/src/cmd/cloud/env/delete.ts +1 -1
  176. package/src/cmd/cloud/env/import.ts +4 -4
  177. package/src/cmd/cloud/env/pull.ts +7 -16
  178. package/src/cmd/cloud/env/push.ts +2 -2
  179. package/src/cmd/cloud/env/set.ts +3 -3
  180. package/src/cmd/cloud/index.ts +2 -0
  181. package/src/cmd/cloud/sandbox/cp.ts +531 -0
  182. package/src/cmd/cloud/sandbox/create.ts +114 -0
  183. package/src/cmd/cloud/sandbox/delete.ts +80 -0
  184. package/src/cmd/cloud/sandbox/exec.ts +254 -0
  185. package/src/cmd/cloud/sandbox/execution/get.ts +106 -0
  186. package/src/cmd/cloud/sandbox/execution/index.ts +25 -0
  187. package/src/cmd/cloud/sandbox/execution/list.ts +111 -0
  188. package/src/cmd/cloud/sandbox/get.ts +104 -0
  189. package/src/cmd/cloud/sandbox/index.ts +46 -0
  190. package/src/cmd/cloud/sandbox/list.ts +129 -0
  191. package/src/cmd/cloud/sandbox/run.ts +170 -0
  192. package/src/cmd/cloud/sandbox/snapshot/create.ts +71 -0
  193. package/src/cmd/cloud/sandbox/snapshot/delete.ts +74 -0
  194. package/src/cmd/cloud/sandbox/snapshot/get.ts +188 -0
  195. package/src/cmd/cloud/sandbox/snapshot/index.ts +28 -0
  196. package/src/cmd/cloud/sandbox/snapshot/list.ts +90 -0
  197. package/src/cmd/cloud/sandbox/snapshot/tag.ts +70 -0
  198. package/src/cmd/cloud/sandbox/util.ts +59 -0
  199. package/src/cmd/cloud/secret/delete.ts +8 -3
  200. package/src/cmd/cloud/secret/import.ts +6 -6
  201. package/src/cmd/cloud/secret/index.ts +1 -0
  202. package/src/cmd/cloud/secret/pull.ts +7 -16
  203. package/src/cmd/cloud/secret/push.ts +3 -3
  204. package/src/cmd/cloud/secret/set.ts +8 -3
  205. package/src/cmd/cloud/storage/create.ts +15 -2
  206. package/src/cmd/cloud/storage/delete.ts +15 -2
  207. package/src/cmd/cloud/stream/list.ts +2 -9
  208. package/src/cmd/dev/index.ts +18 -1
  209. package/src/cmd/profile/create.ts +1 -0
  210. package/src/cmd/project/template-flow.ts +29 -13
  211. package/src/config.ts +3 -0
  212. package/src/env-util.ts +52 -21
  213. package/src/tui.ts +131 -39
  214. package/src/types.ts +18 -16
package/src/cli.ts CHANGED
@@ -141,10 +141,17 @@ async function executeOrValidate(
141
141
  /**
142
142
  * Format a user-friendly message for a validation issue
143
143
  */
144
- function formatValidationIssueMessage(field: string, message: string): string {
144
+ function formatValidationIssueMessage(
145
+ field: string,
146
+ message: string,
147
+ isArg: boolean = false
148
+ ): string {
145
149
  // Detect "expected X, received undefined" pattern (missing required value)
146
150
  if (message.includes('received undefined')) {
147
151
  if (field && field !== 'unknown') {
152
+ if (isArg) {
153
+ return `Missing required argument: <${field}>`;
154
+ }
148
155
  return `Missing required option: --${field}`;
149
156
  }
150
157
  return 'Missing required value';
@@ -155,6 +162,9 @@ function formatValidationIssueMessage(field: string, message: string): string {
155
162
  if (typeMatch) {
156
163
  const [, expected, received] = typeMatch;
157
164
  if (field && field !== 'unknown') {
165
+ if (isArg) {
166
+ return `Invalid value for <${field}>: expected ${expected}, got ${received}`;
167
+ }
158
168
  return `Invalid value for --${field}: expected ${expected}, got ${received}`;
159
169
  }
160
170
  return `Invalid value: expected ${expected}, got ${received}`;
@@ -162,25 +172,69 @@ function formatValidationIssueMessage(field: string, message: string): string {
162
172
 
163
173
  // Default: include the field name if we have it
164
174
  if (field && field !== 'unknown') {
175
+ if (isArg) {
176
+ return `<${field}>: ${message}`;
177
+ }
165
178
  return `--${field}: ${message}`;
166
179
  }
167
180
  return message;
168
181
  }
169
182
 
183
+ /**
184
+ * Custom error class to wrap ZodErrors with context about whether they are for args or options
185
+ */
186
+ class SchemaValidationError extends Error {
187
+ constructor(
188
+ public readonly originalError: unknown,
189
+ public readonly isArg: boolean
190
+ ) {
191
+ super('Schema validation error');
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Parse args schema and wrap any ZodError with context
197
+ */
198
+ function parseArgs<T>(schema: { parse: (input: unknown) => T }, input: unknown): T {
199
+ try {
200
+ return schema.parse(input);
201
+ } catch (error) {
202
+ if (error && typeof error === 'object' && 'issues' in error) {
203
+ throw new SchemaValidationError(error, true);
204
+ }
205
+ throw error;
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Parse options schema (no wrapping needed, isArg defaults to false)
211
+ */
212
+ function parseOptions<T>(schema: { parse: (input: unknown) => T }, input: unknown): T {
213
+ return schema.parse(input);
214
+ }
215
+
170
216
  function handleValidationError(
171
217
  error: unknown,
172
218
  commandName: string,
173
219
  baseCtx: { options: GlobalOptions; logger: Logger }
174
220
  ): never {
175
- if (error && typeof error === 'object' && 'issues' in error) {
176
- const issues = (error as { issues: Array<{ path: string[]; message: string }> }).issues;
221
+ // Unwrap SchemaValidationError to get context about whether it's an arg or option
222
+ let actualError = error;
223
+ let isArg = false;
224
+ if (error instanceof SchemaValidationError) {
225
+ actualError = error.originalError;
226
+ isArg = error.isArg;
227
+ }
228
+
229
+ if (actualError && typeof actualError === 'object' && 'issues' in actualError) {
230
+ const issues = (actualError as { issues: Array<{ path: string[]; message: string }> }).issues;
177
231
 
178
232
  const formattedIssues = issues.map((issue) => {
179
233
  const field = issue.path?.length ? issue.path.join('.') : 'unknown';
180
234
  return {
181
235
  field,
182
236
  message: issue.message,
183
- formatted: formatValidationIssueMessage(field, issue.message),
237
+ formatted: formatValidationIssueMessage(field, issue.message, isArg),
184
238
  };
185
239
  });
186
240
 
@@ -227,6 +281,22 @@ type Normalized = {
227
281
  optionalRegion: boolean;
228
282
  };
229
283
 
284
+ /**
285
+ * Get the full command path for a command (e.g., "cloud sandbox snapshot delete")
286
+ * Uses Commander's _getCommandAndAncestors to traverse the command hierarchy.
287
+ */
288
+ function getFullCommandPath(cmd: Command): string {
289
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
290
+ const ancestors = (cmd as any)._getCommandAndAncestors() as Command[];
291
+ // ancestors is [current, parent, grandparent, ...root] - reverse and skip root program name
292
+ const names = ancestors.map((c) => c.name()).reverse();
293
+ // Skip the first entry if it's the root program (usually empty or 'agentuity')
294
+ if (names.length > 1 && (names[0] === '' || names[0] === 'agentuity')) {
295
+ return names.slice(1).join(' ');
296
+ }
297
+ return names.join(' ');
298
+ }
299
+
230
300
  function normalizeReqs(def: CommandDefinition | SubcommandDefinition): Normalized {
231
301
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
232
302
  const d: any = def as any;
@@ -802,6 +872,7 @@ async function registerSubcommand(
802
872
 
803
873
  if (subcommand.schema?.options) {
804
874
  const parsed = parseOptionsSchema(subcommand.schema.options);
875
+ const aliases = subcommand.schema.aliases ?? {};
805
876
  for (const opt of parsed) {
806
877
  const flag = opt.name
807
878
  .replace(/([a-z0-9])([A-Z])/g, '$1-$2')
@@ -814,8 +885,15 @@ async function registerSubcommand(
814
885
  }
815
886
 
816
887
  const desc = opt.description || '';
817
- // Add short flag alias for verbose
818
- const flagSpec = flag === 'verbose' ? `-v, --${flag}` : `--${flag}`;
888
+ // Build flag spec with aliases (check both camelCase and kebab-case names)
889
+ const optAliases = aliases[opt.name] ?? aliases[flag] ?? [];
890
+ let flagSpec = `--${flag}`;
891
+ if (flag === 'verbose') {
892
+ flagSpec = `-v, --${flag}`;
893
+ } else if (optAliases.length > 0) {
894
+ const shortFlags = optAliases.map((a) => `-${a}`).join(', ');
895
+ flagSpec = `${shortFlags}, --${flag}`;
896
+ }
819
897
  if (opt.type === 'boolean') {
820
898
  if (opt.hasDefault) {
821
899
  const defaultValue =
@@ -1019,10 +1097,10 @@ async function registerSubcommand(
1019
1097
  ctx.projectDir = projectDir;
1020
1098
  }
1021
1099
  if (subcommand.schema.args) {
1022
- ctx.args = subcommand.schema.args.parse(input.args);
1100
+ ctx.args = parseArgs(subcommand.schema.args, input.args);
1023
1101
  }
1024
1102
  if (subcommand.schema.options) {
1025
- ctx.opts = subcommand.schema.options.parse(input.options);
1103
+ ctx.opts = parseOptions(subcommand.schema.options, input.options);
1026
1104
  }
1027
1105
  if (normalized.requiresAPIClient) {
1028
1106
  // Recreate apiClient with auth credentials
@@ -1059,14 +1137,14 @@ async function registerSubcommand(
1059
1137
  }
1060
1138
  await executeOrValidate(
1061
1139
  ctx as CommandContext,
1062
- `${parent.name()} ${subcommand.name}`,
1140
+ getFullCommandPath(cmd),
1063
1141
  subcommand.handler,
1064
1142
  !!subcommand.schema?.response,
1065
1143
  subcommand.webUrl
1066
1144
  );
1067
1145
  } catch (error) {
1068
1146
  if (error && typeof error === 'object' && 'issues' in error) {
1069
- handleValidationError(error, `${parent.name()} ${subcommand.name}`, baseCtx);
1147
+ handleValidationError(error, getFullCommandPath(cmd), baseCtx);
1070
1148
  }
1071
1149
  handleProjectConfigError(
1072
1150
  error,
@@ -1139,7 +1217,7 @@ async function registerSubcommand(
1139
1217
  exitWithError(
1140
1218
  createError(
1141
1219
  ErrorCode.INTERNAL_ERROR,
1142
- `Command '${parent.name()} ${subcommand.name}' declares a response schema but returned no data. This is a bug in the command implementation.`
1220
+ `Command '${getFullCommandPath(cmd)}' declares a response schema but returned no data. This is a bug in the command implementation.`
1143
1221
  ),
1144
1222
  baseCtx.logger,
1145
1223
  baseCtx.options.errorFormat
@@ -1192,10 +1270,10 @@ async function registerSubcommand(
1192
1270
  ctx.projectDir = projectDir;
1193
1271
  }
1194
1272
  if (subcommand.schema.args) {
1195
- ctx.args = subcommand.schema.args.parse(input.args);
1273
+ ctx.args = parseArgs(subcommand.schema.args, input.args);
1196
1274
  }
1197
1275
  if (subcommand.schema.options) {
1198
- ctx.opts = subcommand.schema.options.parse(input.options);
1276
+ ctx.opts = parseOptions(subcommand.schema.options, input.options);
1199
1277
  }
1200
1278
  if (normalized.requiresAPIClient) {
1201
1279
  // Recreate apiClient with auth credentials
@@ -1238,14 +1316,14 @@ async function registerSubcommand(
1238
1316
  }
1239
1317
  await executeOrValidate(
1240
1318
  ctx as CommandContext,
1241
- `${parent.name()} ${subcommand.name}`,
1319
+ getFullCommandPath(cmd),
1242
1320
  subcommand.handler,
1243
1321
  !!subcommand.schema?.response,
1244
1322
  subcommand.webUrl
1245
1323
  );
1246
1324
  } catch (error) {
1247
1325
  if (error && typeof error === 'object' && 'issues' in error) {
1248
- handleValidationError(error, `${parent.name()} ${subcommand.name}`, baseCtx);
1326
+ handleValidationError(error, getFullCommandPath(cmd), baseCtx);
1249
1327
  }
1250
1328
  handleProjectConfigError(
1251
1329
  error,
@@ -1314,7 +1392,7 @@ async function registerSubcommand(
1314
1392
  exitWithError(
1315
1393
  createError(
1316
1394
  ErrorCode.INTERNAL_ERROR,
1317
- `Command '${parent.name()} ${subcommand.name}' declares a response schema but returned no data. This is a bug in the command implementation.`
1395
+ `Command '${getFullCommandPath(cmd)}' declares a response schema but returned no data. This is a bug in the command implementation.`
1318
1396
  ),
1319
1397
  baseCtx.logger,
1320
1398
  baseCtx.options.errorFormat
@@ -1343,10 +1421,10 @@ async function registerSubcommand(
1343
1421
  ctx.projectDir = projectDir;
1344
1422
  }
1345
1423
  if (subcommand.schema.args) {
1346
- ctx.args = subcommand.schema.args.parse(input.args);
1424
+ ctx.args = parseArgs(subcommand.schema.args, input.args);
1347
1425
  }
1348
1426
  if (subcommand.schema.options) {
1349
- ctx.opts = subcommand.schema.options.parse(input.options);
1427
+ ctx.opts = parseOptions(subcommand.schema.options, input.options);
1350
1428
  }
1351
1429
  if (normalized.requiresAPIClient && !ctx.apiClient) {
1352
1430
  ctx.apiClient = createAPIClient(baseCtx, ctx.config as Config | null);
@@ -1363,14 +1441,14 @@ async function registerSubcommand(
1363
1441
  }
1364
1442
  await executeOrValidate(
1365
1443
  ctx as CommandContext,
1366
- `${parent.name()} ${subcommand.name}`,
1444
+ getFullCommandPath(cmd),
1367
1445
  subcommand.handler,
1368
1446
  !!subcommand.schema?.response,
1369
1447
  subcommand.webUrl
1370
1448
  );
1371
1449
  } catch (error) {
1372
1450
  if (error && typeof error === 'object' && 'issues' in error) {
1373
- handleValidationError(error, `${parent.name()} ${subcommand.name}`, baseCtx);
1451
+ handleValidationError(error, getFullCommandPath(cmd), baseCtx);
1374
1452
  }
1375
1453
  handleProjectConfigError(
1376
1454
  error,
@@ -1426,7 +1504,7 @@ async function registerSubcommand(
1426
1504
  exitWithError(
1427
1505
  createError(
1428
1506
  ErrorCode.INTERNAL_ERROR,
1429
- `Command '${parent.name()} ${subcommand.name}' declares a response schema but returned no data. This is a bug in the command implementation.`
1507
+ `Command '${getFullCommandPath(cmd)}' declares a response schema but returned no data. This is a bug in the command implementation.`
1430
1508
  ),
1431
1509
  baseCtx.logger,
1432
1510
  baseCtx.options.errorFormat
@@ -311,40 +311,45 @@ return c.json({ data: 'value' }, 200, {
311
311
  ## Streaming Routes
312
312
 
313
313
  ```typescript
314
- import { createRouter } from '@agentuity/runtime';
314
+ import { createRouter, stream, sse, websocket } from '@agentuity/runtime';
315
315
 
316
316
  const router = createRouter();
317
317
 
318
- // Stream response
319
- router.stream('/events', (c) => {
320
- return new ReadableStream({
321
- start(controller) {
322
- controller.enqueue('event 1\n');
323
- controller.enqueue('event 2\n');
324
- controller.close();
325
- },
326
- });
327
- });
318
+ // Stream response (use with POST)
319
+ router.post(
320
+ '/events',
321
+ stream((c) => {
322
+ return new ReadableStream({
323
+ start(controller) {
324
+ controller.enqueue('event 1\n');
325
+ controller.enqueue('event 2\n');
326
+ controller.close();
327
+ },
328
+ });
329
+ })
330
+ );
328
331
 
329
- // Server-Sent Events
330
- router.sse('/notifications', (c) => {
331
- return (stream) => {
332
+ // Server-Sent Events (use with GET)
333
+ router.get(
334
+ '/notifications',
335
+ sse((c, stream) => {
332
336
  stream.writeSSE({ data: 'Hello', event: 'message' });
333
337
  stream.writeSSE({ data: 'World', event: 'message' });
334
- };
335
- });
338
+ })
339
+ );
336
340
 
337
- // WebSocket
338
- router.websocket('/ws', (c) => {
339
- return (ws) => {
341
+ // WebSocket (use with GET)
342
+ router.get(
343
+ '/ws',
344
+ websocket((c, ws) => {
340
345
  ws.onOpen(() => {
341
346
  ws.send('Connected!');
342
347
  });
343
348
  ws.onMessage((event) => {
344
349
  ws.send(`Echo: ${event.data}`);
345
350
  });
346
- };
347
- });
351
+ })
352
+ );
348
353
 
349
354
  export default router;
350
355
  ```
@@ -8,7 +8,7 @@
8
8
  * This allows detecting if the source template has changed.
9
9
  */
10
10
 
11
- const HASH_REGEX = /\n?<!-- prompt_hash: ([a-f0-9]+) -->$/;
11
+ const HASH_REGEX = /\n?<!-- prompt_hash: ([a-f0-9]+) -->\n?$/;
12
12
 
13
13
  /**
14
14
  * Compute SHA256 hash of content using Bun's built-in hasher.
@@ -37,10 +37,11 @@ export function extractHash(content: string): string | null {
37
37
 
38
38
  /**
39
39
  * Generate content with hash comment appended.
40
+ * Ensures the output ends with a newline for POSIX compliance and Prettier compatibility.
40
41
  */
41
42
  export function appendHashComment(content: string): string {
42
43
  const hash = computeHash(content);
43
- return `${content}\n<!-- prompt_hash: ${hash} -->`;
44
+ return `${content}\n<!-- prompt_hash: ${hash} -->\n`;
44
45
  }
45
46
 
46
47
  /**
@@ -1300,12 +1300,168 @@ export async function parseRoute(
1300
1300
  const action = statement.expression.arguments[0];
1301
1301
  let suffix = '';
1302
1302
  let config: Record<string, unknown> | undefined;
1303
+ // Supported HTTP methods that can be represented in BuildMetadata
1304
+ const SUPPORTED_HTTP_METHODS = ['get', 'post', 'put', 'delete', 'patch'] as const;
1305
+ type SupportedHttpMethod = (typeof SUPPORTED_HTTP_METHODS)[number];
1306
+
1307
+ const isSupportedHttpMethod = (m: string): m is SupportedHttpMethod =>
1308
+ SUPPORTED_HTTP_METHODS.includes(m.toLowerCase() as SupportedHttpMethod);
1309
+
1303
1310
  switch (method) {
1304
1311
  case 'use':
1305
- case 'on':
1306
- case 'all':
1307
1312
  case 'route': {
1308
- // Skip Hono middleware/routing methods - they don't represent API routes
1313
+ // Skip Hono middleware and sub-router mounting - they don't represent API routes
1314
+ continue;
1315
+ }
1316
+ case 'on': {
1317
+ // router.on(method | method[], path, handler)
1318
+ // First arg is method(s), second arg is path
1319
+ const methodArg = statement.expression.arguments[0];
1320
+ const pathArg = statement.expression.arguments[1];
1321
+
1322
+ // Extract methods from first argument
1323
+ const methods: SupportedHttpMethod[] = [];
1324
+
1325
+ if (methodArg && (methodArg as ASTLiteral).type === 'Literal') {
1326
+ // Single method: router.on('GET', '/path', handler)
1327
+ const raw = String((methodArg as ASTLiteral).value || '').toLowerCase();
1328
+ if (isSupportedHttpMethod(raw)) {
1329
+ methods.push(raw as SupportedHttpMethod);
1330
+ }
1331
+ } else if (methodArg && (methodArg as ASTNode).type === 'ArrayExpression') {
1332
+ // Array of methods: router.on(['GET', 'POST'], '/path', handler)
1333
+ const arr = methodArg as { elements: ASTNode[] };
1334
+ for (const el of arr.elements) {
1335
+ if (!el || (el as ASTLiteral).type !== 'Literal') continue;
1336
+ const raw = String((el as ASTLiteral).value || '').toLowerCase();
1337
+ if (isSupportedHttpMethod(raw)) {
1338
+ methods.push(raw as SupportedHttpMethod);
1339
+ }
1340
+ }
1341
+ }
1342
+
1343
+ // Skip if no supported methods or path is not a literal
1344
+ if (
1345
+ methods.length === 0 ||
1346
+ !pathArg ||
1347
+ (pathArg as ASTLiteral).type !== 'Literal'
1348
+ ) {
1349
+ continue;
1350
+ }
1351
+
1352
+ const pathSuffix = String((pathArg as ASTLiteral).value);
1353
+
1354
+ // Create a route entry for each method
1355
+ for (const httpMethod of methods) {
1356
+ const thepath = `${routePrefix}/${routeName}/${pathSuffix}`
1357
+ .replaceAll(/\/{2,}/g, '/')
1358
+ .replaceAll(/\/$/g, '');
1359
+ const id = generateRouteId(
1360
+ projectId,
1361
+ deploymentId,
1362
+ 'api',
1363
+ httpMethod,
1364
+ rel,
1365
+ thepath,
1366
+ version
1367
+ );
1368
+
1369
+ // Check if this route uses validator middleware
1370
+ const validatorInfo = hasValidatorCall(statement.expression.arguments);
1371
+ const routeConfig: Record<string, unknown> = {};
1372
+ if (validatorInfo.hasValidator) {
1373
+ routeConfig.hasValidator = true;
1374
+ if (validatorInfo.agentVariable) {
1375
+ routeConfig.agentVariable = validatorInfo.agentVariable;
1376
+ const agentImportPath = importMap.get(validatorInfo.agentVariable);
1377
+ if (agentImportPath) {
1378
+ routeConfig.agentImportPath = agentImportPath;
1379
+ }
1380
+ }
1381
+ if (validatorInfo.inputSchemaVariable) {
1382
+ routeConfig.inputSchemaVariable =
1383
+ validatorInfo.inputSchemaVariable;
1384
+ }
1385
+ if (validatorInfo.outputSchemaVariable) {
1386
+ routeConfig.outputSchemaVariable =
1387
+ validatorInfo.outputSchemaVariable;
1388
+ }
1389
+ if (validatorInfo.stream !== undefined) {
1390
+ routeConfig.stream = validatorInfo.stream;
1391
+ }
1392
+ }
1393
+
1394
+ routes.push({
1395
+ id,
1396
+ method: httpMethod,
1397
+ type: 'api',
1398
+ filename: rel,
1399
+ path: thepath,
1400
+ version,
1401
+ config: Object.keys(routeConfig).length > 0 ? routeConfig : undefined,
1402
+ });
1403
+ }
1404
+ continue;
1405
+ }
1406
+ case 'all': {
1407
+ // router.all(path, handler) - matches all HTTP methods
1408
+ // First arg is path (same as get/post/etc.)
1409
+ if (!action || (action as ASTLiteral).type !== 'Literal') {
1410
+ continue;
1411
+ }
1412
+
1413
+ const pathSuffix = String((action as ASTLiteral).value);
1414
+
1415
+ // Create a route entry for each supported method
1416
+ for (const httpMethod of SUPPORTED_HTTP_METHODS) {
1417
+ const thepath = `${routePrefix}/${routeName}/${pathSuffix}`
1418
+ .replaceAll(/\/{2,}/g, '/')
1419
+ .replaceAll(/\/$/g, '');
1420
+ const id = generateRouteId(
1421
+ projectId,
1422
+ deploymentId,
1423
+ 'api',
1424
+ httpMethod,
1425
+ rel,
1426
+ thepath,
1427
+ version
1428
+ );
1429
+
1430
+ // Check if this route uses validator middleware
1431
+ const validatorInfo = hasValidatorCall(statement.expression.arguments);
1432
+ const routeConfig: Record<string, unknown> = {};
1433
+ if (validatorInfo.hasValidator) {
1434
+ routeConfig.hasValidator = true;
1435
+ if (validatorInfo.agentVariable) {
1436
+ routeConfig.agentVariable = validatorInfo.agentVariable;
1437
+ const agentImportPath = importMap.get(validatorInfo.agentVariable);
1438
+ if (agentImportPath) {
1439
+ routeConfig.agentImportPath = agentImportPath;
1440
+ }
1441
+ }
1442
+ if (validatorInfo.inputSchemaVariable) {
1443
+ routeConfig.inputSchemaVariable =
1444
+ validatorInfo.inputSchemaVariable;
1445
+ }
1446
+ if (validatorInfo.outputSchemaVariable) {
1447
+ routeConfig.outputSchemaVariable =
1448
+ validatorInfo.outputSchemaVariable;
1449
+ }
1450
+ if (validatorInfo.stream !== undefined) {
1451
+ routeConfig.stream = validatorInfo.stream;
1452
+ }
1453
+ }
1454
+
1455
+ routes.push({
1456
+ id,
1457
+ method: httpMethod,
1458
+ type: 'api',
1459
+ filename: rel,
1460
+ path: thepath,
1461
+ version,
1462
+ config: Object.keys(routeConfig).length > 0 ? routeConfig : undefined,
1463
+ });
1464
+ }
1309
1465
  continue;
1310
1466
  }
1311
1467
  case 'get':
@@ -1315,6 +1471,52 @@ export async function parseRoute(
1315
1471
  case 'delete': {
1316
1472
  if (action && (action as ASTLiteral).type === 'Literal') {
1317
1473
  suffix = String((action as ASTLiteral).value);
1474
+
1475
+ // Check if any argument is a middleware function call (websocket, sse, stream, cron)
1476
+ // New pattern: router.get('/ws', websocket((c, ws) => { ... }))
1477
+ for (const arg of statement.expression.arguments) {
1478
+ if ((arg as ASTCallExpression).type === 'CallExpression') {
1479
+ const callExpr = arg as ASTCallExpression;
1480
+ // Only handle simple Identifier callees (e.g., websocket(), sse())
1481
+ // Skip MemberExpression callees (e.g., obj.method())
1482
+ if (callExpr.callee.type !== 'Identifier') {
1483
+ continue;
1484
+ }
1485
+ const calleeName = (callExpr.callee as ASTNodeIdentifier).name;
1486
+ if (
1487
+ calleeName === 'websocket' ||
1488
+ calleeName === 'sse' ||
1489
+ calleeName === 'stream'
1490
+ ) {
1491
+ type = calleeName;
1492
+ break;
1493
+ }
1494
+ if (calleeName === 'cron') {
1495
+ type = 'cron';
1496
+ // First argument to cron() is the schedule expression
1497
+ if (callExpr.arguments && callExpr.arguments.length > 0) {
1498
+ const cronArg = callExpr.arguments[0] as ASTLiteral;
1499
+ if (cronArg.type === 'Literal') {
1500
+ const expression = String(cronArg.value);
1501
+ try {
1502
+ parseCronExpression(expression, {
1503
+ hasSeconds: false,
1504
+ });
1505
+ } catch (ex) {
1506
+ throw new InvalidRouterConfigError({
1507
+ filename,
1508
+ cause: ex,
1509
+ line: body.loc?.start?.line,
1510
+ message: `invalid cron expression "${expression}" in ${filename} at line ${body.loc?.start?.line}`,
1511
+ });
1512
+ }
1513
+ config = { expression };
1514
+ }
1515
+ }
1516
+ break;
1517
+ }
1518
+ }
1519
+ }
1318
1520
  } else {
1319
1521
  throw new InvalidRouterConfigError({
1320
1522
  filename,
@@ -1327,6 +1529,8 @@ export async function parseRoute(
1327
1529
  case 'stream':
1328
1530
  case 'sse':
1329
1531
  case 'websocket': {
1532
+ // DEPRECATED: router.stream(), router.sse(), router.websocket()
1533
+ // These methods now throw errors at runtime
1330
1534
  type = method;
1331
1535
  method = 'post';
1332
1536
  const theaction = action as ASTLiteral;
@@ -1336,39 +1540,9 @@ export async function parseRoute(
1336
1540
  }
1337
1541
  break;
1338
1542
  }
1339
- case 'sms': {
1340
- type = method;
1341
- method = 'post';
1342
- const theaction = action as ASTObjectExpression;
1343
- if (theaction.type === 'ObjectExpression') {
1344
- config = {};
1345
- theaction.properties.forEach((p) => {
1346
- if (p.value.type === 'Literal') {
1347
- const literal = p.value as ASTLiteral;
1348
- config![p.key.name] = literal.value;
1349
- }
1350
- });
1351
- const number = theaction.properties.find((p) => p.key.name === 'number');
1352
- if (number && number.value.type === 'Literal') {
1353
- const phoneNumber = number.value as ASTLiteral;
1354
- suffix = hash(String(phoneNumber.value));
1355
- break;
1356
- }
1357
- }
1358
- break;
1359
- }
1360
- case 'email': {
1361
- type = method;
1362
- method = 'post';
1363
- const theaction = action as ASTLiteral;
1364
- if (theaction.type === 'Literal') {
1365
- const email = String(theaction.value);
1366
- suffix = hash(email);
1367
- break;
1368
- }
1369
- break;
1370
- }
1371
1543
  case 'cron': {
1544
+ // DEPRECATED: router.cron()
1545
+ // This method now throws errors at runtime
1372
1546
  type = method;
1373
1547
  method = 'post';
1374
1548
  const theaction = action as ASTLiteral;
@@ -1437,8 +1611,11 @@ export async function parseRoute(
1437
1611
  }
1438
1612
  }
1439
1613
 
1440
- // For WebSocket/SSE routes that don't use validator(), fall back to exported schemas
1441
- if (!routeConfig.hasValidator && (type === 'websocket' || type === 'sse')) {
1614
+ // For WebSocket/SSE/stream routes that don't use validator(), fall back to exported schemas
1615
+ if (
1616
+ !routeConfig.hasValidator &&
1617
+ (type === 'websocket' || type === 'sse' || type === 'stream')
1618
+ ) {
1442
1619
  if (!routeConfig.inputSchemaVariable && exportedInputSchemaName) {
1443
1620
  routeConfig.inputSchemaVariable = exportedInputSchemaName;
1444
1621
  }