codequiry-cli 2.0.0 → 2.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/package.json +1 -1
- package/src/commands/account.js +20 -14
- package/src/commands/create.js +4 -3
- package/src/commands/results.js +5 -4
- package/src/commands/scan.js +3 -3
- package/src/commands/status.js +2 -2
- package/src/index.js +1 -1
- package/src/utils/banner.js +2 -2
- package/src/utils/display.js +26 -20
- package/src/utils/poller.js +10 -14
package/package.json
CHANGED
package/src/commands/account.js
CHANGED
|
@@ -12,30 +12,36 @@ async function accountCommand() {
|
|
|
12
12
|
const spinner = ora('Fetching account info...').start();
|
|
13
13
|
|
|
14
14
|
try {
|
|
15
|
-
const
|
|
15
|
+
const data = await getAccount();
|
|
16
16
|
spinner.stop();
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
18
|
+
// API returns: {id, name, email, quota: {remaining, total, unlimited, ...}, is_pro, plan_id, edu_verified}
|
|
19
|
+
const name = data.name || 'N/A';
|
|
20
|
+
const email = data.email || 'N/A';
|
|
21
|
+
const isPro = data.is_pro;
|
|
22
|
+
const planLabel = isPro ? 'Pro' : 'Free';
|
|
23
|
+
const quota = data.quota || {};
|
|
24
|
+
const remaining = quota.remaining ?? 0;
|
|
25
|
+
const isUnlimited = quota.unlimited || false;
|
|
26
|
+
|
|
27
|
+
const quotaDisplay = isUnlimited ? 'Unlimited' : String(remaining);
|
|
23
28
|
const barLen = 20;
|
|
24
|
-
const filled = Math.round((
|
|
29
|
+
const filled = isUnlimited ? barLen : Math.min(barLen, Math.max(0, Math.round((remaining / Math.max(remaining, 1)) * barLen)));
|
|
25
30
|
const quotaBar = chalk.green('█'.repeat(filled)) + chalk.dim('░'.repeat(barLen - filled));
|
|
26
31
|
|
|
27
32
|
const content = [
|
|
28
33
|
chalk.bold('Account'),
|
|
29
34
|
'',
|
|
30
|
-
`${chalk.dim('Name:')} ${
|
|
31
|
-
`${chalk.dim('Email:')} ${
|
|
32
|
-
`${chalk.dim('Plan:')} ${chalk.cyan(
|
|
35
|
+
`${chalk.dim('Name:')} ${name}`,
|
|
36
|
+
`${chalk.dim('Email:')} ${email}`,
|
|
37
|
+
`${chalk.dim('Plan:')} ${chalk.cyan(planLabel)}`,
|
|
38
|
+
`${chalk.dim('EDU:')} ${data.edu_verified ? chalk.green('Verified') : chalk.dim('No')}`,
|
|
33
39
|
'',
|
|
34
|
-
chalk.bold('
|
|
40
|
+
chalk.bold('Quota'),
|
|
35
41
|
'',
|
|
36
|
-
`${chalk.dim('
|
|
37
|
-
`
|
|
38
|
-
].join('\n');
|
|
42
|
+
`${chalk.dim('Remaining:')} ${quotaDisplay}`,
|
|
43
|
+
isUnlimited ? '' : ` ${quotaBar}`,
|
|
44
|
+
].filter(Boolean).join('\n');
|
|
39
45
|
|
|
40
46
|
console.log(
|
|
41
47
|
'\n' +
|
package/src/commands/create.js
CHANGED
|
@@ -46,7 +46,7 @@ async function createCommand(options) {
|
|
|
46
46
|
name: 'languageId',
|
|
47
47
|
message: 'Programming language:',
|
|
48
48
|
choices: langList.map((l) => ({
|
|
49
|
-
name: l.
|
|
49
|
+
name: l.language || l.name,
|
|
50
50
|
value: l.id,
|
|
51
51
|
})),
|
|
52
52
|
});
|
|
@@ -75,8 +75,9 @@ async function createCommand(options) {
|
|
|
75
75
|
|
|
76
76
|
try {
|
|
77
77
|
const result = await createCheck(name, languageId, testType);
|
|
78
|
-
|
|
79
|
-
|
|
78
|
+
// API returns the Assignment object directly
|
|
79
|
+
createSpinner.succeed(`Check created: ${chalk.cyan(result.name || name)} (ID: ${result.id || 'N/A'})`);
|
|
80
|
+
return result;
|
|
80
81
|
} catch (error) {
|
|
81
82
|
createSpinner.fail('Failed to create check.');
|
|
82
83
|
}
|
package/src/commands/results.js
CHANGED
|
@@ -27,7 +27,7 @@ async function resultsCommand(options) {
|
|
|
27
27
|
|
|
28
28
|
// Filter to completed checks
|
|
29
29
|
const completed = checks.filter(
|
|
30
|
-
(c) => c.
|
|
30
|
+
(c) => c.status_id === 4 || c.assignmentstatuses?.status === 'Completed'
|
|
31
31
|
);
|
|
32
32
|
|
|
33
33
|
if (completed.length === 0) {
|
|
@@ -88,8 +88,8 @@ async function resultsCommand(options) {
|
|
|
88
88
|
name: 'submissionId',
|
|
89
89
|
message: 'Select a submission:',
|
|
90
90
|
choices: submissions.map((s) => ({
|
|
91
|
-
name: `${s.id
|
|
92
|
-
value: s.id
|
|
91
|
+
name: `${s.id} - ${s.filename || 'unknown'}`,
|
|
92
|
+
value: s.id,
|
|
93
93
|
})),
|
|
94
94
|
},
|
|
95
95
|
]);
|
|
@@ -103,7 +103,8 @@ async function resultsCommand(options) {
|
|
|
103
103
|
detailSpinner.fail('Failed to fetch details.');
|
|
104
104
|
}
|
|
105
105
|
} else if (action === 'browser') {
|
|
106
|
-
|
|
106
|
+
const url = overview?.overviewURL || `https://codequiry.com/results/${checkId}`;
|
|
107
|
+
await open(url);
|
|
107
108
|
console.log(chalk.dim(' Opened in browser.\n'));
|
|
108
109
|
}
|
|
109
110
|
}
|
package/src/commands/scan.js
CHANGED
|
@@ -67,7 +67,7 @@ async function scanCommand(inputPath, options) {
|
|
|
67
67
|
type: 'list',
|
|
68
68
|
name: 'languageId',
|
|
69
69
|
message: 'Programming language:',
|
|
70
|
-
choices: langList.map((l) => ({ name: l.
|
|
70
|
+
choices: langList.map((l) => ({ name: l.language || l.name, value: l.id })),
|
|
71
71
|
},
|
|
72
72
|
]
|
|
73
73
|
: []),
|
|
@@ -107,7 +107,7 @@ async function scanCommand(inputPath, options) {
|
|
|
107
107
|
let check;
|
|
108
108
|
try {
|
|
109
109
|
const result = await createCheck(checkName, languageId, testType);
|
|
110
|
-
check = result
|
|
110
|
+
check = result; // API returns Assignment object directly
|
|
111
111
|
createSpinner.succeed(`Check created: ${chalk.cyan(check.name || checkName)} (ID: ${check.id})`);
|
|
112
112
|
} catch (error) {
|
|
113
113
|
createSpinner.fail('Failed to create check.');
|
|
@@ -172,7 +172,7 @@ async function scanCommand(inputPath, options) {
|
|
|
172
172
|
if (options.threshold) {
|
|
173
173
|
const submissions = overview?.submissions || overview || [];
|
|
174
174
|
const maxScore = Math.max(
|
|
175
|
-
...submissions.map((s) => parseFloat(s.
|
|
175
|
+
...submissions.map((s) => parseFloat(s.result1 || s.total_result || 0))
|
|
176
176
|
);
|
|
177
177
|
if (maxScore > parseFloat(options.threshold)) {
|
|
178
178
|
process.exit(1);
|
package/src/commands/status.js
CHANGED
|
@@ -53,8 +53,8 @@ async function statusCommand(options) {
|
|
|
53
53
|
try {
|
|
54
54
|
const status = await getCheckStatus(checkId);
|
|
55
55
|
spinner.stop();
|
|
56
|
-
const s = status?.
|
|
57
|
-
const p = status?.progress ??
|
|
56
|
+
const s = status?.status || 'Unknown';
|
|
57
|
+
const p = status?.progress ?? '';
|
|
58
58
|
console.log(`\n Check ${chalk.cyan(checkId)}: ${statusColor(s)}${p ? ` (${p}%)` : ''}\n`);
|
|
59
59
|
} catch (error) {
|
|
60
60
|
spinner.fail('Failed to fetch status.');
|
package/src/index.js
CHANGED
package/src/utils/banner.js
CHANGED
|
@@ -11,9 +11,9 @@ function showBanner() {
|
|
|
11
11
|
horizontalLayout: 'default',
|
|
12
12
|
});
|
|
13
13
|
console.log(gradient.vice(text));
|
|
14
|
-
console.log(chalk.dim(' Source Code Similarity Checker') + ' ' + chalk.dim('v2.0.
|
|
14
|
+
console.log(chalk.dim(' Source Code Similarity Checker') + ' ' + chalk.dim('v2.0.1\n'));
|
|
15
15
|
} catch {
|
|
16
|
-
console.log(gradient.vice('\n Codequiry CLI v2.0.
|
|
16
|
+
console.log(gradient.vice('\n Codequiry CLI v2.0.1\n'));
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
package/src/utils/display.js
CHANGED
|
@@ -14,11 +14,12 @@ function colorScore(score) {
|
|
|
14
14
|
function statusColor(status) {
|
|
15
15
|
const s = String(status).toLowerCase();
|
|
16
16
|
if (s === 'completed' || s === 'done') return chalk.green(status);
|
|
17
|
-
if (s === '
|
|
17
|
+
if (s === 'checking' || s === 'waiting' || s === 'uploading') return chalk.yellow(status);
|
|
18
18
|
if (s === 'failed' || s === 'error') return chalk.red(status);
|
|
19
19
|
return chalk.dim(status);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
+
// API returns array of Assignment objects directly from getChecks
|
|
22
23
|
function checksTable(checks) {
|
|
23
24
|
const table = new Table({
|
|
24
25
|
head: [
|
|
@@ -26,13 +27,12 @@ function checksTable(checks) {
|
|
|
26
27
|
chalk.cyan('Name'),
|
|
27
28
|
chalk.cyan('Language'),
|
|
28
29
|
chalk.cyan('Status'),
|
|
29
|
-
chalk.cyan('Submissions'),
|
|
30
30
|
chalk.cyan('Created'),
|
|
31
31
|
],
|
|
32
32
|
style: { head: [], border: ['dim'] },
|
|
33
33
|
});
|
|
34
34
|
|
|
35
|
-
const list = Array.isArray(checks) ? checks :
|
|
35
|
+
const list = Array.isArray(checks) ? checks : [];
|
|
36
36
|
|
|
37
37
|
if (list.length === 0) {
|
|
38
38
|
console.log(chalk.dim('\n No checks found.\n'));
|
|
@@ -40,12 +40,13 @@ function checksTable(checks) {
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
list.forEach((c) => {
|
|
43
|
+
// Assignment model fields from Laravel
|
|
44
|
+
const statusName = c.assignmentstatuses?.status || '';
|
|
43
45
|
table.push([
|
|
44
46
|
chalk.white(c.id),
|
|
45
47
|
c.name || '',
|
|
46
|
-
c.
|
|
47
|
-
statusColor(
|
|
48
|
-
String(c.submission_count || c.submissions || 0),
|
|
48
|
+
String(c.language_id || ''),
|
|
49
|
+
statusColor(statusName),
|
|
49
50
|
c.created_at ? new Date(c.created_at).toLocaleDateString() : '',
|
|
50
51
|
]);
|
|
51
52
|
});
|
|
@@ -53,8 +54,10 @@ function checksTable(checks) {
|
|
|
53
54
|
console.log('\n' + table.toString() + '\n');
|
|
54
55
|
}
|
|
55
56
|
|
|
57
|
+
// API overview returns {overviewURL, submissions, bardata}
|
|
58
|
+
// submissions have: id, filename, result1 (score), assignment_id, etc.
|
|
56
59
|
function overviewTable(overview) {
|
|
57
|
-
const submissions = overview?.submissions ||
|
|
60
|
+
const submissions = overview?.submissions || [];
|
|
58
61
|
|
|
59
62
|
if (!Array.isArray(submissions) || submissions.length === 0) {
|
|
60
63
|
console.log(chalk.dim('\n No results available.\n'));
|
|
@@ -66,25 +69,25 @@ function overviewTable(overview) {
|
|
|
66
69
|
chalk.cyan('ID'),
|
|
67
70
|
chalk.cyan('Filename'),
|
|
68
71
|
chalk.cyan('Similarity'),
|
|
69
|
-
chalk.cyan('Matches'),
|
|
70
72
|
],
|
|
71
73
|
style: { head: [], border: ['dim'] },
|
|
72
74
|
});
|
|
73
75
|
|
|
74
76
|
submissions.forEach((s) => {
|
|
77
|
+
const score = s.result1 || s.total_result || 0;
|
|
75
78
|
table.push([
|
|
76
|
-
chalk.white(s.id ||
|
|
77
|
-
s.filename ||
|
|
78
|
-
colorScore(
|
|
79
|
-
String(s.match_count || s.matches || ''),
|
|
79
|
+
chalk.white(s.id || ''),
|
|
80
|
+
s.filename || '',
|
|
81
|
+
colorScore(score),
|
|
80
82
|
]);
|
|
81
83
|
});
|
|
82
84
|
|
|
83
85
|
console.log('\n' + table.toString() + '\n');
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
// API results returns {submission, avg, max, min, other_matches, related_submissions, related_files}
|
|
86
89
|
function resultsDetailTable(results) {
|
|
87
|
-
const matches = results?.
|
|
90
|
+
const matches = results?.other_matches || [];
|
|
88
91
|
|
|
89
92
|
if (!Array.isArray(matches) || matches.length === 0) {
|
|
90
93
|
console.log(chalk.dim('\n No matches found.\n'));
|
|
@@ -94,8 +97,7 @@ function resultsDetailTable(results) {
|
|
|
94
97
|
const table = new Table({
|
|
95
98
|
head: [
|
|
96
99
|
chalk.cyan('Matched With'),
|
|
97
|
-
chalk.cyan('
|
|
98
|
-
chalk.cyan('Lines Matched'),
|
|
100
|
+
chalk.cyan('Tokens'),
|
|
99
101
|
chalk.cyan('Type'),
|
|
100
102
|
],
|
|
101
103
|
style: { head: [], border: ['dim'] },
|
|
@@ -103,14 +105,18 @@ function resultsDetailTable(results) {
|
|
|
103
105
|
|
|
104
106
|
matches.forEach((m) => {
|
|
105
107
|
table.push([
|
|
106
|
-
m.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
m.type || m.match_type || '',
|
|
108
|
+
m.filename || m.other_filename || '',
|
|
109
|
+
String(m.tokens || ''),
|
|
110
|
+
m.type || '',
|
|
110
111
|
]);
|
|
111
112
|
});
|
|
112
113
|
|
|
113
|
-
console.log('\n' + table.toString()
|
|
114
|
+
console.log('\n' + table.toString());
|
|
115
|
+
|
|
116
|
+
// Show stats
|
|
117
|
+
if (results.avg !== undefined) {
|
|
118
|
+
console.log(chalk.dim(` Avg: ${results.avg}% Max: ${results.max}% Min: ${results.min}%\n`));
|
|
119
|
+
}
|
|
114
120
|
}
|
|
115
121
|
|
|
116
122
|
module.exports = { colorScore, statusColor, checksTable, overviewTable, resultsDetailTable };
|
package/src/utils/poller.js
CHANGED
|
@@ -2,19 +2,16 @@
|
|
|
2
2
|
|
|
3
3
|
const ora = require('ora');
|
|
4
4
|
const chalk = require('chalk');
|
|
5
|
-
const cliProgress = require('cli-progress');
|
|
6
5
|
const { getCheckStatus } = require('../api/endpoints');
|
|
7
6
|
|
|
8
7
|
async function pollUntilComplete(checkId, { silent = false } = {}) {
|
|
9
8
|
const POLL_INTERVAL = 3000;
|
|
10
9
|
let spinner;
|
|
11
|
-
let progressBar;
|
|
12
10
|
let interrupted = false;
|
|
13
11
|
|
|
14
12
|
const cleanup = () => {
|
|
15
13
|
interrupted = true;
|
|
16
14
|
if (spinner) spinner.stop();
|
|
17
|
-
if (progressBar) progressBar.stop();
|
|
18
15
|
};
|
|
19
16
|
|
|
20
17
|
process.on('SIGINT', cleanup);
|
|
@@ -26,27 +23,26 @@ async function pollUntilComplete(checkId, { silent = false } = {}) {
|
|
|
26
23
|
try {
|
|
27
24
|
while (!interrupted) {
|
|
28
25
|
const status = await getCheckStatus(checkId);
|
|
29
|
-
const
|
|
30
|
-
const progress = status?.progress ??
|
|
26
|
+
const statusId = status?.status_id;
|
|
27
|
+
const progress = status?.progress ?? 0;
|
|
31
28
|
|
|
32
|
-
//
|
|
33
|
-
if (
|
|
29
|
+
// status_id 4 = Completed
|
|
30
|
+
if (statusId === 4) {
|
|
34
31
|
if (spinner) spinner.succeed('Check completed!');
|
|
35
|
-
if (progressBar) progressBar.stop();
|
|
36
32
|
return status;
|
|
37
33
|
}
|
|
38
34
|
|
|
39
|
-
//
|
|
40
|
-
if (
|
|
41
|
-
if (spinner) spinner.fail('Check failed
|
|
42
|
-
if (progressBar) progressBar.stop();
|
|
35
|
+
// status_id 5 = Error/Failed
|
|
36
|
+
if (statusId === 5 || statusId === -1) {
|
|
37
|
+
if (spinner) spinner.fail('Check failed: ' + (status?.status_message || status?.status || ''));
|
|
43
38
|
return status;
|
|
44
39
|
}
|
|
45
40
|
|
|
46
41
|
// Update progress display
|
|
47
42
|
if (!silent && spinner) {
|
|
48
|
-
const
|
|
49
|
-
|
|
43
|
+
const statusText = status?.status || 'Processing';
|
|
44
|
+
const msg = status?.status_message || '';
|
|
45
|
+
spinner.text = `${statusText}... ${chalk.cyan(progress + '%')}${msg ? chalk.dim(' - ' + msg) : ''}`;
|
|
50
46
|
}
|
|
51
47
|
|
|
52
48
|
await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL));
|