subconscious-cli 5.0.3 → 6.0.1

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/bin/auth.js CHANGED
@@ -12,16 +12,23 @@
12
12
  */
13
13
 
14
14
  import { exec } from 'node:child_process';
15
- import { openWindowsBrowser } from './windows/process.js';
16
15
  import fs from 'node:fs/promises';
17
16
  import os from 'node:os';
18
17
  import path from 'node:path';
19
18
  import { c } from './colors.js';
20
- import { clearProfileApiKey, DEFAULT_PROFILE, ensureProfile } from './profiles.js';
19
+ import {
20
+ clearProfileApiKey,
21
+ DEFAULT_PROFILE,
22
+ ensureProfile,
23
+ } from './profiles.js';
21
24
  import { printLoginUpgradeWarning } from './upgrade.js';
25
+ import { openWindowsBrowser } from './windows/process.js';
22
26
 
23
27
  function configDir() {
24
- return process.env.SUBC_CONFIG_DIR?.trim() || path.join(os.homedir(), '.subconscious');
28
+ return (
29
+ process.env.SUBC_CONFIG_DIR?.trim() ||
30
+ path.join(os.homedir(), '.subconscious')
31
+ );
25
32
  }
26
33
 
27
34
  function configFile() {
@@ -78,7 +85,10 @@ export async function getApiKey(profile) {
78
85
 
79
86
  const config = await loadConfig();
80
87
  if (config.subconscious_api_key) {
81
- return { key: config.subconscious_api_key, source: '~/.subconscious/config.json' };
88
+ return {
89
+ key: config.subconscious_api_key,
90
+ source: '~/.subconscious/config.json',
91
+ };
82
92
  }
83
93
  return null;
84
94
  }
@@ -87,9 +97,11 @@ export async function getApiKey(profile) {
87
97
 
88
98
  function openBrowser(url) {
89
99
  if (process.platform === 'win32') {
90
- void openWindowsBrowser(url).then(code => {
91
- if (code) throw new Error('Browser could not be opened');
92
- }).catch(() => console.log(`Please open this URL manually:\n\n ${url}\n`));
100
+ void openWindowsBrowser(url)
101
+ .then((code) => {
102
+ if (code) throw new Error('Browser could not be opened');
103
+ })
104
+ .catch(() => console.log(`Please open this URL manually:\n\n ${url}\n`));
93
105
  return;
94
106
  }
95
107
  const cmd =
@@ -131,7 +143,11 @@ export async function registerDeviceLogin(platformUrl, fetchImpl = fetch) {
131
143
  return data;
132
144
  }
133
145
 
134
- export async function pollDeviceLogin(platformUrl, deviceCode, fetchImpl = fetch) {
146
+ export async function pollDeviceLogin(
147
+ platformUrl,
148
+ deviceCode,
149
+ fetchImpl = fetch,
150
+ ) {
135
151
  const res = await fetchImpl(`${platformUrl}/api/cli/device/token`, {
136
152
  method: 'POST',
137
153
  headers: { 'Content-Type': 'application/json' },
@@ -151,7 +167,9 @@ function printDeviceInstructions(platformUrl, userCode) {
151
167
  console.log(` ${c.dim}Opening browser to sign in...${c.reset}`);
152
168
  console.log();
153
169
  console.log(` ${c.dim}If a window doesn't open, go to:${c.reset}`);
154
- console.log(` ${c.underline}${c.cyan}${terminalLink(verificationUrl)}${c.reset}`);
170
+ console.log(
171
+ ` ${c.underline}${c.cyan}${terminalLink(verificationUrl)}${c.reset}`,
172
+ );
155
173
  console.log();
156
174
  console.log(
157
175
  ` ${c.dim}Or open ${c.reset}${c.underline}${c.cyan}${terminalLink(deviceUrl)}${c.reset}${c.dim} and enter:${c.reset} ${c.bold}${userCode}${c.reset}`,
@@ -161,7 +179,9 @@ function printDeviceInstructions(platformUrl, userCode) {
161
179
  }
162
180
 
163
181
  function printLoginFallback() {
164
- console.error(` ${c.dim}You can also copy an API key from the dashboard and run${c.reset} ${c.cyan}subc update-key <your-api-key>${c.reset}${c.dim}.${c.reset}`);
182
+ console.error(
183
+ ` ${c.dim}You can also copy an API key from the dashboard and run${c.reset} ${c.cyan}subc update-key <your-api-key>${c.reset}${c.dim}.${c.reset}`,
184
+ );
165
185
  }
166
186
 
167
187
  async function saveLoginKey(profileName, token) {
@@ -181,17 +201,23 @@ export async function loginCommand(_argv = [], options = {}) {
181
201
  if (existing) {
182
202
  const profile = await ensureProfile(profileName, existing.key);
183
203
  const logout =
184
- profileName === DEFAULT_PROFILE ? 'subc logout' : `subc --profile ${profileName} logout`;
185
- const masked = existing.key.slice(0, 8) + '...' + existing.key.slice(-4);
204
+ profileName === DEFAULT_PROFILE
205
+ ? 'subc logout'
206
+ : `subc --profile ${profileName} logout`;
207
+ const masked = `${existing.key.slice(0, 8)}...${existing.key.slice(-4)}`;
186
208
  console.log(`\n${c.yellow}Already logged in.${c.reset}`);
187
209
  console.log(` Key: ${c.dim}${masked}${c.reset}`);
188
210
  console.log(` Profile: ${c.dim}${profile.path}${c.reset}`);
189
- console.log(`\n Run ${c.cyan}${logout}${c.reset} first to switch accounts.\n`);
211
+ console.log(
212
+ `\n Run ${c.cyan}${logout}${c.reset} first to switch accounts.\n`,
213
+ );
190
214
  return;
191
215
  }
192
216
 
193
217
  console.log();
194
- console.log(` ${c.magenta}${c.bold}Subconscious${c.reset} ${c.dim}— CLI Login${c.reset}`);
218
+ console.log(
219
+ ` ${c.magenta}${c.bold}Subconscious${c.reset} ${c.dim}— CLI Login${c.reset}`,
220
+ );
195
221
  console.log();
196
222
 
197
223
  const platformUrl = getPlatformUrl(options.profile);
@@ -206,7 +232,10 @@ export async function loginCommand(_argv = [], options = {}) {
206
232
  return;
207
233
  }
208
234
 
209
- const verificationUrl = printDeviceInstructions(platformUrl, registered.user_code);
235
+ const verificationUrl = printDeviceInstructions(
236
+ platformUrl,
237
+ registered.user_code,
238
+ );
210
239
  (options.openBrowser || openBrowser)(verificationUrl);
211
240
 
212
241
  const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
@@ -229,35 +258,47 @@ export async function loginCommand(_argv = [], options = {}) {
229
258
  if (Date.now() - started > DEVICE_POLL_TIMEOUT_MS) {
230
259
  throw new Error('Authentication timed out (5 min). Please try again.');
231
260
  }
232
- const polled = await pollDeviceLogin(platformUrl, registered.device_code, fetchImpl);
261
+ const polled = await pollDeviceLogin(
262
+ platformUrl,
263
+ registered.device_code,
264
+ fetchImpl,
265
+ );
233
266
  if (polled.status === 'approved') {
234
267
  clearInterval(spinner);
235
- process.stdout.write('\r' + ' '.repeat(50) + '\r');
268
+ process.stdout.write(`\r${' '.repeat(50)}\r`);
236
269
  const profile = await saveLoginKey(profileName, polled.key);
237
- const masked = polled.key.slice(0, 8) + '...' + polled.key.slice(-4);
270
+ const masked = `${polled.key.slice(0, 8)}...${polled.key.slice(-4)}`;
238
271
  console.log(` ${c.green}${c.bold}✓ Logged in successfully!${c.reset}`);
239
272
  console.log(` ${c.dim}Key: ${masked}${c.reset}`);
240
273
  if (profileName === DEFAULT_PROFILE) {
241
- console.log(` ${c.dim}Saved to ~/.subconscious/config.json${c.reset}`);
274
+ console.log(
275
+ ` ${c.dim}Saved to ~/.subconscious/config.json${c.reset}`,
276
+ );
242
277
  }
243
278
  console.log(` ${c.dim}Runbook profile: ${profile.path}${c.reset}`);
244
- console.log(` ${c.dim}Launch a terminal agent with ${c.reset}${c.cyan}subc claude${c.reset}${c.dim}, or install editor hooks with ${c.reset}${c.cyan}subc cursor install${c.reset}${c.dim}.${c.reset}`);
245
- console.log(` ${c.dim}Pi needs ${c.reset}${c.cyan}subc pi install${c.reset}${c.dim} first. List profiles with ${c.reset}${c.cyan}subc config${c.reset}${c.dim}.${c.reset}`);
279
+ console.log(
280
+ ` ${c.dim}Launch a terminal agent with ${c.reset}${c.cyan}subc claude${c.reset}${c.dim}, or install editor hooks with ${c.reset}${c.cyan}subc cursor install${c.reset}${c.dim}.${c.reset}`,
281
+ );
282
+ console.log(
283
+ ` ${c.dim}Pi needs ${c.reset}${c.cyan}subc pi install${c.reset}${c.dim} first. List profiles with ${c.reset}${c.cyan}subc config${c.reset}${c.dim}.${c.reset}`,
284
+ );
246
285
  console.log();
247
286
  return;
248
287
  }
249
288
  if (polled.status === 'expired') {
250
289
  throw new Error('Login code expired. Please try again.');
251
290
  }
252
- await new Promise((resolve) => setTimeout(resolve, DEVICE_POLL_INTERVAL_MS));
291
+ await new Promise((resolve) =>
292
+ setTimeout(resolve, DEVICE_POLL_INTERVAL_MS),
293
+ );
253
294
  }
254
295
  clearInterval(spinner);
255
- process.stdout.write('\r' + ' '.repeat(50) + '\r');
296
+ process.stdout.write(`\r${' '.repeat(50)}\r`);
256
297
  console.log(`\n ${c.dim}Login cancelled.${c.reset}\n`);
257
298
  process.exitCode = 1;
258
299
  } catch (error) {
259
300
  clearInterval(spinner);
260
- process.stdout.write('\r' + ' '.repeat(50) + '\r');
301
+ process.stdout.write(`\r${' '.repeat(50)}\r`);
261
302
  console.error(` ${c.red}✗ ${error.message}${c.reset}`);
262
303
  printLoginFallback();
263
304
  console.log();
@@ -282,7 +323,8 @@ export async function updateApiKeyCommand(argv = [], options = {}) {
282
323
  await saveConfig(config);
283
324
  }
284
325
 
285
- const masked = key.length <= 12 ? '********' : `${key.slice(0, 8)}...${key.slice(-4)}`;
326
+ const masked =
327
+ key.length <= 12 ? '********' : `${key.slice(0, 8)}...${key.slice(-4)}`;
286
328
  console.log(`\n ${c.green}${c.bold}✓ API key updated.${c.reset}`);
287
329
  console.log(` ${c.dim}Profile: ${profile.path}${c.reset}`);
288
330
  console.log(` ${c.dim}Key: ${masked}${c.reset}`);
@@ -298,7 +340,8 @@ export async function logoutCommand(_argv = [], options = {}) {
298
340
  const profileName = options.profileName || DEFAULT_PROFILE;
299
341
  const config = await loadConfig();
300
342
  const clearedProfile = await clearProfileApiKey(profileName);
301
- const clearSavedConfig = profileName === DEFAULT_PROFILE && config.subconscious_api_key;
343
+ const clearSavedConfig =
344
+ profileName === DEFAULT_PROFILE && config.subconscious_api_key;
302
345
 
303
346
  if (!clearSavedConfig && !clearedProfile) {
304
347
  console.log(`\n ${c.dim}Not logged in.${c.reset}\n`);
@@ -331,16 +374,19 @@ export async function whoamiCommand(_argv = [], options = {}) {
331
374
  }
332
375
 
333
376
  const { key, source } = auth;
334
- const masked = key.slice(0, 8) + '...' + key.slice(-4);
377
+ const masked = `${key.slice(0, 8)}...${key.slice(-4)}`;
335
378
 
336
379
  console.log();
337
380
 
338
381
  // Validate the key against the server; falls back to offline display if unreachable
339
382
  try {
340
- const res = await fetch(`${getPlatformUrl(options.profile)}/api/cli/whoami`, {
341
- headers: { Authorization: `Bearer ${key}` },
342
- signal: AbortSignal.timeout(5000),
343
- });
383
+ const res = await fetch(
384
+ `${getPlatformUrl(options.profile)}/api/cli/whoami`,
385
+ {
386
+ headers: { Authorization: `Bearer ${key}` },
387
+ signal: AbortSignal.timeout(5000),
388
+ },
389
+ );
344
390
 
345
391
  if (res.status === 404) {
346
392
  printLoginUpgradeWarning();
@@ -365,7 +411,9 @@ export async function whoamiCommand(_argv = [], options = {}) {
365
411
  );
366
412
  }
367
413
  } catch {
368
- console.log(` ${c.green}✓ Authenticated${c.reset} ${c.dim}(offline — key not verified)${c.reset}`);
414
+ console.log(
415
+ ` ${c.green}✓ Authenticated${c.reset} ${c.dim}(offline — key not verified)${c.reset}`,
416
+ );
369
417
  console.log(` ${c.dim}Key: ${masked}${c.reset}`);
370
418
  console.log(` ${c.dim}Source: ${source}${c.reset}`);
371
419
  }
package/bin/branding.js CHANGED
@@ -28,6 +28,8 @@ export function renderBanner(options = {}) {
28
28
  // still get the same logo without ANSI styling.
29
29
  if (!isTTY || term === 'dumb') return ` ${title}`;
30
30
 
31
- const logo = color ? `${BRAND_ORANGE}${LOGO_ART_SMALL}${RESET}` : LOGO_ART_SMALL;
31
+ const logo = color
32
+ ? `${BRAND_ORANGE}${LOGO_ART_SMALL}${RESET}`
33
+ : LOGO_ART_SMALL;
32
34
  return `${logo}\n\n ${title}`;
33
35
  }
package/bin/cli.js CHANGED
@@ -10,43 +10,43 @@
10
10
  */
11
11
 
12
12
  import fs from 'node:fs/promises';
13
- import { c } from './colors.js';
14
- import { renderBanner } from './branding.js';
15
13
  import {
14
+ agentCommandName,
15
+ agentList,
16
+ isAgentHelpRequest,
17
+ parseAgentAction,
18
+ resolveAgent,
19
+ runAgent,
20
+ } from './agents.js';
21
+ import {
22
+ getApiKey,
16
23
  loginCommand,
17
24
  logoutCommand,
18
- getApiKey,
19
25
  updateApiKeyCommand,
20
26
  whoamiCommand,
21
27
  } from './auth.js';
22
- import { upgradeCommand } from './upgrade.js';
23
- import {
24
- resolveAgent,
25
- runAgent,
26
- agentList,
27
- agentCommandName,
28
- isAgentHelpRequest,
29
- parseAgentAction,
30
- } from './agents.js';
28
+ import { renderBanner } from './branding.js';
29
+ import { c } from './colors.js';
30
+ import { feedbackCommand, printFeedbackHelp } from './feedback.js';
31
+ import { resolveModelCatalog } from './models.js';
31
32
  import {
32
33
  configCommand,
33
34
  DEFAULT_PROFILE,
34
35
  loadProfile,
35
36
  modelsCommand,
37
+ SUPPORTED_MODELS as PACKAGED_MODELS,
36
38
  printConfigHelp,
37
- resolvedModelSetting,
38
39
  RUNBOOK_DEFAULTS,
39
- SUPPORTED_MODELS as PACKAGED_MODELS,
40
+ resolvedModelSetting,
40
41
  updatePlatformUrlCommand,
41
42
  updateUrlCommand,
42
43
  validateProfileName,
43
44
  } from './profiles.js';
44
- import { resolveModelCatalog } from './models.js';
45
- import { showUpdateNotice } from './update-check.js';
46
- import { runTui } from './tui.js';
47
45
  import { sessionsCommand } from './sessions.js';
46
+ import { runTui } from './tui.js';
47
+ import { showUpdateNotice } from './update-check.js';
48
+ import { upgradeCommand } from './upgrade.js';
48
49
  import { printUsageHelp, usageCommand } from './usage.js';
49
- import { feedbackCommand, printFeedbackHelp } from './feedback.js';
50
50
 
51
51
  function isHelpArg(arg) {
52
52
  return arg === 'help' || arg === '-h' || arg === '--help';
@@ -54,7 +54,10 @@ function isHelpArg(arg) {
54
54
 
55
55
  function printHelp() {
56
56
  const agents = agentList()
57
- .map(({ name, alias, action }) => ` ${c.cyan}${alias.padEnd(13)}${c.reset}${c.dim}${action} ${name}${c.reset}`)
57
+ .map(
58
+ ({ name, alias, action }) =>
59
+ ` ${c.cyan}${alias.padEnd(13)}${c.reset}${c.dim}${action} ${name}${c.reset}`,
60
+ )
58
61
  .join('\n');
59
62
 
60
63
  console.log(`${renderBanner()}
@@ -330,7 +333,12 @@ async function main() {
330
333
  }
331
334
  }
332
335
 
333
- if (!command || command === '--help' || command === '-h' || (command === 'help' && !args[1])) {
336
+ if (
337
+ !command ||
338
+ command === '--help' ||
339
+ command === '-h' ||
340
+ (command === 'help' && !args[1])
341
+ ) {
334
342
  printHelp();
335
343
  return;
336
344
  }
@@ -8,13 +8,23 @@ import { promisify } from 'node:util';
8
8
 
9
9
  const execFileAsync = promisify(execFile);
10
10
 
11
- const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
11
+ const PNG_SIGNATURE = Buffer.from([
12
+ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a,
13
+ ]);
12
14
 
13
15
  export function imageContentType(buffer) {
14
- if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
16
+ if (
17
+ buffer.length >= 3 &&
18
+ buffer[0] === 0xff &&
19
+ buffer[1] === 0xd8 &&
20
+ buffer[2] === 0xff
21
+ ) {
15
22
  return 'image/jpeg';
16
23
  }
17
- if (buffer.length >= PNG_SIGNATURE.length && buffer.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {
24
+ if (
25
+ buffer.length >= PNG_SIGNATURE.length &&
26
+ buffer.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)
27
+ ) {
18
28
  return 'image/png';
19
29
  }
20
30
  return null;
@@ -52,7 +62,10 @@ async function readLinuxClipboard() {
52
62
  ];
53
63
  for (const [command, args] of commands) {
54
64
  try {
55
- const { stdout } = await execFileAsync(command, args, { encoding: 'buffer', maxBuffer: 8 * 1024 * 1024 });
65
+ const { stdout } = await execFileAsync(command, args, {
66
+ encoding: 'buffer',
67
+ maxBuffer: 8 * 1024 * 1024,
68
+ });
56
69
  if (stdout?.length) return stdout;
57
70
  } catch {
58
71
  // Try the next clipboard tool.
@@ -72,7 +85,11 @@ async function readWindowsClipboard() {
72
85
  Write-Output $path
73
86
  `;
74
87
  try {
75
- const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-Command', script]);
88
+ const { stdout } = await execFileAsync('powershell', [
89
+ '-NoProfile',
90
+ '-Command',
91
+ script,
92
+ ]);
76
93
  const outPath = String(stdout).trim();
77
94
  if (!outPath) return null;
78
95
  const { readFile, rm } = await import('node:fs/promises');
package/bin/colors.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // ANSI color helpers shared across the CLI. Keep redirected output machine
2
2
  // readable and honor the standard NO_COLOR opt-out.
3
- const noColor = Object.prototype.hasOwnProperty.call(process.env, 'NO_COLOR');
3
+ const noColor = Object.hasOwn(process.env, 'NO_COLOR');
4
4
  const forceColor =
5
5
  process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== '0';
6
6
 
package/bin/feedback.js CHANGED
@@ -9,9 +9,9 @@ import fs from 'node:fs/promises';
9
9
  import os from 'node:os';
10
10
  import readline from 'node:readline';
11
11
  import { createInterface } from 'node:readline/promises';
12
- import { c } from './colors.js';
13
12
  import { getApiKey, getPlatformUrl } from './auth.js';
14
13
  import { imageContentType, readClipboardImage } from './clipboard-image.js';
14
+ import { c } from './colors.js';
15
15
  import { DEFAULT_PROFILE } from './profiles.js';
16
16
 
17
17
  export const FEEDBACK_API_PATH = '/api/cli/feedback';
@@ -64,12 +64,19 @@ export async function collectFeedbackContext(options = {}) {
64
64
  };
65
65
  }
66
66
 
67
- export function buildFeedbackPayload({ subject, message, context, attachments }) {
67
+ export function buildFeedbackPayload({
68
+ subject,
69
+ message,
70
+ context,
71
+ attachments,
72
+ }) {
68
73
  const payload = {
69
74
  subject: subject || 'CLI feedback',
70
75
  message,
71
76
  context: Object.fromEntries(
72
- Object.entries(context ?? {}).filter(([, value]) => Boolean(String(value).trim())),
77
+ Object.entries(context ?? {}).filter(([, value]) =>
78
+ Boolean(String(value).trim()),
79
+ ),
73
80
  ),
74
81
  };
75
82
  if (attachments?.length) payload.attachments = attachments;
@@ -79,7 +86,8 @@ export function buildFeedbackPayload({ subject, message, context, attachments })
79
86
  export async function loadFeedbackImages(paths) {
80
87
  const attachments = [];
81
88
  for (const filePath of paths) {
82
- if (!filePath?.trim()) throw new Error('An image path is required after --image');
89
+ if (!filePath?.trim())
90
+ throw new Error('An image path is required after --image');
83
91
  const buffer = await fs.readFile(filePath);
84
92
  const contentType = imageContentType(buffer);
85
93
  if (!contentType) throw new Error(`${filePath} is not a JPEG or PNG`);
@@ -88,7 +96,12 @@ export async function loadFeedbackImages(paths) {
88
96
  return attachments;
89
97
  }
90
98
 
91
- export async function submitFeedback(apiKey, platformUrl, payload, fetchImpl = fetch) {
99
+ export async function submitFeedback(
100
+ apiKey,
101
+ platformUrl,
102
+ payload,
103
+ fetchImpl = fetch,
104
+ ) {
92
105
  const res = await fetchImpl(
93
106
  `${platformUrl.replace(/\/$/, '')}${FEEDBACK_API_PATH}`,
94
107
  {
@@ -164,7 +177,9 @@ function readAttachmentLine(onPaste) {
164
177
  async function promptForAttachments(already = 0) {
165
178
  if (process.stdin.isTTY !== true || already >= MAX_FEEDBACK_IMAGES) return [];
166
179
  const attachments = [];
167
- console.log(`\n Attachments (optional). Ctrl+V pastes a screenshot from the clipboard.`);
180
+ console.log(
181
+ `\n Attachments (optional). Ctrl+V pastes a screenshot from the clipboard.`,
182
+ );
168
183
  console.log(` ${c.dim}Press Enter when you are done.${c.reset}`);
169
184
  while (already + attachments.length < MAX_FEEDBACK_IMAGES) {
170
185
  const line = await readAttachmentLine(async () => {
@@ -172,11 +187,15 @@ async function promptForAttachments(already = 0) {
172
187
  const buffer = await readClipboardImage();
173
188
  const contentType = buffer ? imageContentType(buffer) : null;
174
189
  if (!contentType || !buffer) {
175
- console.log(`\n ${c.yellow}No JPEG or PNG image on the clipboard.${c.reset}`);
190
+ console.log(
191
+ `\n ${c.yellow}No JPEG or PNG image on the clipboard.${c.reset}`,
192
+ );
176
193
  return;
177
194
  }
178
195
  attachments.push({ contentType, data: buffer.toString('base64') });
179
- console.log(` ${c.green}Added screenshot ${already + attachments.length}.${c.reset}`);
196
+ console.log(
197
+ ` ${c.green}Added screenshot ${already + attachments.length}.${c.reset}`,
198
+ );
180
199
  });
181
200
  if (line == null || !line.trim()) break;
182
201
  }
@@ -186,7 +205,8 @@ async function promptForAttachments(already = 0) {
186
205
  async function promptForFeedback(argv) {
187
206
  const rl = createInterface({ input: process.stdin, output: process.stdout });
188
207
  try {
189
- const subject = argv.subject || (await rl.question(`\n Subject (optional): `)).trim();
208
+ const subject =
209
+ argv.subject || (await rl.question(`\n Subject (optional): `)).trim();
190
210
  let message = '';
191
211
  while (!message) {
192
212
  message = (await rl.question(` Message: `)).trim();
@@ -208,7 +228,9 @@ export async function feedbackCommand(argv = [], options = {}) {
208
228
  const auth = await getApiKey(options.profile);
209
229
  if (!auth) {
210
230
  console.log(`\n ${c.dim}Not logged in.${c.reset}`);
211
- console.log(` Run ${c.cyan}subc ${profileFlag}login${c.reset} to get started.\n`);
231
+ console.log(
232
+ ` Run ${c.cyan}subc ${profileFlag}login${c.reset} to get started.\n`,
233
+ );
212
234
  return;
213
235
  }
214
236
 
@@ -216,7 +238,9 @@ export async function feedbackCommand(argv = [], options = {}) {
216
238
  if (!feedback.message) {
217
239
  if (process.stdin.isTTY !== true) {
218
240
  console.error(`\n ${c.red}A message is required.${c.reset}`);
219
- console.error(` Pass one with ${c.cyan}subc feedback --message "..."${c.reset}\n`);
241
+ console.error(
242
+ ` Pass one with ${c.cyan}subc feedback --message "..."${c.reset}\n`,
243
+ );
220
244
  process.exitCode = 1;
221
245
  return;
222
246
  }
@@ -224,7 +248,10 @@ export async function feedbackCommand(argv = [], options = {}) {
224
248
  }
225
249
 
226
250
  const fromFlags = await loadFeedbackImages(parsed.images);
227
- const fromPrompt = feedback.message && parsed.message ? [] : await promptForAttachments(fromFlags.length);
251
+ const fromPrompt =
252
+ feedback.message && parsed.message
253
+ ? []
254
+ : await promptForAttachments(fromFlags.length);
228
255
  const platformUrl = getPlatformUrl(options.profile);
229
256
  const payload = buildFeedbackPayload({
230
257
  subject: feedback.subject,
@@ -237,9 +264,13 @@ export async function feedbackCommand(argv = [], options = {}) {
237
264
  const res = await submitFeedback(auth.key, platformUrl, payload);
238
265
 
239
266
  if (res.status === 404) {
240
- console.error(`\n ${c.yellow}This platform has no feedback endpoint (404).${c.reset}`);
267
+ console.error(
268
+ `\n ${c.yellow}This platform has no feedback endpoint (404).${c.reset}`,
269
+ );
241
270
  console.error(` ${c.dim}Host: ${platformUrl}${c.reset}`);
242
- console.error(` ${c.dim}Point PLATFORM_URL at a platform that includes /api/cli/feedback.${c.reset}\n`);
271
+ console.error(
272
+ ` ${c.dim}Point PLATFORM_URL at a platform that includes /api/cli/feedback.${c.reset}\n`,
273
+ );
243
274
  process.exitCode = 1;
244
275
  return;
245
276
  }
@@ -253,14 +284,18 @@ export async function feedbackCommand(argv = [], options = {}) {
253
284
 
254
285
  if (!res.ok) {
255
286
  const detail = typeof data?.error === 'string' ? data.error : '';
256
- const invalidKey = detail === 'Invalid or revoked API key' || detail === 'No API key provided';
287
+ const invalidKey =
288
+ detail === 'Invalid or revoked API key' ||
289
+ detail === 'No API key provided';
257
290
  console.log(`\n ${c.red}✗ Couldn't send your message.${c.reset}`);
258
291
  if (invalidKey) {
259
292
  console.log(
260
293
  ` Run ${c.cyan}subc ${profileFlag}logout${c.reset} then ${c.cyan}subc ${profileFlag}login${c.reset} to re-authenticate.`,
261
294
  );
262
295
  }
263
- console.log(` Please email ${c.cyan}${SUPPORT_EMAIL}${c.reset} directly.\n`);
296
+ console.log(
297
+ ` Please email ${c.cyan}${SUPPORT_EMAIL}${c.reset} directly.\n`,
298
+ );
264
299
  process.exitCode = 1;
265
300
  return;
266
301
  }
@@ -269,12 +304,16 @@ export async function feedbackCommand(argv = [], options = {}) {
269
304
  console.log(` ${c.dim}Our support team will get back to you.\n`);
270
305
  } catch (error) {
271
306
  if (error.name === 'TimeoutError' || error.name === 'AbortError') {
272
- console.log(`\n ${c.yellow}Could not reach the platform (timed out).${c.reset}`);
307
+ console.log(
308
+ `\n ${c.yellow}Could not reach the platform (timed out).${c.reset}`,
309
+ );
273
310
  console.log(` ${c.dim}Host: ${platformUrl}${c.reset}`);
274
311
  } else {
275
312
  console.log(`\n ${c.red}✗ Couldn't send your message.${c.reset}`);
276
313
  }
277
- console.log(` Please email ${c.cyan}${SUPPORT_EMAIL}${c.reset} directly.\n`);
314
+ console.log(
315
+ ` Please email ${c.cyan}${SUPPORT_EMAIL}${c.reset} directly.\n`,
316
+ );
278
317
  process.exitCode = 1;
279
318
  }
280
319
  }
@@ -7,5 +7,8 @@ const { modelCapabilities = {} } = JSON.parse(
7
7
  );
8
8
 
9
9
  export function modelSupportsVision(modelId) {
10
- return Object.hasOwn(modelCapabilities, modelId) && modelCapabilities[modelId].vision === true;
10
+ return (
11
+ Object.hasOwn(modelCapabilities, modelId) &&
12
+ modelCapabilities[modelId].vision === true
13
+ );
11
14
  }
package/bin/models.js CHANGED
@@ -99,8 +99,10 @@ export async function fetchGatewayModels({
99
99
  if (signal?.aborted) throw abortError('Model discovery was cancelled');
100
100
  const aborted = new Promise((_, reject) => {
101
101
  const fail = () => {
102
- if (signal?.aborted) reject(abortError('Model discovery was cancelled'));
103
- else reject(new Error(`Model discovery timed out after ${timeoutMs}ms`));
102
+ if (signal?.aborted)
103
+ reject(abortError('Model discovery was cancelled'));
104
+ else
105
+ reject(new Error(`Model discovery timed out after ${timeoutMs}ms`));
104
106
  };
105
107
  controller.signal.addEventListener('abort', fail, { once: true });
106
108
  });
@@ -144,13 +146,17 @@ async function fetchPublicModels({ baseUrl, fetchImpl, timeoutMs, signal }) {
144
146
  timeoutMs,
145
147
  signal,
146
148
  });
147
- if (!models.length) throw new Error('Model discovery returned no usable model IDs');
149
+ if (!models.length)
150
+ throw new Error('Model discovery returned no usable model IDs');
148
151
  return models;
149
152
  }
150
153
 
151
154
  function packagedCatalog(selectedModel, fallbackModels, error) {
152
155
  return {
153
- models: normalizeModelIds([selectedModel, ...fallbackModels], selectedModel),
156
+ models: normalizeModelIds(
157
+ [selectedModel, ...fallbackModels],
158
+ selectedModel,
159
+ ),
154
160
  source: 'packaged',
155
161
  error,
156
162
  };
@@ -189,7 +195,12 @@ export async function resolveModelCatalog({
189
195
  } catch (availableError) {
190
196
  if (isAbortError(availableError) || signal?.aborted) throw availableError;
191
197
  try {
192
- const discovered = await fetchPublicModels({ baseUrl, fetchImpl, timeoutMs, signal });
198
+ const discovered = await fetchPublicModels({
199
+ baseUrl,
200
+ fetchImpl,
201
+ timeoutMs,
202
+ signal,
203
+ });
193
204
  return liveCatalog(discovered, selectedModel, 'public');
194
205
  } catch (publicError) {
195
206
  if (isAbortError(publicError) || signal?.aborted) throw publicError;
@@ -199,7 +210,12 @@ export async function resolveModelCatalog({
199
210
  }
200
211
 
201
212
  try {
202
- const discovered = await fetchPublicModels({ baseUrl, fetchImpl, timeoutMs, signal });
213
+ const discovered = await fetchPublicModels({
214
+ baseUrl,
215
+ fetchImpl,
216
+ timeoutMs,
217
+ signal,
218
+ });
203
219
  return liveCatalog(discovered, selectedModel, 'public');
204
220
  } catch (error) {
205
221
  if (isAbortError(error) || signal?.aborted) throw error;
Binary file
Binary file
Binary file
Binary file