vigthoria-cli 1.9.2 → 1.9.5
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/README.md +15 -5
- package/dist/commands/auth.d.ts +28 -38
- package/dist/commands/auth.js +389 -320
- package/dist/commands/chat.d.ts +3 -0
- package/dist/commands/chat.js +66 -15
- package/dist/commands/index.js +1 -1
- package/dist/commands/legion.d.ts +22 -19
- package/dist/commands/legion.js +550 -132
- package/dist/commands/preview.js +32 -7
- package/dist/commands/repo.js +19 -13
- package/dist/commands/update.d.ts +9 -0
- package/dist/commands/update.js +235 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +114 -33
- package/dist/utils/api.d.ts +25 -70
- package/dist/utils/api.js +784 -695
- package/dist/utils/tools.d.ts +11 -0
- package/dist/utils/tools.js +222 -0
- package/package.json +7 -1
package/dist/commands/auth.js
CHANGED
|
@@ -3,407 +3,476 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.
|
|
7
|
-
exports.
|
|
8
|
-
exports.
|
|
9
|
-
exports.
|
|
10
|
-
exports.
|
|
11
|
-
exports.
|
|
12
|
-
exports.
|
|
13
|
-
exports.
|
|
14
|
-
exports.loginAction = loginAction;
|
|
15
|
-
exports.logoutAction = logoutAction;
|
|
6
|
+
exports.loadAuthConfig = loadAuthConfig;
|
|
7
|
+
exports.saveAuthConfig = saveAuthConfig;
|
|
8
|
+
exports.clearAuthConfig = clearAuthConfig;
|
|
9
|
+
exports.getAuthToken = getAuthToken;
|
|
10
|
+
exports.login = login;
|
|
11
|
+
exports.logout = logout;
|
|
12
|
+
exports.whoami = whoami;
|
|
13
|
+
exports.doctor = doctor;
|
|
16
14
|
exports.handleLogin = handleLogin;
|
|
17
15
|
exports.handleLogout = handleLogout;
|
|
18
16
|
exports.statusAction = statusAction;
|
|
19
|
-
exports.
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
const os_1 = __importDefault(require("os"));
|
|
17
|
+
exports.registerAuthCommands = registerAuthCommands;
|
|
18
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
19
|
+
const fs_1 = require("fs");
|
|
20
|
+
const os_1 = require("os");
|
|
24
21
|
const path_1 = __importDefault(require("path"));
|
|
25
|
-
const
|
|
22
|
+
const readline_1 = __importDefault(require("readline"));
|
|
26
23
|
const DEFAULT_API_URL = 'https://coder.vigthoria.io';
|
|
27
|
-
const CONFIG_DIR = path_1.default.join(os_1.
|
|
28
|
-
const
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
24
|
+
const CONFIG_DIR = path_1.default.join((0, os_1.homedir)(), '.vigthoria');
|
|
25
|
+
const CONFIG_FILE = path_1.default.join(CONFIG_DIR, 'config.json');
|
|
26
|
+
const KNOWN_AUTH_BASE_URLS = ['https://coder.vigthoria.io', 'https://api.vigthoria.io'];
|
|
27
|
+
class HttpError extends Error {
|
|
28
|
+
status;
|
|
29
|
+
constructor(status, message) {
|
|
30
|
+
super(message);
|
|
31
|
+
this.status = status;
|
|
32
|
+
}
|
|
33
33
|
}
|
|
34
|
-
function
|
|
35
|
-
|
|
34
|
+
function trimTrailingSlash(value) {
|
|
35
|
+
return value.replace(/\/+$/, '');
|
|
36
36
|
}
|
|
37
|
-
function
|
|
38
|
-
|
|
39
|
-
fs_1.default.writeFileSync(AUTH_FILE, `${JSON.stringify(session, null, 2)}\n`, { mode: 0o600 });
|
|
37
|
+
function uniqueStrings(values) {
|
|
38
|
+
return [...new Set(values.filter(Boolean))];
|
|
40
39
|
}
|
|
41
|
-
function
|
|
42
|
-
|
|
43
|
-
return 'Network error while contacting Vigthoria. Check your connection and API URL.';
|
|
44
|
-
}
|
|
45
|
-
return error instanceof Error ? error.message : String(error);
|
|
40
|
+
function getApiUrl() {
|
|
41
|
+
return trimTrailingSlash(process.env.VIGTHORIA_API_URL || DEFAULT_API_URL);
|
|
46
42
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
return {};
|
|
51
|
-
try {
|
|
52
|
-
const parsed = JSON.parse(text);
|
|
53
|
-
return parsed && typeof parsed === 'object' ? parsed : {};
|
|
43
|
+
function derivePeerHost(baseUrl) {
|
|
44
|
+
if (baseUrl.includes('://api.vigthoria.io')) {
|
|
45
|
+
return baseUrl.replace('://api.vigthoria.io', '://coder.vigthoria.io');
|
|
54
46
|
}
|
|
55
|
-
|
|
56
|
-
return
|
|
47
|
+
if (baseUrl.includes('://coder.vigthoria.io')) {
|
|
48
|
+
return baseUrl.replace('://coder.vigthoria.io', '://api.vigthoria.io');
|
|
57
49
|
}
|
|
50
|
+
return undefined;
|
|
58
51
|
}
|
|
59
|
-
function
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
? data.message
|
|
64
|
-
: undefined;
|
|
65
|
-
if (response.status === 400 || response.status === 401 || response.status === 403) {
|
|
66
|
-
return serverMessage || 'Invalid credentials. Check your email, password, or token and try again.';
|
|
67
|
-
}
|
|
68
|
-
if (response.status === 404)
|
|
69
|
-
return serverMessage || 'Authentication endpoint was not found for the configured API URL.';
|
|
70
|
-
if (response.status >= 500)
|
|
71
|
-
return serverMessage || 'Vigthoria authentication service is temporarily unavailable. Try again later.';
|
|
72
|
-
return serverMessage || `Request failed with HTTP ${response.status}`;
|
|
52
|
+
function getAuthBaseCandidates(seedBaseUrl) {
|
|
53
|
+
const seed = trimTrailingSlash(seedBaseUrl || getApiUrl());
|
|
54
|
+
const peer = derivePeerHost(seed);
|
|
55
|
+
return uniqueStrings([seed, ...(peer ? [peer] : []), ...KNOWN_AUTH_BASE_URLS]).map(trimTrailingSlash);
|
|
73
56
|
}
|
|
74
|
-
function
|
|
57
|
+
function ensureConfigDir() {
|
|
58
|
+
if (!(0, fs_1.existsSync)(CONFIG_DIR)) {
|
|
59
|
+
(0, fs_1.mkdirSync)(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
60
|
+
}
|
|
75
61
|
try {
|
|
76
|
-
|
|
77
|
-
return null;
|
|
78
|
-
const parsed = JSON.parse(fs_1.default.readFileSync(AUTH_FILE, 'utf8'));
|
|
79
|
-
if (!parsed || typeof parsed.accessToken !== 'string' || !parsed.accessToken)
|
|
80
|
-
return null;
|
|
81
|
-
return parsed;
|
|
62
|
+
(0, fs_1.chmodSync)(CONFIG_DIR, 0o700);
|
|
82
63
|
}
|
|
83
|
-
catch
|
|
84
|
-
|
|
85
|
-
return null;
|
|
64
|
+
catch {
|
|
65
|
+
// Best-effort on non-POSIX filesystems.
|
|
86
66
|
}
|
|
87
67
|
}
|
|
88
|
-
function
|
|
89
|
-
const
|
|
90
|
-
if (
|
|
91
|
-
return;
|
|
92
|
-
try {
|
|
93
|
-
if (typeof storage.removeItem === 'function') {
|
|
94
|
-
storage.removeItem('vigthoria.auth');
|
|
95
|
-
storage.removeItem('vigthoria.jwt');
|
|
96
|
-
storage.removeItem('auth');
|
|
97
|
-
storage.removeItem('jwt');
|
|
98
|
-
}
|
|
99
|
-
if (typeof storage.clear === 'function')
|
|
100
|
-
storage.clear();
|
|
101
|
-
}
|
|
102
|
-
catch (error) {
|
|
103
|
-
console.warn('Failed to clear local auth storage:', asErrorMessage(error));
|
|
68
|
+
function humanMessage(error) {
|
|
69
|
+
const raw = error instanceof Error ? error.message : String(error);
|
|
70
|
+
if (/^\s*</.test(raw) || /<!doctype html/i.test(raw)) {
|
|
71
|
+
return 'The Vigthoria service returned an unexpected HTML response. This usually means auth routes are misconfigured server-side.';
|
|
104
72
|
}
|
|
73
|
+
return raw || 'Unknown authentication error.';
|
|
105
74
|
}
|
|
106
|
-
function
|
|
107
|
-
if (!
|
|
108
|
-
return;
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
75
|
+
function extractAuthToken(payload) {
|
|
76
|
+
if (!payload || typeof payload !== 'object') {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
const body = payload;
|
|
80
|
+
return body.token || body.accessToken || body.tokens?.access_token;
|
|
112
81
|
}
|
|
113
|
-
function
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
if (fs_1.default.existsSync(AUTH_FILE))
|
|
117
|
-
fs_1.default.rmSync(AUTH_FILE, { force: true });
|
|
82
|
+
function extractAuthUser(payload, fallbackEmail) {
|
|
83
|
+
if (!payload || typeof payload !== 'object') {
|
|
84
|
+
return fallbackEmail ? { email: fallbackEmail } : undefined;
|
|
118
85
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
86
|
+
const body = payload;
|
|
87
|
+
const user = body.user;
|
|
88
|
+
if (user && typeof user === 'object') {
|
|
89
|
+
const typed = user;
|
|
90
|
+
return {
|
|
91
|
+
id: typed.id ? String(typed.id) : undefined,
|
|
92
|
+
email: typed.email ? String(typed.email) : fallbackEmail,
|
|
93
|
+
name: typed.name ? String(typed.name) : typed.username ? String(typed.username) : undefined,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
if (body.email || body.id || body.username) {
|
|
97
|
+
return {
|
|
98
|
+
id: body.id ? String(body.id) : undefined,
|
|
99
|
+
email: body.email ? String(body.email) : fallbackEmail,
|
|
100
|
+
name: body.username ? String(body.username) : body.name ? String(body.name) : undefined,
|
|
101
|
+
};
|
|
122
102
|
}
|
|
123
|
-
|
|
124
|
-
resetJwtState(state);
|
|
125
|
-
refreshRequests.clear();
|
|
126
|
-
return ok;
|
|
103
|
+
return fallbackEmail ? { email: fallbackEmail } : undefined;
|
|
127
104
|
}
|
|
128
|
-
|
|
129
|
-
|
|
105
|
+
function loadAuthConfig() {
|
|
106
|
+
if (!(0, fs_1.existsSync)(CONFIG_FILE)) {
|
|
107
|
+
return { apiUrl: getApiUrl() };
|
|
108
|
+
}
|
|
130
109
|
try {
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
body: JSON.stringify(body),
|
|
138
|
-
});
|
|
110
|
+
const parsed = JSON.parse((0, fs_1.readFileSync)(CONFIG_FILE, 'utf8'));
|
|
111
|
+
return {
|
|
112
|
+
apiUrl: trimTrailingSlash(parsed.apiUrl || getApiUrl()),
|
|
113
|
+
token: parsed.token || parsed.authToken,
|
|
114
|
+
user: parsed.user,
|
|
115
|
+
};
|
|
139
116
|
}
|
|
140
117
|
catch (error) {
|
|
141
|
-
|
|
118
|
+
console.warn(chalk_1.default.yellow(`Warning: could not read auth config: ${humanMessage(error)}`));
|
|
119
|
+
return { apiUrl: getApiUrl() };
|
|
142
120
|
}
|
|
143
|
-
const data = await parseResponseBody(response);
|
|
144
|
-
if (!response.ok)
|
|
145
|
-
throw new Error(responseErrorMessage(response, data));
|
|
146
|
-
return data;
|
|
147
121
|
}
|
|
148
|
-
|
|
149
|
-
|
|
122
|
+
function saveAuthConfig(config) {
|
|
123
|
+
ensureConfigDir();
|
|
124
|
+
(0, fs_1.writeFileSync)(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
150
125
|
try {
|
|
151
|
-
|
|
152
|
-
headers: token ? { authorization: `Bearer ${token}` } : undefined,
|
|
153
|
-
});
|
|
126
|
+
(0, fs_1.chmodSync)(CONFIG_FILE, 0o600);
|
|
154
127
|
}
|
|
155
|
-
catch
|
|
156
|
-
|
|
128
|
+
catch {
|
|
129
|
+
// Best-effort on non-POSIX filesystems.
|
|
157
130
|
}
|
|
158
|
-
const data = await parseResponseBody(response);
|
|
159
|
-
if (!response.ok)
|
|
160
|
-
throw new Error(responseErrorMessage(response, data));
|
|
161
|
-
return data;
|
|
162
|
-
}
|
|
163
|
-
function normalizeSession(data, apiUrl) {
|
|
164
|
-
const accessToken = data.accessToken || data.token;
|
|
165
|
-
if (!accessToken)
|
|
166
|
-
throw new Error('Authentication response did not include an access token.');
|
|
167
|
-
const now = new Date().toISOString();
|
|
168
|
-
const expiresAt = typeof data.expiresAt === 'number'
|
|
169
|
-
? data.expiresAt
|
|
170
|
-
: typeof data.expiresIn === 'number'
|
|
171
|
-
? Date.now() + data.expiresIn * 1000
|
|
172
|
-
: undefined;
|
|
173
|
-
return {
|
|
174
|
-
accessToken,
|
|
175
|
-
refreshToken: data.refreshToken,
|
|
176
|
-
user: data.user,
|
|
177
|
-
expiresAt,
|
|
178
|
-
apiUrl,
|
|
179
|
-
createdAt: now,
|
|
180
|
-
updatedAt: now,
|
|
181
|
-
};
|
|
182
131
|
}
|
|
183
|
-
function
|
|
184
|
-
|
|
132
|
+
function clearAuthConfig() {
|
|
133
|
+
if ((0, fs_1.existsSync)(CONFIG_FILE)) {
|
|
134
|
+
(0, fs_1.rmSync)(CONFIG_FILE, { force: true });
|
|
135
|
+
}
|
|
185
136
|
}
|
|
186
|
-
function
|
|
187
|
-
|
|
188
|
-
return false;
|
|
189
|
-
if (typeof state.isExpired === 'function')
|
|
190
|
-
return state.isExpired();
|
|
191
|
-
return typeof state.expiresAt === 'number' && Date.now() >= state.expiresAt - REFRESH_SKEW_MS;
|
|
137
|
+
function getAuthToken() {
|
|
138
|
+
return process.env.VIGTHORIA_TOKEN || loadAuthConfig().token;
|
|
192
139
|
}
|
|
193
|
-
function
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
140
|
+
async function requestJson(url, init) {
|
|
141
|
+
const response = await fetch(url, {
|
|
142
|
+
...init,
|
|
143
|
+
headers: {
|
|
144
|
+
accept: 'application/json',
|
|
145
|
+
'content-type': 'application/json',
|
|
146
|
+
...(init.headers || {}),
|
|
147
|
+
},
|
|
148
|
+
});
|
|
149
|
+
const text = await response.text();
|
|
150
|
+
let body = {};
|
|
151
|
+
if (text.trim()) {
|
|
152
|
+
try {
|
|
153
|
+
body = JSON.parse(text);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
if (/^\s*</.test(text) || /<!doctype html/i.test(text)) {
|
|
157
|
+
throw new HttpError(response.status, 'The Vigthoria service returned an unexpected HTML response. Please verify auth routes.');
|
|
158
|
+
}
|
|
159
|
+
throw new HttpError(response.status, text.slice(0, 240));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (!response.ok) {
|
|
163
|
+
const message = typeof body === 'object' && body && 'error' in body
|
|
164
|
+
? String(body.error)
|
|
165
|
+
: typeof body === 'object' && body && 'message' in body
|
|
166
|
+
? String(body.message)
|
|
167
|
+
: `Request failed with status ${response.status}`;
|
|
168
|
+
throw new HttpError(response.status, message);
|
|
169
|
+
}
|
|
170
|
+
return body;
|
|
197
171
|
}
|
|
198
|
-
async function
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
172
|
+
async function ask(question, hidden = false) {
|
|
173
|
+
const rl = readline_1.default.createInterface({ input: process.stdin, output: process.stdout });
|
|
174
|
+
if (!hidden) {
|
|
175
|
+
try {
|
|
176
|
+
return await new Promise((resolve) => rl.question(question, resolve));
|
|
177
|
+
}
|
|
178
|
+
finally {
|
|
179
|
+
rl.close();
|
|
180
|
+
}
|
|
202
181
|
}
|
|
182
|
+
const output = process.stdout;
|
|
183
|
+
const mutableRl = rl;
|
|
184
|
+
const originalWrite = mutableRl._writeToOutput;
|
|
185
|
+
mutableRl._writeToOutput = function writeMasked(value) {
|
|
186
|
+
if (value.includes('\n') || value.includes('\r')) {
|
|
187
|
+
output.write(value);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
output.write('*');
|
|
191
|
+
}
|
|
192
|
+
};
|
|
203
193
|
try {
|
|
204
|
-
|
|
205
|
-
const refreshed = normalizeSession({ ...data, refreshToken: data.refreshToken || session.refreshToken }, session.apiUrl);
|
|
206
|
-
refreshed.createdAt = session.createdAt;
|
|
207
|
-
refreshed.updatedAt = new Date().toISOString();
|
|
208
|
-
writeAuthSession(refreshed);
|
|
209
|
-
state.token = refreshed.accessToken;
|
|
210
|
-
state.expiresAt = refreshed.expiresAt ?? null;
|
|
211
|
-
state.refreshToken = refreshed.refreshToken ?? session.refreshToken;
|
|
212
|
-
state.apiUrl = refreshed.apiUrl;
|
|
213
|
-
return refreshed.accessToken;
|
|
194
|
+
return await new Promise((resolve) => rl.question(question, resolve));
|
|
214
195
|
}
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
196
|
+
finally {
|
|
197
|
+
if (originalWrite) {
|
|
198
|
+
mutableRl._writeToOutput = originalWrite;
|
|
199
|
+
}
|
|
200
|
+
output.write('\n');
|
|
201
|
+
rl.close();
|
|
219
202
|
}
|
|
220
203
|
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
throw new Error('JWT state is required.');
|
|
224
|
-
if (!jwtNeedsRefresh(state))
|
|
225
|
-
return state.token;
|
|
226
|
-
const session = readAuthSession();
|
|
227
|
-
if (!session) {
|
|
228
|
-
resetJwtState(state);
|
|
229
|
-
return null;
|
|
230
|
-
}
|
|
231
|
-
const key = refreshKey(state, session);
|
|
232
|
-
const existing = refreshRequests.get(key);
|
|
233
|
-
if (existing)
|
|
234
|
-
return existing;
|
|
235
|
-
const request = performRefresh(state, session).finally(() => {
|
|
236
|
-
refreshRequests.delete(key);
|
|
237
|
-
});
|
|
238
|
-
refreshRequests.set(key, request);
|
|
239
|
-
return request;
|
|
204
|
+
function markSuccessExit() {
|
|
205
|
+
process.exitCode = 0;
|
|
240
206
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
let user;
|
|
207
|
+
function markErrorExit() {
|
|
208
|
+
process.exitCode = 1;
|
|
209
|
+
}
|
|
210
|
+
async function login(email, password) {
|
|
246
211
|
try {
|
|
247
|
-
const
|
|
248
|
-
|
|
212
|
+
const resolvedEmail = (email || '').trim();
|
|
213
|
+
const resolvedPassword = password || '';
|
|
214
|
+
if (!resolvedEmail || !resolvedPassword) {
|
|
215
|
+
throw new Error('Email and password are required. Pass --email and --password or run login interactively.');
|
|
216
|
+
}
|
|
217
|
+
const authBases = getAuthBaseCandidates(getApiUrl());
|
|
218
|
+
const endpointVariants = [
|
|
219
|
+
{ path: '/auth/login', body: { email: resolvedEmail, password: resolvedPassword } },
|
|
220
|
+
{ path: '/api/auth/login', body: { email: resolvedEmail, password: resolvedPassword } },
|
|
221
|
+
{ path: '/api/login-json', body: { identifier: resolvedEmail, password: resolvedPassword } },
|
|
222
|
+
{ path: '/api/login', body: { email: resolvedEmail, password: resolvedPassword } },
|
|
223
|
+
];
|
|
224
|
+
const attempted = [];
|
|
225
|
+
const failures = [];
|
|
226
|
+
for (const base of authBases) {
|
|
227
|
+
for (const variant of endpointVariants) {
|
|
228
|
+
const endpoint = `${trimTrailingSlash(base)}${variant.path}`;
|
|
229
|
+
attempted.push(endpoint);
|
|
230
|
+
try {
|
|
231
|
+
const result = await requestJson(endpoint, {
|
|
232
|
+
method: 'POST',
|
|
233
|
+
body: JSON.stringify(variant.body),
|
|
234
|
+
});
|
|
235
|
+
const token = extractAuthToken(result);
|
|
236
|
+
if (!token) {
|
|
237
|
+
failures.push(`${endpoint} -> success without token`);
|
|
238
|
+
continue;
|
|
239
|
+
}
|
|
240
|
+
const config = {
|
|
241
|
+
apiUrl: trimTrailingSlash(base),
|
|
242
|
+
token,
|
|
243
|
+
user: extractAuthUser(result, resolvedEmail),
|
|
244
|
+
success: true,
|
|
245
|
+
};
|
|
246
|
+
saveAuthConfig(config);
|
|
247
|
+
markSuccessExit();
|
|
248
|
+
return config;
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
const message = humanMessage(error);
|
|
252
|
+
failures.push(`${endpoint} -> ${message}`);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
throw new Error([
|
|
257
|
+
'Login failed on all known auth endpoints.',
|
|
258
|
+
`Tried: ${attempted.join(', ')}`,
|
|
259
|
+
`Last errors: ${failures.slice(-4).join(' | ')}`,
|
|
260
|
+
].join(' '));
|
|
249
261
|
}
|
|
250
262
|
catch (error) {
|
|
251
|
-
|
|
263
|
+
const message = humanMessage(error);
|
|
264
|
+
markErrorExit();
|
|
265
|
+
throw new Error(message);
|
|
252
266
|
}
|
|
253
|
-
const now = new Date().toISOString();
|
|
254
|
-
const session = { accessToken: token, user, apiUrl, createdAt: now, updatedAt: now };
|
|
255
|
-
writeAuthSession(session);
|
|
256
|
-
return session;
|
|
257
267
|
}
|
|
258
|
-
async function
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
268
|
+
async function logout() {
|
|
269
|
+
const config = loadAuthConfig();
|
|
270
|
+
if (config.token) {
|
|
271
|
+
const bases = getAuthBaseCandidates(config.apiUrl || getApiUrl());
|
|
272
|
+
const logoutPaths = ['/auth/logout', '/api/auth/logout', '/api/logout'];
|
|
273
|
+
let remoteLogoutDone = false;
|
|
274
|
+
for (const base of bases) {
|
|
275
|
+
for (const route of logoutPaths) {
|
|
276
|
+
try {
|
|
277
|
+
await requestJson(`${trimTrailingSlash(base)}${route}`, {
|
|
278
|
+
method: 'POST',
|
|
279
|
+
headers: { authorization: `Bearer ${config.token}` },
|
|
280
|
+
});
|
|
281
|
+
remoteLogoutDone = true;
|
|
282
|
+
break;
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
// Continue trying known routes.
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
if (remoteLogoutDone) {
|
|
289
|
+
break;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
if (!remoteLogoutDone) {
|
|
293
|
+
console.warn(chalk_1.default.yellow('Remote logout endpoint not reachable; local credentials were still cleared.'));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
clearAuthConfig();
|
|
297
|
+
process.exitCode = 0;
|
|
266
298
|
}
|
|
267
|
-
async function
|
|
268
|
-
const
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
const
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
299
|
+
async function whoami() {
|
|
300
|
+
const config = loadAuthConfig();
|
|
301
|
+
if (!config.token) {
|
|
302
|
+
return undefined;
|
|
303
|
+
}
|
|
304
|
+
const bases = getAuthBaseCandidates(config.apiUrl || getApiUrl());
|
|
305
|
+
const attempted = [];
|
|
306
|
+
for (const base of bases) {
|
|
307
|
+
const normalizedBase = trimTrailingSlash(base);
|
|
308
|
+
const getRoutes = ['/auth/me', '/api/auth/me', '/api/user/info', '/api/profile'];
|
|
309
|
+
for (const route of getRoutes) {
|
|
310
|
+
const endpoint = `${normalizedBase}${route}`;
|
|
311
|
+
attempted.push(endpoint);
|
|
312
|
+
try {
|
|
313
|
+
const result = await requestJson(endpoint, {
|
|
314
|
+
method: 'GET',
|
|
315
|
+
headers: { authorization: `Bearer ${config.token}` },
|
|
316
|
+
});
|
|
317
|
+
const user = extractAuthUser(result) || extractAuthUser(result.user);
|
|
318
|
+
if (user) {
|
|
319
|
+
saveAuthConfig({ ...config, apiUrl: normalizedBase, user });
|
|
320
|
+
return user;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
catch {
|
|
324
|
+
// continue fallback route probing
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const verifyEndpoint = `${normalizedBase}/api/auth/verify`;
|
|
328
|
+
attempted.push(verifyEndpoint);
|
|
281
329
|
try {
|
|
282
|
-
const result = await
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
330
|
+
const result = await requestJson(verifyEndpoint, {
|
|
331
|
+
method: 'POST',
|
|
332
|
+
headers: { authorization: `Bearer ${config.token}` },
|
|
333
|
+
body: JSON.stringify({ token: config.token }),
|
|
334
|
+
});
|
|
335
|
+
const user = extractAuthUser(result) || extractAuthUser(result.user);
|
|
336
|
+
if (user) {
|
|
337
|
+
saveAuthConfig({ ...config, apiUrl: normalizedBase, user });
|
|
338
|
+
return user;
|
|
287
339
|
}
|
|
288
340
|
}
|
|
289
|
-
catch
|
|
290
|
-
|
|
291
|
-
if (!/pending|authorization_pending|slow_down/i.test(message))
|
|
292
|
-
throw new Error(message);
|
|
341
|
+
catch {
|
|
342
|
+
// continue fallback route probing
|
|
293
343
|
}
|
|
294
344
|
}
|
|
295
|
-
throw new Error(
|
|
345
|
+
throw new Error(`Unable to verify account on known auth endpoints. Tried ${attempted.join(', ')}`);
|
|
296
346
|
}
|
|
297
|
-
async function
|
|
298
|
-
const
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
}
|
|
307
|
-
const state = {
|
|
308
|
-
token: session.accessToken,
|
|
309
|
-
expiresAt: session.expiresAt ?? null,
|
|
310
|
-
refreshToken: session.refreshToken,
|
|
311
|
-
apiUrl: session.apiUrl,
|
|
312
|
-
isExpired: () => true,
|
|
347
|
+
async function doctor() {
|
|
348
|
+
const config = loadAuthConfig();
|
|
349
|
+
const report = {
|
|
350
|
+
nodeVersion: process.version,
|
|
351
|
+
platform: process.platform,
|
|
352
|
+
arch: process.arch,
|
|
353
|
+
configFile: CONFIG_FILE,
|
|
354
|
+
loggedIn: Boolean(config.token),
|
|
355
|
+
apiUrl: config.apiUrl,
|
|
313
356
|
};
|
|
314
|
-
|
|
315
|
-
return
|
|
357
|
+
process.exitCode = 0;
|
|
358
|
+
return report;
|
|
316
359
|
}
|
|
317
|
-
function
|
|
318
|
-
const user = session.user?.email || session.user?.name || session.user?.id || 'authenticated user';
|
|
319
|
-
console.log(`Authenticated as ${user}`);
|
|
320
|
-
console.log(`API: ${session.apiUrl}`);
|
|
321
|
-
if (session.expiresAt)
|
|
322
|
-
console.log(`Expires: ${new Date(session.expiresAt).toLocaleString()}`);
|
|
323
|
-
}
|
|
324
|
-
async function loginAction(options = {}) {
|
|
360
|
+
async function handleLogin(options = {}) {
|
|
325
361
|
try {
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
:
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
throw new Error(asErrorMessage(error));
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
async function logoutAction(state) {
|
|
340
|
-
const session = readAuthSession();
|
|
341
|
-
if (session) {
|
|
342
|
-
try {
|
|
343
|
-
await postJson(`${session.apiUrl}/api/auth/logout`, { client: 'vigthoria-cli' }, session.accessToken);
|
|
362
|
+
if (options.token) {
|
|
363
|
+
const config = {
|
|
364
|
+
apiUrl: getApiUrl(),
|
|
365
|
+
token: options.token,
|
|
366
|
+
success: true,
|
|
367
|
+
};
|
|
368
|
+
saveAuthConfig(config);
|
|
369
|
+
console.log(chalk_1.default.green('Logged in successfully with API token.'));
|
|
370
|
+
process.exitCode = 0;
|
|
371
|
+
return config;
|
|
344
372
|
}
|
|
345
|
-
|
|
346
|
-
|
|
373
|
+
let email = options.email?.trim();
|
|
374
|
+
let password = options.password;
|
|
375
|
+
if (options.device) {
|
|
376
|
+
console.log(chalk_1.default.yellow('Device-code login is not enabled by this Vigthoria service. Falling back to email and password authentication.'));
|
|
347
377
|
}
|
|
378
|
+
if (!email) {
|
|
379
|
+
email = (await ask('Email: ')).trim();
|
|
380
|
+
}
|
|
381
|
+
if (!password) {
|
|
382
|
+
password = await ask('Password: ', true);
|
|
383
|
+
}
|
|
384
|
+
if (!email || !password) {
|
|
385
|
+
throw new Error('Email and password are required. Use --email and --password, or run vigthoria login interactively.');
|
|
386
|
+
}
|
|
387
|
+
const config = await login(email, password);
|
|
388
|
+
console.log(chalk_1.default.green('Logged in successfully.'));
|
|
389
|
+
if (config.user?.email) {
|
|
390
|
+
console.log(`Account: ${config.user.email}`);
|
|
391
|
+
}
|
|
392
|
+
console.log(`API: ${config.apiUrl}`);
|
|
393
|
+
process.exitCode = 0;
|
|
394
|
+
return config;
|
|
348
395
|
}
|
|
349
|
-
|
|
396
|
+
catch (error) {
|
|
397
|
+
console.error(chalk_1.default.red(`Login failed: ${humanMessage(error)}`));
|
|
350
398
|
process.exitCode = 1;
|
|
351
|
-
|
|
352
|
-
|
|
399
|
+
return undefined;
|
|
400
|
+
}
|
|
353
401
|
}
|
|
354
|
-
async function
|
|
402
|
+
async function handleLogout(_options) {
|
|
355
403
|
try {
|
|
356
|
-
await
|
|
404
|
+
await logout();
|
|
405
|
+
console.log(chalk_1.default.green('Logged out successfully.'));
|
|
406
|
+
process.exitCode = 0;
|
|
357
407
|
}
|
|
358
408
|
catch (error) {
|
|
359
|
-
console.error(
|
|
409
|
+
console.error(chalk_1.default.red(`Logout failed: ${humanMessage(error)}`));
|
|
360
410
|
process.exitCode = 1;
|
|
361
411
|
}
|
|
362
412
|
}
|
|
363
|
-
async function handleLogout(config) {
|
|
364
|
-
await logoutAction(config && typeof config === 'object' ? config : null);
|
|
365
|
-
}
|
|
366
413
|
async function statusAction() {
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
414
|
+
try {
|
|
415
|
+
const config = loadAuthConfig();
|
|
416
|
+
if (!config.token) {
|
|
417
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
418
|
+
process.exitCode = 0;
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
let user = config.user;
|
|
422
|
+
try {
|
|
423
|
+
user = await whoami();
|
|
424
|
+
}
|
|
425
|
+
catch {
|
|
426
|
+
// Keep local status available even if remote whoami endpoint is down.
|
|
427
|
+
}
|
|
428
|
+
console.log(chalk_1.default.green('Logged in.'));
|
|
429
|
+
if (user?.email) {
|
|
430
|
+
console.log(`Account: ${user.email}`);
|
|
431
|
+
}
|
|
432
|
+
console.log(`API: ${config.apiUrl}`);
|
|
433
|
+
process.exitCode = 0;
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
console.error(chalk_1.default.red(`Unable to read status: ${humanMessage(error)}`));
|
|
370
437
|
process.exitCode = 1;
|
|
371
|
-
return;
|
|
372
438
|
}
|
|
373
|
-
printSession(session);
|
|
374
439
|
}
|
|
375
|
-
function
|
|
376
|
-
const auth =
|
|
440
|
+
function registerAuthCommands(program) {
|
|
441
|
+
const auth = program.command('auth').description('Manage Vigthoria CLI authentication');
|
|
377
442
|
auth
|
|
378
443
|
.command('login')
|
|
379
|
-
.description('Sign in
|
|
380
|
-
.option('--token <token>', '
|
|
381
|
-
.option('--email <email>', '
|
|
382
|
-
.option('--password <password>', '
|
|
383
|
-
.option('--
|
|
384
|
-
.option('--device', 'force browser/device-code login')
|
|
444
|
+
.description('Sign in with your Vigthoria account')
|
|
445
|
+
.option('-t, --token <token>', 'API token for token-based authentication')
|
|
446
|
+
.option('-e, --email <email>', 'Account email address')
|
|
447
|
+
.option('-p, --password <password>', 'Account password')
|
|
448
|
+
.option('--device', 'Use OAuth device flow (requires server support)')
|
|
385
449
|
.action(async (options) => {
|
|
386
450
|
await handleLogin(options);
|
|
387
451
|
});
|
|
388
452
|
auth
|
|
389
453
|
.command('logout')
|
|
390
|
-
.description('
|
|
454
|
+
.description('Sign out and remove saved credentials')
|
|
391
455
|
.action(async () => {
|
|
392
|
-
await handleLogout(
|
|
456
|
+
await handleLogout();
|
|
393
457
|
});
|
|
394
458
|
auth
|
|
395
|
-
.command('
|
|
396
|
-
.description('Show
|
|
459
|
+
.command('whoami')
|
|
460
|
+
.description('Show the authenticated Vigthoria account')
|
|
397
461
|
.action(async () => {
|
|
398
|
-
|
|
462
|
+
try {
|
|
463
|
+
const user = await whoami();
|
|
464
|
+
if (!user) {
|
|
465
|
+
console.log(chalk_1.default.yellow('Not logged in.'));
|
|
466
|
+
process.exitCode = 0;
|
|
467
|
+
return;
|
|
468
|
+
}
|
|
469
|
+
console.log(chalk_1.default.green('Logged in.'));
|
|
470
|
+
console.log(user.email || user.name || user.id || 'Authenticated user');
|
|
471
|
+
process.exitCode = 0;
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
console.error(chalk_1.default.red(`Unable to fetch account: ${humanMessage(error)}`));
|
|
475
|
+
process.exitCode = 1;
|
|
476
|
+
}
|
|
399
477
|
});
|
|
400
|
-
auth.action(() => auth.help());
|
|
401
|
-
return auth;
|
|
402
|
-
}
|
|
403
|
-
function registerAuthCommand(program) {
|
|
404
|
-
const command = createAuthCommand();
|
|
405
|
-
program.addCommand(command);
|
|
406
|
-
return command;
|
|
407
478
|
}
|
|
408
|
-
exports.authCommand = createAuthCommand();
|
|
409
|
-
exports.default = exports.authCommand;
|