@agentuity/cli 1.0.41 → 1.0.43

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 (99) hide show
  1. package/dist/cmd/build/ast.d.ts.map +1 -1
  2. package/dist/cmd/build/ast.js +3 -3
  3. package/dist/cmd/build/ast.js.map +1 -1
  4. package/dist/cmd/build/typecheck.d.ts.map +1 -1
  5. package/dist/cmd/build/typecheck.js +52 -1
  6. package/dist/cmd/build/typecheck.js.map +1 -1
  7. package/dist/cmd/build/vite/static-renderer.d.ts.map +1 -1
  8. package/dist/cmd/build/vite/static-renderer.js +22 -8
  9. package/dist/cmd/build/vite/static-renderer.js.map +1 -1
  10. package/dist/cmd/cloud/index.d.ts.map +1 -1
  11. package/dist/cmd/cloud/index.js +4 -0
  12. package/dist/cmd/cloud/index.js.map +1 -1
  13. package/dist/cmd/cloud/monitor.d.ts +3 -0
  14. package/dist/cmd/cloud/monitor.d.ts.map +1 -0
  15. package/dist/cmd/cloud/monitor.js +300 -0
  16. package/dist/cmd/cloud/monitor.js.map +1 -0
  17. package/dist/cmd/cloud/oidc/activity.d.ts +2 -0
  18. package/dist/cmd/cloud/oidc/activity.d.ts.map +1 -0
  19. package/dist/cmd/cloud/oidc/activity.js +57 -0
  20. package/dist/cmd/cloud/oidc/activity.js.map +1 -0
  21. package/dist/cmd/cloud/oidc/create.d.ts +2 -0
  22. package/dist/cmd/cloud/oidc/create.d.ts.map +1 -0
  23. package/dist/cmd/cloud/oidc/create.js +204 -0
  24. package/dist/cmd/cloud/oidc/create.js.map +1 -0
  25. package/dist/cmd/cloud/oidc/delete.d.ts +2 -0
  26. package/dist/cmd/cloud/oidc/delete.d.ts.map +1 -0
  27. package/dist/cmd/cloud/oidc/delete.js +59 -0
  28. package/dist/cmd/cloud/oidc/delete.js.map +1 -0
  29. package/dist/cmd/cloud/oidc/get.d.ts +2 -0
  30. package/dist/cmd/cloud/oidc/get.d.ts.map +1 -0
  31. package/dist/cmd/cloud/oidc/get.js +62 -0
  32. package/dist/cmd/cloud/oidc/get.js.map +1 -0
  33. package/dist/cmd/cloud/oidc/index.d.ts +3 -0
  34. package/dist/cmd/cloud/oidc/index.d.ts.map +1 -0
  35. package/dist/cmd/cloud/oidc/index.js +32 -0
  36. package/dist/cmd/cloud/oidc/index.js.map +1 -0
  37. package/dist/cmd/cloud/oidc/list.d.ts +2 -0
  38. package/dist/cmd/cloud/oidc/list.d.ts.map +1 -0
  39. package/dist/cmd/cloud/oidc/list.js +48 -0
  40. package/dist/cmd/cloud/oidc/list.js.map +1 -0
  41. package/dist/cmd/cloud/oidc/rotate-secret.d.ts +2 -0
  42. package/dist/cmd/cloud/oidc/rotate-secret.d.ts.map +1 -0
  43. package/dist/cmd/cloud/oidc/rotate-secret.js +66 -0
  44. package/dist/cmd/cloud/oidc/rotate-secret.js.map +1 -0
  45. package/dist/cmd/cloud/oidc/users.d.ts +2 -0
  46. package/dist/cmd/cloud/oidc/users.d.ts.map +1 -0
  47. package/dist/cmd/cloud/oidc/users.js +53 -0
  48. package/dist/cmd/cloud/oidc/users.js.map +1 -0
  49. package/dist/cmd/cloud/oidc/util.d.ts +10 -0
  50. package/dist/cmd/cloud/oidc/util.d.ts.map +1 -0
  51. package/dist/cmd/cloud/oidc/util.js +13 -0
  52. package/dist/cmd/cloud/oidc/util.js.map +1 -0
  53. package/dist/cmd/coder/hub-url.d.ts +1 -0
  54. package/dist/cmd/coder/hub-url.d.ts.map +1 -1
  55. package/dist/cmd/coder/hub-url.js +4 -1
  56. package/dist/cmd/coder/hub-url.js.map +1 -1
  57. package/dist/cmd/coder/start.d.ts.map +1 -1
  58. package/dist/cmd/coder/start.js +14 -8
  59. package/dist/cmd/coder/start.js.map +1 -1
  60. package/dist/cmd/coder/tui-init.d.ts +9 -0
  61. package/dist/cmd/coder/tui-init.d.ts.map +1 -0
  62. package/dist/cmd/coder/tui-init.js +56 -0
  63. package/dist/cmd/coder/tui-init.js.map +1 -0
  64. package/dist/config.d.ts.map +1 -1
  65. package/dist/config.js +14 -5
  66. package/dist/config.js.map +1 -1
  67. package/dist/utils/jsonc.d.ts +13 -0
  68. package/dist/utils/jsonc.d.ts.map +1 -0
  69. package/dist/utils/jsonc.js +63 -0
  70. package/dist/utils/jsonc.js.map +1 -0
  71. package/dist/utils/route-migration.d.ts +2 -1
  72. package/dist/utils/route-migration.d.ts.map +1 -1
  73. package/dist/utils/route-migration.js +23 -32
  74. package/dist/utils/route-migration.js.map +1 -1
  75. package/dist/utils/zip.d.ts.map +1 -1
  76. package/dist/utils/zip.js +18 -2
  77. package/dist/utils/zip.js.map +1 -1
  78. package/package.json +6 -7
  79. package/src/cmd/build/ast.ts +6 -3
  80. package/src/cmd/build/typecheck.ts +60 -1
  81. package/src/cmd/build/vite/static-renderer.ts +24 -8
  82. package/src/cmd/cloud/index.ts +4 -0
  83. package/src/cmd/cloud/monitor.ts +375 -0
  84. package/src/cmd/cloud/oidc/activity.ts +64 -0
  85. package/src/cmd/cloud/oidc/create.ts +230 -0
  86. package/src/cmd/cloud/oidc/delete.ts +66 -0
  87. package/src/cmd/cloud/oidc/get.ts +68 -0
  88. package/src/cmd/cloud/oidc/index.ts +35 -0
  89. package/src/cmd/cloud/oidc/list.ts +53 -0
  90. package/src/cmd/cloud/oidc/rotate-secret.ts +80 -0
  91. package/src/cmd/cloud/oidc/users.ts +60 -0
  92. package/src/cmd/cloud/oidc/util.ts +28 -0
  93. package/src/cmd/coder/hub-url.ts +5 -1
  94. package/src/cmd/coder/start.ts +22 -8
  95. package/src/cmd/coder/tui-init.ts +75 -0
  96. package/src/config.ts +16 -5
  97. package/src/utils/jsonc.ts +67 -0
  98. package/src/utils/route-migration.ts +29 -40
  99. package/src/utils/zip.ts +17 -2
@@ -0,0 +1,75 @@
1
+ import { hubFetchHeaders } from './hub-url';
2
+
3
+ export type TuiInitProbeResult =
4
+ | { ok: true }
5
+ | {
6
+ ok: false;
7
+ code: 'unauthorized' | 'http_error' | 'invalid_response' | 'network_error';
8
+ message: string;
9
+ };
10
+
11
+ function normalizeErrorMessage(payload: unknown, fallback: string): string {
12
+ if (
13
+ payload &&
14
+ typeof payload === 'object' &&
15
+ typeof (payload as { error?: unknown }).error === 'string'
16
+ ) {
17
+ return (payload as { error: string }).error;
18
+ }
19
+ return fallback;
20
+ }
21
+
22
+ export async function probeTuiInitAccess(
23
+ hubHttpUrl: string,
24
+ fetchImpl: typeof fetch = fetch
25
+ ): Promise<TuiInitProbeResult> {
26
+ try {
27
+ const response = await fetchImpl(`${hubHttpUrl}/api/hub/tui/init`, {
28
+ headers: hubFetchHeaders({ accept: 'application/json' }),
29
+ signal: AbortSignal.timeout(5_000),
30
+ });
31
+
32
+ let payload: unknown;
33
+ try {
34
+ payload = await response.json();
35
+ } catch {
36
+ payload = undefined;
37
+ }
38
+
39
+ if (response.status === 401 || response.status === 403) {
40
+ return {
41
+ ok: false,
42
+ code: 'unauthorized',
43
+ message: normalizeErrorMessage(payload, `${response.status} ${response.statusText}`),
44
+ };
45
+ }
46
+
47
+ if (!response.ok) {
48
+ return {
49
+ ok: false,
50
+ code: 'http_error',
51
+ message: normalizeErrorMessage(payload, `${response.status} ${response.statusText}`),
52
+ };
53
+ }
54
+
55
+ if (
56
+ !payload ||
57
+ typeof payload !== 'object' ||
58
+ (payload as { type?: unknown }).type !== 'init'
59
+ ) {
60
+ return {
61
+ ok: false,
62
+ code: 'invalid_response',
63
+ message: 'Hub init endpoint did not return an init payload',
64
+ };
65
+ }
66
+
67
+ return { ok: true };
68
+ } catch (error) {
69
+ return {
70
+ ok: false,
71
+ code: 'network_error',
72
+ message: error instanceof Error ? error.message : String(error),
73
+ };
74
+ }
75
+ }
package/src/config.ts CHANGED
@@ -10,7 +10,7 @@ import {
10
10
  APIClient as ServerAPIClient,
11
11
  } from '@agentuity/server';
12
12
  import { YAML } from 'bun';
13
- import JSON5 from 'json5';
13
+ import { parseJSONC } from './utils/jsonc';
14
14
  import { z } from 'zod';
15
15
  import { clearProfileCache } from './cache';
16
16
  import { getCatalystUrl } from './catalyst';
@@ -265,8 +265,19 @@ function formatYAML(obj: unknown, indent = 0): string {
265
265
  } else if (typeof value === 'string') {
266
266
  if (value === '') {
267
267
  lines.push(`${spaces}${key}: ""`);
268
- } else if (value.includes(':') || value.includes('#') || value.includes(' ')) {
269
- lines.push(`${spaces}${key}: "${value}"`);
268
+ } else if (
269
+ value.includes(':') ||
270
+ value.includes('#') ||
271
+ value.includes(' ') ||
272
+ value.includes('\\')
273
+ ) {
274
+ // Use single quotes to avoid YAML escape-sequence processing.
275
+ // Double-quoted YAML strings interpret backslash sequences (\n, \t, etc.),
276
+ // which breaks Windows paths like C:\Users\... where \U would be invalid.
277
+ // Single-quoted strings treat backslashes literally.
278
+ // Escape any embedded single quotes by doubling them (YAML spec).
279
+ const escaped = value.replace(/'/g, "''");
280
+ lines.push(`${spaces}${key}: '${escaped}'`);
270
281
  } else {
271
282
  lines.push(`${spaces}${key}: ${value}`);
272
283
  }
@@ -602,7 +613,7 @@ export async function loadProjectConfig(
602
613
  throw new ProjectConfigNotFoundException({ message: 'project config not found' });
603
614
  }
604
615
  const text = await file.text();
605
- const parsedConfig = JSON5.parse(text);
616
+ const parsedConfig = parseJSONC(text);
606
617
  const result = ProjectSchema.safeParse(parsedConfig);
607
618
  if (!result.success) {
608
619
  tui.error(`Invalid project config at ${configPath}:`);
@@ -707,7 +718,7 @@ export async function updateProjectConfig(
707
718
  }
708
719
 
709
720
  const text = await file.text();
710
- const existing = JSON5.parse(text);
721
+ const existing = parseJSONC(text) as Record<string, unknown>;
711
722
  const updated = { ...existing, ...updates };
712
723
 
713
724
  const result = ProjectSchema.safeParse(updated);
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Parse JSON with Comments (JSONC).
3
+ *
4
+ * Strips single-line (`//`) and block (`/* *​/`) comments as well as trailing
5
+ * commas that appear before `}` or `]`, then delegates to the built-in
6
+ * `JSON.parse`. This covers the comment syntax used by `tsconfig.json` and
7
+ * similar config files without pulling in a full JSON5 parser.
8
+ *
9
+ * String literals are respected — comments and trailing commas inside quoted
10
+ * strings are left untouched.
11
+ */
12
+ export function parseJSONC(text: string): unknown {
13
+ let result = '';
14
+ let i = 0;
15
+ const len = text.length;
16
+
17
+ while (i < len) {
18
+ const ch = text[i];
19
+
20
+ // --- quoted string: copy verbatim, including any escape sequences ---
21
+ if (ch === '"') {
22
+ const start = i;
23
+ i++; // skip opening quote
24
+ while (i < len) {
25
+ if (text[i] === '\\') {
26
+ i += i + 1 < len ? 2 : 1; // skip escaped character (guard end-of-input)
27
+ } else if (text[i] === '"') {
28
+ i++; // skip closing quote
29
+ break;
30
+ } else {
31
+ i++;
32
+ }
33
+ }
34
+ result += text.slice(start, i);
35
+ continue;
36
+ }
37
+
38
+ // --- single-line comment: skip to end of line ---
39
+ if (ch === '/' && text[i + 1] === '/') {
40
+ i += 2;
41
+ while (i < len && text[i] !== '\n') {
42
+ i++;
43
+ }
44
+ continue;
45
+ }
46
+
47
+ // --- block comment: skip to closing *​/ ---
48
+ if (ch === '/' && text[i + 1] === '*') {
49
+ i += 2;
50
+ while (i < len && !(text[i] === '*' && text[i + 1] === '/')) {
51
+ i++;
52
+ }
53
+ if (i < len) {
54
+ i += 2; // skip closing */
55
+ }
56
+ continue;
57
+ }
58
+
59
+ result += ch;
60
+ i++;
61
+ }
62
+
63
+ // Strip trailing commas before } or ] (with optional whitespace between).
64
+ result = result.replace(/,(\s*[}\]])/g, '$1');
65
+
66
+ return JSON.parse(result);
67
+ }
@@ -697,7 +697,8 @@ export function performMigration(rootDir: string, routeFiles: string[]): Migrati
697
697
  * Show the migration notice and optionally perform migration.
698
698
  *
699
699
  * Called during `dev` and `build` after dependency upgrades.
700
- * Shows a banner the first time, then a shorter reminder on subsequent runs.
700
+ * Only prompts in interactive TTY sessions and only once if the user
701
+ * dismisses the prompt, it won't be shown again.
701
702
  *
702
703
  * @returns true if migration was performed, false otherwise
703
704
  */
@@ -707,6 +708,12 @@ export async function promptRouteMigration(
707
708
  options?: { interactive?: boolean }
708
709
  ): Promise<boolean> {
709
710
  const interactive = options?.interactive ?? process.stdin.isTTY;
711
+
712
+ // Only show the interactive migration prompt in TTY sessions
713
+ if (!interactive) {
714
+ return false;
715
+ }
716
+
710
717
  const eligibility = checkMigrationEligibility(rootDir);
711
718
 
712
719
  if (!eligibility.available) {
@@ -715,48 +722,30 @@ export async function promptRouteMigration(
715
722
 
716
723
  const { routeFiles, alreadyNotified } = eligibility;
717
724
 
718
- // Non-interactive mode (CI, piped, AI agent): just log a notice
719
- if (!interactive) {
720
- if (!alreadyNotified) {
721
- logger.info(
722
- '[migration] This project uses file-based routing with %d route files in src/api/. ' +
723
- 'Agentuity is moving to explicit routing, which will become the default in the next major release. ' +
724
- 'Run `agentuity dev --migrate-routes` to migrate.',
725
- routeFiles.length
726
- );
727
- writeMigrationState(rootDir, 'notified');
728
- }
725
+ // Only prompt once if the user has already been notified or dismissed, don't ask again
726
+ if (alreadyNotified) {
729
727
  return false;
730
728
  }
731
729
 
732
- // First time: show full banner
733
- if (!alreadyNotified) {
734
- tui.newline();
735
- tui.banner(
736
- ' Migrate to Explicit Routing',
737
- 'Agentuity is moving to explicit routing, which will become the\n' +
738
- 'default in the next major release. File-based route discovery\n' +
739
- 'will be deprecated.\n' +
740
- '\n' +
741
- `Your project has ${routeFiles.length} route files in src/api/ that are\n` +
742
- 'auto-discovered at build time. Explicit routing gives you a single\n' +
743
- 'src/api/index.ts that imports and mounts all sub-routers — just\n' +
744
- 'like a standard Hono application.\n' +
745
- '\n' +
746
- `${tui.muted('Before:')} ${routeFiles.length} files auto-discovered from src/api/**/*.ts\n` +
747
- `${tui.muted('After:')} One src/api/index.ts that imports and mounts them\n` +
748
- '\n' +
749
- 'Your existing route files are not modified. Your app.ts will be\n' +
750
- 'updated to import the router and pass it to createApp({ router }).',
751
- { centerTitle: false }
752
- );
753
- } else {
754
- // Subsequent runs: shorter reminder
755
- tui.newline();
756
- tui.info(
757
- `${tui.bold('Explicit routing migration available')} — run with ${tui.muted('--migrate-routes')} or choose below.`
758
- );
759
- }
730
+ tui.newline();
731
+ tui.banner(
732
+ '✨ Migrate to Explicit Routing',
733
+ 'Agentuity is moving to explicit routing, which will become the\n' +
734
+ 'default in the next major release. File-based route discovery\n' +
735
+ 'will be deprecated.\n' +
736
+ '\n' +
737
+ `Your project has ${routeFiles.length} route files in src/api/ that are\n` +
738
+ 'auto-discovered at build time. Explicit routing gives you a single\n' +
739
+ 'src/api/index.ts that imports and mounts all sub-routers just\n' +
740
+ 'like a standard Hono application.\n' +
741
+ '\n' +
742
+ `${tui.muted('Before:')} ${routeFiles.length} files auto-discovered from src/api/**/*.ts\n` +
743
+ `${tui.muted('After:')} One src/api/index.ts that imports and mounts them\n` +
744
+ '\n' +
745
+ 'Your existing route files are not modified. Your app.ts will be\n' +
746
+ 'updated to import the router and pass it to createApp({ router }).',
747
+ { centerTitle: false }
748
+ );
760
749
 
761
750
  tui.newline();
762
751
 
package/src/utils/zip.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { readFileSync, lstatSync } from 'node:fs';
1
2
  import { relative } from 'node:path';
2
3
  import { Glob } from 'bun';
3
4
  import AdmZip from 'adm-zip';
@@ -11,7 +12,7 @@ interface Options {
11
12
  export async function zipDir(dir: string, outdir: string, options?: Options) {
12
13
  const zip = new AdmZip();
13
14
  const files = await Array.fromAsync(
14
- new Glob('**/*').scan({ cwd: dir, absolute: true, dot: true })
15
+ new Glob('**/*').scan({ cwd: dir, absolute: true, dot: true, followSymlinks: false })
15
16
  );
16
17
  const total = files.length;
17
18
  let count = 0;
@@ -24,7 +25,21 @@ export async function zipDir(dir: string, outdir: string, options?: Options) {
24
25
  }
25
26
  }
26
27
  if (!skip) {
27
- zip.addLocalFile(file, undefined, rel);
28
+ try {
29
+ // Skip symlinks and directories — symlinks are workspace artefacts
30
+ // (e.g. bun's node_modules links) that cannot be resolved portably
31
+ // across machines and would cause EISDIR errors on extraction.
32
+ const stat = lstatSync(file);
33
+ if (!stat.isSymbolicLink() && !stat.isDirectory()) {
34
+ // Use addFile with explicit Unix permissions (0o644) instead of addLocalFile.
35
+ // On Windows, addLocalFile relies on OS file stats which may produce zip entries
36
+ // with incorrect Unix permission bits, causing EACCES errors when extracted on Linux.
37
+ const data = readFileSync(file);
38
+ zip.addFile(rel, data, '', 0o644);
39
+ }
40
+ } catch (err) {
41
+ throw new Error(`Failed to add file to zip: ${rel} (${file})`, { cause: err });
42
+ }
28
43
  }
29
44
  count++;
30
45
  if (options?.progress) {