@agentuity/cli 0.1.34 → 0.1.36
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.ts +110 -6
- package/dist/cmd/ai/index.d.ts.map +1 -1
- package/dist/cmd/ai/index.js +0 -6
- package/dist/cmd/ai/index.js.map +1 -1
- package/dist/cmd/ai/opencode/uninstall.js +1 -1
- package/dist/cmd/ai/opencode/uninstall.js.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.d.ts +6 -0
- package/dist/cmd/build/vite/bun-dev-server.d.ts.map +1 -1
- package/dist/cmd/build/vite/bun-dev-server.js +100 -33
- package/dist/cmd/build/vite/bun-dev-server.js.map +1 -1
- package/dist/cmd/cloud/apikey/create.d.ts.map +1 -1
- package/dist/cmd/cloud/apikey/create.js +1 -1
- package/dist/cmd/cloud/apikey/create.js.map +1 -1
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +4 -1
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/env/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/env/delete.js +100 -45
- package/dist/cmd/cloud/env/delete.js.map +1 -1
- package/dist/cmd/cloud/env/pull.d.ts.map +1 -1
- package/dist/cmd/cloud/env/pull.js +1 -1
- package/dist/cmd/cloud/env/pull.js.map +1 -1
- package/dist/cmd/cloud/machine/delete.js +1 -1
- package/dist/cmd/cloud/machine/delete.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +58 -10
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +1 -0
- package/dist/cmd/index.js.map +1 -1
- package/dist/cmd/support/index.d.ts +2 -0
- package/dist/cmd/support/index.d.ts.map +1 -0
- package/dist/cmd/support/index.js +11 -0
- package/dist/cmd/support/index.js.map +1 -0
- package/dist/cmd/support/logs/index.d.ts +3 -0
- package/dist/cmd/support/logs/index.d.ts.map +1 -0
- package/dist/cmd/support/logs/index.js +9 -0
- package/dist/cmd/support/logs/index.js.map +1 -0
- package/dist/cmd/support/logs/path.d.ts +3 -0
- package/dist/cmd/support/logs/path.d.ts.map +1 -0
- package/dist/cmd/support/logs/path.js +52 -0
- package/dist/cmd/support/logs/path.js.map +1 -0
- package/dist/cmd/support/logs/show.d.ts +3 -0
- package/dist/cmd/support/logs/show.d.ts.map +1 -0
- package/dist/cmd/support/logs/show.js +121 -0
- package/dist/cmd/support/logs/show.js.map +1 -0
- package/dist/cmd/support/report.d.ts +3 -0
- package/dist/cmd/support/report.d.ts.map +1 -0
- package/dist/cmd/support/report.js +299 -0
- package/dist/cmd/support/report.js.map +1 -0
- package/dist/cmd/support/system.d.ts +3 -0
- package/dist/cmd/support/system.d.ts.map +1 -0
- package/dist/cmd/support/system.js +120 -0
- package/dist/cmd/support/system.js.map +1 -0
- package/dist/cmd/version/index.d.ts.map +1 -1
- package/dist/cmd/version/index.js +1 -0
- package/dist/cmd/version/index.js.map +1 -1
- package/dist/composite-logger.d.ts +35 -0
- package/dist/composite-logger.d.ts.map +1 -0
- package/dist/composite-logger.js +78 -0
- package/dist/composite-logger.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/internal-logger.d.ts +77 -0
- package/dist/internal-logger.d.ts.map +1 -0
- package/dist/internal-logger.js +363 -0
- package/dist/internal-logger.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +6 -6
- package/src/cmd/ai/index.ts +0 -6
- package/src/cmd/ai/opencode/uninstall.ts +1 -1
- package/src/cmd/build/vite/bun-dev-server.ts +113 -36
- package/src/cmd/cloud/apikey/create.ts +3 -1
- package/src/cmd/cloud/deploy.ts +4 -1
- package/src/cmd/cloud/env/delete.ts +100 -45
- package/src/cmd/cloud/env/pull.ts +1 -6
- package/src/cmd/cloud/machine/delete.ts +1 -1
- package/src/cmd/dev/index.ts +59 -11
- package/src/cmd/index.ts +1 -0
- package/src/cmd/support/index.ts +11 -0
- package/src/cmd/support/logs/index.ts +9 -0
- package/src/cmd/support/logs/path.ts +56 -0
- package/src/cmd/support/logs/show.ts +144 -0
- package/src/cmd/support/report.ts +364 -0
- package/src/cmd/support/system.ts +130 -0
- package/src/cmd/version/index.ts +1 -0
- package/src/composite-logger.ts +86 -0
- package/src/index.ts +7 -0
- package/src/internal-logger.ts +435 -0
- package/src/types.ts +6 -0
- package/dist/cmd/ai/skills/generate.d.ts +0 -3
- package/dist/cmd/ai/skills/generate.d.ts.map +0 -1
- package/dist/cmd/ai/skills/generate.js +0 -65
- package/dist/cmd/ai/skills/generate.js.map +0 -1
- package/dist/cmd/ai/skills/generator.d.ts +0 -4
- package/dist/cmd/ai/skills/generator.d.ts.map +0 -1
- package/dist/cmd/ai/skills/generator.js +0 -410
- package/dist/cmd/ai/skills/generator.js.map +0 -1
- package/dist/cmd/ai/skills/index.d.ts +0 -4
- package/dist/cmd/ai/skills/index.d.ts.map +0 -1
- package/dist/cmd/ai/skills/index.js +0 -21
- package/dist/cmd/ai/skills/index.js.map +0 -1
- package/dist/cmd/dev/skills.d.ts +0 -10
- package/dist/cmd/dev/skills.d.ts.map +0 -1
- package/dist/cmd/dev/skills.js +0 -57
- package/dist/cmd/dev/skills.js.map +0 -1
- package/src/cmd/ai/skills/generate.ts +0 -75
- package/src/cmd/ai/skills/generator.ts +0 -527
- package/src/cmd/ai/skills/index.ts +0 -23
- package/src/cmd/dev/skills.ts +0 -82
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { createCommand } from '../../types';
|
|
2
|
+
import logs from './logs';
|
|
3
|
+
import report from './report';
|
|
4
|
+
import system from './system';
|
|
5
|
+
|
|
6
|
+
export const command = createCommand({
|
|
7
|
+
name: 'support',
|
|
8
|
+
description: 'Create support tickets and report issues',
|
|
9
|
+
skipInternalLogging: true,
|
|
10
|
+
subcommands: [logs, report, system],
|
|
11
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { createSubcommand } from '../../../types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { getLatestLogSession, getLogsDirPath } from '../../../internal-logger';
|
|
4
|
+
import * as tui from '../../../tui';
|
|
5
|
+
|
|
6
|
+
const argsSchema = z.object({});
|
|
7
|
+
|
|
8
|
+
const optionsSchema = z.object({
|
|
9
|
+
logs: z.boolean().optional().default(false).describe('Show path to logs directory instead of session'),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export default createSubcommand({
|
|
13
|
+
name: 'path',
|
|
14
|
+
description: 'Show the path to the most recent log session',
|
|
15
|
+
schema: {
|
|
16
|
+
args: argsSchema,
|
|
17
|
+
options: optionsSchema,
|
|
18
|
+
},
|
|
19
|
+
handler: async (ctx) => {
|
|
20
|
+
const { opts } = ctx;
|
|
21
|
+
const isJsonMode = ctx.options.json;
|
|
22
|
+
|
|
23
|
+
if (opts.logs) {
|
|
24
|
+
// Show the logs directory path
|
|
25
|
+
const logsDir = getLogsDirPath();
|
|
26
|
+
if (isJsonMode) {
|
|
27
|
+
console.log(JSON.stringify({ logsDir }));
|
|
28
|
+
} else {
|
|
29
|
+
console.log(logsDir);
|
|
30
|
+
}
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const sessionDir = getLatestLogSession();
|
|
35
|
+
if (!sessionDir) {
|
|
36
|
+
if (isJsonMode) {
|
|
37
|
+
console.log(
|
|
38
|
+
JSON.stringify({
|
|
39
|
+
error: 'No logs found',
|
|
40
|
+
logsDir: getLogsDirPath(),
|
|
41
|
+
})
|
|
42
|
+
);
|
|
43
|
+
} else {
|
|
44
|
+
tui.warning('No CLI logs found');
|
|
45
|
+
tui.info(`Logs directory: ${tui.muted(getLogsDirPath())}`);
|
|
46
|
+
}
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (isJsonMode) {
|
|
51
|
+
console.log(JSON.stringify({ sessionDir }));
|
|
52
|
+
} else {
|
|
53
|
+
console.log(sessionDir);
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { createSubcommand } from '../../../types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
4
|
+
import { getLatestLogSession, getLogsDirPath } from '../../../internal-logger';
|
|
5
|
+
import * as tui from '../../../tui';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
|
|
8
|
+
const argsSchema = z.object({});
|
|
9
|
+
|
|
10
|
+
const optionsSchema = z.object({
|
|
11
|
+
session: z.boolean().optional().default(false).describe('Show session metadata only'),
|
|
12
|
+
tail: z.number().optional().describe('Show last N log entries'),
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
export default createSubcommand({
|
|
16
|
+
name: 'show',
|
|
17
|
+
description: 'Show the most recent CLI command logs',
|
|
18
|
+
schema: {
|
|
19
|
+
args: argsSchema,
|
|
20
|
+
options: optionsSchema,
|
|
21
|
+
},
|
|
22
|
+
handler: async (ctx) => {
|
|
23
|
+
const { opts } = ctx;
|
|
24
|
+
const isJsonMode = ctx.options.json;
|
|
25
|
+
|
|
26
|
+
const sessionDir = getLatestLogSession();
|
|
27
|
+
if (!sessionDir) {
|
|
28
|
+
if (isJsonMode) {
|
|
29
|
+
console.log(
|
|
30
|
+
JSON.stringify({
|
|
31
|
+
error: 'No logs found',
|
|
32
|
+
logsDir: getLogsDirPath(),
|
|
33
|
+
})
|
|
34
|
+
);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
tui.warning('No CLI logs found');
|
|
38
|
+
tui.info(`Logs are stored in: ${tui.muted(getLogsDirPath())}`);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const sessionFile = join(sessionDir, 'session.json');
|
|
43
|
+
const logsFile = join(sessionDir, 'logs.jsonl');
|
|
44
|
+
|
|
45
|
+
if (!existsSync(sessionFile)) {
|
|
46
|
+
if (isJsonMode) {
|
|
47
|
+
console.log(JSON.stringify({ error: 'Session metadata not found' }));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
tui.error('Session metadata not found');
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Read session metadata
|
|
55
|
+
const sessionData = JSON.parse(readFileSync(sessionFile, 'utf-8'));
|
|
56
|
+
|
|
57
|
+
if (opts.session) {
|
|
58
|
+
// Show session metadata only
|
|
59
|
+
if (isJsonMode) {
|
|
60
|
+
console.log(JSON.stringify(sessionData, null, 2));
|
|
61
|
+
} else {
|
|
62
|
+
tui.info(tui.bold('CLI Command Session'));
|
|
63
|
+
tui.newline();
|
|
64
|
+
tui.output(`Session ID: ${tui.colorPrimary(sessionData.sessionId)}`);
|
|
65
|
+
tui.output(`Command: ${tui.colorInfo(sessionData.command)}`);
|
|
66
|
+
tui.output(`Args: ${sessionData.args.join(' ')}`);
|
|
67
|
+
tui.output(`Timestamp: ${sessionData.timestamp}`);
|
|
68
|
+
tui.output(`CLI Version: ${sessionData.cli.version}`);
|
|
69
|
+
tui.output(`Platform: ${sessionData.system.platform}/${sessionData.system.arch}`);
|
|
70
|
+
tui.output(`CWD: ${sessionData.cwd}`);
|
|
71
|
+
tui.newline();
|
|
72
|
+
tui.output(tui.muted(`Session directory: ${sessionDir}`));
|
|
73
|
+
}
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Read log entries
|
|
78
|
+
if (!existsSync(logsFile)) {
|
|
79
|
+
if (isJsonMode) {
|
|
80
|
+
console.log(JSON.stringify({ session: sessionData, logs: [] }));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
tui.warning('No log entries found for this session');
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const logLines = readFileSync(logsFile, 'utf-8')
|
|
88
|
+
.split('\n')
|
|
89
|
+
.filter((line) => line.trim());
|
|
90
|
+
const logEntries = logLines.map((line) => JSON.parse(line));
|
|
91
|
+
|
|
92
|
+
// Apply tail filter if specified
|
|
93
|
+
const displayEntries = opts.tail ? logEntries.slice(-opts.tail) : logEntries;
|
|
94
|
+
|
|
95
|
+
if (isJsonMode) {
|
|
96
|
+
console.log(
|
|
97
|
+
JSON.stringify(
|
|
98
|
+
{
|
|
99
|
+
session: sessionData,
|
|
100
|
+
logs: displayEntries,
|
|
101
|
+
},
|
|
102
|
+
null,
|
|
103
|
+
2
|
|
104
|
+
)
|
|
105
|
+
);
|
|
106
|
+
} else {
|
|
107
|
+
// Human-readable output
|
|
108
|
+
tui.info(tui.bold('CLI Command Session'));
|
|
109
|
+
tui.newline();
|
|
110
|
+
tui.output(`Session ID: ${tui.colorPrimary(sessionData.sessionId)}`);
|
|
111
|
+
tui.output(`Command: ${tui.colorInfo(sessionData.command)}`);
|
|
112
|
+
tui.output(`Timestamp: ${sessionData.timestamp}`);
|
|
113
|
+
tui.output(`Total Logs: ${logEntries.length}`);
|
|
114
|
+
if (opts.tail) {
|
|
115
|
+
tui.output(`Showing: Last ${opts.tail} entries`);
|
|
116
|
+
}
|
|
117
|
+
tui.newline();
|
|
118
|
+
|
|
119
|
+
// Display log entries
|
|
120
|
+
for (const entry of displayEntries) {
|
|
121
|
+
const levelColor =
|
|
122
|
+
entry.level === 'error'
|
|
123
|
+
? tui.colorError
|
|
124
|
+
: entry.level === 'warn'
|
|
125
|
+
? tui.warn
|
|
126
|
+
: entry.level === 'info'
|
|
127
|
+
? tui.colorInfo
|
|
128
|
+
: tui.muted;
|
|
129
|
+
|
|
130
|
+
const timestamp = new Date(entry.timestamp).toLocaleTimeString();
|
|
131
|
+
const level = entry.level.toUpperCase().padEnd(5);
|
|
132
|
+
|
|
133
|
+
tui.output(`${tui.muted(timestamp)} ${levelColor(level)} ${entry.message}`);
|
|
134
|
+
|
|
135
|
+
if (entry.context && Object.keys(entry.context).length > 0) {
|
|
136
|
+
tui.output(tui.muted(` Context: ${JSON.stringify(entry.context)}`));
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
tui.newline();
|
|
141
|
+
tui.output(tui.muted(`Session directory: ${sessionDir}`));
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
});
|
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
import { createSubcommand } from '../../types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { getLatestLogSession } from '../../internal-logger';
|
|
7
|
+
import * as tui from '../../tui';
|
|
8
|
+
import { randomBytes } from 'node:crypto';
|
|
9
|
+
import AdmZip from 'adm-zip';
|
|
10
|
+
import { APIResponseSchema } from '@agentuity/server';
|
|
11
|
+
|
|
12
|
+
const argsSchema = z.object({});
|
|
13
|
+
|
|
14
|
+
const optionsSchema = z.object({
|
|
15
|
+
description: z
|
|
16
|
+
.string()
|
|
17
|
+
.optional()
|
|
18
|
+
.describe('Description of the issue (skips prompt if provided)'),
|
|
19
|
+
noOpen: z
|
|
20
|
+
.boolean()
|
|
21
|
+
.optional()
|
|
22
|
+
.default(false)
|
|
23
|
+
.describe('Do not automatically open GitHub issue in browser'),
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const ReportUploadDataSchema = z.object({
|
|
27
|
+
presigned_url: z.string(),
|
|
28
|
+
url: z.string(),
|
|
29
|
+
report_id: z.string(),
|
|
30
|
+
expires_in: z.number(),
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
const ReportUploadResponseSchema = APIResponseSchema(ReportUploadDataSchema);
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Create a zip file containing session and logs
|
|
37
|
+
*/
|
|
38
|
+
async function createReportZip(sessionDir: string): Promise<string> {
|
|
39
|
+
const sessionFile = join(sessionDir, 'session.json');
|
|
40
|
+
const logsFile = join(sessionDir, 'logs.jsonl');
|
|
41
|
+
|
|
42
|
+
if (!existsSync(sessionFile) || !existsSync(logsFile)) {
|
|
43
|
+
throw new Error('Session files not found');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create zip in temp directory
|
|
47
|
+
const tempZip = join(tmpdir(), `agentuity-report-${randomBytes(8).toString('hex')}.zip`);
|
|
48
|
+
|
|
49
|
+
const zip = new AdmZip();
|
|
50
|
+
zip.addLocalFile(sessionFile);
|
|
51
|
+
zip.addLocalFile(logsFile);
|
|
52
|
+
zip.writeZip(tempZip);
|
|
53
|
+
|
|
54
|
+
return tempZip;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Upload the zip file to S3 using presigned URL
|
|
59
|
+
*/
|
|
60
|
+
async function uploadReport(
|
|
61
|
+
presignedUrl: string,
|
|
62
|
+
zipPath: string,
|
|
63
|
+
logger: import('../../types').Logger
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
const fileBuffer = readFileSync(zipPath);
|
|
66
|
+
|
|
67
|
+
const response = await fetch(presignedUrl, {
|
|
68
|
+
method: 'PUT',
|
|
69
|
+
body: fileBuffer,
|
|
70
|
+
headers: {
|
|
71
|
+
'Content-Type': 'application/zip',
|
|
72
|
+
'Content-Length': fileBuffer.length.toString(),
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const errorText = await response.text();
|
|
78
|
+
logger.error('Upload failed', { status: response.status, error: errorText });
|
|
79
|
+
throw new Error(`Upload failed: ${response.statusText}`);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Create GitHub issue URL with pre-filled template
|
|
85
|
+
*/
|
|
86
|
+
interface SessionData {
|
|
87
|
+
cli?: { version?: string };
|
|
88
|
+
system?: { bunVersion?: string; platform?: string; arch?: string };
|
|
89
|
+
command?: string;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function createGitHubIssueUrl(
|
|
93
|
+
description: string,
|
|
94
|
+
reportUrl: string,
|
|
95
|
+
reportId: string,
|
|
96
|
+
sessionData: SessionData
|
|
97
|
+
): string {
|
|
98
|
+
const title = 'CLI Issue Report';
|
|
99
|
+
|
|
100
|
+
const body = `## Description
|
|
101
|
+
|
|
102
|
+
${description}
|
|
103
|
+
|
|
104
|
+
## Environment
|
|
105
|
+
|
|
106
|
+
- **CLI Version**: ${sessionData.cli?.version || 'unknown'}
|
|
107
|
+
- **Bun Version**: ${sessionData.system?.bunVersion || 'unknown'}
|
|
108
|
+
- **Platform**: ${sessionData.system?.platform || 'unknown'} (${sessionData.system?.arch || 'unknown'})
|
|
109
|
+
|
|
110
|
+
## Report
|
|
111
|
+
|
|
112
|
+
- **Report ID**: \`${reportId}\`
|
|
113
|
+
- **Report Logs**: ${reportUrl}
|
|
114
|
+
- **Command**: \`${sessionData.command || 'unknown'}\`
|
|
115
|
+
|
|
116
|
+
## Additional Context
|
|
117
|
+
|
|
118
|
+
<!-- Add any additional context or screenshots here -->
|
|
119
|
+
|
|
120
|
+
---
|
|
121
|
+
|
|
122
|
+
**Automated report generated by \`agentuity support report\`**
|
|
123
|
+
`;
|
|
124
|
+
|
|
125
|
+
const params = new URLSearchParams({
|
|
126
|
+
title,
|
|
127
|
+
body,
|
|
128
|
+
labels: 'cli,bug',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return `https://github.com/agentuity/sdk/issues/new?${params.toString()}`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Open URL in default browser
|
|
136
|
+
*/
|
|
137
|
+
async function openBrowser(url: string, logger: import('../../types').Logger): Promise<void> {
|
|
138
|
+
try {
|
|
139
|
+
// Use platform-specific command to open URL
|
|
140
|
+
const platform = process.platform;
|
|
141
|
+
let command: string;
|
|
142
|
+
let args: string[];
|
|
143
|
+
|
|
144
|
+
if (platform === 'darwin') {
|
|
145
|
+
command = 'open';
|
|
146
|
+
args = [url];
|
|
147
|
+
} else if (platform === 'win32') {
|
|
148
|
+
command = 'cmd';
|
|
149
|
+
args = ['/c', 'start', url];
|
|
150
|
+
} else {
|
|
151
|
+
// Linux/Unix
|
|
152
|
+
command = 'xdg-open';
|
|
153
|
+
args = [url];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const proc = Bun.spawn([command, ...args], {
|
|
157
|
+
stdout: 'ignore',
|
|
158
|
+
stderr: 'ignore',
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
await proc.exited;
|
|
162
|
+
|
|
163
|
+
if (proc.exitCode !== 0) {
|
|
164
|
+
throw new Error(`Browser process exited with code ${proc.exitCode}`);
|
|
165
|
+
}
|
|
166
|
+
} catch (error) {
|
|
167
|
+
logger.error('Failed to open browser', { error });
|
|
168
|
+
throw new Error('Failed to open browser. Please open the URL manually.');
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export default createSubcommand({
|
|
173
|
+
name: 'report',
|
|
174
|
+
description: 'Create a support ticket with CLI logs',
|
|
175
|
+
requires: {
|
|
176
|
+
auth: true,
|
|
177
|
+
apiClient: true,
|
|
178
|
+
},
|
|
179
|
+
schema: {
|
|
180
|
+
args: argsSchema,
|
|
181
|
+
options: optionsSchema,
|
|
182
|
+
},
|
|
183
|
+
handler: async (ctx) => {
|
|
184
|
+
const { opts, logger, apiClient } = ctx;
|
|
185
|
+
const isJsonMode = ctx.options.json;
|
|
186
|
+
|
|
187
|
+
// Get the latest log session
|
|
188
|
+
const sessionDir = getLatestLogSession();
|
|
189
|
+
if (!sessionDir) {
|
|
190
|
+
if (isJsonMode) {
|
|
191
|
+
console.log(JSON.stringify({ success: false, error: 'No CLI logs found' }));
|
|
192
|
+
} else {
|
|
193
|
+
tui.error('No CLI logs found');
|
|
194
|
+
tui.info('Run a command first to generate logs, then create a support ticket.');
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Read session data to get CLI version
|
|
200
|
+
const sessionFile = join(sessionDir, 'session.json');
|
|
201
|
+
const sessionData = JSON.parse(readFileSync(sessionFile, 'utf-8'));
|
|
202
|
+
const cliVersion = sessionData.cli?.version || 'unknown';
|
|
203
|
+
|
|
204
|
+
// Get issue description from:
|
|
205
|
+
// 1. --description flag
|
|
206
|
+
// 2. stdin (if piped/not a TTY)
|
|
207
|
+
// 3. Interactive prompt (if TTY)
|
|
208
|
+
let description = opts.description;
|
|
209
|
+
|
|
210
|
+
if (!description && !process.stdin.isTTY) {
|
|
211
|
+
// Read description from stdin (piped input)
|
|
212
|
+
try {
|
|
213
|
+
const chunks: Buffer[] = [];
|
|
214
|
+
for await (const chunk of process.stdin) {
|
|
215
|
+
chunks.push(chunk);
|
|
216
|
+
}
|
|
217
|
+
description = Buffer.concat(chunks).toString('utf-8').trim();
|
|
218
|
+
} catch (error) {
|
|
219
|
+
logger.trace('Failed to read stdin', { error });
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!description && !isJsonMode && process.stdin.isTTY) {
|
|
224
|
+
// Prompt user for description
|
|
225
|
+
tui.info(tui.bold('Create a CLI Bug Report'));
|
|
226
|
+
tui.newline();
|
|
227
|
+
tui.output(
|
|
228
|
+
'Please describe the issue you encountered. This will be included in the GitHub issue.'
|
|
229
|
+
);
|
|
230
|
+
tui.output(tui.muted('(Press Enter twice when done, or Ctrl+C to cancel)'));
|
|
231
|
+
tui.newline();
|
|
232
|
+
|
|
233
|
+
const { createPrompt } = tui;
|
|
234
|
+
const prompt = createPrompt();
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
description = await prompt.text({
|
|
238
|
+
message: 'Issue description:',
|
|
239
|
+
hint: 'Describe what happened and what you expected...',
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
// Cleanup stdin after prompt
|
|
243
|
+
if (process.stdin.isTTY) {
|
|
244
|
+
process.stdin.pause();
|
|
245
|
+
}
|
|
246
|
+
} catch (error) {
|
|
247
|
+
// User cancelled
|
|
248
|
+
logger.trace('User cancelled report', { error });
|
|
249
|
+
if (!isJsonMode) {
|
|
250
|
+
tui.warning('Report cancelled');
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (!description || description.trim() === '') {
|
|
257
|
+
if (isJsonMode) {
|
|
258
|
+
console.log(JSON.stringify({ success: false, error: 'Description is required' }));
|
|
259
|
+
} else {
|
|
260
|
+
tui.error(
|
|
261
|
+
'Description is required. Use --description flag or pipe input via stdin.'
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
return;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
try {
|
|
268
|
+
// Step 1: Create presigned upload URL
|
|
269
|
+
if (!isJsonMode) {
|
|
270
|
+
tui.info('Creating upload URL...');
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const uploadResponse = await apiClient.request(
|
|
274
|
+
'POST',
|
|
275
|
+
'/cli/support/upload',
|
|
276
|
+
ReportUploadResponseSchema,
|
|
277
|
+
{
|
|
278
|
+
cliVersion,
|
|
279
|
+
platform: sessionData.system?.platform || 'unknown',
|
|
280
|
+
}
|
|
281
|
+
);
|
|
282
|
+
|
|
283
|
+
// Debug: log the response
|
|
284
|
+
logger.debug('Upload response received', { uploadResponse });
|
|
285
|
+
|
|
286
|
+
if (!uploadResponse.success) {
|
|
287
|
+
const errorMsg = uploadResponse.message || 'Failed to create upload URL';
|
|
288
|
+
logger.error('Upload URL creation failed', { uploadResponse, errorMsg });
|
|
289
|
+
throw new Error(errorMsg);
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const { presigned_url, url: reportUrl, report_id: reportId } = uploadResponse.data;
|
|
293
|
+
|
|
294
|
+
// Step 2: Create zip file
|
|
295
|
+
if (!isJsonMode) {
|
|
296
|
+
tui.info('Creating report archive...');
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const zipPath = await createReportZip(sessionDir);
|
|
300
|
+
|
|
301
|
+
// Step 3: Upload to S3
|
|
302
|
+
if (!isJsonMode) {
|
|
303
|
+
tui.info('Uploading report...');
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
await uploadReport(presigned_url, zipPath, logger);
|
|
307
|
+
|
|
308
|
+
// Step 4: Create GitHub issue URL
|
|
309
|
+
const githubUrl = createGitHubIssueUrl(description, reportUrl, reportId, sessionData);
|
|
310
|
+
|
|
311
|
+
if (isJsonMode) {
|
|
312
|
+
console.log(
|
|
313
|
+
JSON.stringify({
|
|
314
|
+
success: true,
|
|
315
|
+
data: {
|
|
316
|
+
report_url: reportUrl,
|
|
317
|
+
github_url: githubUrl,
|
|
318
|
+
},
|
|
319
|
+
})
|
|
320
|
+
);
|
|
321
|
+
} else {
|
|
322
|
+
tui.newline();
|
|
323
|
+
tui.success('Report created successfully!');
|
|
324
|
+
tui.newline();
|
|
325
|
+
tui.output(`Report URL: ${tui.colorPrimary(reportUrl)}`);
|
|
326
|
+
tui.output(`GitHub Issue: ${tui.colorInfo(githubUrl)}`);
|
|
327
|
+
tui.newline();
|
|
328
|
+
|
|
329
|
+
if (!opts.noOpen) {
|
|
330
|
+
tui.info('Opening GitHub issue in browser...');
|
|
331
|
+
try {
|
|
332
|
+
await openBrowser(githubUrl, logger);
|
|
333
|
+
tui.newline();
|
|
334
|
+
tui.output(
|
|
335
|
+
tui.muted(
|
|
336
|
+
'Please review the pre-filled issue and click "Submit new issue" when ready.'
|
|
337
|
+
)
|
|
338
|
+
);
|
|
339
|
+
} catch {
|
|
340
|
+
tui.warning('Could not open browser automatically');
|
|
341
|
+
tui.output('Please open this URL in your browser:');
|
|
342
|
+
tui.output(tui.link(githubUrl));
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
tui.output('To create the GitHub issue, open:');
|
|
346
|
+
tui.output(tui.link(githubUrl));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
} catch (error) {
|
|
350
|
+
if (isJsonMode) {
|
|
351
|
+
console.log(
|
|
352
|
+
JSON.stringify({
|
|
353
|
+
success: false,
|
|
354
|
+
error: error instanceof Error ? error.message : 'Failed to create report',
|
|
355
|
+
})
|
|
356
|
+
);
|
|
357
|
+
} else {
|
|
358
|
+
tui.error('Failed to create report');
|
|
359
|
+
tui.output(error instanceof Error ? error.message : 'Unknown error');
|
|
360
|
+
}
|
|
361
|
+
logger.fatal('Failed to create report', { error });
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
});
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { createSubcommand } from '../../types';
|
|
2
|
+
import { z } from 'zod';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { platform, arch, cpus, totalmem, homedir } from 'node:os';
|
|
6
|
+
import { getLatestLogSession } from '../../internal-logger';
|
|
7
|
+
import * as tui from '../../tui';
|
|
8
|
+
import { getVersion, getPackageName } from '../../version';
|
|
9
|
+
import { getAuth } from '../../config';
|
|
10
|
+
|
|
11
|
+
const argsSchema = z.object({});
|
|
12
|
+
|
|
13
|
+
const optionsSchema = z.object({});
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Format bytes to human-readable string
|
|
17
|
+
*/
|
|
18
|
+
function formatBytes(bytes: number): string {
|
|
19
|
+
const units = ['B', 'KB', 'MB', 'GB', 'TB'];
|
|
20
|
+
let size = bytes;
|
|
21
|
+
let unitIndex = 0;
|
|
22
|
+
|
|
23
|
+
while (size >= 1024 && unitIndex < units.length - 1) {
|
|
24
|
+
size /= 1024;
|
|
25
|
+
unitIndex++;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return `${size.toFixed(2)} ${units[unitIndex]}`;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Get user ID from latest session
|
|
33
|
+
*/
|
|
34
|
+
function getUserId(): string | null {
|
|
35
|
+
const sessionDir = getLatestLogSession();
|
|
36
|
+
if (!sessionDir) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const sessionFile = join(sessionDir, 'session.json');
|
|
41
|
+
if (!existsSync(sessionFile)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
try {
|
|
46
|
+
const sessionData = JSON.parse(readFileSync(sessionFile, 'utf-8'));
|
|
47
|
+
return sessionData.userId || null;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export default createSubcommand({
|
|
54
|
+
name: 'system',
|
|
55
|
+
description: 'Display system information for support',
|
|
56
|
+
schema: {
|
|
57
|
+
args: argsSchema,
|
|
58
|
+
options: optionsSchema,
|
|
59
|
+
},
|
|
60
|
+
handler: async (ctx) => {
|
|
61
|
+
const isJsonMode = ctx.options.json;
|
|
62
|
+
|
|
63
|
+
// Get userId from auth (keychain or config)
|
|
64
|
+
let userId = 'not authenticated';
|
|
65
|
+
try {
|
|
66
|
+
const auth = await getAuth();
|
|
67
|
+
if (auth?.userId) {
|
|
68
|
+
userId = auth.userId;
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// If that fails, try from session
|
|
72
|
+
const sessionUserId = getUserId();
|
|
73
|
+
if (sessionUserId) {
|
|
74
|
+
userId = sessionUserId;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Gather system information
|
|
79
|
+
const systemInfo = {
|
|
80
|
+
cli: {
|
|
81
|
+
name: getPackageName(),
|
|
82
|
+
version: getVersion(),
|
|
83
|
+
},
|
|
84
|
+
bun: {
|
|
85
|
+
path: process.execPath || 'unknown',
|
|
86
|
+
version: Bun.version || process.version,
|
|
87
|
+
},
|
|
88
|
+
system: {
|
|
89
|
+
platform: platform(),
|
|
90
|
+
arch: arch(),
|
|
91
|
+
cpus: cpus().length,
|
|
92
|
+
memory: totalmem(),
|
|
93
|
+
memoryFormatted: formatBytes(totalmem()),
|
|
94
|
+
},
|
|
95
|
+
paths: {
|
|
96
|
+
cwd: process.cwd(),
|
|
97
|
+
home: homedir(),
|
|
98
|
+
configDir: join(homedir(), '.config', 'agentuity'),
|
|
99
|
+
},
|
|
100
|
+
user: {
|
|
101
|
+
userId: userId,
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
if (isJsonMode) {
|
|
106
|
+
tui.json(systemInfo);
|
|
107
|
+
} else {
|
|
108
|
+
// Display in vertical table format
|
|
109
|
+
tui.info(tui.bold('System Information'));
|
|
110
|
+
tui.newline();
|
|
111
|
+
|
|
112
|
+
// Flatten the data for table display
|
|
113
|
+
const tableData = [
|
|
114
|
+
{ Property: 'CLI Name', Value: systemInfo.cli.name },
|
|
115
|
+
{ Property: 'CLI Version', Value: systemInfo.cli.version },
|
|
116
|
+
{ Property: 'Bun Path', Value: systemInfo.bun.path },
|
|
117
|
+
{ Property: 'Bun Version', Value: systemInfo.bun.version },
|
|
118
|
+
{ Property: 'Platform', Value: systemInfo.system.platform },
|
|
119
|
+
{ Property: 'Architecture', Value: systemInfo.system.arch },
|
|
120
|
+
{ Property: 'CPUs', Value: String(systemInfo.system.cpus) },
|
|
121
|
+
{ Property: 'Memory', Value: systemInfo.system.memoryFormatted },
|
|
122
|
+
{ Property: 'Home Directory', Value: systemInfo.paths.home },
|
|
123
|
+
{ Property: 'Config Directory', Value: systemInfo.paths.configDir },
|
|
124
|
+
{ Property: 'User ID', Value: systemInfo.user.userId },
|
|
125
|
+
];
|
|
126
|
+
|
|
127
|
+
tui.table(tableData, ['Property', 'Value'], { layout: 'vertical' });
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
});
|