@agentuity/cli 0.0.53 → 0.0.55

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 (137) hide show
  1. package/dist/cli.d.ts.map +1 -1
  2. package/dist/cli.js +66 -8
  3. package/dist/cli.js.map +1 -1
  4. package/dist/cmd/auth/ssh/add.d.ts.map +1 -1
  5. package/dist/cmd/auth/ssh/add.js +28 -13
  6. package/dist/cmd/auth/ssh/add.js.map +1 -1
  7. package/dist/cmd/auth/ssh/delete.d.ts.map +1 -1
  8. package/dist/cmd/auth/ssh/delete.js +28 -18
  9. package/dist/cmd/auth/ssh/delete.js.map +1 -1
  10. package/dist/cmd/auth/ssh/list.d.ts.map +1 -1
  11. package/dist/cmd/auth/ssh/list.js +5 -6
  12. package/dist/cmd/auth/ssh/list.js.map +1 -1
  13. package/dist/cmd/build/ast.d.ts +34 -0
  14. package/dist/cmd/build/ast.d.ts.map +1 -1
  15. package/dist/cmd/build/ast.js +165 -5
  16. package/dist/cmd/build/ast.js.map +1 -1
  17. package/dist/cmd/build/bundler.d.ts +2 -1
  18. package/dist/cmd/build/bundler.d.ts.map +1 -1
  19. package/dist/cmd/build/bundler.js +78 -17
  20. package/dist/cmd/build/bundler.js.map +1 -1
  21. package/dist/cmd/build/index.d.ts.map +1 -1
  22. package/dist/cmd/build/index.js +2 -0
  23. package/dist/cmd/build/index.js.map +1 -1
  24. package/dist/cmd/build/plugin.d.ts.map +1 -1
  25. package/dist/cmd/build/plugin.js +75 -7
  26. package/dist/cmd/build/plugin.js.map +1 -1
  27. package/dist/cmd/cloud/deployment/list.d.ts.map +1 -1
  28. package/dist/cmd/cloud/deployment/list.js +28 -23
  29. package/dist/cmd/cloud/deployment/list.js.map +1 -1
  30. package/dist/cmd/cloud/deployment/show.d.ts.map +1 -1
  31. package/dist/cmd/cloud/deployment/show.js +50 -47
  32. package/dist/cmd/cloud/deployment/show.js.map +1 -1
  33. package/dist/cmd/cloud/env/get.d.ts.map +1 -1
  34. package/dist/cmd/cloud/env/get.js +16 -14
  35. package/dist/cmd/cloud/env/get.js.map +1 -1
  36. package/dist/cmd/cloud/env/list.d.ts.map +1 -1
  37. package/dist/cmd/cloud/env/list.js +24 -20
  38. package/dist/cmd/cloud/env/list.js.map +1 -1
  39. package/dist/cmd/cloud/keyvalue/get.d.ts.map +1 -1
  40. package/dist/cmd/cloud/keyvalue/get.js +18 -16
  41. package/dist/cmd/cloud/keyvalue/get.js.map +1 -1
  42. package/dist/cmd/cloud/keyvalue/keys.d.ts.map +1 -1
  43. package/dist/cmd/cloud/keyvalue/keys.js +11 -11
  44. package/dist/cmd/cloud/keyvalue/keys.js.map +1 -1
  45. package/dist/cmd/cloud/keyvalue/list-namespaces.d.ts.map +1 -1
  46. package/dist/cmd/cloud/keyvalue/list-namespaces.js +11 -7
  47. package/dist/cmd/cloud/keyvalue/list-namespaces.js.map +1 -1
  48. package/dist/cmd/cloud/keyvalue/search.d.ts.map +1 -1
  49. package/dist/cmd/cloud/keyvalue/search.js +16 -17
  50. package/dist/cmd/cloud/keyvalue/search.js.map +1 -1
  51. package/dist/cmd/cloud/keyvalue/stats.d.ts.map +1 -1
  52. package/dist/cmd/cloud/keyvalue/stats.js +38 -23
  53. package/dist/cmd/cloud/keyvalue/stats.js.map +1 -1
  54. package/dist/cmd/cloud/objectstore/get.d.ts.map +1 -1
  55. package/dist/cmd/cloud/objectstore/get.js +17 -15
  56. package/dist/cmd/cloud/objectstore/get.js.map +1 -1
  57. package/dist/cmd/cloud/objectstore/list-buckets.d.ts.map +1 -1
  58. package/dist/cmd/cloud/objectstore/list-buckets.js +12 -8
  59. package/dist/cmd/cloud/objectstore/list-buckets.js.map +1 -1
  60. package/dist/cmd/cloud/objectstore/list-keys.d.ts.map +1 -1
  61. package/dist/cmd/cloud/objectstore/list-keys.js +13 -10
  62. package/dist/cmd/cloud/objectstore/list-keys.js.map +1 -1
  63. package/dist/cmd/cloud/resource/list.d.ts.map +1 -1
  64. package/dist/cmd/cloud/resource/list.js +38 -27
  65. package/dist/cmd/cloud/resource/list.js.map +1 -1
  66. package/dist/cmd/cloud/secret/get.d.ts.map +1 -1
  67. package/dist/cmd/cloud/secret/get.js +17 -15
  68. package/dist/cmd/cloud/secret/get.js.map +1 -1
  69. package/dist/cmd/cloud/secret/list.d.ts.map +1 -1
  70. package/dist/cmd/cloud/secret/list.js +24 -20
  71. package/dist/cmd/cloud/secret/list.js.map +1 -1
  72. package/dist/cmd/cloud/session/logs.d.ts.map +1 -1
  73. package/dist/cmd/cloud/session/logs.js +18 -15
  74. package/dist/cmd/cloud/session/logs.js.map +1 -1
  75. package/dist/cmd/dev/agents.d.ts.map +1 -1
  76. package/dist/cmd/dev/agents.js +55 -41
  77. package/dist/cmd/dev/agents.js.map +1 -1
  78. package/dist/cmd/dev/index.d.ts.map +1 -1
  79. package/dist/cmd/dev/index.js +34 -14
  80. package/dist/cmd/dev/index.js.map +1 -1
  81. package/dist/cmd/profile/create.js +1 -1
  82. package/dist/cmd/profile/create.js.map +1 -1
  83. package/dist/cmd/profile/delete.d.ts.map +1 -1
  84. package/dist/cmd/profile/delete.js +1 -1
  85. package/dist/cmd/profile/delete.js.map +1 -1
  86. package/dist/cmd/profile/list.d.ts.map +1 -1
  87. package/dist/cmd/profile/list.js +29 -11
  88. package/dist/cmd/profile/list.js.map +1 -1
  89. package/dist/cmd/profile/show.d.ts.map +1 -1
  90. package/dist/cmd/profile/show.js +7 -10
  91. package/dist/cmd/profile/show.js.map +1 -1
  92. package/dist/cmd/project/delete.js +1 -1
  93. package/dist/cmd/project/delete.js.map +1 -1
  94. package/dist/cmd/version/index.d.ts.map +1 -1
  95. package/dist/cmd/version/index.js +1 -1
  96. package/dist/cmd/version/index.js.map +1 -1
  97. package/dist/tui.d.ts.map +1 -1
  98. package/dist/tui.js +3 -1
  99. package/dist/tui.js.map +1 -1
  100. package/dist/types.d.ts +32 -8
  101. package/dist/types.d.ts.map +1 -1
  102. package/dist/types.js.map +1 -1
  103. package/package.json +3 -3
  104. package/src/cli.ts +109 -8
  105. package/src/cmd/auth/ssh/add.ts +37 -17
  106. package/src/cmd/auth/ssh/delete.ts +36 -23
  107. package/src/cmd/auth/ssh/list.ts +8 -6
  108. package/src/cmd/build/ast.ts +209 -5
  109. package/src/cmd/build/bundler.ts +82 -16
  110. package/src/cmd/build/index.ts +2 -0
  111. package/src/cmd/build/plugin.ts +93 -7
  112. package/src/cmd/cloud/deployment/list.ts +30 -26
  113. package/src/cmd/cloud/deployment/show.ts +47 -42
  114. package/src/cmd/cloud/env/get.ts +14 -12
  115. package/src/cmd/cloud/env/list.ts +24 -22
  116. package/src/cmd/cloud/keyvalue/get.ts +19 -14
  117. package/src/cmd/cloud/keyvalue/keys.ts +10 -12
  118. package/src/cmd/cloud/keyvalue/list-namespaces.ts +10 -8
  119. package/src/cmd/cloud/keyvalue/search.ts +14 -17
  120. package/src/cmd/cloud/keyvalue/stats.ts +52 -28
  121. package/src/cmd/cloud/objectstore/get.ts +18 -13
  122. package/src/cmd/cloud/objectstore/list-buckets.ts +11 -9
  123. package/src/cmd/cloud/objectstore/list-keys.ts +12 -11
  124. package/src/cmd/cloud/resource/list.ts +33 -23
  125. package/src/cmd/cloud/secret/get.ts +15 -13
  126. package/src/cmd/cloud/secret/list.ts +24 -22
  127. package/src/cmd/cloud/session/logs.ts +18 -17
  128. package/src/cmd/dev/agents.ts +70 -50
  129. package/src/cmd/dev/index.ts +41 -14
  130. package/src/cmd/profile/create.ts +3 -3
  131. package/src/cmd/profile/delete.ts +5 -2
  132. package/src/cmd/profile/list.ts +31 -11
  133. package/src/cmd/profile/show.ts +15 -12
  134. package/src/cmd/project/delete.ts +1 -1
  135. package/src/cmd/version/index.ts +5 -1
  136. package/src/tui.ts +3 -1
  137. package/src/types.ts +32 -10
@@ -35,7 +35,7 @@ export const getSubcommand = createSubcommand({
35
35
  idempotent: true,
36
36
 
37
37
  async handler(ctx) {
38
- const { args, opts, apiClient, project } = ctx;
38
+ const { args, opts, apiClient, project, options } = ctx;
39
39
 
40
40
  // Fetch project with unmasked secrets
41
41
  const projectData = await tui.spinner('Fetching secrets', () => {
@@ -49,19 +49,21 @@ export const getSubcommand = createSubcommand({
49
49
  tui.fatal(`Secret '${args.key}' not found`, ErrorCode.RESOURCE_NOT_FOUND);
50
50
  }
51
51
 
52
- if (process.stdout.isTTY) {
53
- // Display the value, masked by default
54
- if (opts?.mask) {
55
- tui.success(`${args.key}=${maskSecret(value)}`);
52
+ if (!options.json) {
53
+ if (process.stdout.isTTY) {
54
+ // Display the value, masked by default
55
+ if (opts?.mask) {
56
+ tui.success(`${args.key}=${maskSecret(value)}`);
57
+ } else {
58
+ tui.success(`${args.key}=${value}`);
59
+ }
56
60
  } else {
57
- tui.success(`${args.key}=${value}`);
58
- }
59
- } else {
60
- // Display the value, masked by default
61
- if (opts?.mask) {
62
- console.log(`${args.key}=${maskSecret(value)}`);
63
- } else {
64
- console.log(`${args.key}=${value}`);
61
+ // Display the value, masked by default
62
+ if (opts?.mask) {
63
+ console.log(`${args.key}=${maskSecret(value)}`);
64
+ } else {
65
+ console.log(`${args.key}=${value}`);
66
+ }
65
67
  }
66
68
  }
67
69
 
@@ -26,7 +26,7 @@ export const listSubcommand = createSubcommand({
26
26
  },
27
27
 
28
28
  async handler(ctx) {
29
- const { opts, apiClient, project } = ctx;
29
+ const { opts, apiClient, project, options } = ctx;
30
30
 
31
31
  // Fetch project with unmasked secrets
32
32
  const projectData = await tui.spinner('Fetching secrets', () => {
@@ -35,28 +35,30 @@ export const listSubcommand = createSubcommand({
35
35
 
36
36
  const secrets = projectData.secrets || {};
37
37
 
38
- if (Object.keys(secrets).length === 0) {
39
- tui.info('No secrets found');
40
- return {};
41
- }
42
-
43
- // Display the secrets
44
- if (process.stdout.isTTY) {
45
- tui.newline();
46
- tui.success(`Secrets (${Object.keys(secrets).length}):`);
47
- tui.newline();
48
- }
49
-
50
- const sortedKeys = Object.keys(secrets).sort();
51
- // For secrets, masking is enabled by default in TTY (can be disabled with --no-mask)
52
- const shouldMask = opts?.mask !== false;
53
- for (const key of sortedKeys) {
54
- const value = secrets[key];
55
- const displayValue = shouldMask ? maskSecret(value) : value;
56
- if (process.stdout.isTTY) {
57
- console.log(`${tui.bold(key)}=${displayValue}`);
38
+ // Skip TUI output in JSON mode
39
+ if (!options.json) {
40
+ if (Object.keys(secrets).length === 0) {
41
+ tui.info('No secrets found');
58
42
  } else {
59
- console.log(`${key}=${displayValue}`);
43
+ // Display the secrets
44
+ if (process.stdout.isTTY) {
45
+ tui.newline();
46
+ tui.success(`Secrets (${Object.keys(secrets).length}):`);
47
+ tui.newline();
48
+ }
49
+
50
+ const sortedKeys = Object.keys(secrets).sort();
51
+ // For secrets, masking is enabled by default in TTY (can be disabled with --no-mask)
52
+ const shouldMask = opts?.mask !== false;
53
+ for (const key of sortedKeys) {
54
+ const value = secrets[key];
55
+ const displayValue = shouldMask ? maskSecret(value) : value;
56
+ if (process.stdout.isTTY) {
57
+ console.log(`${tui.bold(key)}=${displayValue}`);
58
+ } else {
59
+ console.log(`${key}=${displayValue}`);
60
+ }
61
+ }
60
62
  }
61
63
  }
62
64
 
@@ -32,29 +32,30 @@ export const logsSubcommand = createSubcommand({
32
32
  response: SessionLogsResponseSchema,
33
33
  },
34
34
  async handler(ctx) {
35
- const { apiClient, args } = ctx;
35
+ const { apiClient, args, options } = ctx;
36
36
 
37
37
  try {
38
38
  const logs = await sessionLogs(apiClient, { id: args.session_id });
39
39
 
40
- if (logs.length === 0) {
41
- tui.info('No logs found for this session.');
42
- return [];
43
- }
44
-
45
- tui.banner(`Logs for Session ${args.session_id}`, `${logs.length} log entries`);
40
+ if (!options.json) {
41
+ if (logs.length === 0) {
42
+ tui.info('No logs found for this session.');
43
+ } else {
44
+ tui.banner(`Logs for Session ${args.session_id}`, `${logs.length} log entries`);
46
45
 
47
- for (const log of logs) {
48
- const timestamp = new Date(log.timestamp).toLocaleTimeString();
49
- const severity = log.severity.padEnd(5);
50
- const severityColor =
51
- log.severity === 'ERROR'
52
- ? tui.error(severity)
53
- : log.severity === 'WARN'
54
- ? tui.warning(severity)
55
- : tui.muted(severity);
46
+ for (const log of logs) {
47
+ const timestamp = new Date(log.timestamp).toLocaleTimeString();
48
+ const severity = log.severity.padEnd(5);
49
+ const severityColor =
50
+ log.severity === 'ERROR'
51
+ ? tui.error(severity)
52
+ : log.severity === 'WARN'
53
+ ? tui.warning(severity)
54
+ : tui.muted(severity);
56
55
 
57
- console.log(`${tui.muted(timestamp)} ${severityColor} ${log.body}`);
56
+ console.log(`${tui.muted(timestamp)} ${severityColor} ${log.body}`);
57
+ }
58
+ }
58
59
  }
59
60
 
60
61
  return logs;
@@ -46,22 +46,30 @@ export const agentsSubcommand = createSubcommand({
46
46
  .describe('Output format: json or table'),
47
47
  verbose: z.boolean().optional().default(false).describe('Show full IDs and descriptions'),
48
48
  }),
49
+ response: z.array(AgentSchema),
49
50
  },
50
51
  async handler(ctx) {
51
- const { opts, apiClient, project } = ctx;
52
+ const { opts, apiClient, project, options } = ctx;
52
53
  const projectId = project.projectId;
53
54
  const format = opts?.format ?? 'table';
54
55
  const verbose = opts?.verbose ?? false;
55
56
 
56
57
  const deploymentId = opts?.deploymentId;
57
58
  const queryParams = deploymentId ? `?deploymentId=${deploymentId}` : '';
58
- const response = await tui.spinner('Fetching agents', async () => {
59
- return apiClient.request(
60
- 'GET',
61
- `/cli/agent/${projectId}${queryParams}`,
62
- AgentsResponseSchema
63
- );
64
- });
59
+
60
+ const response = options.json
61
+ ? await apiClient.request(
62
+ 'GET',
63
+ `/cli/agent/${projectId}${queryParams}`,
64
+ AgentsResponseSchema
65
+ )
66
+ : await tui.spinner('Fetching agents', async () => {
67
+ return apiClient.request(
68
+ 'GET',
69
+ `/cli/agent/${projectId}${queryParams}`,
70
+ AgentsResponseSchema
71
+ );
72
+ });
65
73
 
66
74
  if (!response.success) {
67
75
  tui.fatal(`Failed to fetch agents: ${response.message ?? 'Unknown error'}`);
@@ -69,54 +77,66 @@ export const agentsSubcommand = createSubcommand({
69
77
 
70
78
  const agents = response.data;
71
79
 
72
- if (format === 'json') {
80
+ if (format === 'json' && !options.json) {
73
81
  console.log(JSON.stringify(agents, null, 2));
74
- return;
82
+ return agents;
75
83
  }
76
84
 
77
- tui.info(`Agents (${agents.length})`);
78
- if (agents.length === 0) {
79
- tui.muted('No agents found');
80
- } else {
81
- console.table(
82
- agents.map((agent) => ({
83
- name: agent.name,
84
- id: verbose ? agent.id : abbreviate(agent.id, 20),
85
- identifier: verbose ? agent.identifier : abbreviate(agent.identifier, 20),
86
- deployment: abbreviate(agent.deploymentId, 20),
87
- version: verbose
88
- ? (agent.version ?? 'N/A')
89
- : (abbreviate(agent.version, 20) ?? 'N/A'),
90
- evals: agent.evals.length,
91
- createdAt: new Date(agent.createdAt).toLocaleString(),
92
- })),
93
- ['name', 'id', 'identifier', 'deployment', 'version', 'evals', 'createdAt']
94
- );
85
+ if (!options.json) {
86
+ tui.info(`Agents (${agents.length})`);
87
+ if (agents.length === 0) {
88
+ tui.muted('No agents found');
89
+ } else {
90
+ console.table(
91
+ agents.map((agent) => ({
92
+ name: agent.name,
93
+ id: verbose ? agent.id : abbreviate(agent.id, 20),
94
+ identifier: verbose ? agent.identifier : abbreviate(agent.identifier, 20),
95
+ deployment: abbreviate(agent.deploymentId, 20),
96
+ version: verbose
97
+ ? (agent.version ?? 'N/A')
98
+ : (abbreviate(agent.version, 20) ?? 'N/A'),
99
+ evals: agent.evals.length,
100
+ createdAt: new Date(agent.createdAt).toLocaleString(),
101
+ })),
102
+ ['name', 'id', 'identifier', 'deployment', 'version', 'evals', 'createdAt']
103
+ );
95
104
 
96
- // Show evals for each agent
97
- for (const agent of agents) {
98
- if (agent.evals.length > 0) {
99
- console.log(`\n Evals for ${agent.name}:`);
100
- console.table(
101
- agent.evals.map((evalItem) => ({
102
- name: evalItem.name,
103
- id: verbose ? evalItem.id : abbreviate(evalItem.id, 20),
104
- identifier: verbose
105
- ? (evalItem.identifier ?? 'N/A')
106
- : (abbreviate(evalItem.identifier, 20) ?? 'N/A'),
107
- deployment: abbreviate(evalItem.deploymentId, 20),
108
- version: verbose
109
- ? (evalItem.version ?? 'N/A')
110
- : (abbreviate(evalItem.version, 20) ?? 'N/A'),
111
- description: verbose
112
- ? (evalItem.description ?? 'N/A')
113
- : abbreviateDescription(evalItem.description),
114
- createdAt: new Date(evalItem.createdAt).toLocaleString(),
115
- })),
116
- ['name', 'id', 'identifier', 'deployment', 'version', 'description', 'createdAt']
117
- );
105
+ // Show evals for each agent
106
+ for (const agent of agents) {
107
+ if (agent.evals.length > 0) {
108
+ console.log(`\n Evals for ${agent.name}:`);
109
+ console.table(
110
+ agent.evals.map((evalItem) => ({
111
+ name: evalItem.name,
112
+ id: verbose ? evalItem.id : abbreviate(evalItem.id, 20),
113
+ identifier: verbose
114
+ ? (evalItem.identifier ?? 'N/A')
115
+ : (abbreviate(evalItem.identifier, 20) ?? 'N/A'),
116
+ deployment: abbreviate(evalItem.deploymentId, 20),
117
+ version: verbose
118
+ ? (evalItem.version ?? 'N/A')
119
+ : (abbreviate(evalItem.version, 20) ?? 'N/A'),
120
+ description: verbose
121
+ ? (evalItem.description ?? 'N/A')
122
+ : abbreviateDescription(evalItem.description),
123
+ createdAt: new Date(evalItem.createdAt).toLocaleString(),
124
+ })),
125
+ [
126
+ 'name',
127
+ 'id',
128
+ 'identifier',
129
+ 'deployment',
130
+ 'version',
131
+ 'description',
132
+ 'createdAt',
133
+ ]
134
+ );
135
+ }
118
136
  }
119
137
  }
120
138
  }
139
+
140
+ return agents;
121
141
  },
122
142
  });
@@ -236,6 +236,7 @@ export const command = createCommand({
236
236
  let showInitialReadyMessage = true;
237
237
  let serverStartTime = 0;
238
238
  let gravityClient: Bun.Subprocess | undefined;
239
+ let initialStartupComplete = false;
239
240
 
240
241
  if (gravityBin && devmode && project) {
241
242
  const sdkKey = await loadProjectSDKKey(rootDir);
@@ -259,7 +260,7 @@ export const command = createCommand({
259
260
  '--url',
260
261
  config?.overrides?.gravity_url ?? 'grpc://devmode.agentuity.com',
261
262
  '--log-level',
262
- 'error',
263
+ process.env.AGENTUITY_GRAVITY_LOG_LEVEL ?? 'error',
263
264
  ],
264
265
  {
265
266
  cwd: rootDir,
@@ -326,6 +327,7 @@ export const command = createCommand({
326
327
  logger.trace('Killing dev server (pid: %d)', pid);
327
328
  shuttingDownForRestart = true;
328
329
  running = false;
330
+ process.kill(pid, 'SIGINT');
329
331
  try {
330
332
  // Kill the process group (negative PID kills entire group)
331
333
  process.kill(-pid, 'SIGTERM');
@@ -411,17 +413,10 @@ export const command = createCommand({
411
413
  logger.trace('Initial server start');
412
414
  }
413
415
  logger.trace('Starting typecheck and build...');
414
- await Promise.all([
415
- tui.runCommand({
416
- command: 'tsc',
417
- cmd: ['bunx', 'tsc', '--noEmit'],
418
- cwd: rootDir,
419
- clearOnSuccess: true,
420
- truncate: false,
421
- maxLinesOutput: 2,
422
- maxLinesOnFailure: 15,
423
- }),
424
- tui.spinner('Building project', async () => {
416
+ await tui.spinner({
417
+ message: 'Building project',
418
+ clearOnSuccess: true,
419
+ callback: async () => {
425
420
  try {
426
421
  logger.trace('Bundle starting...');
427
422
  building = true;
@@ -430,17 +425,31 @@ export const command = createCommand({
430
425
  dev: true,
431
426
  projectId: project?.projectId,
432
427
  deploymentId,
428
+ port: opts.port,
433
429
  });
434
430
  building = false;
435
431
  buildCompletedAt = Date.now();
436
432
  logger.trace('Bundle completed successfully');
433
+ logger.trace('tsc starting...');
434
+ await tui.runCommand({
435
+ command: 'tsc',
436
+ cmd: ['bunx', 'tsc', '--noEmit'],
437
+ cwd: rootDir,
438
+ clearOnSuccess: true,
439
+ truncate: false,
440
+ maxLinesOutput: 2,
441
+ maxLinesOnFailure: 15,
442
+ });
443
+ logger.trace('tsc completed successfully');
437
444
  } catch (error) {
438
445
  building = false;
439
446
  logger.trace('Bundle failed: %s', error);
440
447
  failure('Build failed');
448
+ return;
441
449
  }
442
- }),
443
- ]);
450
+ },
451
+ });
452
+
444
453
  logger.trace('Typecheck and build completed');
445
454
 
446
455
  if (failed) {
@@ -539,8 +548,14 @@ export const command = createCommand({
539
548
 
540
549
  if (showInitialReadyMessage) {
541
550
  showInitialReadyMessage = false;
551
+ // Clear any lingering spinner/command output - clear everything below cursor
552
+ process.stderr.write('\x1B[J'); // Clear from cursor to end of screen
553
+ process.stdout.write('\x1B[J'); // Clear from cursor to end of screen
542
554
  logger.info('DevMode ready 🚀');
543
555
  logger.trace('Initial ready message logged');
556
+ // Mark initial startup complete immediately to prevent watcher restarts
557
+ initialStartupComplete = true;
558
+ logger.trace('Initial startup complete, file watcher restarts now enabled');
544
559
  }
545
560
 
546
561
  logger.trace('Attaching exit handler to dev server process...');
@@ -632,6 +647,7 @@ export const command = createCommand({
632
647
  logger.trace('Starting initial build and server');
633
648
  await restart();
634
649
  logger.trace('Initial restart completed, setting up watchers');
650
+ logger.trace('initialStartupComplete is now: %s', initialStartupComplete);
635
651
 
636
652
  // Setup keyboard shortcuts (only if we have a TTY)
637
653
  if (canDoInput) {
@@ -741,6 +757,17 @@ export const command = createCommand({
741
757
  const watcher = watch(watchDir, { recursive: true }, (eventType, changedFile) => {
742
758
  const absPath = changedFile ? resolve(watchDir, changedFile) : watchDir;
743
759
 
760
+ // Ignore file changes during initial startup to prevent spurious restarts
761
+ if (!initialStartupComplete) {
762
+ logger.trace(
763
+ 'File change ignored (initial startup): %s (event: %s, file: %s)',
764
+ watchDir,
765
+ eventType,
766
+ changedFile
767
+ );
768
+ return;
769
+ }
770
+
744
771
  // Ignore file changes during active build to prevent loops
745
772
  if (building) {
746
773
  logger.trace(
@@ -59,7 +59,7 @@ export const createCommand = createSubcommand({
59
59
  return logger.fatal(
60
60
  `Profile "${name}" already exists at ${existing.filename}`,
61
61
  ErrorCode.RESOURCE_ALREADY_EXISTS
62
- );
62
+ ) as never;
63
63
  }
64
64
 
65
65
  await ensureConfigDir();
@@ -103,10 +103,10 @@ export const createCommand = createSubcommand({
103
103
  } catch (error) {
104
104
  const message = error instanceof Error ? error.message : String(error);
105
105
  const stack = error instanceof Error ? error.stack : undefined;
106
- logger.fatal(
106
+ return logger.fatal(
107
107
  `Failed to create profile: ${message}${stack ? `\n${stack}` : ''}`,
108
108
  ErrorCode.INTERNAL_ERROR
109
- );
109
+ ) as never;
110
110
  }
111
111
  },
112
112
  });
@@ -43,7 +43,7 @@ export const deleteCommand = createSubcommand({
43
43
  const profile = profiles.find((p) => p.name === name);
44
44
 
45
45
  if (!profile) {
46
- return logger.fatal(`Profile "${name}" not found`, ErrorCode.RESOURCE_NOT_FOUND);
46
+ return logger.fatal(`Profile "${name}" not found`, ErrorCode.RESOURCE_NOT_FOUND) as never;
47
47
  }
48
48
 
49
49
  // Ask for confirmation unless --confirm flag is passed
@@ -66,7 +66,10 @@ export const deleteCommand = createSubcommand({
66
66
 
67
67
  return { success: true, name };
68
68
  } catch (error) {
69
- logger.fatal(`Failed to delete profile: ${error}`);
69
+ return logger.fatal(
70
+ `Failed to delete profile: ${error}`,
71
+ ErrorCode.FILE_WRITE_ERROR
72
+ ) as never;
70
73
  }
71
74
  },
72
75
  });
@@ -1,9 +1,18 @@
1
+ import { z } from 'zod';
1
2
  import { createSubcommand } from '../../types';
2
3
  import { fetchProfiles } from '../../config';
3
4
  import { basename, dirname } from 'node:path';
4
5
  import * as tui from '../../tui';
5
6
  import { getCommand } from '../../command-prefix';
6
7
 
8
+ const ProfileListResponseSchema = z.array(
9
+ z.object({
10
+ name: z.string().describe('Profile name'),
11
+ filename: z.string().describe('Profile file path'),
12
+ selected: z.boolean().describe('Whether this profile is currently selected'),
13
+ })
14
+ );
15
+
7
16
  export const listCommand = createSubcommand({
8
17
  name: 'list',
9
18
  description: 'List all available profiles',
@@ -11,21 +20,32 @@ export const listCommand = createSubcommand({
11
20
  idempotent: true,
12
21
  aliases: ['ls'],
13
22
  examples: [getCommand('profile list'), getCommand('profile ls')],
23
+ schema: {
24
+ response: ProfileListResponseSchema,
25
+ },
14
26
 
15
- async handler() {
27
+ async handler(ctx) {
28
+ const { options } = ctx;
16
29
  const profiles = await fetchProfiles();
17
30
 
18
- if (profiles.length === 0) {
19
- tui.info('No profiles found');
20
- return;
31
+ if (!options.json) {
32
+ if (profiles.length === 0) {
33
+ tui.info('No profiles found');
34
+ } else {
35
+ console.log('Available profiles:');
36
+ for (const profile of profiles) {
37
+ const marker = profile.selected ? '•' : ' ';
38
+ const name = tui.padRight(profile.name, 15, ' ');
39
+ const path = `${basename(dirname(profile.filename))}/${basename(profile.filename)}`;
40
+ console.log(`${marker} ${name} ${tui.muted(path)}`);
41
+ }
42
+ }
21
43
  }
22
44
 
23
- console.log('Available profiles:');
24
- for (const profile of profiles) {
25
- const marker = profile.selected ? '•' : ' ';
26
- const name = tui.padRight(profile.name, 15, ' ');
27
- const path = `${basename(dirname(profile.filename))}/${basename(profile.filename)}`;
28
- console.log(`${marker} ${name} ${tui.muted(path)}`);
29
- }
45
+ return profiles.map((p) => ({
46
+ name: p.name,
47
+ filename: p.filename,
48
+ selected: p.selected,
49
+ }));
30
50
  },
31
51
  });
@@ -19,9 +19,6 @@ export const showCommand = createSubcommand({
19
19
  getCommand('profile show staging --json'),
20
20
  ],
21
21
  schema: {
22
- options: z.object({
23
- json: z.boolean().optional().describe('Show the JSON config'),
24
- }),
25
22
  args: z
26
23
  .object({
27
24
  name: z.string().optional().describe('Profile name to show (optional)'),
@@ -32,7 +29,7 @@ export const showCommand = createSubcommand({
32
29
  idempotent: true,
33
30
 
34
31
  async handler(ctx) {
35
- const { logger, args, opts } = ctx;
32
+ const { logger, args, options } = ctx;
36
33
 
37
34
  try {
38
35
  let current = false;
@@ -47,19 +44,25 @@ export const showCommand = createSubcommand({
47
44
  const profile = profiles.find((p) => p.name === name);
48
45
 
49
46
  if (!profile) {
50
- return logger.fatal(`Profile "${name}" not found`, ErrorCode.RESOURCE_NOT_FOUND);
47
+ return logger.fatal(
48
+ `Profile "${name}" not found`,
49
+ ErrorCode.RESOURCE_NOT_FOUND
50
+ ) as never;
51
51
  }
52
52
 
53
53
  const profilePath = profile.filename;
54
54
  current = profile.selected;
55
55
 
56
- tui.info(`Profile: ${profilePath}`);
57
-
58
56
  const content = await loadConfig(current ? undefined : profilePath);
57
+ if (!content) {
58
+ return logger.fatal(
59
+ `Failed to load profile configuration`,
60
+ ErrorCode.INTERNAL_ERROR
61
+ ) as never;
62
+ }
59
63
 
60
- if (opts?.json) {
61
- console.log(JSON.stringify(content, null, 2));
62
- } else {
64
+ if (!options.json) {
65
+ tui.info(`Profile: ${profilePath}`);
63
66
  tui.newline();
64
67
  const textContent = await readFile(profilePath, 'utf-8');
65
68
  console.log(textContent);
@@ -68,9 +71,9 @@ export const showCommand = createSubcommand({
68
71
  return content;
69
72
  } catch (error) {
70
73
  if (error instanceof Error) {
71
- logger.fatal(`Failed to show profile: ${error.message}`);
74
+ return logger.fatal(`Failed to show profile: ${error.message}`) as never;
72
75
  } else {
73
- logger.fatal('Failed to show profile');
76
+ return logger.fatal('Failed to show profile') as never;
74
77
  }
75
78
  }
76
79
  },
@@ -59,7 +59,7 @@ export const deleteSubcommand = createSubcommand({
59
59
 
60
60
  if (projects.length === 0) {
61
61
  tui.info('No projects found to delete');
62
- return;
62
+ return { success: false, projectIds: [], count: 0 };
63
63
  }
64
64
 
65
65
  // Sort projects by name
@@ -24,7 +24,11 @@ export const command = createCommand({
24
24
  return version;
25
25
  } catch (error) {
26
26
  const logger = createLogger();
27
- logger.fatal('Failed to retrieve version: %s', error, ErrorCode.INTERNAL_ERROR);
27
+ return logger.fatal(
28
+ 'Failed to retrieve version: %s',
29
+ error,
30
+ ErrorCode.INTERNAL_ERROR
31
+ ) as never;
28
32
  }
29
33
  },
30
34
  });
package/src/tui.ts CHANGED
@@ -831,7 +831,9 @@ export async function spinner<T>(
831
831
  : await options.callback;
832
832
 
833
833
  // If clearOnSuccess is true, don't show success message
834
- if (!options.clearOnSuccess) {
834
+ // Also skip success message in JSON mode
835
+ const isJsonMode = outputOptions?.json === true;
836
+ if (!options.clearOnSuccess && !isJsonMode) {
835
837
  const successColor = getColor('success');
836
838
  console.error(`${successColor}${ICONS.success} ${message}${reset}`);
837
839
  }