@jawkit.cc/cli 0.1.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/LICENSE +31 -0
- package/README.md +363 -0
- package/content/claude-code/free.tar.gz +0 -0
- package/content/claude-code/professional.tar.gz +0 -0
- package/content/github-copilot/free.tar.gz +0 -0
- package/content/manifest.json +50 -0
- package/dist/chunk-V3HNAAHL.js +1369 -0
- package/dist/chunk-V3HNAAHL.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +1180 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +97 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/package.json +68 -0
|
@@ -0,0 +1,1369 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// package.json
|
|
4
|
+
var version = "0.1.0";
|
|
5
|
+
var description = "CLI toolkit for installing AI coding assistant configurations";
|
|
6
|
+
|
|
7
|
+
// src/lib/logger.ts
|
|
8
|
+
import chalk from "chalk";
|
|
9
|
+
import ora from "ora";
|
|
10
|
+
var Logger = class {
|
|
11
|
+
options;
|
|
12
|
+
currentSpinner = null;
|
|
13
|
+
constructor(options = {}) {
|
|
14
|
+
this.options = options;
|
|
15
|
+
}
|
|
16
|
+
info(message) {
|
|
17
|
+
if (this.options.quiet) return;
|
|
18
|
+
console.log(message);
|
|
19
|
+
}
|
|
20
|
+
success(message) {
|
|
21
|
+
if (this.options.quiet) return;
|
|
22
|
+
console.log(chalk.green("\u2713"), message);
|
|
23
|
+
}
|
|
24
|
+
warn(message) {
|
|
25
|
+
console.log(chalk.yellow("\u26A0"), message);
|
|
26
|
+
}
|
|
27
|
+
error(message) {
|
|
28
|
+
console.error(chalk.red("\u2716"), message);
|
|
29
|
+
}
|
|
30
|
+
debug(message) {
|
|
31
|
+
if (!this.options.verbose) return;
|
|
32
|
+
console.log(chalk.gray(`[debug] ${message}`));
|
|
33
|
+
}
|
|
34
|
+
newline() {
|
|
35
|
+
if (this.options.quiet) return;
|
|
36
|
+
console.log();
|
|
37
|
+
}
|
|
38
|
+
divider(char = "\u2500", length = 40) {
|
|
39
|
+
if (this.options.quiet) return;
|
|
40
|
+
console.log(chalk.gray(char.repeat(length)));
|
|
41
|
+
}
|
|
42
|
+
spinner(text) {
|
|
43
|
+
this.currentSpinner = ora({
|
|
44
|
+
text,
|
|
45
|
+
spinner: "dots"
|
|
46
|
+
});
|
|
47
|
+
return this.currentSpinner;
|
|
48
|
+
}
|
|
49
|
+
stopSpinner() {
|
|
50
|
+
if (this.currentSpinner) {
|
|
51
|
+
this.currentSpinner.stop();
|
|
52
|
+
this.currentSpinner = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
progress(percentage, text) {
|
|
56
|
+
if (this.options.quiet) return;
|
|
57
|
+
const width = 30;
|
|
58
|
+
const filled = Math.round(percentage / 100 * width);
|
|
59
|
+
const empty = width - filled;
|
|
60
|
+
const bar = chalk.green("\u2588".repeat(filled)) + chalk.gray("\u2591".repeat(empty));
|
|
61
|
+
process.stdout.write(`\r${bar} ${percentage}%${text ? ` ${text}` : ""}`);
|
|
62
|
+
if (percentage >= 100) {
|
|
63
|
+
console.log();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
table(data) {
|
|
67
|
+
if (this.options.quiet) return;
|
|
68
|
+
console.table(data);
|
|
69
|
+
}
|
|
70
|
+
json(data) {
|
|
71
|
+
console.log(JSON.stringify(data, null, 2));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
var logger = new Logger();
|
|
75
|
+
|
|
76
|
+
// src/lib/errors.ts
|
|
77
|
+
import chalk2 from "chalk";
|
|
78
|
+
var JawKitError = class extends Error {
|
|
79
|
+
suggestion;
|
|
80
|
+
cause;
|
|
81
|
+
constructor(message, options) {
|
|
82
|
+
super(message);
|
|
83
|
+
this.name = this.constructor.name;
|
|
84
|
+
this.cause = options?.cause;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
var AuthError = class _AuthError extends JawKitError {
|
|
88
|
+
code = "AUTH_ERROR";
|
|
89
|
+
static notAuthenticated() {
|
|
90
|
+
const error = new _AuthError("No license key activated");
|
|
91
|
+
error.suggestion = "Run 'jawkit auth activate <license-key>' to activate your license";
|
|
92
|
+
return error;
|
|
93
|
+
}
|
|
94
|
+
static licenseExpired() {
|
|
95
|
+
const error = new _AuthError("License has expired");
|
|
96
|
+
error.suggestion = "Visit https://jawkit.cc/pro to renew your license";
|
|
97
|
+
return error;
|
|
98
|
+
}
|
|
99
|
+
static activationFailed(reason) {
|
|
100
|
+
const error = new _AuthError(`License activation failed${reason ? `: ${reason}` : ""}`);
|
|
101
|
+
error.suggestion = "Check your license key and try again";
|
|
102
|
+
return error;
|
|
103
|
+
}
|
|
104
|
+
static licenseRevoked() {
|
|
105
|
+
const error = new _AuthError("License has been revoked");
|
|
106
|
+
error.suggestion = "Contact support at support@jawkit.cc";
|
|
107
|
+
return error;
|
|
108
|
+
}
|
|
109
|
+
static licenseNotFound() {
|
|
110
|
+
const error = new _AuthError("License key not found");
|
|
111
|
+
error.suggestion = "Check your license key and try again";
|
|
112
|
+
return error;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
var ContentError = class _ContentError extends JawKitError {
|
|
116
|
+
code = "CONTENT_ERROR";
|
|
117
|
+
static manifestNotFound() {
|
|
118
|
+
const error = new _ContentError("Content manifest not found");
|
|
119
|
+
error.suggestion = "Check your internet connection and try again";
|
|
120
|
+
return error;
|
|
121
|
+
}
|
|
122
|
+
static checksumMismatch(bundle) {
|
|
123
|
+
const error = new _ContentError(
|
|
124
|
+
`Bundle checksum verification failed: ${bundle}`
|
|
125
|
+
);
|
|
126
|
+
error.suggestion = "Try running the command again. The download may have been corrupted.";
|
|
127
|
+
return error;
|
|
128
|
+
}
|
|
129
|
+
static downloadFailed(bundle, status) {
|
|
130
|
+
const error = new _ContentError(
|
|
131
|
+
`Failed to download bundle: ${bundle}${status ? ` (HTTP ${status})` : ""}`
|
|
132
|
+
);
|
|
133
|
+
error.suggestion = "Check your internet connection and try again";
|
|
134
|
+
return error;
|
|
135
|
+
}
|
|
136
|
+
static extractionFailed(bundle) {
|
|
137
|
+
const error = new _ContentError(`Failed to extract bundle: ${bundle}`);
|
|
138
|
+
error.suggestion = "The bundle may be corrupted. Try running jawkit init again to re-download.";
|
|
139
|
+
return error;
|
|
140
|
+
}
|
|
141
|
+
static tierAccessDenied(tier) {
|
|
142
|
+
const error = new _ContentError(`Access denied for tier: ${tier}`);
|
|
143
|
+
error.suggestion = "Upgrade to Professional at https://jawkit.cc/pro";
|
|
144
|
+
return error;
|
|
145
|
+
}
|
|
146
|
+
static signedUrlFailed() {
|
|
147
|
+
const error = new _ContentError("Failed to get download authorization");
|
|
148
|
+
error.suggestion = "Check your license status with: jawkit auth status";
|
|
149
|
+
return error;
|
|
150
|
+
}
|
|
151
|
+
};
|
|
152
|
+
var AgentError = class _AgentError extends JawKitError {
|
|
153
|
+
code = "AGENT_ERROR";
|
|
154
|
+
static notFound(agentId) {
|
|
155
|
+
const error = new _AgentError(`Agent not found: ${agentId}`);
|
|
156
|
+
error.suggestion = "Run 'jawkit init --help' to see available agents";
|
|
157
|
+
return error;
|
|
158
|
+
}
|
|
159
|
+
static installFailed(agentId, reason) {
|
|
160
|
+
const error = new _AgentError(
|
|
161
|
+
`Failed to install ${agentId}${reason ? `: ${reason}` : ""}`
|
|
162
|
+
);
|
|
163
|
+
return error;
|
|
164
|
+
}
|
|
165
|
+
static alreadyInstalled(agentId, path5) {
|
|
166
|
+
const error = new _AgentError(`${agentId} is already installed at ${path5}`);
|
|
167
|
+
error.suggestion = "Run 'jawkit init' again to update (existing content will be backed up)";
|
|
168
|
+
return error;
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
var ConfigError = class _ConfigError extends JawKitError {
|
|
172
|
+
code = "CONFIG_ERROR";
|
|
173
|
+
static readFailed(path5) {
|
|
174
|
+
const error = new _ConfigError(`Failed to read config file: ${path5}`);
|
|
175
|
+
return error;
|
|
176
|
+
}
|
|
177
|
+
static writeFailed(path5) {
|
|
178
|
+
const error = new _ConfigError(`Failed to write config file: ${path5}`);
|
|
179
|
+
return error;
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
function handleError(error, logger2) {
|
|
183
|
+
if (error instanceof JawKitError) {
|
|
184
|
+
logger2.error(error.message);
|
|
185
|
+
if (error.suggestion) {
|
|
186
|
+
logger2.info(chalk2.dim(` Suggestion: ${error.suggestion}`));
|
|
187
|
+
}
|
|
188
|
+
if (error.cause) {
|
|
189
|
+
logger2.debug(`Caused by: ${error.cause.message}`);
|
|
190
|
+
}
|
|
191
|
+
} else if (error instanceof Error) {
|
|
192
|
+
logger2.error(error.message);
|
|
193
|
+
if (error.stack) {
|
|
194
|
+
logger2.debug(error.stack);
|
|
195
|
+
}
|
|
196
|
+
} else {
|
|
197
|
+
logger2.error(String(error));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// src/lib/config.ts
|
|
202
|
+
import fs from "fs/promises";
|
|
203
|
+
import path from "path";
|
|
204
|
+
import os from "os";
|
|
205
|
+
var CONFIG_DIR = path.join(os.homedir(), ".jawkit");
|
|
206
|
+
var CONFIG_FILE = "config.json";
|
|
207
|
+
var ConfigService = class {
|
|
208
|
+
configDir;
|
|
209
|
+
configPath;
|
|
210
|
+
config = null;
|
|
211
|
+
constructor(configDir = CONFIG_DIR) {
|
|
212
|
+
this.configDir = configDir;
|
|
213
|
+
this.configPath = path.join(configDir, CONFIG_FILE);
|
|
214
|
+
}
|
|
215
|
+
async initialize() {
|
|
216
|
+
await fs.mkdir(this.configDir, { recursive: true });
|
|
217
|
+
}
|
|
218
|
+
async load() {
|
|
219
|
+
if (this.config) {
|
|
220
|
+
return this.config;
|
|
221
|
+
}
|
|
222
|
+
try {
|
|
223
|
+
const content = await fs.readFile(this.configPath, "utf-8");
|
|
224
|
+
this.config = JSON.parse(content);
|
|
225
|
+
return this.config;
|
|
226
|
+
} catch {
|
|
227
|
+
this.config = {};
|
|
228
|
+
return this.config;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
async save() {
|
|
232
|
+
await this.initialize();
|
|
233
|
+
await fs.writeFile(
|
|
234
|
+
this.configPath,
|
|
235
|
+
JSON.stringify(this.config ?? {}, null, 2)
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
async get(key) {
|
|
239
|
+
const config = await this.load();
|
|
240
|
+
return config[key];
|
|
241
|
+
}
|
|
242
|
+
async set(key, value) {
|
|
243
|
+
await this.load();
|
|
244
|
+
this.config = { ...this.config, [key]: value };
|
|
245
|
+
await this.save();
|
|
246
|
+
}
|
|
247
|
+
async getInstallations() {
|
|
248
|
+
return await this.get("installations") ?? [];
|
|
249
|
+
}
|
|
250
|
+
async addInstallation(installation) {
|
|
251
|
+
const installations = await this.getInstallations();
|
|
252
|
+
const existingIndex = installations.findIndex(
|
|
253
|
+
(i) => i.path === installation.path && i.agent === installation.agent
|
|
254
|
+
);
|
|
255
|
+
if (existingIndex >= 0) {
|
|
256
|
+
installations[existingIndex] = installation;
|
|
257
|
+
} else {
|
|
258
|
+
installations.push(installation);
|
|
259
|
+
}
|
|
260
|
+
await this.set("installations", installations);
|
|
261
|
+
}
|
|
262
|
+
async updateInstallation(installPath, updates) {
|
|
263
|
+
const installations = await this.getInstallations();
|
|
264
|
+
const index = installations.findIndex((i) => i.path === installPath);
|
|
265
|
+
if (index >= 0) {
|
|
266
|
+
installations[index] = { ...installations[index], ...updates };
|
|
267
|
+
await this.set("installations", installations);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
getConfigDir() {
|
|
271
|
+
return this.configDir;
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
// src/auth/types.ts
|
|
276
|
+
function getTierDisplayName(tier) {
|
|
277
|
+
switch (tier) {
|
|
278
|
+
case "free":
|
|
279
|
+
return "Free";
|
|
280
|
+
case "professional":
|
|
281
|
+
return "Professional ($99)";
|
|
282
|
+
default:
|
|
283
|
+
return "Unknown";
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
// src/auth/auth.service.ts
|
|
288
|
+
import { ofetch } from "ofetch";
|
|
289
|
+
|
|
290
|
+
// src/auth/license-storage.ts
|
|
291
|
+
import fs2 from "fs/promises";
|
|
292
|
+
import path2 from "path";
|
|
293
|
+
import os2 from "os";
|
|
294
|
+
var STORAGE_DIR = path2.join(os2.homedir(), ".jawkit");
|
|
295
|
+
var LICENSE_FILE = "license.json";
|
|
296
|
+
var LicenseStorage = class {
|
|
297
|
+
getFilePath() {
|
|
298
|
+
return path2.join(STORAGE_DIR, LICENSE_FILE);
|
|
299
|
+
}
|
|
300
|
+
/**
|
|
301
|
+
* Store license data
|
|
302
|
+
*/
|
|
303
|
+
async store(license) {
|
|
304
|
+
await fs2.mkdir(STORAGE_DIR, { recursive: true });
|
|
305
|
+
const filePath = this.getFilePath();
|
|
306
|
+
await fs2.writeFile(filePath, JSON.stringify(license, null, 2), {
|
|
307
|
+
mode: 384
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Retrieve stored license
|
|
312
|
+
*/
|
|
313
|
+
async retrieve() {
|
|
314
|
+
try {
|
|
315
|
+
const filePath = this.getFilePath();
|
|
316
|
+
const data = await fs2.readFile(filePath, "utf-8");
|
|
317
|
+
return JSON.parse(data);
|
|
318
|
+
} catch {
|
|
319
|
+
return null;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Clear stored license
|
|
324
|
+
*/
|
|
325
|
+
async clear() {
|
|
326
|
+
try {
|
|
327
|
+
const filePath = this.getFilePath();
|
|
328
|
+
await fs2.unlink(filePath);
|
|
329
|
+
} catch {
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Check if license is stored
|
|
334
|
+
*/
|
|
335
|
+
async hasLicense() {
|
|
336
|
+
const license = await this.retrieve();
|
|
337
|
+
return license !== null;
|
|
338
|
+
}
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
// src/auth/auth.service.ts
|
|
342
|
+
var API_URL = process.env.JAWKIT_API_URL || "https://api.jawkit.cc";
|
|
343
|
+
var AuthService = class {
|
|
344
|
+
licenseStorage;
|
|
345
|
+
constructor() {
|
|
346
|
+
this.licenseStorage = new LicenseStorage();
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Activate a license key
|
|
350
|
+
* Validates against server and stores locally
|
|
351
|
+
*/
|
|
352
|
+
async activate(licenseKey) {
|
|
353
|
+
if (!licenseKey.startsWith("JAWKIT_")) {
|
|
354
|
+
return {
|
|
355
|
+
success: false,
|
|
356
|
+
user: null,
|
|
357
|
+
error: "Invalid license key format. License keys start with JAWKIT_"
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
try {
|
|
361
|
+
const response = await ofetch(
|
|
362
|
+
`${API_URL}/license/validate`,
|
|
363
|
+
{
|
|
364
|
+
method: "POST",
|
|
365
|
+
body: { licenseKey }
|
|
366
|
+
}
|
|
367
|
+
);
|
|
368
|
+
if (!response.valid) {
|
|
369
|
+
return {
|
|
370
|
+
success: false,
|
|
371
|
+
user: null,
|
|
372
|
+
error: "License key validation failed"
|
|
373
|
+
};
|
|
374
|
+
}
|
|
375
|
+
const storedLicense = {
|
|
376
|
+
licenseKey,
|
|
377
|
+
email: response.email,
|
|
378
|
+
tier: response.tier,
|
|
379
|
+
tierExpiresAt: response.tierExpiresAt,
|
|
380
|
+
validatedAt: Date.now()
|
|
381
|
+
};
|
|
382
|
+
await this.licenseStorage.store(storedLicense);
|
|
383
|
+
return {
|
|
384
|
+
success: true,
|
|
385
|
+
user: {
|
|
386
|
+
email: response.email,
|
|
387
|
+
tier: response.tier,
|
|
388
|
+
tierExpiresAt: response.tierExpiresAt
|
|
389
|
+
}
|
|
390
|
+
};
|
|
391
|
+
} catch (error) {
|
|
392
|
+
if (error instanceof Error && "statusCode" in error) {
|
|
393
|
+
const statusCode = error.statusCode;
|
|
394
|
+
if (statusCode === 404) {
|
|
395
|
+
return {
|
|
396
|
+
success: false,
|
|
397
|
+
user: null,
|
|
398
|
+
error: "License key not found"
|
|
399
|
+
};
|
|
400
|
+
}
|
|
401
|
+
if (statusCode === 403) {
|
|
402
|
+
return {
|
|
403
|
+
success: false,
|
|
404
|
+
user: null,
|
|
405
|
+
error: "License has been revoked"
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
return {
|
|
410
|
+
success: false,
|
|
411
|
+
user: null,
|
|
412
|
+
error: error instanceof Error ? error.message : "Failed to validate license"
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Deactivate (remove) stored license
|
|
418
|
+
*/
|
|
419
|
+
async deactivate() {
|
|
420
|
+
await this.licenseStorage.clear();
|
|
421
|
+
}
|
|
422
|
+
/**
|
|
423
|
+
* Check if user has a valid license stored
|
|
424
|
+
*/
|
|
425
|
+
async isAuthenticated() {
|
|
426
|
+
const license = await this.licenseStorage.retrieve();
|
|
427
|
+
if (!license) {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
if (license.tierExpiresAt) {
|
|
431
|
+
const expiresAt = new Date(license.tierExpiresAt);
|
|
432
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return true;
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Get current user profile from stored license
|
|
440
|
+
*/
|
|
441
|
+
async getCurrentUser() {
|
|
442
|
+
const license = await this.licenseStorage.retrieve();
|
|
443
|
+
if (!license) {
|
|
444
|
+
return null;
|
|
445
|
+
}
|
|
446
|
+
let effectiveTier = license.tier;
|
|
447
|
+
if (license.tierExpiresAt) {
|
|
448
|
+
const expiresAt = new Date(license.tierExpiresAt);
|
|
449
|
+
if (expiresAt < /* @__PURE__ */ new Date()) {
|
|
450
|
+
effectiveTier = "free";
|
|
451
|
+
}
|
|
452
|
+
}
|
|
453
|
+
return {
|
|
454
|
+
email: license.email,
|
|
455
|
+
tier: effectiveTier,
|
|
456
|
+
tierExpiresAt: license.tierExpiresAt
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
/**
|
|
460
|
+
* Get user's tier level
|
|
461
|
+
* Returns 'free' if no license or expired
|
|
462
|
+
*/
|
|
463
|
+
async getUserTier() {
|
|
464
|
+
const user = await this.getCurrentUser();
|
|
465
|
+
return user?.tier ?? "free";
|
|
466
|
+
}
|
|
467
|
+
/**
|
|
468
|
+
* Get stored license key for API requests
|
|
469
|
+
*/
|
|
470
|
+
async getLicenseKey() {
|
|
471
|
+
const license = await this.licenseStorage.retrieve();
|
|
472
|
+
return license?.licenseKey ?? null;
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Redeem an invitation code
|
|
476
|
+
* Calls API to exchange invite code for license key, then activates it
|
|
477
|
+
*/
|
|
478
|
+
async redeem(inviteCode, email) {
|
|
479
|
+
if (!inviteCode.startsWith("JAWKIT_INV_")) {
|
|
480
|
+
return {
|
|
481
|
+
success: false,
|
|
482
|
+
user: null,
|
|
483
|
+
error: "Invalid invitation code format. Invitation codes start with JAWKIT_INV_"
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
try {
|
|
487
|
+
const response = await ofetch(`${API_URL}/invitation/redeem`, {
|
|
488
|
+
method: "POST",
|
|
489
|
+
body: { inviteCode, email }
|
|
490
|
+
});
|
|
491
|
+
if (!response.success || !response.licenseKey) {
|
|
492
|
+
return {
|
|
493
|
+
success: false,
|
|
494
|
+
user: null,
|
|
495
|
+
error: "Failed to redeem invitation"
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
const storedLicense = {
|
|
499
|
+
licenseKey: response.licenseKey,
|
|
500
|
+
email: response.email,
|
|
501
|
+
tier: response.tier,
|
|
502
|
+
tierExpiresAt: response.tierExpiresAt,
|
|
503
|
+
validatedAt: Date.now()
|
|
504
|
+
};
|
|
505
|
+
await this.licenseStorage.store(storedLicense);
|
|
506
|
+
return {
|
|
507
|
+
success: true,
|
|
508
|
+
user: {
|
|
509
|
+
email: response.email,
|
|
510
|
+
tier: response.tier,
|
|
511
|
+
tierExpiresAt: response.tierExpiresAt
|
|
512
|
+
}
|
|
513
|
+
};
|
|
514
|
+
} catch (error) {
|
|
515
|
+
if (error instanceof Error && "data" in error) {
|
|
516
|
+
const data = error.data;
|
|
517
|
+
if (data?.error) {
|
|
518
|
+
return {
|
|
519
|
+
success: false,
|
|
520
|
+
user: null,
|
|
521
|
+
error: data.error
|
|
522
|
+
};
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
return {
|
|
526
|
+
success: false,
|
|
527
|
+
user: null,
|
|
528
|
+
error: error instanceof Error ? error.message : "Failed to redeem invitation"
|
|
529
|
+
};
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Re-validate stored license against server
|
|
534
|
+
* Useful for refreshing tier status
|
|
535
|
+
*/
|
|
536
|
+
async revalidate() {
|
|
537
|
+
const license = await this.licenseStorage.retrieve();
|
|
538
|
+
if (!license) {
|
|
539
|
+
return {
|
|
540
|
+
success: false,
|
|
541
|
+
user: null,
|
|
542
|
+
error: "No license key stored"
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
return this.activate(license.licenseKey);
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
var authServiceInstance = null;
|
|
549
|
+
function getAuthService() {
|
|
550
|
+
if (!authServiceInstance) {
|
|
551
|
+
authServiceInstance = new AuthService();
|
|
552
|
+
}
|
|
553
|
+
return authServiceInstance;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// src/content/content.service.ts
|
|
557
|
+
import fs6 from "fs/promises";
|
|
558
|
+
|
|
559
|
+
// src/content/remote-provider.ts
|
|
560
|
+
import { ofetch as ofetch2, FetchError } from "ofetch";
|
|
561
|
+
import { createWriteStream } from "fs";
|
|
562
|
+
import { pipeline } from "stream/promises";
|
|
563
|
+
import { Readable } from "stream";
|
|
564
|
+
var WORKER_URL = process.env.JAWKIT_WORKER_URL || "https://api.jawkit.cc";
|
|
565
|
+
var R2_PUBLIC_URL = process.env.R2_PUBLIC_URL || "https://content.jawkit.cc";
|
|
566
|
+
var RemoteProvider = class {
|
|
567
|
+
r2BaseUrl;
|
|
568
|
+
workerUrl;
|
|
569
|
+
constructor(r2BaseUrl, workerUrl) {
|
|
570
|
+
this.r2BaseUrl = r2BaseUrl || R2_PUBLIC_URL;
|
|
571
|
+
this.workerUrl = workerUrl || WORKER_URL;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Fetch content manifest from R2
|
|
575
|
+
* @param version - 'latest' or specific version (e.g., '1.0.0')
|
|
576
|
+
*/
|
|
577
|
+
async fetchManifest(version2 = "latest") {
|
|
578
|
+
const url = version2 === "latest" ? `${this.r2BaseUrl}/manifests/latest.json` : `${this.r2BaseUrl}/manifests/${version2}.json`;
|
|
579
|
+
try {
|
|
580
|
+
const response = await ofetch2(url, {
|
|
581
|
+
timeout: 1e4,
|
|
582
|
+
retry: 2
|
|
583
|
+
});
|
|
584
|
+
return response;
|
|
585
|
+
} catch (error) {
|
|
586
|
+
if (error instanceof FetchError) {
|
|
587
|
+
if (error.status === 404) {
|
|
588
|
+
throw ContentError.manifestNotFound();
|
|
589
|
+
}
|
|
590
|
+
throw ContentError.downloadFailed("manifest", error.status);
|
|
591
|
+
}
|
|
592
|
+
throw error;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
/**
|
|
596
|
+
* Get bundle download URL
|
|
597
|
+
* - Free tier: Direct public R2 URL
|
|
598
|
+
* - Premium tiers: Signed URL via Cloudflare Worker
|
|
599
|
+
*/
|
|
600
|
+
async getBundleUrl(agentId, version2, tier, licenseKey) {
|
|
601
|
+
if (tier === "free") {
|
|
602
|
+
return `${this.r2BaseUrl}/bundles/${agentId}/${version2}/free.tar.gz`;
|
|
603
|
+
}
|
|
604
|
+
if (!licenseKey) {
|
|
605
|
+
throw ContentError.tierAccessDenied(tier);
|
|
606
|
+
}
|
|
607
|
+
try {
|
|
608
|
+
const response = await ofetch2(
|
|
609
|
+
`${this.workerUrl}/get-download-url`,
|
|
610
|
+
{
|
|
611
|
+
method: "POST",
|
|
612
|
+
headers: {
|
|
613
|
+
Authorization: `License ${licenseKey}`,
|
|
614
|
+
"Content-Type": "application/json"
|
|
615
|
+
},
|
|
616
|
+
body: {
|
|
617
|
+
agent: agentId,
|
|
618
|
+
version: version2,
|
|
619
|
+
tier
|
|
620
|
+
},
|
|
621
|
+
timeout: 1e4
|
|
622
|
+
}
|
|
623
|
+
);
|
|
624
|
+
return response.url;
|
|
625
|
+
} catch (error) {
|
|
626
|
+
if (error instanceof FetchError) {
|
|
627
|
+
if (error.status === 401) {
|
|
628
|
+
throw ContentError.tierAccessDenied(tier);
|
|
629
|
+
}
|
|
630
|
+
if (error.status === 403) {
|
|
631
|
+
throw ContentError.tierAccessDenied(tier);
|
|
632
|
+
}
|
|
633
|
+
throw ContentError.signedUrlFailed();
|
|
634
|
+
}
|
|
635
|
+
throw error;
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Download file to disk with progress callback and retry logic
|
|
640
|
+
*/
|
|
641
|
+
async downloadToFile(url, destPath, _expectedSize, onProgress, maxRetries = 3) {
|
|
642
|
+
let lastError = null;
|
|
643
|
+
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
644
|
+
try {
|
|
645
|
+
const response = await fetch(url);
|
|
646
|
+
if (!response.ok) {
|
|
647
|
+
throw ContentError.downloadFailed(destPath, response.status);
|
|
648
|
+
}
|
|
649
|
+
if (!response.body) {
|
|
650
|
+
throw ContentError.downloadFailed(destPath);
|
|
651
|
+
}
|
|
652
|
+
const reader = response.body.getReader();
|
|
653
|
+
const writer = createWriteStream(destPath);
|
|
654
|
+
let downloaded = 0;
|
|
655
|
+
const readable = new Readable({
|
|
656
|
+
async read() {
|
|
657
|
+
try {
|
|
658
|
+
const { done, value } = await reader.read();
|
|
659
|
+
if (done) {
|
|
660
|
+
this.push(null);
|
|
661
|
+
} else {
|
|
662
|
+
downloaded += value.length;
|
|
663
|
+
onProgress?.(downloaded);
|
|
664
|
+
this.push(value);
|
|
665
|
+
}
|
|
666
|
+
} catch (error) {
|
|
667
|
+
this.destroy(error instanceof Error ? error : new Error(String(error)));
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
});
|
|
671
|
+
await pipeline(readable, writer);
|
|
672
|
+
return;
|
|
673
|
+
} catch (error) {
|
|
674
|
+
lastError = error instanceof Error ? error : new Error(String(error));
|
|
675
|
+
if (error instanceof ContentError) {
|
|
676
|
+
throw error;
|
|
677
|
+
}
|
|
678
|
+
if (attempt < maxRetries) {
|
|
679
|
+
const delay = Math.pow(2, attempt - 1) * 1e3;
|
|
680
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
throw lastError || ContentError.downloadFailed(destPath);
|
|
685
|
+
}
|
|
686
|
+
/**
|
|
687
|
+
* Check if a bundle exists on the remote server
|
|
688
|
+
*/
|
|
689
|
+
async checkBundleExists(agentId, version2, tier) {
|
|
690
|
+
const url = `${this.r2BaseUrl}/bundles/${agentId}/${version2}/${tier}.tar.gz`;
|
|
691
|
+
try {
|
|
692
|
+
const response = await fetch(url, { method: "HEAD" });
|
|
693
|
+
return response.ok;
|
|
694
|
+
} catch {
|
|
695
|
+
return false;
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
/**
|
|
699
|
+
* Get the R2 base URL
|
|
700
|
+
*/
|
|
701
|
+
getR2BaseUrl() {
|
|
702
|
+
return this.r2BaseUrl;
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get the Worker URL
|
|
706
|
+
*/
|
|
707
|
+
getWorkerUrl() {
|
|
708
|
+
return this.workerUrl;
|
|
709
|
+
}
|
|
710
|
+
};
|
|
711
|
+
var remoteProviderInstance = null;
|
|
712
|
+
function getRemoteProvider() {
|
|
713
|
+
if (!remoteProviderInstance) {
|
|
714
|
+
remoteProviderInstance = new RemoteProvider();
|
|
715
|
+
}
|
|
716
|
+
return remoteProviderInstance;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// src/content/cache-manager.ts
|
|
720
|
+
import fs3 from "fs/promises";
|
|
721
|
+
import path3 from "path";
|
|
722
|
+
import os3 from "os";
|
|
723
|
+
var DEFAULT_CACHE_DIR = path3.join(os3.homedir(), ".jawkit", "cache");
|
|
724
|
+
var MANIFEST_FILE = "manifest.json";
|
|
725
|
+
var BUNDLES_DIR = "bundles";
|
|
726
|
+
var TEMP_DIR = "temp";
|
|
727
|
+
var CacheManager = class {
|
|
728
|
+
cacheDir;
|
|
729
|
+
initialized = false;
|
|
730
|
+
constructor(cacheDir = DEFAULT_CACHE_DIR) {
|
|
731
|
+
this.cacheDir = cacheDir;
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* Initialize cache directories
|
|
735
|
+
*/
|
|
736
|
+
async initialize() {
|
|
737
|
+
if (this.initialized) return;
|
|
738
|
+
await fs3.mkdir(this.cacheDir, { recursive: true });
|
|
739
|
+
await fs3.mkdir(path3.join(this.cacheDir, BUNDLES_DIR), { recursive: true });
|
|
740
|
+
await fs3.mkdir(path3.join(this.cacheDir, TEMP_DIR), { recursive: true });
|
|
741
|
+
this.initialized = true;
|
|
742
|
+
}
|
|
743
|
+
// Manifest caching
|
|
744
|
+
/**
|
|
745
|
+
* Save manifest to cache
|
|
746
|
+
*/
|
|
747
|
+
async saveManifest(manifest) {
|
|
748
|
+
await this.initialize();
|
|
749
|
+
const filePath = path3.join(this.cacheDir, MANIFEST_FILE);
|
|
750
|
+
await fs3.writeFile(filePath, JSON.stringify(manifest, null, 2), "utf-8");
|
|
751
|
+
}
|
|
752
|
+
/**
|
|
753
|
+
* Get cached manifest
|
|
754
|
+
*/
|
|
755
|
+
async getManifest() {
|
|
756
|
+
try {
|
|
757
|
+
const filePath = path3.join(this.cacheDir, MANIFEST_FILE);
|
|
758
|
+
const content = await fs3.readFile(filePath, "utf-8");
|
|
759
|
+
return JSON.parse(content);
|
|
760
|
+
} catch {
|
|
761
|
+
return null;
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
// Bundle caching
|
|
765
|
+
/**
|
|
766
|
+
* Save downloaded bundle to cache
|
|
767
|
+
* @param agentId - Agent identifier
|
|
768
|
+
* @param version - Content version
|
|
769
|
+
* @param tier - Tier level
|
|
770
|
+
* @param sourcePath - Path to the downloaded file (will be moved)
|
|
771
|
+
* @returns Path to cached bundle
|
|
772
|
+
*/
|
|
773
|
+
async saveBundle(agentId, version2, tier, sourcePath) {
|
|
774
|
+
await this.initialize();
|
|
775
|
+
const bundleDir = path3.join(this.cacheDir, BUNDLES_DIR, agentId, version2);
|
|
776
|
+
await fs3.mkdir(bundleDir, { recursive: true });
|
|
777
|
+
const destPath = path3.join(bundleDir, `${tier}.tar.gz`);
|
|
778
|
+
await fs3.rename(sourcePath, destPath);
|
|
779
|
+
return destPath;
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Get cached bundle path if exists
|
|
783
|
+
*/
|
|
784
|
+
async getBundle(agentId, version2, tier) {
|
|
785
|
+
const bundlePath = path3.join(
|
|
786
|
+
this.cacheDir,
|
|
787
|
+
BUNDLES_DIR,
|
|
788
|
+
agentId,
|
|
789
|
+
version2,
|
|
790
|
+
`${tier}.tar.gz`
|
|
791
|
+
);
|
|
792
|
+
try {
|
|
793
|
+
await fs3.access(bundlePath);
|
|
794
|
+
return bundlePath;
|
|
795
|
+
} catch {
|
|
796
|
+
return null;
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
/**
|
|
800
|
+
* Check if a bundle exists in cache
|
|
801
|
+
*/
|
|
802
|
+
async hasBundle(agentId, version2, tier) {
|
|
803
|
+
const bundlePath = await this.getBundle(agentId, version2, tier);
|
|
804
|
+
return bundlePath !== null;
|
|
805
|
+
}
|
|
806
|
+
// Temp file management
|
|
807
|
+
/**
|
|
808
|
+
* Get a path in the temp directory for downloads
|
|
809
|
+
*/
|
|
810
|
+
async getTempPath(filename) {
|
|
811
|
+
await this.initialize();
|
|
812
|
+
return path3.join(this.cacheDir, TEMP_DIR, filename);
|
|
813
|
+
}
|
|
814
|
+
/**
|
|
815
|
+
* Clean temporary files
|
|
816
|
+
*/
|
|
817
|
+
async cleanTemp() {
|
|
818
|
+
const tempDir = path3.join(this.cacheDir, TEMP_DIR);
|
|
819
|
+
try {
|
|
820
|
+
await fs3.rm(tempDir, { recursive: true, force: true });
|
|
821
|
+
await fs3.mkdir(tempDir, { recursive: true });
|
|
822
|
+
} catch {
|
|
823
|
+
}
|
|
824
|
+
}
|
|
825
|
+
// Cache management
|
|
826
|
+
/**
|
|
827
|
+
* Clear all cached data
|
|
828
|
+
*/
|
|
829
|
+
async clearCache() {
|
|
830
|
+
await fs3.rm(this.cacheDir, { recursive: true, force: true });
|
|
831
|
+
this.initialized = false;
|
|
832
|
+
await this.initialize();
|
|
833
|
+
}
|
|
834
|
+
/**
|
|
835
|
+
* Clear cache for a specific agent
|
|
836
|
+
*/
|
|
837
|
+
async clearAgentCache(agentId) {
|
|
838
|
+
const agentDir = path3.join(this.cacheDir, BUNDLES_DIR, agentId);
|
|
839
|
+
try {
|
|
840
|
+
await fs3.rm(agentDir, { recursive: true, force: true });
|
|
841
|
+
} catch {
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
/**
|
|
845
|
+
* Clear old versions, keeping only the specified version
|
|
846
|
+
*/
|
|
847
|
+
async clearOldVersions(agentId, keepVersion) {
|
|
848
|
+
const agentDir = path3.join(this.cacheDir, BUNDLES_DIR, agentId);
|
|
849
|
+
let removedCount = 0;
|
|
850
|
+
try {
|
|
851
|
+
const versions = await fs3.readdir(agentDir);
|
|
852
|
+
for (const version2 of versions) {
|
|
853
|
+
if (version2 !== keepVersion) {
|
|
854
|
+
await fs3.rm(path3.join(agentDir, version2), { recursive: true, force: true });
|
|
855
|
+
removedCount++;
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
} catch {
|
|
859
|
+
}
|
|
860
|
+
return removedCount;
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Get total cache size in bytes
|
|
864
|
+
*/
|
|
865
|
+
async getCacheSize() {
|
|
866
|
+
let totalSize = 0;
|
|
867
|
+
async function calculateSize(dirPath) {
|
|
868
|
+
try {
|
|
869
|
+
const entries = await fs3.readdir(dirPath, { withFileTypes: true });
|
|
870
|
+
for (const entry of entries) {
|
|
871
|
+
const entryPath = path3.join(dirPath, entry.name);
|
|
872
|
+
if (entry.isDirectory()) {
|
|
873
|
+
await calculateSize(entryPath);
|
|
874
|
+
} else {
|
|
875
|
+
const stats = await fs3.stat(entryPath);
|
|
876
|
+
totalSize += stats.size;
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
} catch {
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
await calculateSize(this.cacheDir);
|
|
883
|
+
return totalSize;
|
|
884
|
+
}
|
|
885
|
+
/**
|
|
886
|
+
* Get detailed cache information
|
|
887
|
+
*/
|
|
888
|
+
async getCacheInfo() {
|
|
889
|
+
const size = await this.getCacheSize();
|
|
890
|
+
const bundlesDir = path3.join(this.cacheDir, BUNDLES_DIR);
|
|
891
|
+
const agents = {};
|
|
892
|
+
try {
|
|
893
|
+
const agentDirs = await fs3.readdir(bundlesDir);
|
|
894
|
+
for (const agent of agentDirs) {
|
|
895
|
+
const agentPath = path3.join(bundlesDir, agent);
|
|
896
|
+
try {
|
|
897
|
+
const stat = await fs3.stat(agentPath);
|
|
898
|
+
if (stat.isDirectory()) {
|
|
899
|
+
const versions = await fs3.readdir(agentPath);
|
|
900
|
+
const versionDirs = [];
|
|
901
|
+
for (const v of versions) {
|
|
902
|
+
const vPath = path3.join(agentPath, v);
|
|
903
|
+
try {
|
|
904
|
+
const vStat = await fs3.stat(vPath);
|
|
905
|
+
if (vStat.isDirectory()) {
|
|
906
|
+
versionDirs.push(v);
|
|
907
|
+
}
|
|
908
|
+
} catch {
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
agents[agent] = versionDirs;
|
|
912
|
+
}
|
|
913
|
+
} catch {
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
} catch {
|
|
917
|
+
}
|
|
918
|
+
return { size, agents };
|
|
919
|
+
}
|
|
920
|
+
/**
|
|
921
|
+
* Get the cache directory path
|
|
922
|
+
*/
|
|
923
|
+
getCacheDir() {
|
|
924
|
+
return this.cacheDir;
|
|
925
|
+
}
|
|
926
|
+
};
|
|
927
|
+
var cacheManagerInstance = null;
|
|
928
|
+
function getCacheManager() {
|
|
929
|
+
if (!cacheManagerInstance) {
|
|
930
|
+
cacheManagerInstance = new CacheManager();
|
|
931
|
+
}
|
|
932
|
+
return cacheManagerInstance;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
// src/content/bundled-content.ts
|
|
936
|
+
import fs4 from "fs";
|
|
937
|
+
import path4 from "path";
|
|
938
|
+
import { fileURLToPath } from "url";
|
|
939
|
+
var __filename2 = fileURLToPath(import.meta.url);
|
|
940
|
+
var __dirname2 = path4.dirname(__filename2);
|
|
941
|
+
var BUNDLED_DIR = path4.join(__dirname2, "../../content");
|
|
942
|
+
var BundledContent = class {
|
|
943
|
+
bundledDir;
|
|
944
|
+
constructor(bundledDir = BUNDLED_DIR) {
|
|
945
|
+
this.bundledDir = bundledDir;
|
|
946
|
+
}
|
|
947
|
+
/**
|
|
948
|
+
* Check if bundled content exists
|
|
949
|
+
*/
|
|
950
|
+
isAvailable() {
|
|
951
|
+
try {
|
|
952
|
+
const manifestPath = path4.join(this.bundledDir, "manifest.json");
|
|
953
|
+
fs4.accessSync(manifestPath);
|
|
954
|
+
return true;
|
|
955
|
+
} catch {
|
|
956
|
+
return false;
|
|
957
|
+
}
|
|
958
|
+
}
|
|
959
|
+
/**
|
|
960
|
+
* Get bundled manifest (free tier info)
|
|
961
|
+
*/
|
|
962
|
+
getManifest() {
|
|
963
|
+
const manifestPath = path4.join(this.bundledDir, "manifest.json");
|
|
964
|
+
const content = fs4.readFileSync(manifestPath, "utf-8");
|
|
965
|
+
return JSON.parse(content);
|
|
966
|
+
}
|
|
967
|
+
/**
|
|
968
|
+
* Get bundled manifest or null if not available
|
|
969
|
+
*/
|
|
970
|
+
getManifestOrNull() {
|
|
971
|
+
try {
|
|
972
|
+
return this.getManifest();
|
|
973
|
+
} catch {
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
/**
|
|
978
|
+
* Get path to bundled tar.gz file for an agent
|
|
979
|
+
* Only 'free' tier is bundled
|
|
980
|
+
*/
|
|
981
|
+
getBundlePath(agentId, tier = "free") {
|
|
982
|
+
const bundlePath = path4.join(this.bundledDir, agentId, `${tier}.tar.gz`);
|
|
983
|
+
try {
|
|
984
|
+
fs4.accessSync(bundlePath);
|
|
985
|
+
return bundlePath;
|
|
986
|
+
} catch {
|
|
987
|
+
return null;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Check if bundled content exists for an agent
|
|
992
|
+
*/
|
|
993
|
+
hasBundle(agentId) {
|
|
994
|
+
return this.getBundlePath(agentId) !== null;
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* List available bundled agents
|
|
998
|
+
*/
|
|
999
|
+
listAgents() {
|
|
1000
|
+
try {
|
|
1001
|
+
const entries = fs4.readdirSync(this.bundledDir, { withFileTypes: true });
|
|
1002
|
+
return entries.filter((e) => e.isDirectory()).filter((e) => {
|
|
1003
|
+
const bundlePath = path4.join(this.bundledDir, e.name, "free.tar.gz");
|
|
1004
|
+
try {
|
|
1005
|
+
fs4.accessSync(bundlePath);
|
|
1006
|
+
return true;
|
|
1007
|
+
} catch {
|
|
1008
|
+
return false;
|
|
1009
|
+
}
|
|
1010
|
+
}).map((e) => e.name);
|
|
1011
|
+
} catch {
|
|
1012
|
+
return [];
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Get the version of bundled content
|
|
1017
|
+
*/
|
|
1018
|
+
getVersion() {
|
|
1019
|
+
const manifest = this.getManifestOrNull();
|
|
1020
|
+
return manifest?.version ?? null;
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Get the bundled directory path (for debugging)
|
|
1024
|
+
*/
|
|
1025
|
+
getBundledDir() {
|
|
1026
|
+
return this.bundledDir;
|
|
1027
|
+
}
|
|
1028
|
+
};
|
|
1029
|
+
var bundledContentInstance = null;
|
|
1030
|
+
function getBundledContent() {
|
|
1031
|
+
if (!bundledContentInstance) {
|
|
1032
|
+
bundledContentInstance = new BundledContent();
|
|
1033
|
+
}
|
|
1034
|
+
return bundledContentInstance;
|
|
1035
|
+
}
|
|
1036
|
+
|
|
1037
|
+
// src/content/extractor.ts
|
|
1038
|
+
import { createGunzip } from "zlib";
|
|
1039
|
+
import { extract as tarExtract } from "tar";
|
|
1040
|
+
import { createReadStream } from "fs";
|
|
1041
|
+
import fs5 from "fs/promises";
|
|
1042
|
+
import crypto from "crypto";
|
|
1043
|
+
async function extractBundle(bundlePath, targetDir, onProgress) {
|
|
1044
|
+
const filesExtracted = [];
|
|
1045
|
+
let totalSize = 0;
|
|
1046
|
+
const errors = [];
|
|
1047
|
+
try {
|
|
1048
|
+
await fs5.mkdir(targetDir, { recursive: true });
|
|
1049
|
+
await new Promise((resolve, reject) => {
|
|
1050
|
+
const readStream = createReadStream(bundlePath);
|
|
1051
|
+
const gunzip = createGunzip();
|
|
1052
|
+
const extractor = tarExtract({
|
|
1053
|
+
cwd: targetDir,
|
|
1054
|
+
strip: 0,
|
|
1055
|
+
// Don't strip leading path components
|
|
1056
|
+
filter: (entryPath) => {
|
|
1057
|
+
return !entryPath.endsWith("manifest.json");
|
|
1058
|
+
},
|
|
1059
|
+
onentry: (entry) => {
|
|
1060
|
+
filesExtracted.push(entry.path);
|
|
1061
|
+
totalSize += entry.size || 0;
|
|
1062
|
+
onProgress?.({
|
|
1063
|
+
phase: "extracting",
|
|
1064
|
+
totalBytes: totalSize,
|
|
1065
|
+
processedBytes: totalSize,
|
|
1066
|
+
percentage: 100,
|
|
1067
|
+
// We don't know total until done
|
|
1068
|
+
currentFile: entry.path
|
|
1069
|
+
});
|
|
1070
|
+
}
|
|
1071
|
+
});
|
|
1072
|
+
readStream.on("error", reject).pipe(gunzip).on("error", reject).pipe(extractor).on("error", reject).on("finish", resolve);
|
|
1073
|
+
});
|
|
1074
|
+
return {
|
|
1075
|
+
success: true,
|
|
1076
|
+
filesExtracted,
|
|
1077
|
+
totalSize,
|
|
1078
|
+
errors
|
|
1079
|
+
};
|
|
1080
|
+
} catch (error) {
|
|
1081
|
+
const errorMessage = error instanceof Error ? error.message : "Unknown extraction error";
|
|
1082
|
+
errors.push(errorMessage);
|
|
1083
|
+
return {
|
|
1084
|
+
success: false,
|
|
1085
|
+
filesExtracted,
|
|
1086
|
+
totalSize,
|
|
1087
|
+
errors
|
|
1088
|
+
};
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async function verifyChecksum(filePath, expectedChecksum) {
|
|
1092
|
+
const hash = crypto.createHash("sha256");
|
|
1093
|
+
const stream = createReadStream(filePath);
|
|
1094
|
+
for await (const chunk of stream) {
|
|
1095
|
+
hash.update(chunk);
|
|
1096
|
+
}
|
|
1097
|
+
const actualChecksum = `sha256:${hash.digest("hex")}`;
|
|
1098
|
+
return actualChecksum === expectedChecksum;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1101
|
+
// src/content/content.service.ts
|
|
1102
|
+
var ContentService = class {
|
|
1103
|
+
remote;
|
|
1104
|
+
cache;
|
|
1105
|
+
bundled;
|
|
1106
|
+
constructor(remote, cache, bundled) {
|
|
1107
|
+
this.remote = remote || getRemoteProvider();
|
|
1108
|
+
this.cache = cache || getCacheManager();
|
|
1109
|
+
this.bundled = bundled || getBundledContent();
|
|
1110
|
+
}
|
|
1111
|
+
/**
|
|
1112
|
+
* Get the content manifest
|
|
1113
|
+
* Falls back: remote → cache → bundled
|
|
1114
|
+
*/
|
|
1115
|
+
async getManifest(version2 = "latest") {
|
|
1116
|
+
try {
|
|
1117
|
+
const manifest = await this.remote.fetchManifest(version2);
|
|
1118
|
+
await this.cache.saveManifest(manifest);
|
|
1119
|
+
return manifest;
|
|
1120
|
+
} catch {
|
|
1121
|
+
const cached = await this.cache.getManifest();
|
|
1122
|
+
if (cached) {
|
|
1123
|
+
return cached;
|
|
1124
|
+
}
|
|
1125
|
+
const bundledManifest = this.bundled.getManifestOrNull();
|
|
1126
|
+
if (bundledManifest) {
|
|
1127
|
+
return bundledManifest;
|
|
1128
|
+
}
|
|
1129
|
+
throw ContentError.manifestNotFound();
|
|
1130
|
+
}
|
|
1131
|
+
}
|
|
1132
|
+
/**
|
|
1133
|
+
* Get bundle info for a user's tier
|
|
1134
|
+
*/
|
|
1135
|
+
async getBundleInfo(agentId, tier) {
|
|
1136
|
+
const manifest = await this.getManifest();
|
|
1137
|
+
const agentManifest = manifest.agents[agentId];
|
|
1138
|
+
if (!agentManifest) {
|
|
1139
|
+
throw new ContentError(`Agent not found in manifest: ${agentId}`);
|
|
1140
|
+
}
|
|
1141
|
+
const bundleInfo = agentManifest.bundles[tier] || agentManifest.bundles["free"];
|
|
1142
|
+
if (!bundleInfo) {
|
|
1143
|
+
throw new ContentError(`No bundle available for agent: ${agentId}`);
|
|
1144
|
+
}
|
|
1145
|
+
return bundleInfo;
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Download and extract a content bundle
|
|
1149
|
+
*/
|
|
1150
|
+
async downloadAndExtractBundle(agentId, tier, targetDir, onProgress) {
|
|
1151
|
+
const manifest = await this.getManifest();
|
|
1152
|
+
const agentManifest = manifest.agents[agentId];
|
|
1153
|
+
if (!agentManifest) {
|
|
1154
|
+
throw new ContentError(`Agent not found: ${agentId}`);
|
|
1155
|
+
}
|
|
1156
|
+
const effectiveTier = agentManifest.bundles[tier] ? tier : "free";
|
|
1157
|
+
const bundleInfo = agentManifest.bundles[effectiveTier];
|
|
1158
|
+
if (!bundleInfo) {
|
|
1159
|
+
throw new ContentError(`No bundle available for agent: ${agentId}`);
|
|
1160
|
+
}
|
|
1161
|
+
const version2 = agentManifest.version;
|
|
1162
|
+
const cachedBundle = await this.cache.getBundle(agentId, version2, effectiveTier);
|
|
1163
|
+
let bundlePath;
|
|
1164
|
+
if (cachedBundle && await verifyChecksum(cachedBundle, bundleInfo.checksum)) {
|
|
1165
|
+
bundlePath = cachedBundle;
|
|
1166
|
+
} else {
|
|
1167
|
+
bundlePath = await this.downloadBundle(
|
|
1168
|
+
agentId,
|
|
1169
|
+
version2,
|
|
1170
|
+
effectiveTier,
|
|
1171
|
+
bundleInfo,
|
|
1172
|
+
onProgress
|
|
1173
|
+
);
|
|
1174
|
+
}
|
|
1175
|
+
const result = await extractBundle(bundlePath, targetDir, onProgress);
|
|
1176
|
+
await this.cache.clearOldVersions(agentId, version2);
|
|
1177
|
+
return {
|
|
1178
|
+
...result,
|
|
1179
|
+
version: version2
|
|
1180
|
+
};
|
|
1181
|
+
}
|
|
1182
|
+
/**
|
|
1183
|
+
* Download a bundle to cache
|
|
1184
|
+
*/
|
|
1185
|
+
async downloadBundle(agentId, version2, tier, bundleInfo, onProgress) {
|
|
1186
|
+
let licenseKey = null;
|
|
1187
|
+
if (tier !== "free") {
|
|
1188
|
+
const authService = getAuthService();
|
|
1189
|
+
licenseKey = await authService.getLicenseKey();
|
|
1190
|
+
}
|
|
1191
|
+
const downloadUrl = await this.remote.getBundleUrl(
|
|
1192
|
+
agentId,
|
|
1193
|
+
version2,
|
|
1194
|
+
tier,
|
|
1195
|
+
licenseKey
|
|
1196
|
+
);
|
|
1197
|
+
const tempPath = await this.cache.getTempPath(`${agentId}-${version2}-${tier}.tar.gz`);
|
|
1198
|
+
await this.remote.downloadToFile(downloadUrl, tempPath, bundleInfo.size, (downloaded) => {
|
|
1199
|
+
onProgress?.({
|
|
1200
|
+
phase: "downloading",
|
|
1201
|
+
totalBytes: bundleInfo.size,
|
|
1202
|
+
processedBytes: downloaded,
|
|
1203
|
+
percentage: Math.round(downloaded / bundleInfo.size * 100)
|
|
1204
|
+
});
|
|
1205
|
+
});
|
|
1206
|
+
if (!await verifyChecksum(tempPath, bundleInfo.checksum)) {
|
|
1207
|
+
await fs6.unlink(tempPath);
|
|
1208
|
+
throw ContentError.checksumMismatch(bundleInfo.filename);
|
|
1209
|
+
}
|
|
1210
|
+
const cachePath = await this.cache.saveBundle(agentId, version2, tier, tempPath);
|
|
1211
|
+
return cachePath;
|
|
1212
|
+
}
|
|
1213
|
+
/**
|
|
1214
|
+
* Check for content updates
|
|
1215
|
+
* Returns null if no update available or if check fails
|
|
1216
|
+
* Use verbose logging to see failure details
|
|
1217
|
+
*/
|
|
1218
|
+
async checkForUpdates(agentId, currentVersion, verbose = false) {
|
|
1219
|
+
try {
|
|
1220
|
+
const manifest = await this.remote.fetchManifest("latest");
|
|
1221
|
+
const agentManifest = manifest.agents[agentId];
|
|
1222
|
+
if (!agentManifest) {
|
|
1223
|
+
if (verbose) {
|
|
1224
|
+
console.error(`Update check: agent "${agentId}" not found in manifest`);
|
|
1225
|
+
}
|
|
1226
|
+
return null;
|
|
1227
|
+
}
|
|
1228
|
+
if (this.isNewerVersion(agentManifest.version, currentVersion)) {
|
|
1229
|
+
const professional = agentManifest.bundles["professional"];
|
|
1230
|
+
const free = agentManifest.bundles["free"];
|
|
1231
|
+
const displayBundle = professional || free;
|
|
1232
|
+
if (!displayBundle) {
|
|
1233
|
+
return null;
|
|
1234
|
+
}
|
|
1235
|
+
return {
|
|
1236
|
+
currentVersion,
|
|
1237
|
+
latestVersion: agentManifest.version,
|
|
1238
|
+
changelog: manifest.changelog,
|
|
1239
|
+
publishedAt: new Date(manifest.publishedAt),
|
|
1240
|
+
bundleSize: displayBundle.size,
|
|
1241
|
+
fileCount: displayBundle.fileCount
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
return null;
|
|
1245
|
+
} catch (error) {
|
|
1246
|
+
if (verbose) {
|
|
1247
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1248
|
+
console.error(`Update check failed: ${message}`);
|
|
1249
|
+
}
|
|
1250
|
+
return null;
|
|
1251
|
+
}
|
|
1252
|
+
}
|
|
1253
|
+
/**
|
|
1254
|
+
* Extract bundled fallback content (free tier only)
|
|
1255
|
+
*/
|
|
1256
|
+
async extractFallbackContent(agentId, targetDir, onProgress) {
|
|
1257
|
+
const bundlePath = this.bundled.getBundlePath(agentId);
|
|
1258
|
+
if (!bundlePath) {
|
|
1259
|
+
throw new ContentError("No bundled fallback content available");
|
|
1260
|
+
}
|
|
1261
|
+
const result = await extractBundle(bundlePath, targetDir, onProgress);
|
|
1262
|
+
const manifest = this.bundled.getManifestOrNull();
|
|
1263
|
+
return {
|
|
1264
|
+
...result,
|
|
1265
|
+
version: manifest?.version || "bundled"
|
|
1266
|
+
};
|
|
1267
|
+
}
|
|
1268
|
+
/**
|
|
1269
|
+
* Check if bundled fallback is available for an agent
|
|
1270
|
+
*/
|
|
1271
|
+
hasFallbackContent(agentId) {
|
|
1272
|
+
return this.bundled.hasBundle(agentId);
|
|
1273
|
+
}
|
|
1274
|
+
/**
|
|
1275
|
+
* Compare semantic versions
|
|
1276
|
+
*/
|
|
1277
|
+
isNewerVersion(latest, current) {
|
|
1278
|
+
const latestParts = latest.split(".").map(Number);
|
|
1279
|
+
const currentParts = current.split(".").map(Number);
|
|
1280
|
+
for (let i = 0; i < 3; i++) {
|
|
1281
|
+
const l = latestParts[i] || 0;
|
|
1282
|
+
const c = currentParts[i] || 0;
|
|
1283
|
+
if (l > c) return true;
|
|
1284
|
+
if (l < c) return false;
|
|
1285
|
+
}
|
|
1286
|
+
return false;
|
|
1287
|
+
}
|
|
1288
|
+
};
|
|
1289
|
+
var contentServiceInstance = null;
|
|
1290
|
+
function getContentService() {
|
|
1291
|
+
if (!contentServiceInstance) {
|
|
1292
|
+
contentServiceInstance = new ContentService();
|
|
1293
|
+
}
|
|
1294
|
+
return contentServiceInstance;
|
|
1295
|
+
}
|
|
1296
|
+
|
|
1297
|
+
// src/services/cli-version.ts
|
|
1298
|
+
import { ofetch as ofetch3 } from "ofetch";
|
|
1299
|
+
var NPM_REGISTRY = "https://registry.npmjs.org";
|
|
1300
|
+
var PACKAGE_NAME = "@jawkit/cli";
|
|
1301
|
+
function getCliVersion() {
|
|
1302
|
+
return version;
|
|
1303
|
+
}
|
|
1304
|
+
async function checkCliUpdate() {
|
|
1305
|
+
try {
|
|
1306
|
+
const response = await ofetch3(
|
|
1307
|
+
`${NPM_REGISTRY}/${encodeURIComponent(PACKAGE_NAME)}`,
|
|
1308
|
+
{
|
|
1309
|
+
timeout: 5e3,
|
|
1310
|
+
headers: {
|
|
1311
|
+
Accept: "application/json"
|
|
1312
|
+
}
|
|
1313
|
+
}
|
|
1314
|
+
);
|
|
1315
|
+
return response["dist-tags"].latest;
|
|
1316
|
+
} catch {
|
|
1317
|
+
return null;
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
function isNewerVersion(latest, current) {
|
|
1321
|
+
const latestParts = latest.split(".").map(Number);
|
|
1322
|
+
const currentParts = current.split(".").map(Number);
|
|
1323
|
+
for (let i = 0; i < 3; i++) {
|
|
1324
|
+
const l = latestParts[i] || 0;
|
|
1325
|
+
const c = currentParts[i] || 0;
|
|
1326
|
+
if (l > c) return true;
|
|
1327
|
+
if (l < c) return false;
|
|
1328
|
+
}
|
|
1329
|
+
return false;
|
|
1330
|
+
}
|
|
1331
|
+
|
|
1332
|
+
// src/services/content-version.ts
|
|
1333
|
+
async function checkContentUpdates(currentVersions) {
|
|
1334
|
+
const contentService = new ContentService();
|
|
1335
|
+
const updates = {};
|
|
1336
|
+
try {
|
|
1337
|
+
const manifest = await contentService.getManifest("latest");
|
|
1338
|
+
for (const [agentId, agentManifest] of Object.entries(manifest.agents)) {
|
|
1339
|
+
const current = currentVersions[agentId];
|
|
1340
|
+
const latest = agentManifest.version;
|
|
1341
|
+
if (!current || isNewerVersion(latest, current)) {
|
|
1342
|
+
updates[agentId] = latest;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
} catch {
|
|
1346
|
+
}
|
|
1347
|
+
return updates;
|
|
1348
|
+
}
|
|
1349
|
+
|
|
1350
|
+
export {
|
|
1351
|
+
version,
|
|
1352
|
+
description,
|
|
1353
|
+
Logger,
|
|
1354
|
+
JawKitError,
|
|
1355
|
+
AuthError,
|
|
1356
|
+
ContentError,
|
|
1357
|
+
AgentError,
|
|
1358
|
+
ConfigError,
|
|
1359
|
+
handleError,
|
|
1360
|
+
ConfigService,
|
|
1361
|
+
getTierDisplayName,
|
|
1362
|
+
getAuthService,
|
|
1363
|
+
getContentService,
|
|
1364
|
+
getCliVersion,
|
|
1365
|
+
checkCliUpdate,
|
|
1366
|
+
isNewerVersion,
|
|
1367
|
+
checkContentUpdates
|
|
1368
|
+
};
|
|
1369
|
+
//# sourceMappingURL=chunk-V3HNAAHL.js.map
|