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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codequiry-cli",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Codequiry CLI - Source code similarity checker",
5
5
  "main": "src/index.js",
6
6
  "bin": {
@@ -12,30 +12,36 @@ async function accountCommand() {
12
12
  const spinner = ora('Fetching account info...').start();
13
13
 
14
14
  try {
15
- const account = await getAccount();
15
+ const data = await getAccount();
16
16
  spinner.stop();
17
17
 
18
- const user = account.user || account;
19
- const plan = user.plan_name || user.plan || 'Free';
20
- const checksUsed = user.checks_used ?? user.procheck_used ?? 0;
21
- const checksLimit = user.checks_limit ?? user.procheck_limit ?? 0;
22
- const quotaPct = checksLimit > 0 ? Math.round((checksUsed / checksLimit) * 100) : 0;
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((quotaPct / 100) * barLen);
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:')} ${user.name || user.email || 'N/A'}`,
31
- `${chalk.dim('Email:')} ${user.email || 'N/A'}`,
32
- `${chalk.dim('Plan:')} ${chalk.cyan(plan)}`,
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('Usage'),
40
+ chalk.bold('Quota'),
35
41
  '',
36
- `${chalk.dim('Checks:')} ${checksUsed} / ${checksLimit}`,
37
- ` ${quotaBar} ${quotaPct}%`,
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' +
@@ -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.name || l.language,
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
- createSpinner.succeed(`Check created: ${chalk.cyan(result.check?.name || name)} (ID: ${result.check?.id || 'N/A'})`);
79
- return result.check || result;
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
  }
@@ -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.status === 5 || c.status_name === 'Completed' || c.status === 'completed'
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 || s.submission_id} - ${s.filename || s.name}`,
92
- value: s.id || s.submission_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
- await open(`https://codequiry.com/results/${checkId}`);
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
  }
@@ -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.name || l.language, value: l.id })),
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.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.result_score || s.similarity || 0))
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);
@@ -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?.status_name || status?.status || 'Unknown';
57
- const p = status?.progress ?? status?.percentage ?? '';
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
@@ -6,7 +6,7 @@ const program = new Command();
6
6
  program
7
7
  .name('codequiry')
8
8
  .description('Codequiry CLI - Source Code Similarity Checker')
9
- .version('2.0.0');
9
+ .version('2.0.1');
10
10
 
11
11
  // Auth command
12
12
  program
@@ -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.0\n'));
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.0\n'));
16
+ console.log(gradient.vice('\n Codequiry CLI v2.0.1\n'));
17
17
  }
18
18
  }
19
19
 
@@ -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 === 'processing' || s === 'running' || s === 'in_progress') return chalk.yellow(status);
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 : 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.language_name || c.language || '',
47
- statusColor(c.status_name || c.status || ''),
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 || overview || [];
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 || s.submission_id || ''),
77
- s.filename || s.name || '',
78
- colorScore(s.result_score || s.similarity || s.score || 0),
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?.matches || results?.results || 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('Similarity'),
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.matched_filename || m.other_filename || m.name || '',
107
- colorScore(m.score || m.similarity || 0),
108
- String(m.lines_matched || m.matched_lines || ''),
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() + '\n');
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 };
@@ -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 statusVal = status?.status ?? status?.check_status ?? status;
30
- const progress = status?.progress ?? status?.percentage ?? 0;
26
+ const statusId = status?.status_id;
27
+ const progress = status?.progress ?? 0;
31
28
 
32
- // Check if completed
33
- if (statusVal === 5 || statusVal === 'completed' || statusVal === 'done') {
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
- // Check if failed
40
- if (statusVal === 'failed' || statusVal === 'error' || statusVal === -1) {
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 pct = typeof progress === 'number' ? progress : 0;
49
- spinner.text = `Processing... ${chalk.cyan(pct + '%')}`;
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));