subconscious-cli 5.0.1 → 5.0.3
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/cli.js +21 -0
- package/bin/clipboard-image.js +93 -0
- package/bin/feedback.js +299 -0
- package/bin/native/subc-tui-darwin-amd64 +0 -0
- package/bin/native/subc-tui-darwin-arm64 +0 -0
- package/bin/native/subc-tui-linux-amd64 +0 -0
- package/bin/native/subc-tui-linux-arm64 +0 -0
- package/bin/native/subc-tui-windows-amd64.exe +0 -0
- package/bin/native/subc-tui-windows-arm64.exe +0 -0
- package/bin/registry.generated.json +1 -0
- package/package.json +2 -2
package/bin/cli.js
CHANGED
|
@@ -46,6 +46,7 @@ import { showUpdateNotice } from './update-check.js';
|
|
|
46
46
|
import { runTui } from './tui.js';
|
|
47
47
|
import { sessionsCommand } from './sessions.js';
|
|
48
48
|
import { printUsageHelp, usageCommand } from './usage.js';
|
|
49
|
+
import { feedbackCommand, printFeedbackHelp } from './feedback.js';
|
|
49
50
|
|
|
50
51
|
function isHelpArg(arg) {
|
|
51
52
|
return arg === 'help' || arg === '-h' || arg === '--help';
|
|
@@ -70,6 +71,7 @@ function printHelp() {
|
|
|
70
71
|
${c.cyan}logout${c.reset} Remove saved credentials
|
|
71
72
|
${c.cyan}whoami${c.reset} Show current authentication status
|
|
72
73
|
${c.cyan}usage${c.reset} Show billing, quota, and model usage
|
|
74
|
+
${c.cyan}feedback${c.reset} Send feedback or get help from support
|
|
73
75
|
${c.cyan}upgrade${c.reset} Upgrade this CLI to the latest version
|
|
74
76
|
|
|
75
77
|
${c.bold}Profiles${c.reset}
|
|
@@ -100,6 +102,7 @@ ${agents}
|
|
|
100
102
|
${c.dim}$${c.reset} subc sessions resume claude:SESSION_ID --harness codex
|
|
101
103
|
${c.dim}$${c.reset} subc usage
|
|
102
104
|
${c.dim}$${c.reset} subc usage --json
|
|
105
|
+
${c.dim}$${c.reset} subc feedback --subject "Bug" --message "Playground won't load"
|
|
103
106
|
${c.dim}$${c.reset} subc claude
|
|
104
107
|
${c.dim}$${c.reset} subc claude help
|
|
105
108
|
${c.dim}$${c.reset} subc cursor install
|
|
@@ -179,6 +182,14 @@ Usage:
|
|
|
179
182
|
subc usage help
|
|
180
183
|
|
|
181
184
|
Show billing mode, daily token allowance, credit balance, and per-model usage.
|
|
185
|
+
`,
|
|
186
|
+
feedback: `
|
|
187
|
+
Usage:
|
|
188
|
+
subc feedback
|
|
189
|
+
subc feedback -s "..." -m "..."
|
|
190
|
+
subc feedback help
|
|
191
|
+
|
|
192
|
+
Send a message to the Subconscious support team.
|
|
182
193
|
`,
|
|
183
194
|
models: `
|
|
184
195
|
Usage:
|
|
@@ -445,6 +456,16 @@ async function main() {
|
|
|
445
456
|
return;
|
|
446
457
|
}
|
|
447
458
|
|
|
459
|
+
if (command === 'feedback') {
|
|
460
|
+
if (isHelpArg(args[1])) {
|
|
461
|
+
printFeedbackHelp();
|
|
462
|
+
return;
|
|
463
|
+
}
|
|
464
|
+
const profile = await loadProfile(profileName);
|
|
465
|
+
await feedbackCommand(args.slice(1), { profile, profileName });
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
448
469
|
const agent = resolveAgent(command);
|
|
449
470
|
if (agent) {
|
|
450
471
|
const agentArgs = args.slice(1);
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads a PNG or JPEG from the OS clipboard. Terminal Ctrl+V does not deliver
|
|
3
|
+
* image pixels, so the feedback prompt calls this when that key is pressed.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { execFile } from 'node:child_process';
|
|
7
|
+
import { promisify } from 'node:util';
|
|
8
|
+
|
|
9
|
+
const execFileAsync = promisify(execFile);
|
|
10
|
+
|
|
11
|
+
const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
12
|
+
|
|
13
|
+
export function imageContentType(buffer) {
|
|
14
|
+
if (buffer.length >= 3 && buffer[0] === 0xff && buffer[1] === 0xd8 && buffer[2] === 0xff) {
|
|
15
|
+
return 'image/jpeg';
|
|
16
|
+
}
|
|
17
|
+
if (buffer.length >= PNG_SIGNATURE.length && buffer.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) {
|
|
18
|
+
return 'image/png';
|
|
19
|
+
}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function readDarwinClipboard() {
|
|
24
|
+
const script = `
|
|
25
|
+
try
|
|
26
|
+
set pngData to the clipboard as «class PNGf»
|
|
27
|
+
on error
|
|
28
|
+
return "NONE"
|
|
29
|
+
end try
|
|
30
|
+
set outPath to POSIX path of (path to temporary items from user domain) & "subc-clipboard.png"
|
|
31
|
+
set outFile to open for access POSIX file outPath with write permission
|
|
32
|
+
set eof of outFile to 0
|
|
33
|
+
write pngData to outFile
|
|
34
|
+
close access outFile
|
|
35
|
+
return outPath
|
|
36
|
+
`;
|
|
37
|
+
const { stdout } = await execFileAsync('osascript', ['-e', script]);
|
|
38
|
+
const outPath = String(stdout).trim();
|
|
39
|
+
if (!outPath || outPath === 'NONE') return null;
|
|
40
|
+
const { readFile, rm } = await import('node:fs/promises');
|
|
41
|
+
try {
|
|
42
|
+
return await readFile(outPath);
|
|
43
|
+
} finally {
|
|
44
|
+
await rm(outPath, { force: true });
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
async function readLinuxClipboard() {
|
|
49
|
+
const commands = [
|
|
50
|
+
['wl-paste', ['--type', 'image/png']],
|
|
51
|
+
['xclip', ['-selection', 'clipboard', '-t', 'image/png', '-o']],
|
|
52
|
+
];
|
|
53
|
+
for (const [command, args] of commands) {
|
|
54
|
+
try {
|
|
55
|
+
const { stdout } = await execFileAsync(command, args, { encoding: 'buffer', maxBuffer: 8 * 1024 * 1024 });
|
|
56
|
+
if (stdout?.length) return stdout;
|
|
57
|
+
} catch {
|
|
58
|
+
// Try the next clipboard tool.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
async function readWindowsClipboard() {
|
|
65
|
+
const script = `
|
|
66
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
67
|
+
Add-Type -AssemblyName System.Drawing
|
|
68
|
+
$image = [System.Windows.Forms.Clipboard]::GetImage()
|
|
69
|
+
if ($null -eq $image) { exit 2 }
|
|
70
|
+
$path = Join-Path $env:TEMP 'subc-clipboard.png'
|
|
71
|
+
$image.Save($path, [System.Drawing.Imaging.ImageFormat]::Png)
|
|
72
|
+
Write-Output $path
|
|
73
|
+
`;
|
|
74
|
+
try {
|
|
75
|
+
const { stdout } = await execFileAsync('powershell', ['-NoProfile', '-Command', script]);
|
|
76
|
+
const outPath = String(stdout).trim();
|
|
77
|
+
if (!outPath) return null;
|
|
78
|
+
const { readFile, rm } = await import('node:fs/promises');
|
|
79
|
+
try {
|
|
80
|
+
return await readFile(outPath);
|
|
81
|
+
} finally {
|
|
82
|
+
await rm(outPath, { force: true });
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export async function readClipboardImage() {
|
|
90
|
+
if (process.platform === 'darwin') return readDarwinClipboard();
|
|
91
|
+
if (process.platform === 'win32') return readWindowsClipboard();
|
|
92
|
+
return readLinuxClipboard();
|
|
93
|
+
}
|
package/bin/feedback.js
ADDED
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `subc feedback` — send a message to the Subconscious support team.
|
|
3
|
+
*
|
|
4
|
+
* POSTs to the platform's /api/cli/feedback endpoint with the profile's API
|
|
5
|
+
* key; the platform emails support@subconscious.dev and posts to Slack.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'node:fs/promises';
|
|
9
|
+
import os from 'node:os';
|
|
10
|
+
import readline from 'node:readline';
|
|
11
|
+
import { createInterface } from 'node:readline/promises';
|
|
12
|
+
import { c } from './colors.js';
|
|
13
|
+
import { getApiKey, getPlatformUrl } from './auth.js';
|
|
14
|
+
import { imageContentType, readClipboardImage } from './clipboard-image.js';
|
|
15
|
+
import { DEFAULT_PROFILE } from './profiles.js';
|
|
16
|
+
|
|
17
|
+
export const FEEDBACK_API_PATH = '/api/cli/feedback';
|
|
18
|
+
export const SUPPORT_EMAIL = 'support@subconscious.dev';
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Parses feedback command arguments. Shared with tests.
|
|
22
|
+
*
|
|
23
|
+
* -s, --subject <text> One-line summary (default: "CLI feedback")
|
|
24
|
+
* -m, --message <text> The message body (prompts when omitted on a TTY)
|
|
25
|
+
* --image <path> JPEG or PNG to attach (repeatable)
|
|
26
|
+
*/
|
|
27
|
+
export function parseFeedbackArgs(argv = []) {
|
|
28
|
+
let subject = '';
|
|
29
|
+
let message = '';
|
|
30
|
+
const images = [];
|
|
31
|
+
for (let i = 0; i < argv.length; i++) {
|
|
32
|
+
const arg = argv[i];
|
|
33
|
+
if (arg === '-s' || arg === '--subject') {
|
|
34
|
+
subject = argv[++i] ?? '';
|
|
35
|
+
} else if (arg.startsWith('--subject=')) {
|
|
36
|
+
subject = arg.slice('--subject='.length);
|
|
37
|
+
} else if (arg === '-m' || arg === '--message') {
|
|
38
|
+
message = argv[++i] ?? '';
|
|
39
|
+
} else if (arg.startsWith('--message=')) {
|
|
40
|
+
message = arg.slice('--message='.length);
|
|
41
|
+
} else if (arg === '--image') {
|
|
42
|
+
images.push(argv[++i] ?? '');
|
|
43
|
+
} else if (arg.startsWith('--image=')) {
|
|
44
|
+
images.push(arg.slice('--image='.length));
|
|
45
|
+
} else if (arg !== '') {
|
|
46
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return { subject: subject.trim(), message: message.trim(), images };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Triage context sent alongside the message. Never includes the API key. */
|
|
53
|
+
export async function collectFeedbackContext(options = {}) {
|
|
54
|
+
const pkgPath = new URL('../package.json', import.meta.url);
|
|
55
|
+
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf-8'));
|
|
56
|
+
const platformUrl = getPlatformUrl(options.profile);
|
|
57
|
+
return {
|
|
58
|
+
'CLI version': pkg.version,
|
|
59
|
+
Profile: options.profileName ?? DEFAULT_PROFILE,
|
|
60
|
+
'Platform URL': platformUrl,
|
|
61
|
+
'Gateway URL': options.profile?.values?.GATEWAY_URL?.trim() ?? '',
|
|
62
|
+
Node: process.version,
|
|
63
|
+
OS: `${os.platform()} ${os.release()}`,
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function buildFeedbackPayload({ subject, message, context, attachments }) {
|
|
68
|
+
const payload = {
|
|
69
|
+
subject: subject || 'CLI feedback',
|
|
70
|
+
message,
|
|
71
|
+
context: Object.fromEntries(
|
|
72
|
+
Object.entries(context ?? {}).filter(([, value]) => Boolean(String(value).trim())),
|
|
73
|
+
),
|
|
74
|
+
};
|
|
75
|
+
if (attachments?.length) payload.attachments = attachments;
|
|
76
|
+
return payload;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export async function loadFeedbackImages(paths) {
|
|
80
|
+
const attachments = [];
|
|
81
|
+
for (const filePath of paths) {
|
|
82
|
+
if (!filePath?.trim()) throw new Error('An image path is required after --image');
|
|
83
|
+
const buffer = await fs.readFile(filePath);
|
|
84
|
+
const contentType = imageContentType(buffer);
|
|
85
|
+
if (!contentType) throw new Error(`${filePath} is not a JPEG or PNG`);
|
|
86
|
+
attachments.push({ contentType, data: buffer.toString('base64') });
|
|
87
|
+
}
|
|
88
|
+
return attachments;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function submitFeedback(apiKey, platformUrl, payload, fetchImpl = fetch) {
|
|
92
|
+
const res = await fetchImpl(
|
|
93
|
+
`${platformUrl.replace(/\/$/, '')}${FEEDBACK_API_PATH}`,
|
|
94
|
+
{
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: {
|
|
97
|
+
Authorization: `Bearer ${apiKey}`,
|
|
98
|
+
'Content-Type': 'application/json',
|
|
99
|
+
},
|
|
100
|
+
body: JSON.stringify(payload),
|
|
101
|
+
signal: AbortSignal.timeout(payload.attachments?.length ? 30000 : 8000),
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
return res;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const MAX_FEEDBACK_IMAGES = 3;
|
|
108
|
+
|
|
109
|
+
function readAttachmentLine(onPaste) {
|
|
110
|
+
return new Promise((resolve) => {
|
|
111
|
+
const rl = readline.createInterface({
|
|
112
|
+
input: process.stdin,
|
|
113
|
+
output: process.stdout,
|
|
114
|
+
prompt: ' Attachments: ',
|
|
115
|
+
});
|
|
116
|
+
readline.emitKeypressEvents(process.stdin, rl);
|
|
117
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(true);
|
|
118
|
+
rl.prompt();
|
|
119
|
+
let line = '';
|
|
120
|
+
const finish = (value) => {
|
|
121
|
+
process.stdin.off('keypress', onKey);
|
|
122
|
+
if (process.stdin.isTTY) process.stdin.setRawMode(false);
|
|
123
|
+
rl.close();
|
|
124
|
+
resolve(value);
|
|
125
|
+
};
|
|
126
|
+
const onKey = (str, key) => {
|
|
127
|
+
if (!key) return;
|
|
128
|
+
if (key.ctrl && key.name === 'c') {
|
|
129
|
+
finish(null);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (key.ctrl && key.name === 'v') {
|
|
133
|
+
void onPaste();
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (key.name === 'return') {
|
|
137
|
+
process.stdout.write('\n');
|
|
138
|
+
if (line.trim()) {
|
|
139
|
+
console.log(
|
|
140
|
+
` ${c.dim}Paste a screenshot with Ctrl+V. Press Enter with nothing typed to continue.${c.reset}`,
|
|
141
|
+
);
|
|
142
|
+
line = '';
|
|
143
|
+
rl.prompt();
|
|
144
|
+
return;
|
|
145
|
+
}
|
|
146
|
+
finish('');
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
if (key.name === 'backspace') {
|
|
150
|
+
if (!line.length) return;
|
|
151
|
+
line = line.slice(0, -1);
|
|
152
|
+
process.stdout.write('\b \b');
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
if (str && !key.ctrl && !key.meta) {
|
|
156
|
+
line += str;
|
|
157
|
+
process.stdout.write(str);
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
process.stdin.on('keypress', onKey);
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function promptForAttachments(already = 0) {
|
|
165
|
+
if (process.stdin.isTTY !== true || already >= MAX_FEEDBACK_IMAGES) return [];
|
|
166
|
+
const attachments = [];
|
|
167
|
+
console.log(`\n Attachments (optional). Ctrl+V pastes a screenshot from the clipboard.`);
|
|
168
|
+
console.log(` ${c.dim}Press Enter when you are done.${c.reset}`);
|
|
169
|
+
while (already + attachments.length < MAX_FEEDBACK_IMAGES) {
|
|
170
|
+
const line = await readAttachmentLine(async () => {
|
|
171
|
+
if (already + attachments.length >= MAX_FEEDBACK_IMAGES) return;
|
|
172
|
+
const buffer = await readClipboardImage();
|
|
173
|
+
const contentType = buffer ? imageContentType(buffer) : null;
|
|
174
|
+
if (!contentType || !buffer) {
|
|
175
|
+
console.log(`\n ${c.yellow}No JPEG or PNG image on the clipboard.${c.reset}`);
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
attachments.push({ contentType, data: buffer.toString('base64') });
|
|
179
|
+
console.log(` ${c.green}Added screenshot ${already + attachments.length}.${c.reset}`);
|
|
180
|
+
});
|
|
181
|
+
if (line == null || !line.trim()) break;
|
|
182
|
+
}
|
|
183
|
+
return attachments;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async function promptForFeedback(argv) {
|
|
187
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
188
|
+
try {
|
|
189
|
+
const subject = argv.subject || (await rl.question(`\n Subject (optional): `)).trim();
|
|
190
|
+
let message = '';
|
|
191
|
+
while (!message) {
|
|
192
|
+
message = (await rl.question(` Message: `)).trim();
|
|
193
|
+
if (!message) console.log(` ${c.dim}Message cannot be empty.${c.reset}`);
|
|
194
|
+
}
|
|
195
|
+
return { subject, message };
|
|
196
|
+
} finally {
|
|
197
|
+
rl.close();
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
export async function feedbackCommand(argv = [], options = {}) {
|
|
202
|
+
const parsed = parseFeedbackArgs(argv);
|
|
203
|
+
const profileFlag =
|
|
204
|
+
options.profileName && options.profileName !== DEFAULT_PROFILE
|
|
205
|
+
? `--profile ${options.profileName} `
|
|
206
|
+
: '';
|
|
207
|
+
|
|
208
|
+
const auth = await getApiKey(options.profile);
|
|
209
|
+
if (!auth) {
|
|
210
|
+
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`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
let feedback = parsed;
|
|
216
|
+
if (!feedback.message) {
|
|
217
|
+
if (process.stdin.isTTY !== true) {
|
|
218
|
+
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`);
|
|
220
|
+
process.exitCode = 1;
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
feedback = await promptForFeedback(parsed);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const fromFlags = await loadFeedbackImages(parsed.images);
|
|
227
|
+
const fromPrompt = feedback.message && parsed.message ? [] : await promptForAttachments(fromFlags.length);
|
|
228
|
+
const platformUrl = getPlatformUrl(options.profile);
|
|
229
|
+
const payload = buildFeedbackPayload({
|
|
230
|
+
subject: feedback.subject,
|
|
231
|
+
message: feedback.message,
|
|
232
|
+
context: await collectFeedbackContext(options),
|
|
233
|
+
attachments: [...fromFlags, ...fromPrompt],
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
const res = await submitFeedback(auth.key, platformUrl, payload);
|
|
238
|
+
|
|
239
|
+
if (res.status === 404) {
|
|
240
|
+
console.error(`\n ${c.yellow}This platform has no feedback endpoint (404).${c.reset}`);
|
|
241
|
+
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`);
|
|
243
|
+
process.exitCode = 1;
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
let data;
|
|
248
|
+
try {
|
|
249
|
+
data = await res.json();
|
|
250
|
+
} catch {
|
|
251
|
+
data = {};
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
if (!res.ok) {
|
|
255
|
+
const detail = typeof data?.error === 'string' ? data.error : '';
|
|
256
|
+
const invalidKey = detail === 'Invalid or revoked API key' || detail === 'No API key provided';
|
|
257
|
+
console.log(`\n ${c.red}✗ Couldn't send your message.${c.reset}`);
|
|
258
|
+
if (invalidKey) {
|
|
259
|
+
console.log(
|
|
260
|
+
` Run ${c.cyan}subc ${profileFlag}logout${c.reset} then ${c.cyan}subc ${profileFlag}login${c.reset} to re-authenticate.`,
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
console.log(` Please email ${c.cyan}${SUPPORT_EMAIL}${c.reset} directly.\n`);
|
|
264
|
+
process.exitCode = 1;
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
console.log(`\n ${c.green}✓${c.reset} Feedback sent to ${SUPPORT_EMAIL}.`);
|
|
269
|
+
console.log(` ${c.dim}Our support team will get back to you.\n`);
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (error.name === 'TimeoutError' || error.name === 'AbortError') {
|
|
272
|
+
console.log(`\n ${c.yellow}Could not reach the platform (timed out).${c.reset}`);
|
|
273
|
+
console.log(` ${c.dim}Host: ${platformUrl}${c.reset}`);
|
|
274
|
+
} else {
|
|
275
|
+
console.log(`\n ${c.red}✗ Couldn't send your message.${c.reset}`);
|
|
276
|
+
}
|
|
277
|
+
console.log(` Please email ${c.cyan}${SUPPORT_EMAIL}${c.reset} directly.\n`);
|
|
278
|
+
process.exitCode = 1;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function printFeedbackHelp() {
|
|
283
|
+
console.log(`
|
|
284
|
+
Usage:
|
|
285
|
+
subc feedback
|
|
286
|
+
subc feedback --subject "..." --message "..."
|
|
287
|
+
subc feedback --image ./screenshot.png
|
|
288
|
+
subc -p NAME feedback
|
|
289
|
+
subc feedback help
|
|
290
|
+
|
|
291
|
+
Send a message to the Subconscious support team (${SUPPORT_EMAIL}).
|
|
292
|
+
Your CLI version, profile, and environment are included so support can
|
|
293
|
+
triage without a back-and-forth.
|
|
294
|
+
|
|
295
|
+
-s, --subject <text> One-line summary (prompted when omitted on a TTY)
|
|
296
|
+
-m, --message <text> The message body (prompted when omitted on a TTY)
|
|
297
|
+
--image <path> JPEG or PNG to attach (repeatable). With -m, skips the paste prompt.
|
|
298
|
+
`);
|
|
299
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "subconscious-cli",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.3",
|
|
4
4
|
"description": "CLI for Subconscious — run Subconscious Code, Claude Code, Codex, OpenCode, DeepSeek Harness, Cursor, Copilot, and Pi",
|
|
5
5
|
"bin": {
|
|
6
6
|
"subc": "bin/cli.js"
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
"build:tui": "node scripts/build-tui.js",
|
|
10
10
|
"build:tui:host": "node scripts/build-tui.js --host",
|
|
11
11
|
"test": "node --test",
|
|
12
|
-
"test:windows": "node --test test/windows.test.js test/models.test.js test/model-capabilities.test.js test/auth.test.js test/tui.test.js test/update-check.test.js test/sessions.test.js test/branding.test.js test/claude-version.test.js test/usage.test.js test/release.test.js",
|
|
12
|
+
"test:windows": "node --test test/windows.test.js test/models.test.js test/model-capabilities.test.js test/auth.test.js test/tui.test.js test/update-check.test.js test/sessions.test.js test/branding.test.js test/claude-version.test.js test/usage.test.js test/feedback.test.js test/release.test.js",
|
|
13
13
|
"test:tui": "go -C tui test ./...",
|
|
14
14
|
"prepack": "npm run build:tui",
|
|
15
15
|
"prepublishOnly": "node scripts/require-publish-script.js"
|