@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.
@@ -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