@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/dist/cli.js ADDED
@@ -0,0 +1,1180 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ AgentError,
4
+ ConfigService,
5
+ Logger,
6
+ checkCliUpdate,
7
+ checkContentUpdates,
8
+ description,
9
+ getAuthService,
10
+ getCliVersion,
11
+ getContentService,
12
+ getTierDisplayName,
13
+ handleError,
14
+ isNewerVersion,
15
+ version
16
+ } from "./chunk-V3HNAAHL.js";
17
+
18
+ // src/cli.ts
19
+ import { Command as Command5 } from "commander";
20
+
21
+ // src/commands/init.ts
22
+ import { Command } from "commander";
23
+ import { select, confirm } from "@inquirer/prompts";
24
+ import chalk2 from "chalk";
25
+ import fs4 from "fs/promises";
26
+ import path4 from "path";
27
+
28
+ // src/agents/base-agent.ts
29
+ import fs from "fs/promises";
30
+ import path from "path";
31
+ import crypto from "crypto";
32
+ var BaseAgent = class {
33
+ /**
34
+ * Default detection based on markers
35
+ */
36
+ async detect(projectPath) {
37
+ for (const marker of this.config.detection.markers) {
38
+ const markerPath = path.join(projectPath, marker);
39
+ try {
40
+ await fs.access(markerPath);
41
+ return true;
42
+ } catch {
43
+ }
44
+ }
45
+ return false;
46
+ }
47
+ /**
48
+ * Create target directory structure
49
+ */
50
+ async prepare(projectPath) {
51
+ const targetDir = path.join(projectPath, this.config.targetDirectory);
52
+ await fs.mkdir(targetDir, { recursive: true });
53
+ for (const contentType of this.config.supportedContentTypes) {
54
+ const mapping = this.config.fileMappings.find(
55
+ (m) => m.contentType === contentType
56
+ );
57
+ if (mapping && mapping.targetSubdir) {
58
+ const subdir = path.join(targetDir, mapping.targetSubdir);
59
+ await fs.mkdir(subdir, { recursive: true });
60
+ }
61
+ }
62
+ }
63
+ /**
64
+ * Install content files
65
+ */
66
+ async install(projectPath, files, options) {
67
+ const result = {
68
+ success: true,
69
+ installed: [],
70
+ skipped: [],
71
+ errors: []
72
+ };
73
+ for (const file of files) {
74
+ if (!options.contentTypes.includes(file.type)) {
75
+ result.skipped.push({
76
+ sourcePath: file.sourcePath,
77
+ targetPath: this.getTargetPath(projectPath, file),
78
+ reason: "filtered"
79
+ });
80
+ continue;
81
+ }
82
+ const targetPath = this.getTargetPath(projectPath, file);
83
+ try {
84
+ const exists = await this.fileExists(targetPath);
85
+ if (exists && !options.force) {
86
+ result.skipped.push({
87
+ sourcePath: file.sourcePath,
88
+ targetPath,
89
+ reason: "exists"
90
+ });
91
+ continue;
92
+ }
93
+ if (exists && options.backup) {
94
+ await this.backupFile(targetPath);
95
+ }
96
+ const content = this.transform(file.content, file);
97
+ if (!options.dryRun) {
98
+ await fs.mkdir(path.dirname(targetPath), { recursive: true });
99
+ await fs.writeFile(targetPath, content, "utf-8");
100
+ }
101
+ result.installed.push({
102
+ sourcePath: file.sourcePath,
103
+ targetPath,
104
+ contentType: file.type
105
+ });
106
+ } catch (error) {
107
+ result.success = false;
108
+ result.errors.push({
109
+ sourcePath: file.sourcePath,
110
+ targetPath,
111
+ error: error instanceof Error ? error.message : "Unknown error"
112
+ });
113
+ }
114
+ }
115
+ return result;
116
+ }
117
+ /**
118
+ * Verify installed files
119
+ */
120
+ async verify(projectPath) {
121
+ const targetDir = path.join(projectPath, this.config.targetDirectory);
122
+ try {
123
+ await fs.access(targetDir);
124
+ return {
125
+ valid: true,
126
+ verified: [targetDir],
127
+ failed: []
128
+ };
129
+ } catch {
130
+ return {
131
+ valid: false,
132
+ verified: [],
133
+ failed: [
134
+ {
135
+ path: targetDir,
136
+ expected: "exists",
137
+ actual: "missing",
138
+ reason: "missing"
139
+ }
140
+ ]
141
+ };
142
+ }
143
+ }
144
+ /**
145
+ * Clean up installed files
146
+ */
147
+ async clean(_projectPath) {
148
+ return {
149
+ success: true,
150
+ removed: [],
151
+ restored: []
152
+ };
153
+ }
154
+ /**
155
+ * Get target path for a content file
156
+ */
157
+ getTargetPath(projectPath, file) {
158
+ const mapping = this.config.fileMappings.find(
159
+ (m) => m.contentType === file.type
160
+ );
161
+ if (!mapping) {
162
+ throw new Error(`No mapping found for content type: ${file.type}`);
163
+ }
164
+ const filename = path.basename(file.sourcePath);
165
+ const targetFilename = mapping.nameTransform ? mapping.nameTransform(filename) : filename;
166
+ return path.join(
167
+ projectPath,
168
+ this.config.targetDirectory,
169
+ mapping.targetSubdir,
170
+ targetFilename
171
+ );
172
+ }
173
+ /**
174
+ * Transform content before writing (override in subclasses if needed)
175
+ */
176
+ transform(content, _file) {
177
+ return content;
178
+ }
179
+ /**
180
+ * Check if file exists
181
+ */
182
+ async fileExists(filePath) {
183
+ try {
184
+ await fs.access(filePath);
185
+ return true;
186
+ } catch {
187
+ return false;
188
+ }
189
+ }
190
+ /**
191
+ * Backup a file before overwriting
192
+ */
193
+ async backupFile(filePath) {
194
+ const backupPath = `${filePath}.jawkit-backup`;
195
+ await fs.copyFile(filePath, backupPath);
196
+ }
197
+ /**
198
+ * Calculate file checksum
199
+ */
200
+ calculateChecksum(content) {
201
+ return crypto.createHash("sha256").update(content).digest("hex");
202
+ }
203
+ };
204
+
205
+ // src/agents/claude-code/index.ts
206
+ import fs2 from "fs/promises";
207
+ import path2 from "path";
208
+ var ClaudeCodeAgent = class extends BaseAgent {
209
+ config = {
210
+ id: "claude-code",
211
+ name: "Claude Code",
212
+ description: "Anthropic Claude Code AI assistant",
213
+ targetDirectory: ".claude",
214
+ supportedContentTypes: ["commands", "skills", "agents"],
215
+ fileMappings: [
216
+ {
217
+ contentType: "commands",
218
+ sourcePattern: "commands/**/*.md",
219
+ targetSubdir: "commands"
220
+ },
221
+ {
222
+ contentType: "skills",
223
+ sourcePattern: "skills/**/*.md",
224
+ targetSubdir: "skills"
225
+ },
226
+ {
227
+ contentType: "agents",
228
+ sourcePattern: "agents/**/*.md",
229
+ targetSubdir: "agents"
230
+ }
231
+ ],
232
+ detection: {
233
+ markers: [
234
+ ".claude",
235
+ // Existing Claude directory
236
+ "CLAUDE.md",
237
+ // Claude project file
238
+ ".claude.json"
239
+ // Claude config
240
+ ],
241
+ priority: 100
242
+ // Highest priority - primary agent
243
+ },
244
+ docsUrl: "https://docs.anthropic.com/claude-code"
245
+ };
246
+ /**
247
+ * Enhanced detection: also detect any code project
248
+ * Claude Code is our primary agent, so we suggest it for all code projects
249
+ */
250
+ async detect(projectPath) {
251
+ const hasMarkers = await super.detect(projectPath);
252
+ if (hasMarkers) return true;
253
+ return this.isCodeProject(projectPath);
254
+ }
255
+ /**
256
+ * Check if the directory appears to be a code project
257
+ */
258
+ async isCodeProject(projectPath) {
259
+ const codeMarkers = [
260
+ // JavaScript/TypeScript
261
+ "package.json",
262
+ "tsconfig.json",
263
+ // Rust
264
+ "Cargo.toml",
265
+ // Python
266
+ "pyproject.toml",
267
+ "requirements.txt",
268
+ "setup.py",
269
+ // Go
270
+ "go.mod",
271
+ // Java/Kotlin
272
+ "pom.xml",
273
+ "build.gradle",
274
+ "build.gradle.kts",
275
+ // Ruby
276
+ "Gemfile",
277
+ // PHP
278
+ "composer.json",
279
+ // .NET
280
+ "*.csproj",
281
+ "*.fsproj",
282
+ // Git (any repo is likely code)
283
+ ".git"
284
+ ];
285
+ for (const marker of codeMarkers) {
286
+ if (marker.includes("*")) {
287
+ try {
288
+ const files = await fs2.readdir(projectPath);
289
+ const pattern = marker.replace("*", "");
290
+ if (files.some((f) => f.endsWith(pattern))) {
291
+ return true;
292
+ }
293
+ } catch {
294
+ }
295
+ } else {
296
+ try {
297
+ await fs2.access(path2.join(projectPath, marker));
298
+ return true;
299
+ } catch {
300
+ }
301
+ }
302
+ }
303
+ return false;
304
+ }
305
+ };
306
+ var claudeCodeAgent = new ClaudeCodeAgent();
307
+
308
+ // src/agents/github-copilot/index.ts
309
+ import fs3 from "fs/promises";
310
+ import path3 from "path";
311
+ var GitHubCopilotAgent = class extends BaseAgent {
312
+ config = {
313
+ id: "github-copilot",
314
+ name: "GitHub Copilot",
315
+ description: "GitHub Copilot AI assistant",
316
+ targetDirectory: ".github",
317
+ supportedContentTypes: ["commands", "skills"],
318
+ fileMappings: [
319
+ {
320
+ contentType: "commands",
321
+ sourcePattern: "commands/**/*.md",
322
+ targetSubdir: "copilot/commands"
323
+ },
324
+ {
325
+ contentType: "skills",
326
+ sourcePattern: "skills/**/*.md",
327
+ targetSubdir: "copilot/skills"
328
+ }
329
+ ],
330
+ detection: {
331
+ markers: [
332
+ ".github",
333
+ ".github/copilot-instructions.md"
334
+ ],
335
+ priority: 50
336
+ // Lower priority than Claude Code
337
+ },
338
+ docsUrl: "https://docs.github.com/copilot"
339
+ };
340
+ /**
341
+ * Transform content for GitHub Copilot format if needed
342
+ * Adds a title header if not present
343
+ */
344
+ transform(content, file) {
345
+ if (!content.startsWith("# ")) {
346
+ const filename = path3.basename(file.sourcePath, ".md");
347
+ const title = filename.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
348
+ return `# ${title}
349
+
350
+ ${content}`;
351
+ }
352
+ return content;
353
+ }
354
+ /**
355
+ * GitHub Copilot specific: also create main instructions file
356
+ */
357
+ async prepare(projectPath) {
358
+ await super.prepare(projectPath);
359
+ const instructionsPath = path3.join(
360
+ projectPath,
361
+ ".github",
362
+ "copilot-instructions.md"
363
+ );
364
+ try {
365
+ await fs3.access(instructionsPath);
366
+ } catch {
367
+ const defaultContent = `# GitHub Copilot Instructions
368
+
369
+ This file configures GitHub Copilot behavior for this project.
370
+
371
+ ## Custom Commands
372
+
373
+ See the \`copilot/commands/\` directory for available commands.
374
+
375
+ ## Skills
376
+
377
+ See the \`copilot/skills/\` directory for available skills.
378
+
379
+ ---
380
+
381
+ *Managed by JawKit CLI*
382
+ `;
383
+ await fs3.writeFile(instructionsPath, defaultContent, "utf-8");
384
+ }
385
+ }
386
+ };
387
+ var gitHubCopilotAgent = new GitHubCopilotAgent();
388
+
389
+ // src/agents/registry.ts
390
+ var AgentRegistry = class {
391
+ agents = /* @__PURE__ */ new Map();
392
+ constructor() {
393
+ this.register(claudeCodeAgent);
394
+ this.register(gitHubCopilotAgent);
395
+ }
396
+ /**
397
+ * Register an agent adapter
398
+ */
399
+ register(agent) {
400
+ if (this.agents.has(agent.config.id)) {
401
+ throw new Error(`Agent already registered: ${agent.config.id}`);
402
+ }
403
+ this.agents.set(agent.config.id, agent);
404
+ }
405
+ /**
406
+ * Get agent by ID
407
+ */
408
+ get(id) {
409
+ return this.agents.get(id);
410
+ }
411
+ /**
412
+ * Get all registered agents
413
+ */
414
+ getAll() {
415
+ return Array.from(this.agents.values());
416
+ }
417
+ /**
418
+ * Get all agent IDs
419
+ */
420
+ getIds() {
421
+ return Array.from(this.agents.keys());
422
+ }
423
+ /**
424
+ * Detect applicable agents for a project
425
+ * Returns agents sorted by priority (highest first)
426
+ */
427
+ async detectAgents(projectPath) {
428
+ const detected = [];
429
+ for (const agent of this.agents.values()) {
430
+ if (await agent.detect(projectPath)) {
431
+ detected.push(agent);
432
+ }
433
+ }
434
+ return detected.sort(
435
+ (a, b) => b.config.detection.priority - a.config.detection.priority
436
+ );
437
+ }
438
+ /**
439
+ * Check if an agent ID is valid
440
+ */
441
+ isValidAgent(id) {
442
+ return this.agents.has(id);
443
+ }
444
+ /**
445
+ * Get agent count
446
+ */
447
+ get size() {
448
+ return this.agents.size;
449
+ }
450
+ };
451
+ var agentRegistry = new AgentRegistry();
452
+
453
+ // src/lib/promo.ts
454
+ import chalk from "chalk";
455
+ var PROMO_URL = "https://jawkit.cc/pro";
456
+ function getPromoMessage() {
457
+ return [
458
+ "",
459
+ chalk.dim("\u2500".repeat(60)),
460
+ `${chalk.yellow("\u{1F48E}")} ${chalk.bold("Professional")}: Advanced skills + priority support`,
461
+ chalk.dim(` ${PROMO_URL}`),
462
+ chalk.dim("\u2500".repeat(60))
463
+ ];
464
+ }
465
+ function showPromoIfFree(tier, logger) {
466
+ if (tier === "free") {
467
+ for (const line of getPromoMessage()) {
468
+ logger.info(line);
469
+ }
470
+ }
471
+ }
472
+
473
+ // src/commands/init.ts
474
+ function initCommand() {
475
+ return new Command("init").description("Initialize JawKit content for a coding agent").option("-a, --agent <agent>", "Agent to install (claude-code, github-copilot)").option("-y, --yes", "Skip confirmation prompts").option("--dry-run", "Show what would be installed without making changes").action(async (options) => {
476
+ const logger = new Logger({});
477
+ const authService = getAuthService();
478
+ const contentService = getContentService();
479
+ const projectPath = process.cwd();
480
+ try {
481
+ logger.info("JawKit Init");
482
+ logger.divider();
483
+ const isAuthenticated = await authService.isAuthenticated();
484
+ const user = isAuthenticated ? await authService.getCurrentUser() : null;
485
+ const userTier = user?.tier ?? "free";
486
+ if (isAuthenticated && user) {
487
+ logger.info(`Licensed: ${chalk2.cyan(user.email)} (${getTierDisplayName(userTier)})`);
488
+ } else {
489
+ logger.info(chalk2.yellow("No license - using free tier content"));
490
+ logger.info(chalk2.dim("Run 'jawkit auth activate <key>' or 'jawkit auth redeem' for Professional"));
491
+ logger.info(chalk2.dim(`Purchase at ${chalk2.cyan("https://jawkit.cc/pro")}`));
492
+ }
493
+ logger.divider();
494
+ let agent;
495
+ if (options.agent) {
496
+ const specifiedAgent = agentRegistry.get(options.agent);
497
+ if (!specifiedAgent) {
498
+ const availableIds = agentRegistry.getIds().join(", ");
499
+ throw AgentError.notFound(`${options.agent}. Available: ${availableIds}`);
500
+ }
501
+ agent = specifiedAgent;
502
+ } else {
503
+ const detectedAgents = await agentRegistry.detectAgents(projectPath);
504
+ if (detectedAgents.length === 0) {
505
+ const defaultAgent = agentRegistry.get("claude-code");
506
+ if (!defaultAgent) {
507
+ throw new Error("Claude Code agent not found");
508
+ }
509
+ agent = defaultAgent;
510
+ logger.info(`No specific agent detected, using ${chalk2.cyan(agent.config.name)}`);
511
+ } else if (detectedAgents.length === 1) {
512
+ const singleAgent = detectedAgents[0];
513
+ if (!singleAgent) {
514
+ throw new Error("No agent found");
515
+ }
516
+ agent = singleAgent;
517
+ logger.info(`Detected ${chalk2.cyan(agent.config.name)}`);
518
+ } else if (options.yes) {
519
+ const firstAgent = detectedAgents[0];
520
+ if (!firstAgent) {
521
+ throw new Error("No agent found");
522
+ }
523
+ agent = firstAgent;
524
+ logger.info(`Using ${chalk2.cyan(agent.config.name)} (highest priority)`);
525
+ } else {
526
+ const selectedId = await select({
527
+ message: "Select agent to initialize:",
528
+ choices: detectedAgents.map((a) => ({
529
+ name: a.config.name,
530
+ value: a.config.id,
531
+ description: a.config.description
532
+ }))
533
+ });
534
+ agent = agentRegistry.get(selectedId);
535
+ }
536
+ }
537
+ logger.info(`Agent: ${chalk2.cyan(agent.config.name)}`);
538
+ logger.info(`Target: ${chalk2.dim(agent.config.targetDirectory + "/")}`);
539
+ logger.divider();
540
+ const bundleInfo = await contentService.getBundleInfo(agent.config.id, userTier);
541
+ logger.info(`Bundle size: ${chalk2.dim(formatBytes(bundleInfo.size))}`);
542
+ logger.info(`Files: ${chalk2.dim(bundleInfo.fileCount.toString())}`);
543
+ if (!options.yes && !options.dryRun) {
544
+ const shouldContinue = await confirm({
545
+ message: "Proceed with installation?",
546
+ default: true
547
+ });
548
+ if (!shouldContinue) {
549
+ logger.info("Installation cancelled");
550
+ return;
551
+ }
552
+ }
553
+ const targetDir = path4.join(projectPath, agent.config.targetDirectory);
554
+ let backupPath = null;
555
+ try {
556
+ const stats = await fs4.stat(targetDir);
557
+ if (stats.isDirectory()) {
558
+ backupPath = `${targetDir}.backup`;
559
+ logger.info(chalk2.yellow(`Existing ${agent.config.targetDirectory}/ found - will backup to ${agent.config.targetDirectory}.backup/`));
560
+ }
561
+ } catch {
562
+ }
563
+ if (options.dryRun) {
564
+ logger.info(chalk2.yellow("\n[DRY RUN] Would perform the following:"));
565
+ if (backupPath) {
566
+ logger.info(` - Backup existing: ${agent.config.targetDirectory}/ \u2192 ${agent.config.targetDirectory}.backup/`);
567
+ }
568
+ logger.info(` - Prepare directory: ${agent.config.targetDirectory}/`);
569
+ logger.info(` - Download bundle: ${bundleInfo.filename}`);
570
+ logger.info(` - Extract ${bundleInfo.fileCount} files`);
571
+ return;
572
+ }
573
+ logger.divider();
574
+ if (backupPath) {
575
+ const backupSpinner = logger.spinner("Backing up existing content...");
576
+ try {
577
+ await fs4.rm(backupPath, { recursive: true, force: true });
578
+ } catch {
579
+ }
580
+ await fs4.rename(targetDir, backupPath);
581
+ backupSpinner.succeed(`Backed up to ${agent.config.targetDirectory}.backup/`);
582
+ }
583
+ const prepareSpinner = logger.spinner("Preparing target directory...");
584
+ await agent.prepare(projectPath);
585
+ prepareSpinner.succeed("Target directory ready");
586
+ const downloadSpinner = logger.spinner("Downloading content...");
587
+ const result = await contentService.downloadAndExtractBundle(
588
+ agent.config.id,
589
+ userTier,
590
+ targetDir,
591
+ (progress) => {
592
+ if (progress.phase === "downloading") {
593
+ downloadSpinner.text = `Downloading... ${progress.percentage}%`;
594
+ } else if (progress.phase === "extracting") {
595
+ downloadSpinner.text = `Extracting ${progress.currentFile || ""}`;
596
+ }
597
+ }
598
+ );
599
+ if (result.success) {
600
+ downloadSpinner.succeed(`Installed ${result.filesExtracted.length} files (v${result.version})`);
601
+ const config = new ConfigService();
602
+ await config.addInstallation({
603
+ path: projectPath,
604
+ agent: agent.config.id,
605
+ contentVersion: result.version,
606
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
607
+ files: result.filesExtracted,
608
+ tier: userTier,
609
+ checksum: bundleInfo.checksum
610
+ });
611
+ const currentVersions = await config.get("contentVersions") ?? {};
612
+ await config.set("contentVersions", {
613
+ ...currentVersions,
614
+ [agent.config.id]: result.version
615
+ });
616
+ } else {
617
+ downloadSpinner.fail("Installation failed");
618
+ for (const error of result.errors) {
619
+ logger.error(` ${error}`);
620
+ }
621
+ process.exit(1);
622
+ }
623
+ logger.divider();
624
+ logger.success("JawKit content installed successfully!");
625
+ logger.info(`
626
+ Installed to: ${chalk2.cyan(targetDir)}`);
627
+ if (result.filesExtracted.length > 0) {
628
+ logger.info("\nFiles installed:");
629
+ for (const file of result.filesExtracted.slice(0, 5)) {
630
+ logger.info(chalk2.dim(` ${file}`));
631
+ }
632
+ if (result.filesExtracted.length > 5) {
633
+ logger.info(chalk2.dim(` ... and ${result.filesExtracted.length - 5} more`));
634
+ }
635
+ }
636
+ showPromoIfFree(userTier, logger);
637
+ } catch (error) {
638
+ handleError(error, logger);
639
+ process.exit(1);
640
+ }
641
+ });
642
+ }
643
+ function formatBytes(bytes) {
644
+ if (bytes === 0) return "0 B";
645
+ const k = 1024;
646
+ const sizes = ["B", "KB", "MB", "GB"];
647
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
648
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(1))} ${sizes[i]}`;
649
+ }
650
+
651
+ // src/commands/auth.ts
652
+ import { Command as Command2 } from "commander";
653
+ import { confirm as confirm2, input } from "@inquirer/prompts";
654
+ import chalk3 from "chalk";
655
+ var EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
656
+ function isValidEmail(email) {
657
+ return EMAIL_REGEX.test(email);
658
+ }
659
+ function authCommand() {
660
+ const auth = new Command2("auth").description("Manage license key authentication");
661
+ auth.command("activate <license-key>").description("Activate a license key").action(async (licenseKey, options) => {
662
+ const logger = new Logger(options);
663
+ const authService = getAuthService();
664
+ try {
665
+ const isAuthenticated = await authService.isAuthenticated();
666
+ if (isAuthenticated) {
667
+ const user = await authService.getCurrentUser();
668
+ if (user) {
669
+ logger.info(`You already have an active license for ${chalk3.cyan(user.email)} (${getTierDisplayName(user.tier)})`);
670
+ logger.divider();
671
+ const continueActivation = await confirm2({
672
+ message: "Replace with new license key?",
673
+ default: false
674
+ });
675
+ if (!continueActivation) {
676
+ return;
677
+ }
678
+ }
679
+ }
680
+ const spinner = logger.spinner("Validating license key...");
681
+ const result = await authService.activate(licenseKey);
682
+ if (result.success && result.user) {
683
+ spinner.succeed("License activated!");
684
+ logger.divider();
685
+ logger.info(`${chalk3.green("Email:")} ${result.user.email}`);
686
+ logger.info(`${chalk3.green("Tier:")} ${getTierDisplayName(result.user.tier)}`);
687
+ if (result.user.tierExpiresAt) {
688
+ const expiresDate = new Date(result.user.tierExpiresAt);
689
+ logger.info(`${chalk3.green("Expires:")} ${expiresDate.toLocaleDateString()}`);
690
+ } else {
691
+ logger.info(`${chalk3.green("Expires:")} Never (Lifetime)`);
692
+ }
693
+ logger.divider();
694
+ logger.info("You now have access to Professional content!");
695
+ } else {
696
+ spinner.fail("License activation failed");
697
+ if (result.error) {
698
+ logger.error(result.error);
699
+ }
700
+ process.exit(1);
701
+ }
702
+ } catch (error) {
703
+ handleError(error, logger);
704
+ process.exit(1);
705
+ }
706
+ });
707
+ auth.command("redeem [invite-code]").description("Redeem an invitation code to get a license").option("-e, --email <email>", "Email address for the license").action(async (inviteCode, options) => {
708
+ const logger = new Logger(options);
709
+ const authService = getAuthService();
710
+ try {
711
+ const isAuthenticated = await authService.isAuthenticated();
712
+ if (isAuthenticated) {
713
+ const user = await authService.getCurrentUser();
714
+ if (user) {
715
+ logger.info(`You already have an active license for ${chalk3.cyan(user.email)} (${getTierDisplayName(user.tier)})`);
716
+ logger.divider();
717
+ const continueRedeem = await confirm2({
718
+ message: "Replace with new license from invitation?",
719
+ default: false
720
+ });
721
+ if (!continueRedeem) {
722
+ return;
723
+ }
724
+ }
725
+ }
726
+ let code = inviteCode;
727
+ if (!code) {
728
+ code = await input({
729
+ message: "Enter invitation code:",
730
+ validate: (value) => {
731
+ if (!value.startsWith("JAWKIT_INV_")) {
732
+ return "Invitation codes start with JAWKIT_INV_";
733
+ }
734
+ return true;
735
+ }
736
+ });
737
+ }
738
+ let email = options.email;
739
+ if (!email) {
740
+ email = await input({
741
+ message: "Enter your email address:",
742
+ validate: (value) => {
743
+ if (!isValidEmail(value)) {
744
+ return "Please enter a valid email address (e.g., user@example.com)";
745
+ }
746
+ return true;
747
+ }
748
+ });
749
+ } else {
750
+ if (!isValidEmail(email)) {
751
+ logger.error("Invalid email format. Please provide a valid email address (e.g., user@example.com)");
752
+ process.exit(1);
753
+ }
754
+ }
755
+ const spinner = logger.spinner("Redeeming invitation...");
756
+ const result = await authService.redeem(code, email);
757
+ if (result.success && result.user) {
758
+ spinner.succeed("Invitation redeemed!");
759
+ logger.divider();
760
+ logger.info(`${chalk3.green("Email:")} ${result.user.email}`);
761
+ logger.info(`${chalk3.green("Tier:")} ${getTierDisplayName(result.user.tier)}`);
762
+ if (result.user.tierExpiresAt) {
763
+ const expiresDate = new Date(result.user.tierExpiresAt);
764
+ logger.info(`${chalk3.green("Expires:")} ${expiresDate.toLocaleDateString()}`);
765
+ } else {
766
+ logger.info(`${chalk3.green("Expires:")} Never (Lifetime)`);
767
+ }
768
+ logger.divider();
769
+ logger.info("Your license has been activated!");
770
+ } else {
771
+ spinner.fail("Redemption failed");
772
+ if (result.error) {
773
+ logger.error(result.error);
774
+ }
775
+ process.exit(1);
776
+ }
777
+ } catch (error) {
778
+ handleError(error, logger);
779
+ process.exit(1);
780
+ }
781
+ });
782
+ auth.command("deactivate").description("Remove stored license key").action(async (options) => {
783
+ const logger = new Logger(options);
784
+ const authService = getAuthService();
785
+ try {
786
+ const isAuthenticated = await authService.isAuthenticated();
787
+ if (!isAuthenticated) {
788
+ logger.info("No license key is currently stored");
789
+ return;
790
+ }
791
+ const user = await authService.getCurrentUser();
792
+ const proceed = await confirm2({
793
+ message: `Remove license for ${user?.email}?`,
794
+ default: false
795
+ });
796
+ if (!proceed) {
797
+ return;
798
+ }
799
+ await authService.deactivate();
800
+ logger.success("License key removed");
801
+ logger.info(chalk3.dim("Free tier content is still accessible."));
802
+ } catch (error) {
803
+ handleError(error, logger);
804
+ process.exit(1);
805
+ }
806
+ });
807
+ auth.command("status").description("Show current license status").option("--json", "Output as JSON").option("--revalidate", "Revalidate license with server").action(async (options) => {
808
+ const logger = new Logger(options);
809
+ const authService = getAuthService();
810
+ try {
811
+ if (options.revalidate) {
812
+ const spinner = logger.spinner("Revalidating license...");
813
+ const result = await authService.revalidate();
814
+ if (result.success) {
815
+ spinner.succeed("License revalidated");
816
+ } else {
817
+ spinner.fail("Revalidation failed");
818
+ if (result.error) {
819
+ logger.error(result.error);
820
+ }
821
+ }
822
+ logger.divider();
823
+ }
824
+ const isAuthenticated = await authService.isAuthenticated();
825
+ const user = isAuthenticated ? await authService.getCurrentUser() : null;
826
+ if (options.json) {
827
+ logger.json({
828
+ authenticated: isAuthenticated,
829
+ user: user ? {
830
+ email: user.email,
831
+ tier: user.tier,
832
+ tierDisplayName: getTierDisplayName(user.tier),
833
+ tierExpiresAt: user.tierExpiresAt
834
+ } : null
835
+ });
836
+ return;
837
+ }
838
+ logger.info("License Status");
839
+ logger.divider();
840
+ if (!isAuthenticated || !user) {
841
+ logger.info(`${chalk3.yellow("Status:")} No license activated`);
842
+ logger.divider();
843
+ logger.info("Run 'jawkit auth activate <key>' or 'jawkit auth redeem' for Professional.");
844
+ logger.info("Free tier content is available without a license.");
845
+ logger.info(`Purchase at ${chalk3.cyan("https://jawkit.cc/pro")}`);
846
+ return;
847
+ }
848
+ logger.info(`${chalk3.green("Status:")} Licensed ${chalk3.green("\u2713")}`);
849
+ logger.info(`${chalk3.green("Email:")} ${user.email}`);
850
+ logger.info(`${chalk3.green("Tier:")} ${getTierDisplayName(user.tier)}`);
851
+ if (user.tierExpiresAt) {
852
+ const expiresDate = new Date(user.tierExpiresAt);
853
+ const now = /* @__PURE__ */ new Date();
854
+ const daysUntilExpiry = Math.ceil((expiresDate.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24));
855
+ if (daysUntilExpiry < 0) {
856
+ logger.info(`${chalk3.red("Expired:")} ${expiresDate.toLocaleDateString()} (fallen back to Free tier)`);
857
+ } else if (daysUntilExpiry <= 7) {
858
+ logger.info(`${chalk3.yellow("Expires:")} ${expiresDate.toLocaleDateString()} (${daysUntilExpiry} days)`);
859
+ } else {
860
+ logger.info(`${chalk3.green("Expires:")} ${expiresDate.toLocaleDateString()}`);
861
+ }
862
+ } else {
863
+ logger.info(`${chalk3.green("Expires:")} Never (Lifetime)`);
864
+ }
865
+ } catch (error) {
866
+ handleError(error, logger);
867
+ process.exit(1);
868
+ }
869
+ });
870
+ return auth;
871
+ }
872
+
873
+ // src/commands/version.ts
874
+ import { Command as Command3 } from "commander";
875
+ function versionCommand() {
876
+ return new Command3("version").description("Show CLI and content version information").option("--check", "Check for available updates").option("--json", "Output as JSON format").action(async (options) => {
877
+ const logger = new Logger(options);
878
+ const config = new ConfigService();
879
+ try {
880
+ const cliVersion = getCliVersion();
881
+ const contentVersions = await config.get("contentVersions") ?? {};
882
+ let cliLatest = null;
883
+ let contentUpdates = {};
884
+ if (options.check) {
885
+ const spinner = logger.spinner("Checking for updates...");
886
+ spinner.start();
887
+ cliLatest = await checkCliUpdate();
888
+ contentUpdates = await checkContentUpdates(contentVersions);
889
+ spinner.stop();
890
+ }
891
+ if (options.json) {
892
+ const output = {
893
+ cli: {
894
+ current: cliVersion,
895
+ latest: cliLatest ?? cliVersion,
896
+ updateAvailable: cliLatest ? isNewerVersion(cliLatest, cliVersion) : false
897
+ },
898
+ content: Object.fromEntries(
899
+ Object.entries(contentVersions).map(([agent, version2]) => [
900
+ agent,
901
+ {
902
+ current: version2,
903
+ latest: contentUpdates[agent] ?? version2,
904
+ updateAvailable: Boolean(contentUpdates[agent])
905
+ }
906
+ ])
907
+ )
908
+ };
909
+ logger.json(output);
910
+ } else {
911
+ let cliLine = `@jawkit/cli v${cliVersion}`;
912
+ if (cliLatest && isNewerVersion(cliLatest, cliVersion)) {
913
+ cliLine += ` (latest: v${cliLatest} available)`;
914
+ logger.info(cliLine);
915
+ logger.info(" Run: npm update -g @jawkit/cli");
916
+ } else if (options.check) {
917
+ cliLine += " (up to date)";
918
+ logger.info(cliLine);
919
+ } else {
920
+ logger.info(cliLine);
921
+ }
922
+ logger.newline();
923
+ if (Object.keys(contentVersions).length > 0) {
924
+ logger.info("Content Versions:");
925
+ for (const [agent, version2] of Object.entries(contentVersions)) {
926
+ const latestVersion = contentUpdates[agent];
927
+ let line = ` ${agent.padEnd(18)} v${version2}`;
928
+ if (latestVersion) {
929
+ line += ` (latest: v${latestVersion} available)`;
930
+ } else if (options.check) {
931
+ line += " (up to date)";
932
+ }
933
+ logger.info(line);
934
+ }
935
+ if (Object.keys(contentUpdates).length > 0) {
936
+ logger.newline();
937
+ logger.info("Run 'jawkit upgrade' to update content.");
938
+ }
939
+ } else {
940
+ logger.info("No content installed yet.");
941
+ logger.info("Run 'jawkit init' to install content.");
942
+ }
943
+ }
944
+ } catch (error) {
945
+ handleError(error, logger);
946
+ process.exit(1);
947
+ }
948
+ });
949
+ }
950
+
951
+ // src/commands/upgrade.ts
952
+ import { Command as Command4 } from "commander";
953
+ import { confirm as confirm3 } from "@inquirer/prompts";
954
+ import fs5 from "fs/promises";
955
+ import path5 from "path";
956
+ function upgradeCommand() {
957
+ return new Command4("upgrade").description("Upgrade content in current folder (use --all for all projects)").option("-a, --agent <agent>", "Upgrade specific agent only").option("--all", "Upgrade all registered projects").option("-y, --yes", "Skip confirmation prompts").option("--check-only", "Only check for updates, do not upgrade").option("--force", "Force re-download even if up to date").action(async (options) => {
958
+ const logger = new Logger(options);
959
+ const config = new ConfigService();
960
+ const contentService = getContentService();
961
+ const authService = getAuthService();
962
+ const projectPath = process.cwd();
963
+ try {
964
+ const allInstallations = await config.getInstallations();
965
+ let targetInstalls;
966
+ if (options.all) {
967
+ targetInstalls = allInstallations;
968
+ } else {
969
+ const currentInstall = allInstallations.find(
970
+ (i) => i.path === projectPath
971
+ );
972
+ if (!currentInstall) {
973
+ logger.warn("No JawKit installation found in current folder.");
974
+ logger.info(`Current folder: ${projectPath}`);
975
+ logger.newline();
976
+ logger.info("Run 'jawkit init' to install content first.");
977
+ logger.info("Or use 'jawkit upgrade --all' to upgrade all registered projects.");
978
+ return;
979
+ }
980
+ const agent = agentRegistry.get(currentInstall.agent);
981
+ if (agent) {
982
+ const targetDir = path5.join(projectPath, agent.config.targetDirectory);
983
+ try {
984
+ await fs5.access(targetDir);
985
+ } catch {
986
+ logger.warn("Content directory not found (may have been deleted).");
987
+ logger.info(`Expected: ${targetDir}`);
988
+ logger.newline();
989
+ logger.info("Run 'jawkit init' to reinstall content.");
990
+ return;
991
+ }
992
+ }
993
+ targetInstalls = [currentInstall];
994
+ }
995
+ if (targetInstalls.length === 0) {
996
+ logger.warn("No registered installations found.");
997
+ logger.info("Run 'jawkit init' in a project to install content first.");
998
+ return;
999
+ }
1000
+ const cliVersion = getCliVersion();
1001
+ logger.info(`CLI: @jawkit/cli v${cliVersion}`);
1002
+ for (const install of targetInstalls) {
1003
+ const displayPath = options.all ? ` (${install.path})` : "";
1004
+ logger.info(`Content: ${install.agent} v${install.contentVersion}${displayPath}`);
1005
+ }
1006
+ logger.newline();
1007
+ const spinner = logger.spinner("Checking for updates...");
1008
+ spinner.start();
1009
+ const manifest = await contentService.getManifest("latest");
1010
+ const currentTier = await authService.getUserTier();
1011
+ const installUpdates = [];
1012
+ for (const install of targetInstalls) {
1013
+ const agentId = install.agent;
1014
+ if (options.agent && agentId !== options.agent) continue;
1015
+ const agentManifest = manifest.agents[agentId];
1016
+ const latest = agentManifest?.version;
1017
+ const current = install.contentVersion;
1018
+ const installedTier = install.tier || "free";
1019
+ if (!latest) continue;
1020
+ const bundleInfo = agentManifest.bundles[currentTier] || agentManifest.bundles["free"];
1021
+ const latestChecksum = bundleInfo?.checksum || "";
1022
+ const installedChecksum = install.checksum;
1023
+ const hasVersionUpdate = !current || isNewerVersion(latest, current);
1024
+ const hasTierChange = installedTier !== currentTier;
1025
+ const hasChecksumChange = latestChecksum && installedChecksum && latestChecksum !== installedChecksum;
1026
+ const needsChecksumUpdate = latestChecksum && !installedChecksum;
1027
+ let reason = null;
1028
+ if (options.force) reason = "force";
1029
+ else if (hasVersionUpdate) reason = "version";
1030
+ else if (hasTierChange) reason = "tier";
1031
+ else if (hasChecksumChange || needsChecksumUpdate) reason = "checksum";
1032
+ if (reason) {
1033
+ installUpdates.push({
1034
+ install,
1035
+ agentId,
1036
+ currentVersion: current || "not installed",
1037
+ latestVersion: latest,
1038
+ latestChecksum,
1039
+ changelog: manifest.changelog,
1040
+ reason,
1041
+ tierChange: hasTierChange ? { from: installedTier, to: currentTier } : void 0
1042
+ });
1043
+ }
1044
+ }
1045
+ spinner.stop();
1046
+ if (installUpdates.length === 0) {
1047
+ logger.success("All content is up to date!");
1048
+ return;
1049
+ }
1050
+ const agentGroups = /* @__PURE__ */ new Map();
1051
+ for (const update of installUpdates) {
1052
+ const existing = agentGroups.get(update.agentId) || [];
1053
+ existing.push(update);
1054
+ agentGroups.set(update.agentId, existing);
1055
+ }
1056
+ logger.info("Updates Available:");
1057
+ logger.divider();
1058
+ for (const [agentId, updates] of agentGroups) {
1059
+ const first = updates[0];
1060
+ if (first.tierChange) {
1061
+ logger.info(
1062
+ `${agentId}: ${first.tierChange.from} \u2192 ${first.tierChange.to} tier`
1063
+ );
1064
+ } else if (first.reason === "checksum") {
1065
+ logger.info(
1066
+ `${agentId}: v${first.currentVersion} (content updated)`
1067
+ );
1068
+ } else if (first.reason === "force") {
1069
+ logger.info(
1070
+ `${agentId}: v${first.currentVersion} (forced)`
1071
+ );
1072
+ } else {
1073
+ logger.info(
1074
+ `${agentId}: v${first.currentVersion} \u2192 v${first.latestVersion}`
1075
+ );
1076
+ }
1077
+ if (first.changelog && first.reason === "version") {
1078
+ logger.info(" Changelog:");
1079
+ const lines = first.changelog.split("\n").slice(0, 5);
1080
+ for (const line of lines) {
1081
+ if (line.trim()) {
1082
+ logger.info(` ${line}`);
1083
+ }
1084
+ }
1085
+ }
1086
+ }
1087
+ logger.newline();
1088
+ logger.info(`${installUpdates.length} project(s) to update:`);
1089
+ for (const update of installUpdates) {
1090
+ logger.info(` - ${update.install.path}`);
1091
+ }
1092
+ if (options.checkOnly) {
1093
+ logger.newline();
1094
+ logger.info("Run 'jawkit upgrade' to apply updates.");
1095
+ return;
1096
+ }
1097
+ if (!options.yes) {
1098
+ const proceed = await confirm3({
1099
+ message: "Apply updates?",
1100
+ default: true
1101
+ });
1102
+ if (!proceed) {
1103
+ logger.info("Upgrade cancelled.");
1104
+ return;
1105
+ }
1106
+ }
1107
+ const tier = await authService.getUserTier();
1108
+ const currentVersions = await config.get("contentVersions") ?? {};
1109
+ for (const [agentId, updates] of agentGroups) {
1110
+ logger.newline();
1111
+ const agentSpinner = logger.spinner(
1112
+ `Updating ${agentId} content...`
1113
+ );
1114
+ agentSpinner.start();
1115
+ const agent = agentRegistry.get(agentId);
1116
+ if (!agent) {
1117
+ agentSpinner.fail(`Agent not found: ${agentId}`);
1118
+ continue;
1119
+ }
1120
+ let totalUpdated = 0;
1121
+ for (const update of updates) {
1122
+ try {
1123
+ const targetDir = `${update.install.path}/${agent.config.targetDirectory}`;
1124
+ const result = await contentService.downloadAndExtractBundle(
1125
+ agentId,
1126
+ tier,
1127
+ targetDir,
1128
+ (progress) => {
1129
+ if (progress.phase === "downloading") {
1130
+ agentSpinner.text = `Downloading ${agentId}: ${progress.percentage}%`;
1131
+ } else {
1132
+ agentSpinner.text = `Extracting ${agentId}: ${progress.currentFile || ""}`;
1133
+ }
1134
+ }
1135
+ );
1136
+ await config.updateInstallation(update.install.path, {
1137
+ contentVersion: update.latestVersion,
1138
+ installedAt: (/* @__PURE__ */ new Date()).toISOString(),
1139
+ files: result.filesExtracted,
1140
+ tier,
1141
+ checksum: update.latestChecksum
1142
+ });
1143
+ totalUpdated++;
1144
+ } catch (err) {
1145
+ logger.error(
1146
+ `Failed to update ${update.install.path}: ${err instanceof Error ? err.message : "Unknown error"}`
1147
+ );
1148
+ }
1149
+ }
1150
+ const latestVersion = updates[0].latestVersion;
1151
+ await config.set("contentVersions", {
1152
+ ...currentVersions,
1153
+ [agentId]: latestVersion
1154
+ });
1155
+ agentSpinner.succeed(
1156
+ `Updated ${totalUpdated} project(s) to ${agentId} v${latestVersion}`
1157
+ );
1158
+ }
1159
+ logger.newline();
1160
+ logger.success("Upgrade complete!");
1161
+ showPromoIfFree(tier, logger);
1162
+ } catch (error) {
1163
+ handleError(error, logger);
1164
+ process.exit(1);
1165
+ }
1166
+ });
1167
+ }
1168
+
1169
+ // src/cli.ts
1170
+ var program = new Command5();
1171
+ program.name("jawkit").description(description).version(version, "-v, --version", "Show CLI version");
1172
+ program.addCommand(initCommand());
1173
+ program.addCommand(authCommand());
1174
+ program.addCommand(versionCommand());
1175
+ program.addCommand(upgradeCommand());
1176
+ program.action(() => {
1177
+ program.help();
1178
+ });
1179
+ program.parse();
1180
+ //# sourceMappingURL=cli.js.map