@ktmcp-cli/authentiq 1.0.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/AGENT.md +66 -0
- package/LICENSE +21 -0
- package/README.md +82 -0
- package/bin/authentiq.js +9 -0
- package/package.json +27 -0
- package/src/api.js +151 -0
- package/src/config.js +21 -0
- package/src/index.js +469 -0
package/AGENT.md
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# AGENT.md — Authentiq CLI for AI Agents
|
|
2
|
+
|
|
3
|
+
This document explains how to use the Authentiq CLI as an AI agent.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
The `authentiq` CLI provides access to the Authentiq Identity API. Requires a Bearer token.
|
|
8
|
+
|
|
9
|
+
## Prerequisites
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
authentiq config set token YOUR_BEARER_TOKEN
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## All Commands
|
|
16
|
+
|
|
17
|
+
### Config
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
authentiq config get <key>
|
|
21
|
+
authentiq config set <key> <value>
|
|
22
|
+
authentiq config list
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Sessions
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
authentiq sessions list
|
|
29
|
+
authentiq sessions create --scope "openid email"
|
|
30
|
+
authentiq sessions create --scope "openid profile" --callback https://example.com/callback
|
|
31
|
+
authentiq sessions get <session-id>
|
|
32
|
+
authentiq sessions delete <session-id>
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### Keys
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
authentiq keys register --key '{"kty":"EC","crv":"P-256","x":"...","y":"..."}'
|
|
39
|
+
authentiq keys get <pk>
|
|
40
|
+
authentiq keys update <pk> --data '{"status":"active"}'
|
|
41
|
+
authentiq keys revoke <pk>
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Scopes
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
authentiq scopes list
|
|
48
|
+
authentiq scopes get <scope-id>
|
|
49
|
+
authentiq scopes request --data '{"scope":"email"}'
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## JSON Output
|
|
53
|
+
|
|
54
|
+
All commands support `--json`:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
authentiq sessions list --json
|
|
58
|
+
authentiq keys get <pk> --json
|
|
59
|
+
authentiq scopes list --json
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Error Handling
|
|
63
|
+
|
|
64
|
+
The CLI exits with code 1 on error and prints to stderr.
|
|
65
|
+
- `Authentication failed` — Run `authentiq config set token YOUR_TOKEN`
|
|
66
|
+
- `Resource not found` — Check the ID is correct
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 KTMCP
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
> "Six months ago, everyone was talking about MCPs. And I was like, screw MCPs. Every MCP would be better as a CLI."
|
|
2
|
+
>
|
|
3
|
+
> — [Peter Steinberger](https://twitter.com/steipete), Founder of OpenClaw
|
|
4
|
+
> [Watch on YouTube (~2:39:00)](https://www.youtube.com/@lexfridman) | [Lex Fridman Podcast #491](https://lexfridman.com/peter-steinberger/)
|
|
5
|
+
|
|
6
|
+
# Authentiq CLI
|
|
7
|
+
|
|
8
|
+
Production-ready CLI for the Authentiq Identity API. Manage sessions, keys, and scopes directly from your terminal.
|
|
9
|
+
|
|
10
|
+
## Installation
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
npm install -g @ktmcp-cli/authentiq
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
## Configuration
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
authentiq config set token YOUR_BEARER_TOKEN
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Usage
|
|
23
|
+
|
|
24
|
+
### Sessions
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
# List all sessions
|
|
28
|
+
authentiq sessions list
|
|
29
|
+
|
|
30
|
+
# Create a new session
|
|
31
|
+
authentiq sessions create --scope "openid email"
|
|
32
|
+
authentiq sessions create --scope "openid profile" --callback https://example.com/callback
|
|
33
|
+
|
|
34
|
+
# Get session details
|
|
35
|
+
authentiq sessions get <session-id>
|
|
36
|
+
|
|
37
|
+
# Delete a session
|
|
38
|
+
authentiq sessions delete <session-id>
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
### Keys
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
# Register a public key (JWK format)
|
|
45
|
+
authentiq keys register --key '{"kty":"EC","crv":"P-256","x":"...","y":"..."}'
|
|
46
|
+
|
|
47
|
+
# Get key details
|
|
48
|
+
authentiq keys get <pk>
|
|
49
|
+
|
|
50
|
+
# Update a key
|
|
51
|
+
authentiq keys update <pk> --data '{"status":"active"}'
|
|
52
|
+
|
|
53
|
+
# Revoke a key
|
|
54
|
+
authentiq keys revoke <pk>
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Scopes
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
# List available scopes
|
|
61
|
+
authentiq scopes list
|
|
62
|
+
|
|
63
|
+
# Get scope details
|
|
64
|
+
authentiq scopes get openid
|
|
65
|
+
|
|
66
|
+
# Request additional scopes
|
|
67
|
+
authentiq scopes request --data '{"scope":"email"}'
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### JSON Output
|
|
71
|
+
|
|
72
|
+
All commands support `--json` for machine-readable output:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
authentiq sessions list --json
|
|
76
|
+
authentiq sessions get <id> --json
|
|
77
|
+
authentiq scopes list --json
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## License
|
|
81
|
+
|
|
82
|
+
MIT
|
package/bin/authentiq.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@ktmcp-cli/authentiq",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-ready CLI for Authentiq Identity API - Kill The MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "src/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"authentiq": "bin/authentiq.js"
|
|
9
|
+
},
|
|
10
|
+
"keywords": ["authentiq", "identity", "sessions", "keys", "scopes", "cli", "api", "ktmcp"],
|
|
11
|
+
"author": "KTMCP",
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"commander": "^12.0.0",
|
|
15
|
+
"axios": "^1.6.7",
|
|
16
|
+
"chalk": "^5.3.0",
|
|
17
|
+
"ora": "^8.0.1",
|
|
18
|
+
"conf": "^12.0.0"
|
|
19
|
+
},
|
|
20
|
+
"engines": { "node": ">=18.0.0" },
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/ktmcp-cli/authentiq.git"
|
|
24
|
+
},
|
|
25
|
+
"homepage": "https://killthemcp.com/authentiq-cli",
|
|
26
|
+
"bugs": { "url": "https://github.com/ktmcp-cli/authentiq/issues" }
|
|
27
|
+
}
|
package/src/api.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import axios from 'axios';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
|
|
4
|
+
const BASE_URL = 'https://6-dot-authentiqio.appspot.com';
|
|
5
|
+
|
|
6
|
+
function getClient() {
|
|
7
|
+
const token = getConfig('token');
|
|
8
|
+
return axios.create({
|
|
9
|
+
baseURL: BASE_URL,
|
|
10
|
+
headers: {
|
|
11
|
+
'Accept': 'application/json',
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
...(token ? { 'Authorization': `Bearer ${token}` } : {})
|
|
14
|
+
},
|
|
15
|
+
timeout: 15000
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function handleApiError(error) {
|
|
20
|
+
if (error.response) {
|
|
21
|
+
const status = error.response.status;
|
|
22
|
+
const data = error.response.data;
|
|
23
|
+
if (status === 401) {
|
|
24
|
+
throw new Error('Authentication failed. Run: authentiq config set token YOUR_TOKEN');
|
|
25
|
+
} else if (status === 403) {
|
|
26
|
+
throw new Error('Access forbidden. Check your API permissions.');
|
|
27
|
+
} else if (status === 404) {
|
|
28
|
+
throw new Error('Resource not found.');
|
|
29
|
+
} else if (status === 429) {
|
|
30
|
+
throw new Error('Rate limit exceeded. Please wait before retrying.');
|
|
31
|
+
} else {
|
|
32
|
+
const message = data?.message || data?.error || JSON.stringify(data);
|
|
33
|
+
throw new Error(`API Error (${status}): ${message}`);
|
|
34
|
+
}
|
|
35
|
+
} else if (error.request) {
|
|
36
|
+
throw new Error('No response from Authentiq API. Check your internet connection.');
|
|
37
|
+
} else {
|
|
38
|
+
throw error;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ============================================================
|
|
43
|
+
// SESSIONS
|
|
44
|
+
// ============================================================
|
|
45
|
+
|
|
46
|
+
export async function createSession(data) {
|
|
47
|
+
try {
|
|
48
|
+
const response = await getClient().post('/session', data);
|
|
49
|
+
return response.data;
|
|
50
|
+
} catch (error) {
|
|
51
|
+
handleApiError(error);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function getSession(sessionId) {
|
|
56
|
+
try {
|
|
57
|
+
const response = await getClient().get(`/session/${sessionId}`);
|
|
58
|
+
return response.data;
|
|
59
|
+
} catch (error) {
|
|
60
|
+
handleApiError(error);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export async function deleteSession(sessionId) {
|
|
65
|
+
try {
|
|
66
|
+
const response = await getClient().delete(`/session/${sessionId}`);
|
|
67
|
+
return response.data;
|
|
68
|
+
} catch (error) {
|
|
69
|
+
handleApiError(error);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function listSessions() {
|
|
74
|
+
try {
|
|
75
|
+
const response = await getClient().get('/session');
|
|
76
|
+
return response.data || [];
|
|
77
|
+
} catch (error) {
|
|
78
|
+
handleApiError(error);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// ============================================================
|
|
83
|
+
// KEYS
|
|
84
|
+
// ============================================================
|
|
85
|
+
|
|
86
|
+
export async function registerKey(keyData) {
|
|
87
|
+
try {
|
|
88
|
+
const response = await getClient().post('/key', keyData);
|
|
89
|
+
return response.data;
|
|
90
|
+
} catch (error) {
|
|
91
|
+
handleApiError(error);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function getKey(pk) {
|
|
96
|
+
try {
|
|
97
|
+
const response = await getClient().get(`/key/${pk}`);
|
|
98
|
+
return response.data;
|
|
99
|
+
} catch (error) {
|
|
100
|
+
handleApiError(error);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export async function revokeKey(pk) {
|
|
105
|
+
try {
|
|
106
|
+
const response = await getClient().delete(`/key/${pk}`);
|
|
107
|
+
return response.data;
|
|
108
|
+
} catch (error) {
|
|
109
|
+
handleApiError(error);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
export async function updateKey(pk, keyData) {
|
|
114
|
+
try {
|
|
115
|
+
const response = await getClient().put(`/key/${pk}`, keyData);
|
|
116
|
+
return response.data;
|
|
117
|
+
} catch (error) {
|
|
118
|
+
handleApiError(error);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================================
|
|
123
|
+
// SCOPES
|
|
124
|
+
// ============================================================
|
|
125
|
+
|
|
126
|
+
export async function listScopes() {
|
|
127
|
+
try {
|
|
128
|
+
const response = await getClient().get('/scope');
|
|
129
|
+
return response.data || [];
|
|
130
|
+
} catch (error) {
|
|
131
|
+
handleApiError(error);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
export async function getScope(scopeId) {
|
|
136
|
+
try {
|
|
137
|
+
const response = await getClient().get(`/scope/${scopeId}`);
|
|
138
|
+
return response.data;
|
|
139
|
+
} catch (error) {
|
|
140
|
+
handleApiError(error);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export async function requestScope(scopeData) {
|
|
145
|
+
try {
|
|
146
|
+
const response = await getClient().post('/scope', scopeData);
|
|
147
|
+
return response.data;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
handleApiError(error);
|
|
150
|
+
}
|
|
151
|
+
}
|
package/src/config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import Conf from 'conf';
|
|
2
|
+
|
|
3
|
+
const config = new Conf({ projectName: '@ktmcp-cli/authentiq' });
|
|
4
|
+
|
|
5
|
+
export function getConfig(key) {
|
|
6
|
+
return config.get(key);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function setConfig(key, value) {
|
|
10
|
+
config.set(key, value);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function isConfigured() {
|
|
14
|
+
return !!config.get('token');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function getAllConfig() {
|
|
18
|
+
return config.store;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export default config;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import ora from 'ora';
|
|
4
|
+
import { getConfig, setConfig, getAllConfig, isConfigured } from './config.js';
|
|
5
|
+
import {
|
|
6
|
+
createSession,
|
|
7
|
+
getSession,
|
|
8
|
+
deleteSession,
|
|
9
|
+
listSessions,
|
|
10
|
+
registerKey,
|
|
11
|
+
getKey,
|
|
12
|
+
revokeKey,
|
|
13
|
+
updateKey,
|
|
14
|
+
listScopes,
|
|
15
|
+
getScope,
|
|
16
|
+
requestScope
|
|
17
|
+
} from './api.js';
|
|
18
|
+
|
|
19
|
+
const program = new Command();
|
|
20
|
+
|
|
21
|
+
// ============================================================
|
|
22
|
+
// Helpers
|
|
23
|
+
// ============================================================
|
|
24
|
+
|
|
25
|
+
function printSuccess(message) {
|
|
26
|
+
console.log(chalk.green('✓') + ' ' + message);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function printError(message) {
|
|
30
|
+
console.error(chalk.red('✗') + ' ' + message);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function printTable(data, columns) {
|
|
34
|
+
if (!data || data.length === 0) {
|
|
35
|
+
console.log(chalk.yellow('No results found.'));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const widths = {};
|
|
40
|
+
columns.forEach(col => {
|
|
41
|
+
widths[col.key] = col.label.length;
|
|
42
|
+
data.forEach(row => {
|
|
43
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
44
|
+
if (val.length > widths[col.key]) widths[col.key] = val.length;
|
|
45
|
+
});
|
|
46
|
+
widths[col.key] = Math.min(widths[col.key], 40);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const header = columns.map(col => col.label.padEnd(widths[col.key])).join(' ');
|
|
50
|
+
console.log(chalk.bold(chalk.cyan(header)));
|
|
51
|
+
console.log(chalk.dim('─'.repeat(header.length)));
|
|
52
|
+
|
|
53
|
+
data.forEach(row => {
|
|
54
|
+
const line = columns.map(col => {
|
|
55
|
+
const val = String(col.format ? col.format(row[col.key], row) : (row[col.key] ?? ''));
|
|
56
|
+
return val.substring(0, widths[col.key]).padEnd(widths[col.key]);
|
|
57
|
+
}).join(' ');
|
|
58
|
+
console.log(line);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
console.log(chalk.dim(`\n${data.length} result(s)`));
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function printJson(data) {
|
|
65
|
+
console.log(JSON.stringify(data, null, 2));
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function withSpinner(message, fn) {
|
|
69
|
+
const spinner = ora(message).start();
|
|
70
|
+
try {
|
|
71
|
+
const result = await fn();
|
|
72
|
+
spinner.stop();
|
|
73
|
+
return result;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
spinner.stop();
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function requireAuth() {
|
|
81
|
+
if (!isConfigured()) {
|
|
82
|
+
printError('Authentiq token not configured.');
|
|
83
|
+
console.log('\nRun the following to configure:');
|
|
84
|
+
console.log(chalk.cyan(' authentiq config set token YOUR_BEARER_TOKEN'));
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ============================================================
|
|
90
|
+
// Program metadata
|
|
91
|
+
// ============================================================
|
|
92
|
+
|
|
93
|
+
program
|
|
94
|
+
.name('authentiq')
|
|
95
|
+
.description(chalk.bold('Authentiq CLI') + ' - Identity and session management from your terminal')
|
|
96
|
+
.version('1.0.0');
|
|
97
|
+
|
|
98
|
+
// ============================================================
|
|
99
|
+
// CONFIG
|
|
100
|
+
// ============================================================
|
|
101
|
+
|
|
102
|
+
const configCmd = program.command('config').description('Manage CLI configuration');
|
|
103
|
+
|
|
104
|
+
configCmd
|
|
105
|
+
.command('get <key>')
|
|
106
|
+
.description('Get a configuration value')
|
|
107
|
+
.action((key) => {
|
|
108
|
+
const value = getConfig(key);
|
|
109
|
+
if (value === undefined) {
|
|
110
|
+
printError(`Key '${key}' not found`);
|
|
111
|
+
} else {
|
|
112
|
+
console.log(value);
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
configCmd
|
|
117
|
+
.command('set <key> <value>')
|
|
118
|
+
.description('Set a configuration value')
|
|
119
|
+
.action((key, value) => {
|
|
120
|
+
setConfig(key, value);
|
|
121
|
+
printSuccess(`Config '${key}' set`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
configCmd
|
|
125
|
+
.command('list')
|
|
126
|
+
.description('List all configuration values')
|
|
127
|
+
.action(() => {
|
|
128
|
+
const all = getAllConfig();
|
|
129
|
+
console.log(chalk.bold('\nAuthentiq CLI Configuration\n'));
|
|
130
|
+
if (Object.keys(all).length === 0) {
|
|
131
|
+
console.log(chalk.yellow('No configuration set.'));
|
|
132
|
+
console.log('\nRun: ' + chalk.cyan('authentiq config set token YOUR_TOKEN'));
|
|
133
|
+
} else {
|
|
134
|
+
Object.entries(all).forEach(([k, v]) => {
|
|
135
|
+
const displayVal = k === 'token' ? chalk.green('*'.repeat(Math.min(String(v).length, 8))) : chalk.cyan(String(v));
|
|
136
|
+
console.log(`${k}: ${displayVal}`);
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// ============================================================
|
|
142
|
+
// SESSIONS
|
|
143
|
+
// ============================================================
|
|
144
|
+
|
|
145
|
+
const sessionsCmd = program.command('sessions').description('Manage identity sessions');
|
|
146
|
+
|
|
147
|
+
sessionsCmd
|
|
148
|
+
.command('list')
|
|
149
|
+
.description('List active sessions')
|
|
150
|
+
.option('--json', 'Output as JSON')
|
|
151
|
+
.action(async (options) => {
|
|
152
|
+
requireAuth();
|
|
153
|
+
try {
|
|
154
|
+
const sessions = await withSpinner('Fetching sessions...', () => listSessions());
|
|
155
|
+
|
|
156
|
+
if (options.json) {
|
|
157
|
+
printJson(sessions);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const data = Array.isArray(sessions) ? sessions : (sessions ? [sessions] : []);
|
|
162
|
+
printTable(data, [
|
|
163
|
+
{ key: 'session', label: 'Session ID', format: (v) => v ? String(v).substring(0, 16) + '...' : 'N/A' },
|
|
164
|
+
{ key: 'status', label: 'Status' },
|
|
165
|
+
{ key: 'created', label: 'Created' },
|
|
166
|
+
{ key: 'scope', label: 'Scope', format: (v) => Array.isArray(v) ? v.join(' ') : (v || '') }
|
|
167
|
+
]);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
printError(error.message);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
sessionsCmd
|
|
175
|
+
.command('create')
|
|
176
|
+
.description('Create a new session')
|
|
177
|
+
.option('--scope <scope>', 'Requested scope (space-separated)', 'openid')
|
|
178
|
+
.option('--callback <url>', 'Callback URL')
|
|
179
|
+
.option('--json', 'Output as JSON')
|
|
180
|
+
.action(async (options) => {
|
|
181
|
+
requireAuth();
|
|
182
|
+
try {
|
|
183
|
+
const sessionData = {
|
|
184
|
+
scope: options.scope.split(' '),
|
|
185
|
+
...(options.callback && { callback: options.callback })
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const session = await withSpinner('Creating session...', () => createSession(sessionData));
|
|
189
|
+
|
|
190
|
+
if (options.json) {
|
|
191
|
+
printJson(session);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
printSuccess('Session created');
|
|
196
|
+
if (session) {
|
|
197
|
+
console.log('Session: ', chalk.cyan(session.session || JSON.stringify(session)));
|
|
198
|
+
if (session.status) console.log('Status: ', session.status);
|
|
199
|
+
if (session.url) console.log('Auth URL: ', chalk.blue(session.url));
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
printError(error.message);
|
|
203
|
+
process.exit(1);
|
|
204
|
+
}
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
sessionsCmd
|
|
208
|
+
.command('get <session-id>')
|
|
209
|
+
.description('Get session details')
|
|
210
|
+
.option('--json', 'Output as JSON')
|
|
211
|
+
.action(async (sessionId, options) => {
|
|
212
|
+
requireAuth();
|
|
213
|
+
try {
|
|
214
|
+
const session = await withSpinner(`Fetching session ${sessionId}...`, () =>
|
|
215
|
+
getSession(sessionId)
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
if (!session) {
|
|
219
|
+
printError('Session not found');
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (options.json) {
|
|
224
|
+
printJson(session);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
console.log(chalk.bold('\nSession Details\n'));
|
|
229
|
+
console.log('Session ID: ', chalk.cyan(session.session || sessionId));
|
|
230
|
+
console.log('Status: ', session.status || 'N/A');
|
|
231
|
+
console.log('Created: ', session.created || 'N/A');
|
|
232
|
+
if (session.scope) console.log('Scope: ', Array.isArray(session.scope) ? session.scope.join(' ') : session.scope);
|
|
233
|
+
console.log('');
|
|
234
|
+
} catch (error) {
|
|
235
|
+
printError(error.message);
|
|
236
|
+
process.exit(1);
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
sessionsCmd
|
|
241
|
+
.command('delete <session-id>')
|
|
242
|
+
.description('Delete (revoke) a session')
|
|
243
|
+
.action(async (sessionId) => {
|
|
244
|
+
requireAuth();
|
|
245
|
+
try {
|
|
246
|
+
await withSpinner(`Deleting session ${sessionId}...`, () => deleteSession(sessionId));
|
|
247
|
+
printSuccess(`Session ${sessionId} deleted`);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
printError(error.message);
|
|
250
|
+
process.exit(1);
|
|
251
|
+
}
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
// ============================================================
|
|
255
|
+
// KEYS
|
|
256
|
+
// ============================================================
|
|
257
|
+
|
|
258
|
+
const keysCmd = program.command('keys').description('Manage identity keys');
|
|
259
|
+
|
|
260
|
+
keysCmd
|
|
261
|
+
.command('register')
|
|
262
|
+
.description('Register a new public key')
|
|
263
|
+
.requiredOption('--key <json>', 'Public key data as JSON (JWK format)')
|
|
264
|
+
.option('--json', 'Output as JSON')
|
|
265
|
+
.action(async (options) => {
|
|
266
|
+
requireAuth();
|
|
267
|
+
let keyData;
|
|
268
|
+
try {
|
|
269
|
+
keyData = JSON.parse(options.key);
|
|
270
|
+
} catch {
|
|
271
|
+
printError('Invalid JSON for --key');
|
|
272
|
+
process.exit(1);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
try {
|
|
276
|
+
const result = await withSpinner('Registering key...', () => registerKey(keyData));
|
|
277
|
+
|
|
278
|
+
if (options.json) {
|
|
279
|
+
printJson(result);
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
printSuccess('Key registered');
|
|
284
|
+
if (result) {
|
|
285
|
+
console.log('Key ID: ', chalk.cyan(result.pk || result.kid || JSON.stringify(result)));
|
|
286
|
+
}
|
|
287
|
+
} catch (error) {
|
|
288
|
+
printError(error.message);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
keysCmd
|
|
294
|
+
.command('get <pk>')
|
|
295
|
+
.description('Get key details by public key ID')
|
|
296
|
+
.option('--json', 'Output as JSON')
|
|
297
|
+
.action(async (pk, options) => {
|
|
298
|
+
requireAuth();
|
|
299
|
+
try {
|
|
300
|
+
const key = await withSpinner(`Fetching key ${pk}...`, () => getKey(pk));
|
|
301
|
+
|
|
302
|
+
if (!key) {
|
|
303
|
+
printError('Key not found');
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
if (options.json) {
|
|
308
|
+
printJson(key);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
console.log(chalk.bold('\nKey Details\n'));
|
|
313
|
+
console.log('Key ID: ', chalk.cyan(key.pk || key.kid || pk));
|
|
314
|
+
console.log('Type: ', key.kty || 'N/A');
|
|
315
|
+
console.log('Algorithm: ', key.alg || 'N/A');
|
|
316
|
+
console.log('Status: ', key.status || 'N/A');
|
|
317
|
+
console.log('');
|
|
318
|
+
} catch (error) {
|
|
319
|
+
printError(error.message);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
|
|
324
|
+
keysCmd
|
|
325
|
+
.command('revoke <pk>')
|
|
326
|
+
.description('Revoke a public key')
|
|
327
|
+
.action(async (pk) => {
|
|
328
|
+
requireAuth();
|
|
329
|
+
try {
|
|
330
|
+
await withSpinner(`Revoking key ${pk}...`, () => revokeKey(pk));
|
|
331
|
+
printSuccess(`Key ${pk} revoked`);
|
|
332
|
+
} catch (error) {
|
|
333
|
+
printError(error.message);
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
keysCmd
|
|
339
|
+
.command('update <pk>')
|
|
340
|
+
.description('Update a public key')
|
|
341
|
+
.requiredOption('--data <json>', 'Updated key data as JSON')
|
|
342
|
+
.option('--json', 'Output as JSON')
|
|
343
|
+
.action(async (pk, options) => {
|
|
344
|
+
requireAuth();
|
|
345
|
+
let updateData;
|
|
346
|
+
try {
|
|
347
|
+
updateData = JSON.parse(options.data);
|
|
348
|
+
} catch {
|
|
349
|
+
printError('Invalid JSON for --data');
|
|
350
|
+
process.exit(1);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
try {
|
|
354
|
+
const result = await withSpinner(`Updating key ${pk}...`, () => updateKey(pk, updateData));
|
|
355
|
+
|
|
356
|
+
if (options.json) {
|
|
357
|
+
printJson(result);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
printSuccess(`Key ${pk} updated`);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
printError(error.message);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
|
|
368
|
+
// ============================================================
|
|
369
|
+
// SCOPES
|
|
370
|
+
// ============================================================
|
|
371
|
+
|
|
372
|
+
const scopesCmd = program.command('scopes').description('Manage identity scopes');
|
|
373
|
+
|
|
374
|
+
scopesCmd
|
|
375
|
+
.command('list')
|
|
376
|
+
.description('List available scopes')
|
|
377
|
+
.option('--json', 'Output as JSON')
|
|
378
|
+
.action(async (options) => {
|
|
379
|
+
requireAuth();
|
|
380
|
+
try {
|
|
381
|
+
const scopes = await withSpinner('Fetching scopes...', () => listScopes());
|
|
382
|
+
|
|
383
|
+
if (options.json) {
|
|
384
|
+
printJson(scopes);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const data = Array.isArray(scopes) ? scopes : (scopes ? [scopes] : []);
|
|
389
|
+
printTable(data, [
|
|
390
|
+
{ key: 'name', label: 'Name' },
|
|
391
|
+
{ key: 'essential', label: 'Essential', format: (v) => v ? 'Yes' : 'No' },
|
|
392
|
+
{ key: 'description', label: 'Description' }
|
|
393
|
+
]);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
printError(error.message);
|
|
396
|
+
process.exit(1);
|
|
397
|
+
}
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
scopesCmd
|
|
401
|
+
.command('get <scope-id>')
|
|
402
|
+
.description('Get scope details')
|
|
403
|
+
.option('--json', 'Output as JSON')
|
|
404
|
+
.action(async (scopeId, options) => {
|
|
405
|
+
requireAuth();
|
|
406
|
+
try {
|
|
407
|
+
const scope = await withSpinner(`Fetching scope ${scopeId}...`, () => getScope(scopeId));
|
|
408
|
+
|
|
409
|
+
if (!scope) {
|
|
410
|
+
printError('Scope not found');
|
|
411
|
+
process.exit(1);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
if (options.json) {
|
|
415
|
+
printJson(scope);
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
console.log(chalk.bold('\nScope Details\n'));
|
|
420
|
+
console.log('Name: ', chalk.cyan(scope.name || scopeId));
|
|
421
|
+
console.log('Essential: ', scope.essential ? chalk.green('Yes') : 'No');
|
|
422
|
+
console.log('Description: ', scope.description || 'N/A');
|
|
423
|
+
console.log('');
|
|
424
|
+
} catch (error) {
|
|
425
|
+
printError(error.message);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
scopesCmd
|
|
431
|
+
.command('request')
|
|
432
|
+
.description('Request additional scopes')
|
|
433
|
+
.requiredOption('--data <json>', 'Scope request data as JSON')
|
|
434
|
+
.option('--json', 'Output as JSON')
|
|
435
|
+
.action(async (options) => {
|
|
436
|
+
requireAuth();
|
|
437
|
+
let scopeData;
|
|
438
|
+
try {
|
|
439
|
+
scopeData = JSON.parse(options.data);
|
|
440
|
+
} catch {
|
|
441
|
+
printError('Invalid JSON for --data');
|
|
442
|
+
process.exit(1);
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
const result = await withSpinner('Requesting scope...', () => requestScope(scopeData));
|
|
447
|
+
|
|
448
|
+
if (options.json) {
|
|
449
|
+
printJson(result);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
printSuccess('Scope requested');
|
|
454
|
+
if (result) console.log(JSON.stringify(result, null, 2));
|
|
455
|
+
} catch (error) {
|
|
456
|
+
printError(error.message);
|
|
457
|
+
process.exit(1);
|
|
458
|
+
}
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
// ============================================================
|
|
462
|
+
// Parse
|
|
463
|
+
// ============================================================
|
|
464
|
+
|
|
465
|
+
program.parse(process.argv);
|
|
466
|
+
|
|
467
|
+
if (process.argv.length <= 2) {
|
|
468
|
+
program.help();
|
|
469
|
+
}
|