@oxlayer/cli 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +152 -0
- package/dist/api.service-CDPTVMJM.js +17 -0
- package/dist/api.service-CDPTVMJM.js.map +1 -0
- package/dist/chunk-DOYXC6AO.js +93 -0
- package/dist/chunk-DOYXC6AO.js.map +1 -0
- package/dist/chunk-HOFSFUN5.js +108 -0
- package/dist/chunk-HOFSFUN5.js.map +1 -0
- package/dist/chunk-HQST7GVM.js +26 -0
- package/dist/chunk-HQST7GVM.js.map +1 -0
- package/dist/chunk-M3AAB53U.js +105 -0
- package/dist/chunk-M3AAB53U.js.map +1 -0
- package/dist/chunk-QERPYPW4.js +284 -0
- package/dist/chunk-QERPYPW4.js.map +1 -0
- package/dist/cli-VUBGPXL3.js +30 -0
- package/dist/cli-VUBGPXL3.js.map +1 -0
- package/dist/cli.js +1328 -0
- package/dist/cli.js.map +1 -0
- package/dist/device-auth.service-CMNCNKYX.js +42 -0
- package/dist/device-auth.service-CMNCNKYX.js.map +1 -0
- package/dist/package.service-XK5PPZVF.js +24 -0
- package/dist/package.service-XK5PPZVF.js.map +1 -0
- package/package.json +43 -0
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__esm,
|
|
3
|
+
init_esm_shims
|
|
4
|
+
} from "./chunk-HQST7GVM.js";
|
|
5
|
+
|
|
6
|
+
// src/services/device-auth.service.ts
|
|
7
|
+
import { promises as fs } from "fs";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { homedir } from "os";
|
|
10
|
+
import { randomBytes } from "crypto";
|
|
11
|
+
async function getDeviceId() {
|
|
12
|
+
try {
|
|
13
|
+
const registry = await loadDeviceRegistry();
|
|
14
|
+
const hostname = await getHostname();
|
|
15
|
+
let device = registry.devices.find((d) => d.deviceName === hostname);
|
|
16
|
+
if (!device) {
|
|
17
|
+
device = {
|
|
18
|
+
deviceId: generateDeviceId(),
|
|
19
|
+
deviceName: hostname,
|
|
20
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21
|
+
lastSeenAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
22
|
+
};
|
|
23
|
+
registry.devices.push(device);
|
|
24
|
+
await saveDeviceRegistry(registry);
|
|
25
|
+
} else {
|
|
26
|
+
device.lastSeenAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
27
|
+
await saveDeviceRegistry(registry);
|
|
28
|
+
}
|
|
29
|
+
return device.deviceId;
|
|
30
|
+
} catch {
|
|
31
|
+
return generateDeviceId();
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
async function getDeviceName() {
|
|
35
|
+
return getHostname();
|
|
36
|
+
}
|
|
37
|
+
function generateDeviceId() {
|
|
38
|
+
return `oxl_dev_${randomBytes(16).toString("hex")}`;
|
|
39
|
+
}
|
|
40
|
+
async function getHostname() {
|
|
41
|
+
const os = await import("os");
|
|
42
|
+
return os.hostname();
|
|
43
|
+
}
|
|
44
|
+
async function loadDeviceRegistry() {
|
|
45
|
+
try {
|
|
46
|
+
const content = await fs.readFile(DEVICES_FILE, "utf-8");
|
|
47
|
+
return JSON.parse(content);
|
|
48
|
+
} catch {
|
|
49
|
+
return { devices: [] };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async function saveDeviceRegistry(registry) {
|
|
53
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
54
|
+
await fs.writeFile(DEVICES_FILE, JSON.stringify(registry, null, 2));
|
|
55
|
+
}
|
|
56
|
+
async function initiateDeviceAuth(environment = "development", apiEndpoint) {
|
|
57
|
+
const deviceId = await getDeviceId();
|
|
58
|
+
const deviceName = await getDeviceName();
|
|
59
|
+
const endpoint = apiEndpoint || DEFAULT_API_ENDPOINT;
|
|
60
|
+
const response = await fetch(`${endpoint}/v1/cli/device/code`, {
|
|
61
|
+
method: "POST",
|
|
62
|
+
headers: { "Content-Type": "application/json" },
|
|
63
|
+
body: JSON.stringify({
|
|
64
|
+
deviceId,
|
|
65
|
+
deviceName,
|
|
66
|
+
environment
|
|
67
|
+
})
|
|
68
|
+
});
|
|
69
|
+
if (!response.ok) {
|
|
70
|
+
const error = await response.text();
|
|
71
|
+
throw new Error(`Failed to initiate device auth: ${error}`);
|
|
72
|
+
}
|
|
73
|
+
return response.json();
|
|
74
|
+
}
|
|
75
|
+
async function pollForToken(pollEndpoint, deviceCode, options = {}) {
|
|
76
|
+
const interval = options.interval || DEFAULT_POLL_INTERVAL;
|
|
77
|
+
const maxAttempts = options.maxAttempts || MAX_POLL_ATTEMPTS;
|
|
78
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
79
|
+
options.onProgress?.(attempt, maxAttempts);
|
|
80
|
+
const response = await fetch(pollEndpoint, {
|
|
81
|
+
method: "POST",
|
|
82
|
+
headers: { "Content-Type": "application/json" },
|
|
83
|
+
body: JSON.stringify({ deviceCode })
|
|
84
|
+
});
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new Error(`Poll request failed: ${response.statusText}`);
|
|
87
|
+
}
|
|
88
|
+
const result = await response.json();
|
|
89
|
+
if (!result.pending) {
|
|
90
|
+
if (result.error) {
|
|
91
|
+
throw new Error(`Authorization failed: ${result.error}`);
|
|
92
|
+
}
|
|
93
|
+
return { ...result, success: true };
|
|
94
|
+
}
|
|
95
|
+
await sleep(interval * 1e3);
|
|
96
|
+
}
|
|
97
|
+
throw new Error("Authorization timed out. Please try again.");
|
|
98
|
+
}
|
|
99
|
+
function sleep(ms) {
|
|
100
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
101
|
+
}
|
|
102
|
+
async function saveConfig(config) {
|
|
103
|
+
await fs.mkdir(CONFIG_DIR, { recursive: true });
|
|
104
|
+
await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {
|
|
105
|
+
mode: 384
|
|
106
|
+
// Read/write for owner only
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async function loadConfig() {
|
|
110
|
+
try {
|
|
111
|
+
const content = await fs.readFile(CONFIG_FILE, "utf-8");
|
|
112
|
+
return JSON.parse(content);
|
|
113
|
+
} catch {
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async function removeConfig() {
|
|
118
|
+
try {
|
|
119
|
+
await fs.unlink(CONFIG_FILE);
|
|
120
|
+
} catch {
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function getAccessToken() {
|
|
124
|
+
const envToken = process.env.OXLAYER_TOKEN;
|
|
125
|
+
if (envToken) {
|
|
126
|
+
return envToken;
|
|
127
|
+
}
|
|
128
|
+
const config = await loadConfig();
|
|
129
|
+
return config?.token ?? null;
|
|
130
|
+
}
|
|
131
|
+
function validateToken(token) {
|
|
132
|
+
const MIN_TOKEN_LENGTH = 32;
|
|
133
|
+
const isJwt = token.startsWith("eyJ");
|
|
134
|
+
const isScopedToken = token.startsWith("oxl_cli_");
|
|
135
|
+
return typeof token === "string" && (isJwt || isScopedToken) && token.length >= MIN_TOKEN_LENGTH;
|
|
136
|
+
}
|
|
137
|
+
function isTokenExpired(config) {
|
|
138
|
+
const expiresAt = new Date(config.tokenInfo.expiresAt);
|
|
139
|
+
return expiresAt < /* @__PURE__ */ new Date();
|
|
140
|
+
}
|
|
141
|
+
async function refreshManifest(config, apiEndpoint) {
|
|
142
|
+
const endpoint = apiEndpoint || config.apiEndpoint || DEFAULT_API_ENDPOINT;
|
|
143
|
+
const response = await fetch(`${endpoint}/v1/cli/manifest`, {
|
|
144
|
+
headers: {
|
|
145
|
+
"Content-Type": "application/json",
|
|
146
|
+
"Authorization": `Bearer ${config.token}`
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
if (!response.ok) {
|
|
150
|
+
return {
|
|
151
|
+
...config,
|
|
152
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
const manifest = await response.json();
|
|
156
|
+
return {
|
|
157
|
+
...config,
|
|
158
|
+
manifest,
|
|
159
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
async function getManifest(apiEndpoint) {
|
|
163
|
+
const config = await loadConfig();
|
|
164
|
+
if (!config) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
if (config.manifest) {
|
|
168
|
+
const expiresAt = new Date(config.manifest.expiresAt);
|
|
169
|
+
if (expiresAt > /* @__PURE__ */ new Date()) {
|
|
170
|
+
return config.manifest;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const updated = await refreshManifest(config, apiEndpoint);
|
|
174
|
+
await saveConfig(updated);
|
|
175
|
+
return updated.manifest || null;
|
|
176
|
+
}
|
|
177
|
+
async function isAuthenticated() {
|
|
178
|
+
const config = await loadConfig();
|
|
179
|
+
if (!config) {
|
|
180
|
+
return false;
|
|
181
|
+
}
|
|
182
|
+
if (isTokenExpired(config)) {
|
|
183
|
+
return false;
|
|
184
|
+
}
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
async function getAuthInfo() {
|
|
188
|
+
const config = await loadConfig();
|
|
189
|
+
if (!config) {
|
|
190
|
+
return { authenticated: false };
|
|
191
|
+
}
|
|
192
|
+
return {
|
|
193
|
+
authenticated: !isTokenExpired(config),
|
|
194
|
+
deviceId: config.tokenInfo.deviceId,
|
|
195
|
+
organizationId: config.organizationId,
|
|
196
|
+
expiresAt: config.tokenInfo.expiresAt,
|
|
197
|
+
scopes: config.tokenInfo.scopes
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
function buildVerificationUrl(baseUrl, deviceCode) {
|
|
201
|
+
const url = new URL(baseUrl);
|
|
202
|
+
url.searchParams.set("device_code", deviceCode);
|
|
203
|
+
return url.toString();
|
|
204
|
+
}
|
|
205
|
+
async function openBrowser(url) {
|
|
206
|
+
const { exec } = await import("child_process");
|
|
207
|
+
const platform = process.platform;
|
|
208
|
+
let command;
|
|
209
|
+
switch (platform) {
|
|
210
|
+
case "darwin":
|
|
211
|
+
command = `open "${url}"`;
|
|
212
|
+
break;
|
|
213
|
+
case "win32":
|
|
214
|
+
command = `start "" "${url}"`;
|
|
215
|
+
break;
|
|
216
|
+
default:
|
|
217
|
+
command = `xdg-open "${url}"`;
|
|
218
|
+
}
|
|
219
|
+
return new Promise((resolve, reject) => {
|
|
220
|
+
exec(command, (error) => {
|
|
221
|
+
if (error) {
|
|
222
|
+
reject(new Error(`Failed to open browser: ${error.message}`));
|
|
223
|
+
} else {
|
|
224
|
+
resolve();
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
async function migrateFromApiKey(apiKey) {
|
|
230
|
+
const deviceId = await getDeviceId();
|
|
231
|
+
const deviceName = await getDeviceName();
|
|
232
|
+
const endpoint = DEFAULT_API_ENDPOINT;
|
|
233
|
+
const response = await fetch(`${endpoint}/v1/cli/migrate`, {
|
|
234
|
+
method: "POST",
|
|
235
|
+
headers: { "Content-Type": "application/json" },
|
|
236
|
+
body: JSON.stringify({ apiKey, deviceId, deviceName })
|
|
237
|
+
});
|
|
238
|
+
if (!response.ok) {
|
|
239
|
+
throw new Error(`Migration failed: ${response.statusText}`);
|
|
240
|
+
}
|
|
241
|
+
const result = await response.json();
|
|
242
|
+
return {
|
|
243
|
+
token: result.accessToken,
|
|
244
|
+
tokenInfo: result.tokenInfo,
|
|
245
|
+
organizationId: result.organizationId,
|
|
246
|
+
environment: "development",
|
|
247
|
+
vendorDir: ".capabilities-vendor",
|
|
248
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
var CONFIG_DIR, CONFIG_FILE, DEVICES_FILE, DEFAULT_API_ENDPOINT, DEFAULT_POLL_INTERVAL, MAX_POLL_ATTEMPTS;
|
|
252
|
+
var init_device_auth_service = __esm({
|
|
253
|
+
"src/services/device-auth.service.ts"() {
|
|
254
|
+
init_esm_shims();
|
|
255
|
+
CONFIG_DIR = join(homedir(), ".oxlayer");
|
|
256
|
+
CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
257
|
+
DEVICES_FILE = join(CONFIG_DIR, "devices.json");
|
|
258
|
+
DEFAULT_API_ENDPOINT = "http://localhost:3001";
|
|
259
|
+
DEFAULT_POLL_INTERVAL = 5;
|
|
260
|
+
MAX_POLL_ATTEMPTS = 120;
|
|
261
|
+
}
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
export {
|
|
265
|
+
getDeviceId,
|
|
266
|
+
getDeviceName,
|
|
267
|
+
initiateDeviceAuth,
|
|
268
|
+
pollForToken,
|
|
269
|
+
saveConfig,
|
|
270
|
+
loadConfig,
|
|
271
|
+
removeConfig,
|
|
272
|
+
getAccessToken,
|
|
273
|
+
validateToken,
|
|
274
|
+
isTokenExpired,
|
|
275
|
+
refreshManifest,
|
|
276
|
+
getManifest,
|
|
277
|
+
isAuthenticated,
|
|
278
|
+
getAuthInfo,
|
|
279
|
+
buildVerificationUrl,
|
|
280
|
+
openBrowser,
|
|
281
|
+
migrateFromApiKey,
|
|
282
|
+
init_device_auth_service
|
|
283
|
+
};
|
|
284
|
+
//# sourceMappingURL=chunk-QERPYPW4.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/services/device-auth.service.ts"],"sourcesContent":["/**\n * Device Authorization Service\n *\n * Implements the Device Authorization Flow (RFC 8628) for CLI authentication.\n * This enables browser-based login without copying API keys.\n *\n * Flow:\n * 1. CLI requests a device code\n * 2. CLI opens browser with verification URL\n * 3. User logs in and approves the device\n * 4. CLI polls for token completion\n * 5. Token is saved locally\n */\n\nimport type {\n CliConfig,\n DeviceCodeResponse,\n TokenPollResponse,\n TokenScope,\n CapabilityManifest,\n} from '../types/capabilities.js';\nimport { promises as fs } from 'fs';\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { randomBytes } from 'crypto';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst CONFIG_DIR = join(homedir(), '.oxlayer');\nconst CONFIG_FILE = join(CONFIG_DIR, 'config.json');\nconst DEVICES_FILE = join(CONFIG_DIR, 'devices.json');\n\nconst DEFAULT_API_ENDPOINT = process.env.OXLAYER_API_ENDPOINT || 'https://api.oxlayer.dev';\nconst DEFAULT_POLL_INTERVAL = 5; // seconds\nconst MAX_POLL_ATTEMPTS = 120; // 10 minutes total\n\n// ============================================================================\n// Types\n// ============================================================================\n\ninterface DeviceRegistry {\n devices: Array<{\n deviceId: string;\n deviceName: string;\n createdAt: string;\n lastSeenAt: string;\n }>;\n}\n\ninterface PollingOptions {\n interval?: number;\n maxAttempts?: number;\n onProgress?: (attempt: number, maxAttempts: number) => void;\n}\n\n// ============================================================================\n// Device ID Management\n// ============================================================================\n\n/**\n * Get or create a persistent device ID for this machine\n */\nexport async function getDeviceId(): Promise<string> {\n try {\n const registry = await loadDeviceRegistry();\n const hostname = await getHostname();\n\n // Find existing device for this hostname\n let device = registry.devices.find(d => d.deviceName === hostname);\n\n if (!device) {\n // Create new device\n device = {\n deviceId: generateDeviceId(),\n deviceName: hostname,\n createdAt: new Date().toISOString(),\n lastSeenAt: new Date().toISOString(),\n };\n registry.devices.push(device);\n await saveDeviceRegistry(registry);\n } else {\n // Update last seen\n device.lastSeenAt = new Date().toISOString();\n await saveDeviceRegistry(registry);\n }\n\n return device.deviceId;\n } catch {\n // Fallback to ephemeral device ID\n return generateDeviceId();\n }\n}\n\n/**\n * Get human-readable device name\n */\nexport async function getDeviceName(): Promise<string> {\n return getHostname();\n}\n\nfunction generateDeviceId(): string {\n return `oxl_dev_${randomBytes(16).toString('hex')}`;\n}\n\nasync function getHostname(): Promise<string> {\n const os = await import('os');\n return os.hostname();\n}\n\nasync function loadDeviceRegistry(): Promise<DeviceRegistry> {\n try {\n const content = await fs.readFile(DEVICES_FILE, 'utf-8');\n return JSON.parse(content);\n } catch {\n return { devices: [] };\n }\n}\n\nasync function saveDeviceRegistry(registry: DeviceRegistry): Promise<void> {\n await fs.mkdir(CONFIG_DIR, { recursive: true });\n await fs.writeFile(DEVICES_FILE, JSON.stringify(registry, null, 2));\n}\n\n// ============================================================================\n// Device Authorization Flow\n// ============================================================================\n\n/**\n * Initiate device authorization flow\n *\n * Returns a device code and verification URL for the user to visit\n */\nexport async function initiateDeviceAuth(\n environment: 'development' | 'staging' | 'production' = 'development',\n apiEndpoint?: string\n): Promise<DeviceCodeResponse> {\n const deviceId = await getDeviceId();\n const deviceName = await getDeviceName();\n const endpoint = apiEndpoint || DEFAULT_API_ENDPOINT;\n\n const response = await fetch(`${endpoint}/v1/cli/device/code`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n deviceId,\n deviceName,\n environment,\n }),\n });\n\n if (!response.ok) {\n const error = await response.text();\n throw new Error(`Failed to initiate device auth: ${error}`);\n }\n\n return response.json();\n}\n\n/**\n * Poll for token completion\n *\n * Polls the endpoint until the user completes authorization or timeout\n */\nexport async function pollForToken(\n pollEndpoint: string,\n deviceCode: string,\n options: PollingOptions = {}\n): Promise<TokenPollResponse & { success: true }> {\n const interval = options.interval || DEFAULT_POLL_INTERVAL;\n const maxAttempts = options.maxAttempts || MAX_POLL_ATTEMPTS;\n\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n options.onProgress?.(attempt, maxAttempts);\n\n const response = await fetch(pollEndpoint, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ deviceCode }),\n });\n\n if (!response.ok) {\n throw new Error(`Poll request failed: ${response.statusText}`);\n }\n\n const result: TokenPollResponse = await response.json();\n\n if (!result.pending) {\n if (result.error) {\n throw new Error(`Authorization failed: ${result.error}`);\n }\n\n return { ...result, success: true };\n }\n\n // Wait before next poll\n await sleep(interval * 1000);\n }\n\n throw new Error('Authorization timed out. Please try again.');\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n// ============================================================================\n// Token Management\n// ============================================================================\n\n/**\n * Save CLI configuration with scoped token\n */\nexport async function saveConfig(config: CliConfig): Promise<void> {\n await fs.mkdir(CONFIG_DIR, { recursive: true });\n await fs.writeFile(CONFIG_FILE, JSON.stringify(config, null, 2), {\n mode: 0o600, // Read/write for owner only\n });\n}\n\n/**\n * Load CLI configuration\n */\nexport async function loadConfig(): Promise<CliConfig | null> {\n try {\n const content = await fs.readFile(CONFIG_FILE, 'utf-8');\n return JSON.parse(content);\n } catch {\n return null;\n }\n}\n\n/**\n * Remove CLI configuration (logout)\n */\nexport async function removeConfig(): Promise<void> {\n try {\n await fs.unlink(CONFIG_FILE);\n } catch {\n // Ignore if file doesn't exist\n }\n}\n\n/**\n * Get scoped access token\n * Checks config file and falls back to environment variable\n */\nexport async function getAccessToken(): Promise<string | null> {\n // Check environment variable first\n const envToken = process.env.OXLAYER_TOKEN;\n if (envToken) {\n return envToken;\n }\n\n // Check config file\n const config = await loadConfig();\n return config?.token ?? null;\n}\n\n/**\n * Validate token format\n * Accepts both JWT tokens (from device auth flow) and legacy scoped tokens (oxl_cli_*)\n */\nexport function validateToken(token: string): boolean {\n const MIN_TOKEN_LENGTH = 32;\n const isJwt = token.startsWith('eyJ'); // JWTs start with base64-encoded '{'\n const isScopedToken = token.startsWith('oxl_cli_');\n return (\n typeof token === 'string' &&\n (isJwt || isScopedToken) &&\n token.length >= MIN_TOKEN_LENGTH\n );\n}\n\n/**\n * Check if token is expired\n */\nexport function isTokenExpired(config: CliConfig): boolean {\n const expiresAt = new Date(config.tokenInfo.expiresAt);\n return expiresAt < new Date();\n}\n\n/**\n * Refresh capability manifest from API\n */\nexport async function refreshManifest(\n config: CliConfig,\n apiEndpoint?: string\n): Promise<CliConfig> {\n const endpoint = apiEndpoint || config.apiEndpoint || DEFAULT_API_ENDPOINT;\n\n const response = await fetch(`${endpoint}/v1/cli/manifest`, {\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${config.token}`,\n },\n });\n\n if (!response.ok) {\n // Not implemented yet - return config without manifest\n return {\n ...config,\n updatedAt: new Date().toISOString(),\n };\n }\n\n const manifest: CapabilityManifest = await response.json();\n\n return {\n ...config,\n manifest,\n updatedAt: new Date().toISOString(),\n };\n}\n\n/**\n * Get cached or fetch new capability manifest\n */\nexport async function getManifest(\n apiEndpoint?: string\n): Promise<CapabilityManifest | null> {\n const config = await loadConfig();\n if (!config) {\n return null;\n }\n\n // Check if cached manifest is still valid\n if (config.manifest) {\n const expiresAt = new Date(config.manifest.expiresAt);\n if (expiresAt > new Date()) {\n return config.manifest;\n }\n }\n\n // Refresh manifest\n const updated = await refreshManifest(config, apiEndpoint);\n await saveConfig(updated);\n\n return updated.manifest || null;\n}\n\n// ============================================================================\n// Auth State Helpers\n// ============================================================================\n\n/**\n * Check if user is authenticated\n */\nexport async function isAuthenticated(): Promise<boolean> {\n const config = await loadConfig();\n if (!config) {\n return false;\n }\n\n // Check token expiration\n if (isTokenExpired(config)) {\n return false;\n }\n\n return true;\n}\n\n/**\n * Get authentication info for display\n */\nexport async function getAuthInfo(): Promise<{\n authenticated: boolean;\n deviceId?: string;\n organizationId?: string;\n expiresAt?: string;\n scopes?: string[];\n}> {\n const config = await loadConfig();\n\n if (!config) {\n return { authenticated: false };\n }\n\n return {\n authenticated: !isTokenExpired(config),\n deviceId: config.tokenInfo.deviceId,\n organizationId: config.organizationId,\n expiresAt: config.tokenInfo.expiresAt,\n scopes: config.tokenInfo.scopes,\n };\n}\n\n// ============================================================================\n// URL Helpers\n// ============================================================================\n\n/**\n * Build verification URL with device code\n */\nexport function buildVerificationUrl(baseUrl: string, deviceCode: string): string {\n const url = new URL(baseUrl);\n url.searchParams.set('device_code', deviceCode);\n return url.toString();\n}\n\n/**\n * Open browser to verification URL\n */\nexport async function openBrowser(url: string): Promise<void> {\n const { exec } = await import('child_process');\n\n const platform = process.platform;\n\n let command: string;\n switch (platform) {\n case 'darwin':\n command = `open \"${url}\"`;\n break;\n case 'win32':\n command = `start \"\" \"${url}\"`;\n break;\n default:\n command = `xdg-open \"${url}\"`;\n }\n\n return new Promise((resolve, reject) => {\n exec(command, (error) => {\n if (error) {\n reject(new Error(`Failed to open browser: ${error.message}`));\n } else {\n resolve();\n }\n });\n });\n}\n\n// ============================================================================\n// Migration Helpers (for transitioning from API key to token)\n// ============================================================================\n\n/**\n * Migrate legacy API key config to new token-based config\n */\nexport async function migrateFromApiKey(apiKey: string): Promise<CliConfig> {\n const deviceId = await getDeviceId();\n const deviceName = await getDeviceName();\n\n // Exchange API key for scoped token\n const endpoint = DEFAULT_API_ENDPOINT;\n const response = await fetch(`${endpoint}/v1/cli/migrate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ apiKey, deviceId, deviceName }),\n });\n\n if (!response.ok) {\n throw new Error(`Migration failed: ${response.statusText}`);\n }\n\n const result = await response.json();\n\n return {\n token: result.accessToken,\n tokenInfo: result.tokenInfo,\n organizationId: result.organizationId,\n environment: 'development',\n vendorDir: '.capabilities-vendor',\n updatedAt: new Date().toISOString(),\n };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import {
|
|
2
|
+
confirm,
|
|
3
|
+
createSpinner,
|
|
4
|
+
error,
|
|
5
|
+
formatDuration,
|
|
6
|
+
formatSize,
|
|
7
|
+
getBanner,
|
|
8
|
+
header,
|
|
9
|
+
info,
|
|
10
|
+
printCapabilities,
|
|
11
|
+
printList,
|
|
12
|
+
success,
|
|
13
|
+
warning
|
|
14
|
+
} from "./chunk-HOFSFUN5.js";
|
|
15
|
+
import "./chunk-HQST7GVM.js";
|
|
16
|
+
export {
|
|
17
|
+
confirm,
|
|
18
|
+
createSpinner,
|
|
19
|
+
error,
|
|
20
|
+
formatDuration,
|
|
21
|
+
formatSize,
|
|
22
|
+
getBanner,
|
|
23
|
+
header,
|
|
24
|
+
info,
|
|
25
|
+
printCapabilities,
|
|
26
|
+
printList,
|
|
27
|
+
success,
|
|
28
|
+
warning
|
|
29
|
+
};
|
|
30
|
+
//# sourceMappingURL=cli-VUBGPXL3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"sourcesContent":[],"mappings":"","names":[]}
|