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