@kata.dev/challenge-cli 1.2.1 → 1.2.2
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/challenge.js +4 -0
- package/package.json +1 -1
- package/src/commands/login.js +70 -11
- package/src/commands/logout.js +59 -0
- package/src/commands/me.js +46 -0
package/bin/challenge.js
CHANGED
|
@@ -13,6 +13,8 @@ import { registerPublishCommand } from '../src/commands/publish.js';
|
|
|
13
13
|
import { registerStartCommand } from '../src/commands/start.js';
|
|
14
14
|
import { registerSubmitCommand } from '../src/commands/submit.js';
|
|
15
15
|
import { registerScoreCommand } from '../src/commands/score.js';
|
|
16
|
+
import { registerMeCommand } from '../src/commands/me.js';
|
|
17
|
+
import { registerLogoutCommand } from '../src/commands/logout.js';
|
|
16
18
|
|
|
17
19
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
18
20
|
const pkg = JSON.parse(readFileSync(join(__dirname, '..', 'package.json'), 'utf8'));
|
|
@@ -32,6 +34,8 @@ registerPublishCommand(program);
|
|
|
32
34
|
registerStartCommand(program);
|
|
33
35
|
registerSubmitCommand(program);
|
|
34
36
|
registerScoreCommand(program);
|
|
37
|
+
registerMeCommand(program);
|
|
38
|
+
registerLogoutCommand(program);
|
|
35
39
|
|
|
36
40
|
program.parseAsync(process.argv).catch((err) => {
|
|
37
41
|
console.error(chalk.red('Error:'), err.message);
|
package/package.json
CHANGED
package/src/commands/login.js
CHANGED
|
@@ -1,25 +1,84 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
2
3
|
import { saveConfig, loadConfig } from '../lib/config.js';
|
|
4
|
+
import { requestJson } from '../lib/http.js';
|
|
3
5
|
|
|
4
6
|
export function registerLoginCommand(program) {
|
|
5
7
|
program
|
|
6
8
|
.command('login')
|
|
7
|
-
.description('
|
|
9
|
+
.description('Authenticate with the Eval Engine API')
|
|
8
10
|
.requiredOption('--api <url>', 'Eval Engine API base URL')
|
|
9
|
-
.
|
|
11
|
+
.option('--token <token>', 'JWT token (will auto-generate a PAT)')
|
|
12
|
+
.option('--pat <token>', 'Personal Access Token (direct PAT login)')
|
|
10
13
|
.action(async (opts) => {
|
|
14
|
+
const apiUrl = opts.api.replace(/\/$/, '');
|
|
15
|
+
|
|
16
|
+
if (!opts.token && !opts.pat) {
|
|
17
|
+
console.error(
|
|
18
|
+
chalk.red('Error:') + ' Provide either ' +
|
|
19
|
+
chalk.cyan('--token <jwt>') + ' or ' +
|
|
20
|
+
chalk.cyan('--pat <token>')
|
|
21
|
+
);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
|
|
11
25
|
const existing = loadConfig();
|
|
12
26
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
27
|
+
// Direct PAT login
|
|
28
|
+
if (opts.pat) {
|
|
29
|
+
const config = {
|
|
30
|
+
...existing,
|
|
31
|
+
apiUrl,
|
|
32
|
+
token: opts.pat,
|
|
33
|
+
};
|
|
34
|
+
const configPath = saveConfig(config);
|
|
35
|
+
|
|
36
|
+
console.log(chalk.green('✔') + ' PAT saved to ' + chalk.dim(configPath));
|
|
37
|
+
console.log(' API: ' + chalk.cyan(config.apiUrl));
|
|
38
|
+
console.log(' Token: ' + chalk.dim(config.token.slice(0, 12) + '…'));
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// JWT login → auto-generate PAT
|
|
43
|
+
const jwt = opts.token;
|
|
44
|
+
const spinner = ora('Generating Personal Access Token…').start();
|
|
45
|
+
|
|
46
|
+
try {
|
|
47
|
+
const result = await requestJson(`${apiUrl}/auth/tokens`, {
|
|
48
|
+
method: 'POST',
|
|
49
|
+
token: jwt,
|
|
50
|
+
body: { name: `CLI Token (${new Date().toISOString().slice(0, 10)})` },
|
|
51
|
+
});
|
|
18
52
|
|
|
19
|
-
|
|
53
|
+
const config = {
|
|
54
|
+
...existing,
|
|
55
|
+
apiUrl,
|
|
56
|
+
token: result.token,
|
|
57
|
+
};
|
|
58
|
+
const configPath = saveConfig(config);
|
|
20
59
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
60
|
+
spinner.succeed('PAT generated and saved');
|
|
61
|
+
console.log();
|
|
62
|
+
console.log(' API: ' + chalk.cyan(config.apiUrl));
|
|
63
|
+
console.log(' Token: ' + chalk.dim(config.token.slice(0, 12) + '…'));
|
|
64
|
+
console.log(' Role: ' + chalk.dim(result.role));
|
|
65
|
+
console.log(' Saved: ' + chalk.dim(configPath));
|
|
66
|
+
console.log();
|
|
67
|
+
console.log(chalk.dim('This token does not expire. Revoke with: ') + chalk.cyan('npx @kata.dev/challenge-cli logout'));
|
|
68
|
+
console.log();
|
|
69
|
+
} catch (err) {
|
|
70
|
+
spinner.fail('Failed to generate PAT');
|
|
71
|
+
console.error(chalk.red('Error:') + ` ${err.message}`);
|
|
72
|
+
console.log();
|
|
73
|
+
console.log(chalk.dim('Falling back to direct JWT storage…'));
|
|
74
|
+
const config = {
|
|
75
|
+
...existing,
|
|
76
|
+
apiUrl,
|
|
77
|
+
token: jwt,
|
|
78
|
+
};
|
|
79
|
+
const configPath = saveConfig(config);
|
|
80
|
+
console.log(chalk.yellow('⚠') + ' JWT saved to ' + chalk.dim(configPath));
|
|
81
|
+
console.log(chalk.dim(' Note: JWT tokens expire. For long-lived access, ensure the server supports PAT creation.'));
|
|
82
|
+
}
|
|
24
83
|
});
|
|
25
84
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import ora from 'ora';
|
|
3
|
+
import { resolveApiUrl, resolveToken, saveConfig, loadConfig } from '../lib/config.js';
|
|
4
|
+
import { requestJson } from '../lib/http.js';
|
|
5
|
+
|
|
6
|
+
export function registerLogoutCommand(program) {
|
|
7
|
+
program
|
|
8
|
+
.command('logout')
|
|
9
|
+
.description('Revoke current token and clear local credentials')
|
|
10
|
+
.option('--api <url>', 'API base URL (falls back to config)')
|
|
11
|
+
.option('--token <token>', 'Auth token (falls back to config)')
|
|
12
|
+
.action(async (opts) => {
|
|
13
|
+
const apiBase = resolveApiUrl(opts.api);
|
|
14
|
+
const token = resolveToken(opts.token);
|
|
15
|
+
|
|
16
|
+
if (!token) {
|
|
17
|
+
console.log(chalk.dim('Not logged in — nothing to do.'));
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Try to get identity to find PAT ID for revocation
|
|
22
|
+
const spinner = ora('Revoking token…').start();
|
|
23
|
+
try {
|
|
24
|
+
// Check if it's a PAT and get its ID
|
|
25
|
+
if (token.startsWith('kat_')) {
|
|
26
|
+
const me = await requestJson(`${apiBase}/auth/me`, {
|
|
27
|
+
method: 'GET',
|
|
28
|
+
token,
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
if (me.patId) {
|
|
32
|
+
await requestJson(`${apiBase}/auth/tokens/${me.patId}`, {
|
|
33
|
+
method: 'DELETE',
|
|
34
|
+
token,
|
|
35
|
+
});
|
|
36
|
+
spinner.succeed('Token revoked on server');
|
|
37
|
+
} else {
|
|
38
|
+
spinner.info('Token is not a PAT — cannot revoke server-side');
|
|
39
|
+
}
|
|
40
|
+
} else {
|
|
41
|
+
spinner.info('JWT tokens cannot be revoked — clearing local credentials only');
|
|
42
|
+
}
|
|
43
|
+
} catch (err) {
|
|
44
|
+
spinner.warn(`Could not revoke token: ${err.message}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Clear local config
|
|
48
|
+
try {
|
|
49
|
+
const config = loadConfig();
|
|
50
|
+
delete config.token;
|
|
51
|
+
saveConfig(config);
|
|
52
|
+
console.log(chalk.green('✔') + ' Local credentials cleared');
|
|
53
|
+
} catch {
|
|
54
|
+
console.log(chalk.dim('No local config to clear'));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
console.log();
|
|
58
|
+
});
|
|
59
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
import { resolveApiUrl, resolveToken } from '../lib/config.js';
|
|
3
|
+
import { requestJson } from '../lib/http.js';
|
|
4
|
+
|
|
5
|
+
export function registerMeCommand(program) {
|
|
6
|
+
program
|
|
7
|
+
.command('me')
|
|
8
|
+
.description('Show current authenticated identity')
|
|
9
|
+
.option('--api <url>', 'API base URL (falls back to config)')
|
|
10
|
+
.option('--token <token>', 'Auth token (falls back to config)')
|
|
11
|
+
.action(async (opts) => {
|
|
12
|
+
const apiBase = resolveApiUrl(opts.api);
|
|
13
|
+
const token = resolveToken(opts.token);
|
|
14
|
+
if (!token) {
|
|
15
|
+
console.error(
|
|
16
|
+
chalk.red('Error:') + ' Not logged in.\n' +
|
|
17
|
+
'Run ' + chalk.cyan('npx @kata.dev/challenge-cli login') + ' first.'
|
|
18
|
+
);
|
|
19
|
+
process.exit(1);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
const me = await requestJson(`${apiBase}/auth/me`, {
|
|
24
|
+
method: 'GET',
|
|
25
|
+
token,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
console.log();
|
|
29
|
+
console.log(chalk.bold(' Authenticated Identity'));
|
|
30
|
+
console.log(chalk.dim(' ─────────────────────'));
|
|
31
|
+
console.log(` ${chalk.dim('Subject:')} ${chalk.cyan(me.sub)}`);
|
|
32
|
+
console.log(` ${chalk.dim('Roles:')} ${(me.roles || []).join(', ') || chalk.dim('none')}`);
|
|
33
|
+
console.log(` ${chalk.dim('Token Type:')} ${me.tokenType === 'pat' ? chalk.green('PAT') : chalk.yellow('JWT')}`);
|
|
34
|
+
if (me.orgId) {
|
|
35
|
+
console.log(` ${chalk.dim('Org ID:')} ${me.orgId}`);
|
|
36
|
+
}
|
|
37
|
+
if (me.patId) {
|
|
38
|
+
console.log(` ${chalk.dim('PAT ID:')} ${me.patId}`);
|
|
39
|
+
}
|
|
40
|
+
console.log();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
console.error(chalk.red('Error:') + ` ${err.message}`);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|