@profoundlogic/coderflow-cli 0.12.144 → 0.13.0

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.
package/README.md CHANGED
@@ -51,6 +51,15 @@ coder discard --yes
51
51
  ## Attaching to Containers
52
52
 
53
53
  ```bash
54
+ # Discover interactive containers and running containers retained by completed tasks
55
+ coder containers
56
+
57
+ # Include stopped, unknown-state, and active-task containers
58
+ coder containers --all
59
+
60
+ # Emit machine-readable output without a human-oriented preamble
61
+ coder containers --json
62
+
54
63
  # Connect to last container
55
64
  coder attach
56
65
 
@@ -59,8 +68,28 @@ coder attach --shell
59
68
 
60
69
  # Connect to specific container
61
70
  coder attach <container-id>
71
+
72
+ # Connect to a completed task's retained container without finding its container ID
73
+ coder attach <task-id>
74
+
75
+ # Show task state and retained-container state separately
76
+ coder status <task-id>
77
+
78
+ # Emit task and container status as JSON
79
+ coder status <task-id> --json
62
80
  ```
63
81
 
82
+ The default container list preserves existing interactive-container entries and
83
+ adds running retained-task containers. Retained entries show both task status
84
+ and container status plus an attach command using the public task ID.
85
+ Task-backed entries require both container-shell access and normal task
86
+ visibility; containers for private (`shared: false`) tasks are shown only to
87
+ owners who retain `tasks:view` or an administrator with `tasks:view_any`.
88
+ The `tasks:view_any` permission is independently sufficient for private-task
89
+ visibility.
90
+ When supplying a container ID directly, use the full ID or an unambiguous
91
+ prefix of at least 12 hexadecimal characters.
92
+
64
93
  ## Configuration and Profiles
65
94
 
66
95
  Manage connection settings:
package/coder.js CHANGED
@@ -116,7 +116,7 @@ async function main() {
116
116
  break;
117
117
 
118
118
  case 'status':
119
- await getStatus(args[1]);
119
+ await getStatus(args[1], args.slice(2));
120
120
  break;
121
121
 
122
122
  case 'results':
@@ -1,11 +1,61 @@
1
1
  /**
2
- * Command: coder attach [container-id] - Attach to a running container
2
+ * Command: coder attach [container-or-task-id] - Attach to a running container
3
3
  */
4
4
 
5
5
  import { request } from '../http-client.js';
6
6
  import { getLastContainerId, saveLastContainerId } from '../config.js';
7
7
  import { connectTerminal } from '../terminal-client.js';
8
8
 
9
+ export function looksLikeTaskId(value) {
10
+ return /^\d{10,}-[a-z0-9]+$/i.test(value || '');
11
+ }
12
+
13
+ export function formatAttachLookupError(error) {
14
+ return error?.serverMessage
15
+ || error?.responseBody?.message
16
+ || error?.message
17
+ || 'Unknown error';
18
+ }
19
+
20
+ /**
21
+ * Resolve either a task ID or a container ID to attachable container details.
22
+ */
23
+ export async function resolveAttachTarget(target, requestFn = request) {
24
+ if (!looksLikeTaskId(target)) {
25
+ return {
26
+ containerInfo: await requestFn(`/containers/${encodeURIComponent(target)}`, { exitOnError: false }),
27
+ taskId: null
28
+ };
29
+ }
30
+
31
+ const task = await requestFn(`/tasks/${encodeURIComponent(target)}`, { exitOnError: false });
32
+ if (!task.containerId) {
33
+ throw new Error(`Task ${target} does not have a retained container`);
34
+ }
35
+ if (task.containerState === 'not_found') {
36
+ throw new Error(`Task ${target}'s retained container no longer exists`);
37
+ }
38
+
39
+ let containerInfo;
40
+ try {
41
+ containerInfo = await requestFn(`/containers/${encodeURIComponent(task.containerId)}`, { exitOnError: false });
42
+ } catch (error) {
43
+ if (error.statusCode === 404) {
44
+ throw new Error(`Task ${target}'s retained container no longer exists`, { cause: error });
45
+ }
46
+ throw error;
47
+ }
48
+ return {
49
+ containerInfo: {
50
+ ...containerInfo,
51
+ isTaskContainer: true,
52
+ taskId: containerInfo.taskId || target,
53
+ taskStatus: containerInfo.taskStatus || task.status
54
+ },
55
+ taskId: target
56
+ };
57
+ }
58
+
9
59
  export async function attachToContainer(args = []) {
10
60
  let containerId = null;
11
61
  let startShell = false;
@@ -38,7 +88,7 @@ export async function attachToContainer(args = []) {
38
88
  if (!containerId) {
39
89
  console.error('Error: No container ID specified and no previous container found.');
40
90
  console.error('\nUsage:');
41
- console.error(' coder attach <container-id> # Attach to specific container');
91
+ console.error(' coder attach <container-or-task-id> # Attach to specific container or task');
42
92
  console.error(' coder attach # Attach to last container');
43
93
  console.error('\nTo see available containers, run:');
44
94
  console.error(' coder containers');
@@ -46,15 +96,20 @@ export async function attachToContainer(args = []) {
46
96
  }
47
97
  console.log(`Attaching to last container: ${containerId}`);
48
98
  } else {
49
- console.log(`Attaching to container: ${containerId}`);
99
+ const targetType = looksLikeTaskId(containerId) ? 'task' : 'container';
100
+ console.log(`Attaching to ${targetType}: ${containerId}`);
50
101
  }
51
102
 
52
103
  // Fetch container info from server to verify it exists and get details
53
104
  let containerInfo;
54
105
  try {
55
- containerInfo = await request(`/containers/${containerId}`);
106
+ const resolved = await resolveAttachTarget(containerId);
107
+ containerInfo = resolved.containerInfo;
108
+ if (resolved.taskId) {
109
+ console.log(`Resolved task to container: ${containerInfo.containerId}`);
110
+ }
56
111
  } catch (error) {
57
- console.error(`\n✗ Failed to find container: ${error.message}`);
112
+ console.error(`\n✗ Failed to find container: ${formatAttachLookupError(error)}`);
58
113
  console.error('\nTo see available containers, run:');
59
114
  console.error(' coder containers');
60
115
  process.exit(1);
@@ -69,6 +124,7 @@ export async function attachToContainer(args = []) {
69
124
  console.log(` Status: ${containerInfo.status}`);
70
125
  if (containerInfo.isTaskContainer) {
71
126
  console.log(` Type: Task container (${containerInfo.taskId})`);
127
+ console.log(` Task Status: ${containerInfo.taskStatus || 'unknown'}`);
72
128
  }
73
129
 
74
130
  if (containerInfo.status !== 'running') {
@@ -38,6 +38,24 @@ function parseCleanArgs(args) {
38
38
  return options;
39
39
  }
40
40
 
41
+ function parseListArgs(args) {
42
+ const options = { json: false, all: false };
43
+
44
+ for (const arg of args) {
45
+ if (arg === '--json') {
46
+ options.json = true;
47
+ } else if (arg === '--all' || arg === '-a') {
48
+ options.all = true;
49
+ } else {
50
+ console.error(`Error: Unknown option: ${arg}`);
51
+ console.error('Usage: coder containers [--all] [--json]');
52
+ process.exit(1);
53
+ }
54
+ }
55
+
56
+ return options;
57
+ }
58
+
41
59
  async function confirm(message) {
42
60
  const rl = createInterface({
43
61
  input: process.stdin,
@@ -84,18 +102,38 @@ function filterContainers(containers, options) {
84
102
  });
85
103
  }
86
104
 
87
- async function fetchContainers() {
88
- const data = await request('/containers');
105
+ async function fetchContainers({ includeTaskContainers = false } = {}, requestFn = request) {
106
+ const path = includeTaskContainers ? '/containers?include=tasks' : '/containers';
107
+ const data = await requestFn(path);
89
108
  return data.containers || [];
90
109
  }
91
110
 
92
- async function listContainers() {
93
- console.log('Fetching Coder containers...\n');
111
+ export async function listContainers(args = [], requestFn = request) {
112
+ const options = parseListArgs(args);
113
+ if (!options.json) {
114
+ console.log('Fetching Coder containers...\n');
115
+ }
94
116
 
95
- const containers = await fetchContainers();
117
+ const fetchedContainers = await fetchContainers({ includeTaskContainers: true }, requestFn);
118
+ // Interactive containers preserve the legacy list behavior. The default task
119
+ // view focuses on the newly discoverable/attachable case from this guide:
120
+ // retained containers that are still running. --all exposes the rest.
121
+ const containers = options.all
122
+ ? fetchedContainers
123
+ : fetchedContainers.filter(container => (
124
+ !container.isTaskContainer || (container.retained && container.status === 'running')
125
+ ));
126
+
127
+ if (options.json) {
128
+ console.log(JSON.stringify({ count: containers.length, containers }, null, 2));
129
+ return;
130
+ }
96
131
 
97
132
  if (containers.length === 0) {
98
133
  console.log('No Coder containers found.');
134
+ if (!options.all) {
135
+ console.log('Use "coder containers --all" to include stopped, unknown-state, and active-task containers.');
136
+ }
99
137
  return;
100
138
  }
101
139
 
@@ -105,6 +143,17 @@ async function listContainers() {
105
143
  const statusIcon = container.status === 'running' ? '▶' : '■';
106
144
  console.log(` ${statusIcon} ${container.name} (${container.containerId})`);
107
145
  console.log(` Status: ${container.status}`);
146
+ if (container.isTaskContainer) {
147
+ console.log(` Type: ${container.retained ? 'Retained task container' : 'Task container'}`);
148
+ console.log(` Task: ${container.taskId}`);
149
+ console.log(` Task status: ${container.taskStatus || 'unknown'}`);
150
+ if (container.taskName) {
151
+ console.log(` Task name: ${container.taskName}`);
152
+ }
153
+ if (container.status === 'running') {
154
+ console.log(` Attach: coder attach ${container.taskId}`);
155
+ }
156
+ }
108
157
  if (container.environment) {
109
158
  console.log(` Env: ${container.environment}`);
110
159
  }
@@ -115,18 +164,20 @@ async function listContainers() {
115
164
  }
116
165
  }
117
166
 
118
- async function deleteContainer(container) {
119
- await request(`/containers/${container.name}`, {
167
+ async function deleteContainer(container, requestFn = request) {
168
+ await requestFn(`/containers/${container.name}`, {
120
169
  method: 'DELETE'
121
170
  });
122
171
  }
123
172
 
124
- async function cleanContainers(args) {
173
+ export async function cleanContainers(args, requestFn = request) {
125
174
  const options = parseCleanArgs(args);
126
175
 
127
176
  console.log('Fetching Coder containers...\n');
128
177
 
129
- const allContainers = await fetchContainers();
178
+ // Task containers have their own lifecycle and must never be candidates for
179
+ // the interactive-container cleanup command.
180
+ const allContainers = await fetchContainers({}, requestFn);
130
181
 
131
182
  if (allContainers.length === 0) {
132
183
  console.log('No Coder containers found. Nothing to clean.');
@@ -181,7 +232,7 @@ async function cleanContainers(args) {
181
232
 
182
233
  for (const container of containersToDelete) {
183
234
  try {
184
- await deleteContainer(container);
235
+ await deleteContainer(container, requestFn);
185
236
  deletedCount += 1;
186
237
  } catch (error) {
187
238
  console.error(`✗ Failed to delete ${container.name}: ${error.message}`);
@@ -196,17 +247,19 @@ async function cleanContainers(args) {
196
247
  }
197
248
  }
198
249
 
199
- export async function handleContainers(args = []) {
250
+ export async function handleContainers(args = [], requestFn = request) {
200
251
  const subcommand = args[0];
201
252
 
202
253
  if (subcommand === 'clean') {
203
- await cleanContainers(args.slice(1));
204
- } else if (!subcommand) {
205
- await listContainers();
254
+ await cleanContainers(args.slice(1), requestFn);
255
+ } else if (!subcommand || subcommand.startsWith('-')) {
256
+ await listContainers(args, requestFn);
206
257
  } else {
207
258
  console.error(`Error: Unknown subcommand: ${subcommand}`);
208
- console.error('Usage: coder containers [clean]');
209
- console.error(' coder containers # List all containers');
259
+ console.error('Usage: coder containers [--all] [--json|clean]');
260
+ console.error(' coder containers # List interactive and task containers');
261
+ console.error(' coder containers --all # Include stopped, unknown, and active-task containers');
262
+ console.error(' coder containers --json # List containers as JSON');
210
263
  console.error(' coder containers clean # Clean containers');
211
264
  process.exit(1);
212
265
  }
@@ -235,6 +235,14 @@ async function ssoLogin() {
235
235
  console.error(`Error: Lost connection to server: ${error.message}`);
236
236
  process.exit(1);
237
237
  }
238
+ if (error.code === 'INVALID_SSO_APPROVAL_RESPONSE') {
239
+ process.removeListener('SIGINT', cleanup);
240
+ process.stdout.write('\r' + ' '.repeat(60) + '\r');
241
+ console.error('');
242
+ console.error(`Error: ${error.message}.`);
243
+ console.error('Please update the CLI/server or contact your administrator.');
244
+ process.exit(1);
245
+ }
238
246
  // For other errors, continue polling
239
247
  }
240
248
 
@@ -4,16 +4,31 @@
4
4
 
5
5
  import { request } from '../http-client.js';
6
6
 
7
- export async function getStatus(taskId) {
7
+ export async function getStatus(taskId, args = [], requestFn = request) {
8
8
  if (!taskId) {
9
9
  console.error('Error: Task ID required');
10
10
  console.error('Usage: coder status <task-id>');
11
11
  process.exit(1);
12
12
  }
13
13
 
14
- console.log(`Fetching status for task ${taskId}...`);
14
+ const json = args.includes('--json');
15
+ const unknownOption = args.find(arg => arg !== '--json');
16
+ if (unknownOption) {
17
+ console.error(`Error: Unknown option: ${unknownOption}`);
18
+ console.error('Usage: coder status <task-id> [--json]');
19
+ process.exit(1);
20
+ }
15
21
 
16
- const data = await request(`/tasks/${taskId}`);
22
+ if (!json) {
23
+ console.log(`Fetching status for task ${taskId}...`);
24
+ }
25
+
26
+ const data = await requestFn(`/tasks/${taskId}`);
27
+
28
+ if (json) {
29
+ console.log(JSON.stringify(data, null, 2));
30
+ return;
31
+ }
17
32
 
18
33
  console.log(`\nTask Status:`);
19
34
  console.log(` Task ID: ${data.taskId}`);
@@ -28,6 +43,20 @@ export async function getStatus(taskId) {
28
43
  console.log(` Exit Code: ${data.exitCode}`);
29
44
  }
30
45
 
46
+ if (data.containerId) {
47
+ const containerState = data.containerState || 'unknown';
48
+ const containerStateLabel = containerState === 'not_found'
49
+ ? 'not found'
50
+ : (containerState === 'exited' || containerState === 'dead' ? 'stopped' : containerState);
51
+ console.log(` Container ID: ${data.containerId.substring(0, 12)}`);
52
+ console.log(` Container: ${containerStateLabel}`);
53
+ if (containerState === 'running') {
54
+ console.log(` Attach: coder attach ${taskId}`);
55
+ }
56
+ } else {
57
+ console.log(' Container: not available');
58
+ }
59
+
31
60
  // Custom task detail fields (per-environment definitions; values on the task)
32
61
  const customFields = data.customFields || {};
33
62
  if (Object.keys(customFields).length > 0) {
package/lib/config.js CHANGED
@@ -165,6 +165,10 @@ export async function getCredentialsPathForDisplay() {
165
165
  * Save API key to config file or active profile
166
166
  */
167
167
  export async function saveApiKey(apiKey) {
168
+ if (typeof apiKey !== 'string' || apiKey.trim().length === 0) {
169
+ throw new Error('Cannot save an empty API key');
170
+ }
171
+
168
172
  // Check if there's an active profile (including CLI override)
169
173
  const activeProfileName = cliProfileOverride || await getActiveProfileName();
170
174
 
package/lib/help.js CHANGED
@@ -38,12 +38,12 @@ Examples:
38
38
  `,
39
39
 
40
40
  attach: `
41
- Usage: coder attach [container-id] [options]
41
+ Usage: coder attach [container-or-task-id] [options]
42
42
 
43
43
  Connect to a running container.
44
44
 
45
45
  Arguments:
46
- container-id Container ID to attach to (optional - uses last container if omitted)
46
+ container-or-task-id Container or task ID to attach to (optional - uses last container if omitted)
47
47
 
48
48
  Options:
49
49
  --agent=<agent> Agent to use (claude, codex, gemini, bob, grok, kimi)
@@ -52,6 +52,7 @@ Options:
52
52
  Examples:
53
53
  coder attach # Reconnect to last container
54
54
  coder attach 188b8e72d656 # Connect to specific container
55
+ coder attach 1784748839353-e7hk4lh2c # Connect to a task's retained container
55
56
  coder attach --shell # Connect with bash shell
56
57
  coder attach --agent=codex # Connect with specific agent
57
58
  `,
@@ -139,13 +140,16 @@ Examples:
139
140
  `,
140
141
 
141
142
  status: `
142
- Usage: coder status <task-id>
143
+ Usage: coder status <task-id> [--json]
143
144
 
144
- Check the status of a task.
145
+ Check task status and retained-container availability.
145
146
 
146
147
  Arguments:
147
148
  task-id Task ID to check (required)
148
149
 
150
+ Options:
151
+ --json Print machine-readable JSON only
152
+
149
153
  Examples:
150
154
  coder status 1759542727986-3uoyf48lr
151
155
  `,
@@ -275,11 +279,16 @@ Examples:
275
279
  containers: `
276
280
  Usage: coder containers [command] [options]
277
281
 
278
- List or clean containers.
282
+ List or clean containers. The default list includes interactive containers and
283
+ running task containers retained after completion.
279
284
 
280
285
  Commands:
281
- (none) List all containers
282
- clean Clean up containers
286
+ (none) List interactive and task containers
287
+ clean Clean up interactive containers
288
+
289
+ Options (for list):
290
+ --all, -a Include stopped, unknown-state, and active-task containers
291
+ --json Print machine-readable JSON only
283
292
 
284
293
  Options (for clean):
285
294
  --stopped Clean stopped containers
@@ -289,6 +298,8 @@ Options (for clean):
289
298
 
290
299
  Examples:
291
300
  coder containers
301
+ coder containers --all
302
+ coder containers --json
292
303
  coder containers clean --stopped --yes
293
304
  coder containers clean --older-than=7d --dry-run
294
305
  `
@@ -315,7 +326,7 @@ Usage: coder <command> [options]
315
326
  Commands:
316
327
  coder apply [task-id] Apply patches from completed task to local repos
317
328
  coder discard [--env=<environment>] [--yes] Discard applied changes from repos
318
- coder attach [container-id] [options] Connect to a running container
329
+ coder attach [container-or-task-id] [options] Connect to a running container
319
330
 
320
331
  Setup:
321
332
  coder login [--sso] Authenticate with server
@@ -355,7 +366,7 @@ Applying Changes:
355
366
  coder discard [--env=<environment>] [--yes] Discard applied changes from repos
356
367
 
357
368
  Connecting to Containers:
358
- coder attach [container-id] [--agent=<agent>] [--shell] Connect to a running container
369
+ coder attach [container-or-task-id] [--agent=<agent>] [--shell] Connect to a running container
359
370
 
360
371
  Setup & Configuration:
361
372
  coder login [--sso] Authenticate with server
@@ -4,6 +4,48 @@
4
4
 
5
5
  import { getServerUrl, getApiKey } from './config.js';
6
6
 
7
+ /**
8
+ * Build a consistent error object from an HTTP error response.
9
+ */
10
+ export function buildHttpError(status, data) {
11
+ let message;
12
+ let detailedError = null;
13
+
14
+ if (typeof data === 'string') {
15
+ message = data.trim() || `HTTP ${status}`;
16
+ detailedError = data.trim();
17
+ } else {
18
+ // A 400 response commonly uses `error` as a generic label ("Bad Request")
19
+ // and `message` for the actionable reason. Surface the useful reason first.
20
+ message = (status === 400 && data?.message)
21
+ || data?.error
22
+ || data?.message
23
+ || `HTTP ${status}`;
24
+
25
+ if (data?.error && data?.message) {
26
+ detailedError = `${data.error}: ${data.message}`;
27
+ } else if (data?.message) {
28
+ detailedError = data.message;
29
+ } else if (data?.error) {
30
+ detailedError = data.error;
31
+ }
32
+ }
33
+
34
+ const error = new Error(message);
35
+ error.detailedError = detailedError;
36
+ error.statusCode = status;
37
+ if (data && typeof data === 'object') {
38
+ error.responseBody = data;
39
+ error.serverMessage = typeof data.message === 'string' ? data.message : null;
40
+ for (const field of ['errorCode', 'requiredPermission', 'containerId', 'taskId', 'details']) {
41
+ if (Object.prototype.hasOwnProperty.call(data, field)) {
42
+ error[field] = data[field];
43
+ }
44
+ }
45
+ }
46
+ return error;
47
+ }
48
+
7
49
  /**
8
50
  * Format a helpful error message based on error type
9
51
  */
@@ -106,10 +148,9 @@ function formatErrorMessage(error, url, response, serverUrl) {
106
148
  export async function request(path, options = {}) {
107
149
  const serverUrl = await getServerUrl();
108
150
  const url = `${serverUrl}${path}`;
151
+ const { parse = 'json', exitOnError = true, ...fetchOptions } = options;
109
152
 
110
153
  try {
111
- const { parse = 'json', ...fetchOptions } = options;
112
-
113
154
  // Get API key and add to headers if available
114
155
  const apiKey = await getApiKey();
115
156
  const headers = {
@@ -140,30 +181,9 @@ export async function request(path, options = {}) {
140
181
  }
141
182
 
142
183
  if (!response.ok) {
143
- let message;
144
- let detailedError = null;
145
-
146
- if (typeof data === 'string') {
147
- message = data.trim() || `HTTP ${response.status}`;
148
- // Try to parse error details from string response
149
- detailedError = data.trim();
150
- } else {
151
- message = data?.error || data?.message || `HTTP ${response.status}`;
152
- // Capture additional error details if available
153
- if (data?.error && data?.message) {
154
- detailedError = `${data.error}: ${data.message}`;
155
- } else if (data?.message) {
156
- detailedError = data.message;
157
- } else if (data?.error) {
158
- detailedError = data.error;
159
- }
160
- }
161
-
162
- const error = new Error(message);
163
- error.detailedError = detailedError;
164
- error.statusCode = response.status;
184
+ const error = buildHttpError(response.status, data);
165
185
 
166
- if (formatErrorMessage(error, url, response, serverUrl)) {
186
+ if (exitOnError && formatErrorMessage(error, url, response, serverUrl)) {
167
187
  process.exit(1);
168
188
  }
169
189
  throw error;
@@ -172,7 +192,7 @@ export async function request(path, options = {}) {
172
192
  return data;
173
193
  } catch (error) {
174
194
  // Handle network/connection errors
175
- if (formatErrorMessage(error, url, null, serverUrl)) {
195
+ if (!error.statusCode && exitOnError && formatErrorMessage(error, url, null, serverUrl)) {
176
196
  process.exit(1);
177
197
  }
178
198
  throw error;
package/lib/oidc.js CHANGED
@@ -111,11 +111,18 @@ export async function pollDeviceFlow(deviceCode) {
111
111
  }
112
112
 
113
113
  const data = await response.json();
114
+ const apiKey = data.api_key || data.apiKey;
115
+
116
+ if (data.status === 'approved' || apiKey) {
117
+ if (!apiKey) {
118
+ const error = new Error('SSO approval response did not include an API key');
119
+ error.code = 'INVALID_SSO_APPROVAL_RESPONSE';
120
+ throw error;
121
+ }
114
122
 
115
- if (data.status === 'approved' || data.apiKey) {
116
123
  return {
117
124
  status: 'approved',
118
- apiKey: data.apiKey,
125
+ apiKey,
119
126
  user: data.user
120
127
  };
121
128
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@profoundlogic/coderflow-cli",
3
- "version": "0.12.144",
3
+ "version": "0.13.0",
4
4
  "description": "AI Coder CLI - Command-line interface for managing AI coding tasks",
5
5
  "main": "coder.js",
6
6
  "type": "module",
@@ -12,7 +12,7 @@
12
12
  "test:all": "node --test tests/**/*.test.js"
13
13
  },
14
14
  "engines": {
15
- "node": ">=18"
15
+ "node": ">=24"
16
16
  },
17
17
  "files": [
18
18
  "coder.js",
@@ -28,8 +28,8 @@
28
28
  "license": "SEE LICENSE IN LICENSE.txt",
29
29
  "homepage": "https://coderflow.ai",
30
30
  "dependencies": {
31
- "ws": "^8.18.0",
32
- "@inquirer/prompts": "^7.2.1",
33
- "open": "^10.1.0"
31
+ "ws": "^8.21.1",
32
+ "@inquirer/prompts": "^8.5.2",
33
+ "open": "^11.0.0"
34
34
  }
35
35
  }