@closedloop-ai/cli 0.2.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/dist/__tests__/cli.test.js +37 -0
- package/dist/__tests__/cli.test.js.map +1 -0
- package/dist/__tests__/config.test.js +33 -0
- package/dist/__tests__/config.test.js.map +1 -0
- package/dist/__tests__/errors.test.js +109 -0
- package/dist/__tests__/errors.test.js.map +1 -0
- package/dist/__tests__/setup.js +20 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/__tests__/validation.test.js +124 -0
- package/dist/__tests__/validation.test.js.map +1 -0
- package/dist/api/auth.js +27 -0
- package/dist/api/auth.js.map +1 -0
- package/dist/api/feedback.js +70 -0
- package/dist/api/feedback.js.map +1 -0
- package/dist/api/input.js +60 -0
- package/dist/api/input.js.map +1 -0
- package/dist/api/usage.js +49 -0
- package/dist/api/usage.js.map +1 -0
- package/dist/cli.js +156 -0
- package/dist/cli.js.map +1 -0
- package/dist/commands/config.js +186 -0
- package/dist/commands/config.js.map +1 -0
- package/dist/commands/feedback.js +234 -0
- package/dist/commands/feedback.js.map +1 -0
- package/dist/commands/input.js +370 -0
- package/dist/commands/input.js.map +1 -0
- package/dist/commands/list.js +81 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/status.js +93 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/submit.js +225 -0
- package/dist/commands/submit.js.map +1 -0
- package/dist/commands/usage.js +168 -0
- package/dist/commands/usage.js.map +1 -0
- package/dist/config/constants.js +37 -0
- package/dist/config/constants.js.map +1 -0
- package/dist/config/index.js +84 -0
- package/dist/config/index.js.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/config.js +55 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/errors.js +70 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/validation.js +133 -0
- package/dist/utils/validation.js.map +1 -0
- package/jest.config.js +14 -0
- package/package.json +42 -0
- package/src/__tests__/cli.test.ts +44 -0
- package/src/__tests__/config.test.ts +38 -0
- package/src/__tests__/errors.test.ts +132 -0
- package/src/__tests__/setup.ts +21 -0
- package/src/__tests__/validation.test.ts +156 -0
- package/src/api/auth.ts +21 -0
- package/src/api/feedback.ts +64 -0
- package/src/api/input.ts +55 -0
- package/src/api/usage.ts +45 -0
- package/src/cli.ts +158 -0
- package/src/commands/config.ts +192 -0
- package/src/commands/feedback.ts +250 -0
- package/src/commands/input.ts +389 -0
- package/src/commands/list.ts +84 -0
- package/src/commands/status.ts +96 -0
- package/src/commands/submit.ts +214 -0
- package/src/config/constants.ts +36 -0
- package/src/config/index.ts +59 -0
- package/src/types/index.ts +119 -0
- package/src/utils/errors.ts +98 -0
- package/src/utils/validation.ts +166 -0
- package/tsconfig.json +25 -0
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { table } from 'table';
|
|
3
|
+
import { listFeedbacks, getFeedbackDetail } from '../api/feedback';
|
|
4
|
+
import { CommandOptions, FeedbackData, ListResponse } from '../types';
|
|
5
|
+
import { CONFIG, MESSAGES } from '../config/constants';
|
|
6
|
+
import { validateUuid, validatePagination } from '../utils/validation';
|
|
7
|
+
|
|
8
|
+
// UUID regex pattern for detecting IDs
|
|
9
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10
|
+
|
|
11
|
+
// Main feedback command handler
|
|
12
|
+
export async function handleFeedbackCommand(id: string | undefined, options: CommandOptions) {
|
|
13
|
+
// If ID provided, show specific feedback details
|
|
14
|
+
if (id) {
|
|
15
|
+
if (!validateUuid(id)) {
|
|
16
|
+
console.log(chalk.yellow('š” Invalid feedback ID format'));
|
|
17
|
+
console.log(chalk.black('Feedback ID should be a UUID like: 2ea8f556-052b-4f5c-bf86-833780b3d00d'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
return await showFeedback(id, options);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// If search query provided, search feedback
|
|
24
|
+
if (options.search) {
|
|
25
|
+
return await searchFeedback(options.search, options);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Otherwise, list all feedback
|
|
29
|
+
return await listFeedback(options);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Helper functions
|
|
33
|
+
async function listFeedback(options: CommandOptions) {
|
|
34
|
+
const { page, limit } = validatePagination(options.page, options.limit);
|
|
35
|
+
const status = options.status;
|
|
36
|
+
const search = options.search;
|
|
37
|
+
|
|
38
|
+
const result = await listFeedbacks({
|
|
39
|
+
page,
|
|
40
|
+
limit,
|
|
41
|
+
status,
|
|
42
|
+
search
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
if (options.json) {
|
|
46
|
+
console.log(JSON.stringify({ success: true, data: result }, null, 2));
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const feedbacks = result.feedbacks || [];
|
|
51
|
+
|
|
52
|
+
if (feedbacks.length === 0) {
|
|
53
|
+
console.log(chalk.yellow('š No feedback found'));
|
|
54
|
+
if (search) {
|
|
55
|
+
console.log(chalk.gray(`Try a different search term or remove --search to see all feedback`));
|
|
56
|
+
}
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
console.log(chalk.blue('\nšÆ AI-Generated Feedback:'));
|
|
61
|
+
|
|
62
|
+
const tableData = [
|
|
63
|
+
[
|
|
64
|
+
chalk.bold('ID'),
|
|
65
|
+
chalk.bold('Title'),
|
|
66
|
+
chalk.bold('Severity'),
|
|
67
|
+
chalk.bold('Input ID'),
|
|
68
|
+
chalk.bold('Created')
|
|
69
|
+
]
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
feedbacks.forEach((feedback: any) => {
|
|
73
|
+
tableData.push([
|
|
74
|
+
feedback.id,
|
|
75
|
+
feedback.title || chalk.gray('null'),
|
|
76
|
+
feedback.severity || 'medium',
|
|
77
|
+
feedback.input_id || chalk.gray('null'),
|
|
78
|
+
feedback.timestamp ? new Date(feedback.timestamp).toLocaleString() : 'Unknown'
|
|
79
|
+
]);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
console.log(table(tableData, {
|
|
83
|
+
border: {
|
|
84
|
+
topBody: 'ā',
|
|
85
|
+
topJoin: 'ā¬',
|
|
86
|
+
topLeft: 'ā',
|
|
87
|
+
topRight: 'ā',
|
|
88
|
+
bottomBody: 'ā',
|
|
89
|
+
bottomJoin: 'ā“',
|
|
90
|
+
bottomLeft: 'ā',
|
|
91
|
+
bottomRight: 'ā',
|
|
92
|
+
bodyLeft: 'ā',
|
|
93
|
+
bodyRight: 'ā',
|
|
94
|
+
bodyJoin: 'ā',
|
|
95
|
+
joinBody: 'ā',
|
|
96
|
+
joinLeft: 'ā',
|
|
97
|
+
joinRight: 'ā¤',
|
|
98
|
+
joinJoin: 'ā¼'
|
|
99
|
+
}
|
|
100
|
+
}));
|
|
101
|
+
|
|
102
|
+
if (result.pagination) {
|
|
103
|
+
console.log(chalk.black(`\nPage ${result.pagination.page} of ${result.pagination.pages} (${result.pagination.total} total feedback)`));
|
|
104
|
+
if (result.pagination.page < result.pagination.pages) {
|
|
105
|
+
console.log(chalk.gray(`š” Use --page ${result.pagination.page + 1} to see next page`));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function showFeedback(id: string, options: CommandOptions) {
|
|
111
|
+
try {
|
|
112
|
+
const result = await getFeedbackDetail(id);
|
|
113
|
+
|
|
114
|
+
if (options.json) {
|
|
115
|
+
console.log(JSON.stringify({ success: true, data: result }, null, 2));
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log(chalk.blue('\nšÆ Feedback Details:'));
|
|
120
|
+
console.log(chalk.black('ā'.repeat(50)));
|
|
121
|
+
console.log(chalk.blue('ID:'), result.id);
|
|
122
|
+
console.log(chalk.blue('Title:'), result.title || chalk.gray('null'));
|
|
123
|
+
console.log(chalk.blue('Clarity:'), result.clarity || chalk.gray('null'));
|
|
124
|
+
console.log(chalk.blue('Deal Blocker:'), result.deal_blocker ? chalk.red('Yes') : chalk.green('No'));
|
|
125
|
+
console.log(chalk.blue('Pain Point:'), result.pain_point || chalk.gray('null'));
|
|
126
|
+
console.log(chalk.blue('Workaround:'), result.workaround || chalk.gray('null'));
|
|
127
|
+
console.log(chalk.blue('Competitor Gap:'), result.competitor_gap || chalk.gray('null'));
|
|
128
|
+
console.log(chalk.blue('Willingness to Pay:'), result.willingness_to_pay || chalk.gray('null'));
|
|
129
|
+
console.log(chalk.blue('Use Case:'), result.use_case || chalk.gray('null'));
|
|
130
|
+
console.log(chalk.blue('Feature Area:'), result.feature_area || chalk.gray('null'));
|
|
131
|
+
console.log(chalk.blue('Source URL:'), result.source_url || chalk.gray('null'));
|
|
132
|
+
console.log(chalk.blue('Created:'), result.timestamp ? new Date(result.timestamp).toLocaleString() : 'Unknown');
|
|
133
|
+
} catch (error: any) {
|
|
134
|
+
if (options.json) {
|
|
135
|
+
console.log(JSON.stringify({
|
|
136
|
+
success: false,
|
|
137
|
+
error: error.message,
|
|
138
|
+
details: error.response?.data || 'Unknown error occurred'
|
|
139
|
+
}, null, 2));
|
|
140
|
+
} else {
|
|
141
|
+
if (error.response?.status === 404) {
|
|
142
|
+
console.error(chalk.red('ā Feedback not found'));
|
|
143
|
+
console.log(chalk.black('The feedback ID you provided does not exist or is not accessible.'));
|
|
144
|
+
} else if (error.response?.status === 401) {
|
|
145
|
+
console.error(chalk.red('ā Authentication Error:'), 'Invalid API key');
|
|
146
|
+
console.log(chalk.black('\nš” To fix:'));
|
|
147
|
+
console.log(chalk.black(' 1. Get your free API key at: https://closedloop.sh'));
|
|
148
|
+
console.log(chalk.black(' 2. Run: cl config set --api-key <your-key>'));
|
|
149
|
+
} else {
|
|
150
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
151
|
+
console.log(chalk.black('\nš” Get help: cl --help'));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
process.exit(1);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
async function searchFeedback(query: string, options: any) {
|
|
159
|
+
const page = parseInt(options.page) || 1;
|
|
160
|
+
const limit = parseInt(options.limit) || 20;
|
|
161
|
+
|
|
162
|
+
const result = await listFeedbacks({
|
|
163
|
+
page,
|
|
164
|
+
limit,
|
|
165
|
+
search: query
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
if (options.json) {
|
|
169
|
+
console.log(JSON.stringify({ success: true, data: result }, null, 2));
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const feedbacks = result.feedbacks || [];
|
|
174
|
+
|
|
175
|
+
if (feedbacks.length === 0) {
|
|
176
|
+
console.log(chalk.yellow(`š No feedback found for "${query}"`));
|
|
177
|
+
console.log(chalk.gray('Try a different search term or remove --search to see all feedback'));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log(chalk.blue(`\nš Search Results for "${query}":`));
|
|
182
|
+
|
|
183
|
+
const tableData = [
|
|
184
|
+
[
|
|
185
|
+
chalk.bold('ID'),
|
|
186
|
+
chalk.bold('Title'),
|
|
187
|
+
chalk.bold('Severity'),
|
|
188
|
+
chalk.bold('Input ID'),
|
|
189
|
+
chalk.bold('Created')
|
|
190
|
+
]
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
feedbacks.forEach((feedback: any) => {
|
|
194
|
+
tableData.push([
|
|
195
|
+
feedback.id,
|
|
196
|
+
feedback.title || chalk.gray('null'),
|
|
197
|
+
feedback.severity || 'medium',
|
|
198
|
+
feedback.input_id || chalk.gray('null'),
|
|
199
|
+
feedback.timestamp ? new Date(feedback.timestamp).toLocaleString() : 'Unknown'
|
|
200
|
+
]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
console.log(table(tableData, {
|
|
204
|
+
border: {
|
|
205
|
+
topBody: 'ā',
|
|
206
|
+
topJoin: 'ā¬',
|
|
207
|
+
topLeft: 'ā',
|
|
208
|
+
topRight: 'ā',
|
|
209
|
+
bottomBody: 'ā',
|
|
210
|
+
bottomJoin: 'ā“',
|
|
211
|
+
bottomLeft: 'ā',
|
|
212
|
+
bottomRight: 'ā',
|
|
213
|
+
bodyLeft: 'ā',
|
|
214
|
+
bodyRight: 'ā',
|
|
215
|
+
bodyJoin: 'ā',
|
|
216
|
+
joinBody: 'ā',
|
|
217
|
+
joinLeft: 'ā',
|
|
218
|
+
joinRight: 'ā¤',
|
|
219
|
+
joinJoin: 'ā¼'
|
|
220
|
+
}
|
|
221
|
+
}));
|
|
222
|
+
|
|
223
|
+
if (result.pagination) {
|
|
224
|
+
console.log(chalk.black(`Page ${result.pagination.page} of ${result.pagination.pages} (${result.pagination.total} total results)`));
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function handleError(error: any, options: any) {
|
|
229
|
+
if (options.json) {
|
|
230
|
+
console.log(JSON.stringify({
|
|
231
|
+
success: false,
|
|
232
|
+
error: error.message,
|
|
233
|
+
details: error.response?.data || 'Unknown error occurred'
|
|
234
|
+
}, null, 2));
|
|
235
|
+
} else {
|
|
236
|
+
if (error.response?.status === 404) {
|
|
237
|
+
console.error(chalk.red('ā Not found'));
|
|
238
|
+
console.log(chalk.black('The requested resource does not exist or is not accessible.'));
|
|
239
|
+
} else if (error.response?.status === 401) {
|
|
240
|
+
console.error(chalk.red('ā Authentication Error:'), 'Invalid API key');
|
|
241
|
+
console.log(chalk.black('\nš” To fix:'));
|
|
242
|
+
console.log(chalk.black(' 1. Get your free API key at: https://closedloop.sh'));
|
|
243
|
+
console.log(chalk.black(' 2. Run: cl config set --api-key <your-key>'));
|
|
244
|
+
} else {
|
|
245
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
246
|
+
console.log(chalk.black('\nš” Get help: cl --help'));
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
process.exit(1);
|
|
250
|
+
}
|
|
@@ -0,0 +1,389 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import inquirer from 'inquirer';
|
|
4
|
+
import { table } from 'table';
|
|
5
|
+
import { submitFeedback } from '../api/feedback';
|
|
6
|
+
import { listInputs as listInputsAPI, getInputDetail, getInputStatus } from '../api/input';
|
|
7
|
+
import { CommandOptions, InputData, ListResponse } from '../types';
|
|
8
|
+
import { CONFIG, MESSAGES } from '../config/constants';
|
|
9
|
+
import { validateUuid, validateAndSanitizeInput, validatePagination } from '../utils/validation';
|
|
10
|
+
|
|
11
|
+
// UUID regex pattern for detecting IDs
|
|
12
|
+
const UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
13
|
+
|
|
14
|
+
// Main input command handler
|
|
15
|
+
export async function handleInputCommand(content: string | undefined, options: CommandOptions) {
|
|
16
|
+
// Check for common command mistakes
|
|
17
|
+
if (content && ['list', 'show', 'status', 'help'].includes(content.toLowerCase())) {
|
|
18
|
+
console.log(chalk.yellow('š” Did you mean one of these commands?'));
|
|
19
|
+
console.log(chalk.black(''));
|
|
20
|
+
console.log(chalk.blue(' cl input') + chalk.black(' # List all inputs'));
|
|
21
|
+
console.log(chalk.blue(' cl input <id>') + chalk.black(' # Show specific input details'));
|
|
22
|
+
console.log(chalk.blue(' cl input "your content"') + chalk.black(' # Submit new input for analysis'));
|
|
23
|
+
console.log(chalk.black(''));
|
|
24
|
+
console.log(chalk.gray('Examples:'));
|
|
25
|
+
console.log(chalk.gray(' cl input "Dashboard is confusing"'));
|
|
26
|
+
console.log(chalk.gray(' cl input 74e3dd87-878f-41cf-8e5a-87527bbf7770'));
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// If content looks like a UUID, treat it as an ID and show details
|
|
31
|
+
if (content && validateUuid(content)) {
|
|
32
|
+
return await showInput(content, options);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// If content provided and not a UUID, create new input
|
|
36
|
+
if (content) {
|
|
37
|
+
return await createInput(content, options);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// If no content provided, list inputs
|
|
41
|
+
return await listInputs(options);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Helper functions
|
|
45
|
+
async function createInput(content: string, options: CommandOptions) {
|
|
46
|
+
// Validate and sanitize input data
|
|
47
|
+
const validatedData = validateAndSanitizeInput({
|
|
48
|
+
content,
|
|
49
|
+
title: options.title,
|
|
50
|
+
source_url: options.url,
|
|
51
|
+
customer_id: options.customer,
|
|
52
|
+
reporter_name: options.name,
|
|
53
|
+
reporter_email: options.email
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
let feedbackData: Partial<InputData> = validatedData;
|
|
57
|
+
|
|
58
|
+
if (options.interactive || !content) {
|
|
59
|
+
const answers = await inquirer.prompt([
|
|
60
|
+
{
|
|
61
|
+
type: 'input',
|
|
62
|
+
name: 'content',
|
|
63
|
+
message: 'What customer input would you like to analyze?',
|
|
64
|
+
validate: (input) => input.length >= 10 || 'Input must be at least 10 characters',
|
|
65
|
+
default: content
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
type: 'input',
|
|
69
|
+
name: 'title',
|
|
70
|
+
message: 'Title for this input (optional):',
|
|
71
|
+
default: options.title
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
type: 'input',
|
|
75
|
+
name: 'url',
|
|
76
|
+
message: 'Source URL (optional):',
|
|
77
|
+
default: options.url
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
type: 'input',
|
|
81
|
+
name: 'customer_id',
|
|
82
|
+
message: 'Customer ID (optional):',
|
|
83
|
+
default: options.customer
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
type: 'input',
|
|
87
|
+
name: 'reporter_name',
|
|
88
|
+
message: 'Reporter name (optional):',
|
|
89
|
+
default: options.name
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
type: 'input',
|
|
93
|
+
name: 'reporter_email',
|
|
94
|
+
message: 'Reporter email (optional):',
|
|
95
|
+
default: options.email
|
|
96
|
+
}
|
|
97
|
+
]);
|
|
98
|
+
|
|
99
|
+
feedbackData = {
|
|
100
|
+
content: answers.content,
|
|
101
|
+
...(answers.title && { title: answers.title }),
|
|
102
|
+
...(answers.url && { source_url: answers.url }),
|
|
103
|
+
...(answers.customer_id && { customer_id: answers.customer_id }),
|
|
104
|
+
...(answers.reporter_name && { reporter_name: answers.reporter_name }),
|
|
105
|
+
...(answers.reporter_email && { reporter_email: answers.reporter_email })
|
|
106
|
+
};
|
|
107
|
+
} else {
|
|
108
|
+
feedbackData = {
|
|
109
|
+
content,
|
|
110
|
+
...(options.title && { title: options.title }),
|
|
111
|
+
...(options.url && { source_url: options.url }),
|
|
112
|
+
...(options.customer && { customer_id: options.customer }),
|
|
113
|
+
...(options.name && { reporter_name: options.name }),
|
|
114
|
+
...(options.email && { reporter_email: options.email })
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const spinner = ora('Analyzing your input...').start();
|
|
119
|
+
|
|
120
|
+
const result = await submitFeedback(feedbackData);
|
|
121
|
+
spinner.succeed('Submitted!');
|
|
122
|
+
|
|
123
|
+
if (options.wait) {
|
|
124
|
+
if (options.json) {
|
|
125
|
+
// For JSON, we'll output the final result after waiting
|
|
126
|
+
} else {
|
|
127
|
+
console.log(chalk.blue(`Input ID: ${result.id}`));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const waitSpinner = ora('Processing...').start();
|
|
131
|
+
let attempts = 0;
|
|
132
|
+
const maxAttempts = CONFIG.MAX_RETRY_ATTEMPTS;
|
|
133
|
+
|
|
134
|
+
while (attempts < maxAttempts) {
|
|
135
|
+
await new Promise(resolve => setTimeout(resolve, CONFIG.POLL_INTERVAL_MS));
|
|
136
|
+
|
|
137
|
+
try {
|
|
138
|
+
const statusResult = await getInputStatus(result.id);
|
|
139
|
+
|
|
140
|
+
if (statusResult.status === 'completed') {
|
|
141
|
+
waitSpinner.succeed('Processing complete!');
|
|
142
|
+
|
|
143
|
+
if (options.json) {
|
|
144
|
+
console.log(JSON.stringify({ success: true, data: statusResult }, null, 2));
|
|
145
|
+
} else {
|
|
146
|
+
console.log(chalk.blue(`š Results: ${statusResult.feedback_count || 0} customer product feedback generated, ${statusResult.credits_consumed || 0} credits used`));
|
|
147
|
+
if (statusResult.feedback_count > 0) {
|
|
148
|
+
console.log(chalk.black('Run "cl feedback" to see generated feedback'));
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
break;
|
|
152
|
+
} else if (statusResult.status === 'failed') {
|
|
153
|
+
waitSpinner.fail('Processing failed');
|
|
154
|
+
if (options.json) {
|
|
155
|
+
console.log(JSON.stringify({ success: false, error: 'Processing failed', data: statusResult }, null, 2));
|
|
156
|
+
} else {
|
|
157
|
+
console.log(chalk.red('\nā Processing failed. Check the status for more details.'));
|
|
158
|
+
}
|
|
159
|
+
break;
|
|
160
|
+
} else {
|
|
161
|
+
waitSpinner.text = `Processing... (${statusResult.status})`;
|
|
162
|
+
}
|
|
163
|
+
} catch (error) {
|
|
164
|
+
waitSpinner.text = 'Processing... (checking status)';
|
|
165
|
+
}
|
|
166
|
+
attempts++;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (attempts >= maxAttempts) {
|
|
170
|
+
waitSpinner.warn('Processing is taking longer than expected');
|
|
171
|
+
if (options.json) {
|
|
172
|
+
console.log(JSON.stringify({ success: false, error: 'Processing timeout', data: { id: result.id, message: 'Processing is taking longer than expected' } }, null, 2));
|
|
173
|
+
} else {
|
|
174
|
+
console.log(chalk.yellow('\nā³ Processing is still in progress. You can check status manually:'));
|
|
175
|
+
console.log(chalk.black(` cl input status ${result.id}`));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
} else {
|
|
179
|
+
spinner.succeed('Submitted!');
|
|
180
|
+
|
|
181
|
+
if (options.json) {
|
|
182
|
+
console.log(JSON.stringify({ success: true, data: result }, null, 2));
|
|
183
|
+
} else {
|
|
184
|
+
console.log(chalk.green('ā
Submitted!'));
|
|
185
|
+
console.log(chalk.blue(`Input ID: ${result.id}`));
|
|
186
|
+
console.log(chalk.black('Run "cl input status ' + result.id + '" to check progress'));
|
|
187
|
+
console.log(chalk.black('Or use "cl input --wait" to wait for completion'));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function listRecentInputs(options: any) {
|
|
193
|
+
const result = await listInputsAPI(1, 5);
|
|
194
|
+
const inputs = result.inputs || [];
|
|
195
|
+
|
|
196
|
+
if (options.json) {
|
|
197
|
+
console.log(JSON.stringify({ success: true, data: { inputs, pagination: result.pagination } }, null, 2));
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (inputs.length === 0) {
|
|
202
|
+
console.log(chalk.yellow('No recent inputs found.'));
|
|
203
|
+
console.log(chalk.black('Submit your first input: cl input "Your customer feedback here"'));
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
console.log(chalk.blue('\nš Recent Customer Inputs:'));
|
|
208
|
+
console.log(chalk.black('ā'.repeat(60)));
|
|
209
|
+
|
|
210
|
+
inputs.forEach((input: any, index: number) => {
|
|
211
|
+
const statusColor = input.status === 'completed' ? 'green' :
|
|
212
|
+
input.status === 'processing' ? 'yellow' : 'red';
|
|
213
|
+
|
|
214
|
+
console.log(chalk.blue(`${index + 1}.`), chalk.bold(input.title || 'Untitled'));
|
|
215
|
+
console.log(chalk.black(` ID: ${input.id}`));
|
|
216
|
+
console.log(chalk.black(` Status: ${chalk[statusColor](input.status || 'unknown')}`));
|
|
217
|
+
console.log(chalk.black(` Content: ${input.content ? input.content.substring(0, 100) + (input.content.length > 100 ? '...' : '') : 'N/A'}`));
|
|
218
|
+
console.log(chalk.black(` Submitted: ${input.created_at ? new Date(input.created_at).toLocaleString() : 'Unknown'}`));
|
|
219
|
+
console.log();
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
console.log(chalk.black('Run "cl input list" to see all inputs'));
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function listInputs(options: CommandOptions) {
|
|
226
|
+
const { page, limit } = validatePagination(options.page, options.limit);
|
|
227
|
+
const status = options.status;
|
|
228
|
+
|
|
229
|
+
const result = await listInputsAPI(page, limit);
|
|
230
|
+
const inputs = result.inputs || [];
|
|
231
|
+
|
|
232
|
+
if (options.json) {
|
|
233
|
+
console.log(JSON.stringify({ success: true, data: { inputs, pagination: result.pagination } }, null, 2));
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (inputs.length === 0) {
|
|
238
|
+
console.log(chalk.yellow('No inputs found.'));
|
|
239
|
+
console.log(chalk.black('Submit your first input: cl input "Your customer feedback here"'));
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Create table
|
|
244
|
+
const tableData = [
|
|
245
|
+
[
|
|
246
|
+
chalk.bold('ID'),
|
|
247
|
+
chalk.bold('Title'),
|
|
248
|
+
chalk.bold('Status'),
|
|
249
|
+
chalk.bold('Feedback'),
|
|
250
|
+
chalk.bold('Credits'),
|
|
251
|
+
chalk.bold('Submitted')
|
|
252
|
+
]
|
|
253
|
+
];
|
|
254
|
+
|
|
255
|
+
inputs.forEach((input: any) => {
|
|
256
|
+
const statusColor = input.status === 'completed' ? 'green' :
|
|
257
|
+
input.status === 'processing' ? 'yellow' : 'red';
|
|
258
|
+
|
|
259
|
+
tableData.push([
|
|
260
|
+
input.id,
|
|
261
|
+
input.title || chalk.gray('null'),
|
|
262
|
+
chalk[statusColor](input.status || 'unknown'),
|
|
263
|
+
input.feedback_count || 0,
|
|
264
|
+
input.credits_consumed || 0,
|
|
265
|
+
input.created_at ? new Date(input.created_at).toLocaleString() : 'Unknown'
|
|
266
|
+
]);
|
|
267
|
+
});
|
|
268
|
+
|
|
269
|
+
console.log(chalk.blue('\nš Customer Inputs:'));
|
|
270
|
+
console.log(table(tableData, {
|
|
271
|
+
border: {
|
|
272
|
+
topBody: 'ā',
|
|
273
|
+
topJoin: 'ā¬',
|
|
274
|
+
topLeft: 'ā',
|
|
275
|
+
topRight: 'ā',
|
|
276
|
+
bottomBody: 'ā',
|
|
277
|
+
bottomJoin: 'ā“',
|
|
278
|
+
bottomLeft: 'ā',
|
|
279
|
+
bottomRight: 'ā',
|
|
280
|
+
bodyLeft: 'ā',
|
|
281
|
+
bodyRight: 'ā',
|
|
282
|
+
bodyJoin: 'ā',
|
|
283
|
+
joinBody: 'ā',
|
|
284
|
+
joinLeft: 'ā',
|
|
285
|
+
joinRight: 'ā¤',
|
|
286
|
+
joinJoin: 'ā¼'
|
|
287
|
+
}
|
|
288
|
+
}));
|
|
289
|
+
|
|
290
|
+
if (result.pagination) {
|
|
291
|
+
console.log(chalk.black(`\nPage ${result.pagination.page} of ${result.pagination.pages} (${result.pagination.total} total inputs)`));
|
|
292
|
+
if (result.pagination.page < result.pagination.pages) {
|
|
293
|
+
console.log(chalk.gray(`š” Use --page ${result.pagination.page + 1} to see next page`));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
async function showInput(id: string, options: CommandOptions) {
|
|
299
|
+
const result = await getInputDetail(id);
|
|
300
|
+
|
|
301
|
+
if (options.json) {
|
|
302
|
+
console.log(JSON.stringify({ success: true, data: result }, null, 2));
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
console.log(chalk.blue('\nš Input Details:'));
|
|
307
|
+
console.log(chalk.black('ā'.repeat(50)));
|
|
308
|
+
console.log(chalk.blue('ID:'), result.id);
|
|
309
|
+
console.log(chalk.blue('Title:'), result.title || chalk.gray('null'));
|
|
310
|
+
console.log(chalk.blue('Status:'), result.status);
|
|
311
|
+
console.log(chalk.blue('Content:'), result.content || 'No content');
|
|
312
|
+
console.log(chalk.blue('Source URL:'), result.source_url || chalk.gray('null'));
|
|
313
|
+
console.log(chalk.blue('Customer ID:'), result.customer_id || chalk.gray('null'));
|
|
314
|
+
console.log(chalk.blue('Reporter:'), result.reporter_name || chalk.gray('null'));
|
|
315
|
+
console.log(chalk.blue('Email:'), result.reporter_email || chalk.gray('null'));
|
|
316
|
+
console.log(chalk.blue('AI Feedback Generated:'), result.feedback_count || 0);
|
|
317
|
+
console.log(chalk.blue('Credits Used:'), result.credits_consumed || 0);
|
|
318
|
+
console.log(chalk.blue('Submitted:'), result.created_at ? new Date(result.created_at).toLocaleString() : 'Unknown');
|
|
319
|
+
console.log(chalk.blue('Updated:'), result.updated_at ? new Date(result.updated_at).toLocaleString() : 'Unknown');
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
async function checkInputStatus(id: string, options: CommandOptions) {
|
|
323
|
+
const result = await getInputStatus(id);
|
|
324
|
+
|
|
325
|
+
if (options.json) {
|
|
326
|
+
console.log(JSON.stringify({ success: true, data: result }, null, 2));
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
console.log(chalk.blue('\nš Processing Status:'));
|
|
331
|
+
console.log(chalk.black('ā'.repeat(40)));
|
|
332
|
+
console.log(chalk.blue('ID:'), result.id);
|
|
333
|
+
console.log(chalk.blue('Status:'), result.status);
|
|
334
|
+
console.log(chalk.blue('Created:'), result.created_at ? new Date(result.created_at).toLocaleString() : 'Unknown');
|
|
335
|
+
console.log(chalk.blue('Updated:'), result.updated_at ? new Date(result.updated_at).toLocaleString() : 'Unknown');
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function handleError(error: any, options: any) {
|
|
339
|
+
if (options.json) {
|
|
340
|
+
console.log(JSON.stringify({
|
|
341
|
+
success: false,
|
|
342
|
+
error: error.message,
|
|
343
|
+
details: error.response?.data || 'Unknown error occurred'
|
|
344
|
+
}, null, 2));
|
|
345
|
+
} else {
|
|
346
|
+
if (error.response?.status === 400) {
|
|
347
|
+
const errorData = error.response.data;
|
|
348
|
+
if (errorData?.code === 'VALIDATION_ERROR') {
|
|
349
|
+
console.error(chalk.red('ā Validation Error:'), errorData.error);
|
|
350
|
+
console.log(chalk.black('\nš” Common issues:'));
|
|
351
|
+
console.log(chalk.black(' ⢠Content must be at least 10 characters'));
|
|
352
|
+
console.log(chalk.black(' ⢠Check that all required fields are provided'));
|
|
353
|
+
console.log(chalk.black(' ⢠Ensure API key is valid: cl config'));
|
|
354
|
+
} else if (errorData?.code === 'PROCESSING_ERROR') {
|
|
355
|
+
console.error(chalk.red('ā Processing Error:'), errorData.error);
|
|
356
|
+
console.log(chalk.black('\nš” This usually means:'));
|
|
357
|
+
console.log(chalk.black(' ⢠The content could not be processed by our AI'));
|
|
358
|
+
console.log(chalk.black(' ⢠Try rephrasing your input'));
|
|
359
|
+
console.log(chalk.black(' ⢠Make sure the content is clear and meaningful'));
|
|
360
|
+
console.log(chalk.black(' ⢠Check API key: cl config'));
|
|
361
|
+
} else {
|
|
362
|
+
console.error(chalk.red('ā Bad Request:'), errorData?.error || 'Invalid input provided');
|
|
363
|
+
console.log(chalk.black('\nš” Try:'));
|
|
364
|
+
console.log(chalk.black(' ⢠Check your input content'));
|
|
365
|
+
console.log(chalk.black(' ⢠Verify API key: cl config'));
|
|
366
|
+
console.log(chalk.black(' ⢠Get help: cl input --help'));
|
|
367
|
+
}
|
|
368
|
+
} else if (error.response?.status === 401) {
|
|
369
|
+
console.error(chalk.red('ā Authentication Error:'), 'Invalid API key');
|
|
370
|
+
console.log(chalk.black('\nš” To fix:'));
|
|
371
|
+
console.log(chalk.black(' 1. Get your free API key at: https://closedloop.sh'));
|
|
372
|
+
console.log(chalk.black(' 2. Run: cl config set-api-key <your-key>'));
|
|
373
|
+
} else if (error.response?.status === 429) {
|
|
374
|
+
console.error(chalk.red('ā Rate Limited:'), 'Too many requests');
|
|
375
|
+
console.log(chalk.black('\nš” Wait a moment and try again'));
|
|
376
|
+
} else if (error.response?.status >= 500) {
|
|
377
|
+
console.error(chalk.red('ā Server Error:'), 'Our servers are having issues');
|
|
378
|
+
console.log(chalk.black('\nš” Try again in a few minutes'));
|
|
379
|
+
} else if (error.code === 'ENOTFOUND' || error.code === 'ECONNREFUSED') {
|
|
380
|
+
console.error(chalk.red('ā Connection Error:'), 'Cannot connect to ClosedLoop AI servers');
|
|
381
|
+
console.log(chalk.black('\nš” Check your internet connection and try again'));
|
|
382
|
+
} else {
|
|
383
|
+
console.error(chalk.red('ā Error:'), error.message);
|
|
384
|
+
console.log(chalk.black('\nš” Get help: cl --help'));
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
process.exit(1);
|
|
388
|
+
}
|
|
389
|
+
|