vigthoria-cli 1.8.19 → 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 +16 -10
- package/dist/commands/auth.d.ts +36 -18
- package/dist/commands/auth.js +440 -329
- package/dist/commands/chat.d.ts +12 -0
- package/dist/commands/chat.js +287 -48
- package/dist/commands/config.d.ts +2 -0
- package/dist/commands/config.js +40 -20
- package/dist/commands/index.d.ts +12 -0
- package/dist/commands/index.js +182 -0
- package/dist/commands/legion.d.ts +49 -7
- package/dist/commands/legion.js +1418 -72
- 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 +4 -1
- package/dist/index.js +472 -51
- package/dist/utils/api.d.ts +24 -9
- package/dist/utils/api.js +720 -159
- package/dist/utils/config.js +9 -10
- package/dist/utils/context-ranker.d.ts +24 -0
- package/dist/utils/context-ranker.js +147 -0
- package/dist/utils/post-write-validator.d.ts +25 -0
- package/dist/utils/post-write-validator.js +138 -0
- package/dist/utils/session.d.ts +19 -0
- package/dist/utils/session.js +91 -6
- package/dist/utils/task-display.d.ts +31 -0
- package/dist/utils/task-display.js +115 -0
- package/dist/utils/tools.d.ts +26 -0
- package/dist/utils/tools.js +563 -58
- package/dist/utils/workspace-cache.d.ts +31 -0
- package/dist/utils/workspace-cache.js +96 -0
- package/package.json +13 -3
package/dist/commands/auth.js
CHANGED
|
@@ -1,367 +1,478 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
/**
|
|
3
|
-
* Auth Command - Authentication management
|
|
4
|
-
*/
|
|
5
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
6
|
-
if (k2 === undefined) k2 = k;
|
|
7
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
8
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
9
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
10
|
-
}
|
|
11
|
-
Object.defineProperty(o, k2, desc);
|
|
12
|
-
}) : (function(o, m, k, k2) {
|
|
13
|
-
if (k2 === undefined) k2 = k;
|
|
14
|
-
o[k2] = m[k];
|
|
15
|
-
}));
|
|
16
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
17
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
18
|
-
}) : function(o, v) {
|
|
19
|
-
o["default"] = v;
|
|
20
|
-
});
|
|
21
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
22
|
-
var ownKeys = function(o) {
|
|
23
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
24
|
-
var ar = [];
|
|
25
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
26
|
-
return ar;
|
|
27
|
-
};
|
|
28
|
-
return ownKeys(o);
|
|
29
|
-
};
|
|
30
|
-
return function (mod) {
|
|
31
|
-
if (mod && mod.__esModule) return mod;
|
|
32
|
-
var result = {};
|
|
33
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
34
|
-
__setModuleDefault(result, mod);
|
|
35
|
-
return result;
|
|
36
|
-
};
|
|
37
|
-
})();
|
|
38
2
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
39
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
40
4
|
};
|
|
41
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
-
exports.
|
|
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;
|
|
14
|
+
exports.handleLogin = handleLogin;
|
|
15
|
+
exports.handleLogout = handleLogout;
|
|
16
|
+
exports.statusAction = statusAction;
|
|
17
|
+
exports.registerAuthCommands = registerAuthCommands;
|
|
43
18
|
const chalk_1 = __importDefault(require("chalk"));
|
|
44
|
-
const
|
|
45
|
-
const
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
function ask(rl, question) {
|
|
59
|
-
return new Promise((resolve) => rl.question(question, resolve));
|
|
19
|
+
const fs_1 = require("fs");
|
|
20
|
+
const os_1 = require("os");
|
|
21
|
+
const path_1 = __importDefault(require("path"));
|
|
22
|
+
const readline_1 = __importDefault(require("readline"));
|
|
23
|
+
const DEFAULT_API_URL = 'https://coder.vigthoria.io';
|
|
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
|
+
}
|
|
60
33
|
}
|
|
61
|
-
function
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
34
|
+
function trimTrailingSlash(value) {
|
|
35
|
+
return value.replace(/\/+$/, '');
|
|
36
|
+
}
|
|
37
|
+
function uniqueStrings(values) {
|
|
38
|
+
return [...new Set(values.filter(Boolean))];
|
|
39
|
+
}
|
|
40
|
+
function getApiUrl() {
|
|
41
|
+
return trimTrailingSlash(process.env.VIGTHORIA_API_URL || DEFAULT_API_URL);
|
|
42
|
+
}
|
|
43
|
+
function derivePeerHost(baseUrl) {
|
|
44
|
+
if (baseUrl.includes('://api.vigthoria.io')) {
|
|
45
|
+
return baseUrl.replace('://api.vigthoria.io', '://coder.vigthoria.io');
|
|
46
|
+
}
|
|
47
|
+
if (baseUrl.includes('://coder.vigthoria.io')) {
|
|
48
|
+
return baseUrl.replace('://coder.vigthoria.io', '://api.vigthoria.io');
|
|
49
|
+
}
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
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);
|
|
56
|
+
}
|
|
57
|
+
function ensureConfigDir() {
|
|
58
|
+
if (!(0, fs_1.existsSync)(CONFIG_DIR)) {
|
|
59
|
+
(0, fs_1.mkdirSync)(CONFIG_DIR, { recursive: true, mode: 0o700 });
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
(0, fs_1.chmodSync)(CONFIG_DIR, 0o700);
|
|
63
|
+
}
|
|
64
|
+
catch {
|
|
65
|
+
// Best-effort on non-POSIX filesystems.
|
|
66
|
+
}
|
|
67
|
+
}
|
|
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.';
|
|
72
|
+
}
|
|
73
|
+
return raw || 'Unknown authentication error.';
|
|
74
|
+
}
|
|
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;
|
|
81
|
+
}
|
|
82
|
+
function extractAuthUser(payload, fallbackEmail) {
|
|
83
|
+
if (!payload || typeof payload !== 'object') {
|
|
84
|
+
return fallbackEmail ? { email: fallbackEmail } : undefined;
|
|
85
|
+
}
|
|
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,
|
|
102
94
|
};
|
|
103
|
-
|
|
104
|
-
|
|
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
|
+
};
|
|
102
|
+
}
|
|
103
|
+
return fallbackEmail ? { email: fallbackEmail } : undefined;
|
|
105
104
|
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
105
|
+
function loadAuthConfig() {
|
|
106
|
+
if (!(0, fs_1.existsSync)(CONFIG_FILE)) {
|
|
107
|
+
return { apiUrl: getApiUrl() };
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
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
|
+
};
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.warn(chalk_1.default.yellow(`Warning: could not read auth config: ${humanMessage(error)}`));
|
|
119
|
+
return { apiUrl: getApiUrl() };
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function saveAuthConfig(config) {
|
|
123
|
+
ensureConfigDir();
|
|
124
|
+
(0, fs_1.writeFileSync)(CONFIG_FILE, `${JSON.stringify(config, null, 2)}\n`, 'utf8');
|
|
125
|
+
try {
|
|
126
|
+
(0, fs_1.chmodSync)(CONFIG_FILE, 0o600);
|
|
127
|
+
}
|
|
128
|
+
catch {
|
|
129
|
+
// Best-effort on non-POSIX filesystems.
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
function clearAuthConfig() {
|
|
133
|
+
if ((0, fs_1.existsSync)(CONFIG_FILE)) {
|
|
134
|
+
(0, fs_1.rmSync)(CONFIG_FILE, { force: true });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
function getAuthToken() {
|
|
138
|
+
return process.env.VIGTHORIA_TOKEN || loadAuthConfig().token;
|
|
139
|
+
}
|
|
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()) {
|
|
128
152
|
try {
|
|
129
|
-
|
|
130
|
-
console.log(chalk_1.default.white(' 2) API Token'));
|
|
131
|
-
console.log(chalk_1.default.white(' 3) Browser Login'));
|
|
132
|
-
console.log();
|
|
133
|
-
const choice = (await ask(rl, chalk_1.default.cyan('? ') + 'Choose login method (1/2/3): ')).trim();
|
|
134
|
-
switch (choice) {
|
|
135
|
-
case '1':
|
|
136
|
-
case 'credentials': {
|
|
137
|
-
const email = (await ask(rl, chalk_1.default.cyan('? ') + 'Email: ')).trim();
|
|
138
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
139
|
-
if (!emailRegex.test(email)) {
|
|
140
|
-
this.logger.error('Please enter a valid email');
|
|
141
|
-
rl.close();
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
const password = await askHidden(rl, chalk_1.default.cyan('? ') + 'Password: ');
|
|
145
|
-
rl.close();
|
|
146
|
-
if (password.length < 6) {
|
|
147
|
-
this.logger.error('Password must be at least 6 characters');
|
|
148
|
-
return;
|
|
149
|
-
}
|
|
150
|
-
await this.doCredentialLogin(email, password);
|
|
151
|
-
break;
|
|
152
|
-
}
|
|
153
|
-
case '2':
|
|
154
|
-
case 'token': {
|
|
155
|
-
const token = await askHidden(rl, chalk_1.default.cyan('? ') + 'API Token: ');
|
|
156
|
-
rl.close();
|
|
157
|
-
if (!token) {
|
|
158
|
-
this.logger.error('Please enter your API token');
|
|
159
|
-
return;
|
|
160
|
-
}
|
|
161
|
-
await this.loginWithToken(token);
|
|
162
|
-
break;
|
|
163
|
-
}
|
|
164
|
-
case '3':
|
|
165
|
-
case 'browser':
|
|
166
|
-
rl.close();
|
|
167
|
-
await this.loginWithBrowser();
|
|
168
|
-
break;
|
|
169
|
-
default:
|
|
170
|
-
rl.close();
|
|
171
|
-
this.logger.error('Invalid choice. Please enter 1, 2, or 3.');
|
|
172
|
-
break;
|
|
173
|
-
}
|
|
153
|
+
body = JSON.parse(text);
|
|
174
154
|
}
|
|
175
|
-
catch
|
|
176
|
-
|
|
177
|
-
|
|
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));
|
|
178
160
|
}
|
|
179
161
|
}
|
|
180
|
-
|
|
181
|
-
const
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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;
|
|
171
|
+
}
|
|
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));
|
|
186
177
|
}
|
|
187
|
-
|
|
188
|
-
|
|
178
|
+
finally {
|
|
179
|
+
rl.close();
|
|
189
180
|
}
|
|
190
181
|
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
|
|
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);
|
|
197
188
|
}
|
|
198
189
|
else {
|
|
199
|
-
|
|
190
|
+
output.write('*');
|
|
200
191
|
}
|
|
192
|
+
};
|
|
193
|
+
try {
|
|
194
|
+
return await new Promise((resolve) => rl.question(question, resolve));
|
|
201
195
|
}
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
console.log();
|
|
206
|
-
console.log(chalk_1.default.cyan('Visit: https://coder.vigthoria.io/cli-auth'));
|
|
207
|
-
console.log();
|
|
208
|
-
console.log(chalk_1.default.gray('After authenticating, copy the token and run:'));
|
|
209
|
-
console.log(chalk_1.default.white(' vigthoria login --token YOUR_TOKEN'));
|
|
210
|
-
console.log();
|
|
211
|
-
}
|
|
212
|
-
async logout() {
|
|
213
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
214
|
-
const answer = await ask(rl, chalk_1.default.cyan('? ') + 'Are you sure you want to logout? (y/N): ');
|
|
215
|
-
rl.close();
|
|
216
|
-
const confirm = /^y(es)?$/i.test(answer.trim());
|
|
217
|
-
if (confirm) {
|
|
218
|
-
this.config.clearAuth();
|
|
219
|
-
this.logger.success('Logged out successfully');
|
|
196
|
+
finally {
|
|
197
|
+
if (originalWrite) {
|
|
198
|
+
mutableRl._writeToOutput = originalWrite;
|
|
220
199
|
}
|
|
200
|
+
output.write('\n');
|
|
201
|
+
rl.close();
|
|
221
202
|
}
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
203
|
+
}
|
|
204
|
+
function markSuccessExit() {
|
|
205
|
+
process.exitCode = 0;
|
|
206
|
+
}
|
|
207
|
+
function markErrorExit() {
|
|
208
|
+
process.exitCode = 1;
|
|
209
|
+
}
|
|
210
|
+
async function login(email, password) {
|
|
211
|
+
try {
|
|
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.');
|
|
232
216
|
}
|
|
233
|
-
const
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
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
|
+
}
|
|
253
255
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
clearTimeout(capabilityTimeout);
|
|
290
|
-
spinner.stop();
|
|
291
|
-
// --- Display API Status ---
|
|
292
|
-
console.log(chalk_1.default.white('API Status:'));
|
|
293
|
-
console.log(chalk_1.default.gray(' Overall: ') + (apiStatus.overallOk ? chalk_1.default.green('Healthy') : chalk_1.default.yellow('Degraded')));
|
|
294
|
-
console.log(chalk_1.default.gray(' Coder API: ') + (apiStatus.coder.ok ? chalk_1.default.green('Online') : chalk_1.default.red('Offline')) + chalk_1.default.gray(` (${apiStatus.coder.endpoint})`));
|
|
295
|
-
if (apiStatus.coder.error) {
|
|
296
|
-
console.log(chalk_1.default.gray(' Error: ') + chalk_1.default.red(apiStatus.coder.error));
|
|
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(' '));
|
|
261
|
+
}
|
|
262
|
+
catch (error) {
|
|
263
|
+
const message = humanMessage(error);
|
|
264
|
+
markErrorExit();
|
|
265
|
+
throw new Error(message);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
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
|
+
}
|
|
297
291
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
console.log(chalk_1.default.gray(' Models Available: ') + chalk_1.default.cyan(String(apiStatus.models.details.modelCount)));
|
|
292
|
+
if (!remoteLogoutDone) {
|
|
293
|
+
console.warn(chalk_1.default.yellow('Remote logout endpoint not reachable; local credentials were still cleared.'));
|
|
301
294
|
}
|
|
302
|
-
|
|
303
|
-
|
|
295
|
+
}
|
|
296
|
+
clearAuthConfig();
|
|
297
|
+
process.exitCode = 0;
|
|
298
|
+
}
|
|
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
|
+
}
|
|
304
326
|
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
327
|
+
const verifyEndpoint = `${normalizedBase}/api/auth/verify`;
|
|
328
|
+
attempted.push(verifyEndpoint);
|
|
329
|
+
try {
|
|
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;
|
|
309
339
|
}
|
|
310
340
|
}
|
|
311
|
-
|
|
312
|
-
|
|
341
|
+
catch {
|
|
342
|
+
// continue fallback route probing
|
|
313
343
|
}
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
344
|
+
}
|
|
345
|
+
throw new Error(`Unable to verify account on known auth endpoints. Tried ${attempted.join(', ')}`);
|
|
346
|
+
}
|
|
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,
|
|
356
|
+
};
|
|
357
|
+
process.exitCode = 0;
|
|
358
|
+
return report;
|
|
359
|
+
}
|
|
360
|
+
async function handleLogin(options = {}) {
|
|
361
|
+
try {
|
|
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;
|
|
372
|
+
}
|
|
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.'));
|
|
377
|
+
}
|
|
378
|
+
if (!email) {
|
|
379
|
+
email = (await ask('Email: ')).trim();
|
|
326
380
|
}
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
// Repo Memory — separate auth scope, only affects repo commands
|
|
330
|
-
console.log(chalk_1.default.gray(' Repo Memory: ') + (capabilityStatus.repoMemory.ok ? chalk_1.default.green('Active') : chalk_1.default.gray('Not deployed (optional)')));
|
|
331
|
-
if (capabilityStatus.repoMemory.details?.compactContextLength !== undefined) {
|
|
332
|
-
console.log(chalk_1.default.gray(' Compact Context: ') + chalk_1.default.cyan(`${capabilityStatus.repoMemory.details.compactContextLength} chars`));
|
|
381
|
+
if (!password) {
|
|
382
|
+
password = await ask('Password: ', true);
|
|
333
383
|
}
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
// --- Display Auth Scopes ---
|
|
337
|
-
console.log();
|
|
338
|
-
console.log(chalk_1.default.white('Auth Scopes:'));
|
|
339
|
-
console.log(chalk_1.default.gray(' Model Auth: ') + (tokenValidation.valid ? chalk_1.default.green('Valid') : chalk_1.default.red('Invalid')) + chalk_1.default.gray(' (used by chat, agent, review, explain, generate, fix)'));
|
|
340
|
-
if (!tokenValidation.valid && tokenValidation.error) {
|
|
341
|
-
console.log(chalk_1.default.gray(' ') + chalk_1.default.red(tokenValidation.error));
|
|
384
|
+
if (!email || !password) {
|
|
385
|
+
throw new Error('Email and password are required. Use --email and --password, or run vigthoria login interactively.');
|
|
342
386
|
}
|
|
343
|
-
|
|
344
|
-
console.log(chalk_1.default.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
// process.exit() is invoked while socket handles are mid-close.
|
|
350
|
-
this.api.destroy();
|
|
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}`);
|
|
351
393
|
process.exitCode = 0;
|
|
394
|
+
return config;
|
|
395
|
+
}
|
|
396
|
+
catch (error) {
|
|
397
|
+
console.error(chalk_1.default.red(`Login failed: ${humanMessage(error)}`));
|
|
398
|
+
process.exitCode = 1;
|
|
399
|
+
return undefined;
|
|
352
400
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
401
|
+
}
|
|
402
|
+
async function handleLogout(_options) {
|
|
403
|
+
try {
|
|
404
|
+
await logout();
|
|
405
|
+
console.log(chalk_1.default.green('Logged out successfully.'));
|
|
406
|
+
process.exitCode = 0;
|
|
407
|
+
}
|
|
408
|
+
catch (error) {
|
|
409
|
+
console.error(chalk_1.default.red(`Logout failed: ${humanMessage(error)}`));
|
|
410
|
+
process.exitCode = 1;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
async function statusAction() {
|
|
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.
|
|
360
427
|
}
|
|
361
|
-
console.log();
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
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;
|
|
365
434
|
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
console.error(chalk_1.default.red(`Unable to read status: ${humanMessage(error)}`));
|
|
437
|
+
process.exitCode = 1;
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
function registerAuthCommands(program) {
|
|
441
|
+
const auth = program.command('auth').description('Manage Vigthoria CLI authentication');
|
|
442
|
+
auth
|
|
443
|
+
.command('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)')
|
|
449
|
+
.action(async (options) => {
|
|
450
|
+
await handleLogin(options);
|
|
451
|
+
});
|
|
452
|
+
auth
|
|
453
|
+
.command('logout')
|
|
454
|
+
.description('Sign out and remove saved credentials')
|
|
455
|
+
.action(async () => {
|
|
456
|
+
await handleLogout();
|
|
457
|
+
});
|
|
458
|
+
auth
|
|
459
|
+
.command('whoami')
|
|
460
|
+
.description('Show the authenticated Vigthoria account')
|
|
461
|
+
.action(async () => {
|
|
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
|
+
}
|
|
477
|
+
});
|
|
366
478
|
}
|
|
367
|
-
exports.AuthCommand = AuthCommand;
|