prjct-cli 0.61.0 → 0.63.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/CHANGELOG.md +72 -0
- package/core/__tests__/services/dependency-validator.test.ts +175 -0
- package/core/constants/index.ts +54 -0
- package/core/infrastructure/setup.ts +40 -3
- package/core/services/dependency-validator.ts +318 -0
- package/core/services/git-analyzer.ts +38 -12
- package/core/services/skill-installer.ts +21 -3
- package/core/sync/sync-client.ts +32 -2
- package/dist/bin/prjct.mjs +322 -19
- package/dist/core/infrastructure/setup.js +314 -8
- package/package.json +1 -1
|
@@ -60,7 +60,7 @@ __export(ai_provider_exports, {
|
|
|
60
60
|
});
|
|
61
61
|
function whichCommand(command) {
|
|
62
62
|
try {
|
|
63
|
-
const result = (0,
|
|
63
|
+
const result = (0, import_node_child_process2.execSync)(`which ${command}`, { stdio: "pipe", encoding: "utf-8" });
|
|
64
64
|
return result.trim();
|
|
65
65
|
} catch {
|
|
66
66
|
return null;
|
|
@@ -68,7 +68,7 @@ function whichCommand(command) {
|
|
|
68
68
|
}
|
|
69
69
|
function getCliVersion(command) {
|
|
70
70
|
try {
|
|
71
|
-
const result = (0,
|
|
71
|
+
const result = (0, import_node_child_process2.execSync)(`${command} --version`, { stdio: "pipe", encoding: "utf-8" });
|
|
72
72
|
const match = result.match(/\d+\.\d+\.\d+/);
|
|
73
73
|
return match ? match[0] : result.trim();
|
|
74
74
|
} catch {
|
|
@@ -233,11 +233,11 @@ function selectProvider() {
|
|
|
233
233
|
detection
|
|
234
234
|
};
|
|
235
235
|
}
|
|
236
|
-
var
|
|
236
|
+
var import_node_child_process2, import_node_fs2, import_node_os, import_node_path2, ClaudeProvider, GeminiProvider, AntigravityProvider, CursorProvider, WindsurfProvider, Providers, ai_provider_default;
|
|
237
237
|
var init_ai_provider = __esm({
|
|
238
238
|
"core/infrastructure/ai-provider.ts"() {
|
|
239
239
|
"use strict";
|
|
240
|
-
|
|
240
|
+
import_node_child_process2 = require("node:child_process");
|
|
241
241
|
import_node_fs2 = __toESM(require("node:fs"));
|
|
242
242
|
import_node_os = __toESM(require("node:os"));
|
|
243
243
|
import_node_path2 = __toESM(require("node:path"));
|
|
@@ -404,11 +404,287 @@ __export(setup_exports, {
|
|
|
404
404
|
run: () => run
|
|
405
405
|
});
|
|
406
406
|
module.exports = __toCommonJS(setup_exports);
|
|
407
|
-
var
|
|
407
|
+
var import_node_child_process3 = require("node:child_process");
|
|
408
408
|
var import_node_fs3 = __toESM(require("node:fs"));
|
|
409
409
|
var import_node_os4 = __toESM(require("node:os"));
|
|
410
410
|
var import_node_path5 = __toESM(require("node:path"));
|
|
411
411
|
|
|
412
|
+
// core/constants/index.ts
|
|
413
|
+
var TIMEOUTS = {
|
|
414
|
+
/** Tool availability checks (git --version, npm --version) */
|
|
415
|
+
TOOL_CHECK: 5e3,
|
|
416
|
+
/** Standard git operations (status, add, commit) */
|
|
417
|
+
GIT_OPERATION: 1e4,
|
|
418
|
+
/** Git clone with --depth 1 */
|
|
419
|
+
GIT_CLONE: 6e4,
|
|
420
|
+
/** HTTP fetch/API requests */
|
|
421
|
+
API_REQUEST: 3e4,
|
|
422
|
+
/** npm install -g (CLI installation) - 2 minutes */
|
|
423
|
+
NPM_INSTALL: 12e4,
|
|
424
|
+
/** User-defined workflow hooks */
|
|
425
|
+
WORKFLOW_HOOK: 6e4
|
|
426
|
+
};
|
|
427
|
+
function getTimeout(key) {
|
|
428
|
+
const envVar = `PRJCT_TIMEOUT_${key}`;
|
|
429
|
+
const envValue = process.env[envVar];
|
|
430
|
+
if (envValue) {
|
|
431
|
+
const parsed = Number.parseInt(envValue, 10);
|
|
432
|
+
if (!Number.isNaN(parsed) && parsed > 0) {
|
|
433
|
+
return parsed;
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
return TIMEOUTS[key];
|
|
437
|
+
}
|
|
438
|
+
__name(getTimeout, "getTimeout");
|
|
439
|
+
|
|
440
|
+
// core/services/dependency-validator.ts
|
|
441
|
+
var import_node_child_process = require("node:child_process");
|
|
442
|
+
|
|
443
|
+
// core/utils/error-messages.ts
|
|
444
|
+
function createError(message, hint, options) {
|
|
445
|
+
return {
|
|
446
|
+
message,
|
|
447
|
+
hint,
|
|
448
|
+
...options
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
__name(createError, "createError");
|
|
452
|
+
|
|
453
|
+
// core/services/dependency-validator.ts
|
|
454
|
+
var TOOLS = {
|
|
455
|
+
git: {
|
|
456
|
+
name: "git",
|
|
457
|
+
command: "git --version",
|
|
458
|
+
versionRegex: /git version ([\d.]+)/,
|
|
459
|
+
required: true,
|
|
460
|
+
installHint: "Install Git: https://git-scm.com/downloads",
|
|
461
|
+
docs: "https://git-scm.com/doc"
|
|
462
|
+
},
|
|
463
|
+
node: {
|
|
464
|
+
name: "node",
|
|
465
|
+
command: "node --version",
|
|
466
|
+
versionRegex: /v([\d.]+)/,
|
|
467
|
+
required: true,
|
|
468
|
+
installHint: "Install Node.js: https://nodejs.org",
|
|
469
|
+
docs: "https://nodejs.org/docs"
|
|
470
|
+
},
|
|
471
|
+
bun: {
|
|
472
|
+
name: "bun",
|
|
473
|
+
command: "bun --version",
|
|
474
|
+
versionRegex: /([\d.]+)/,
|
|
475
|
+
required: false,
|
|
476
|
+
installHint: "Install Bun: curl -fsSL https://bun.sh/install | bash",
|
|
477
|
+
docs: "https://bun.sh/docs"
|
|
478
|
+
},
|
|
479
|
+
gh: {
|
|
480
|
+
name: "gh",
|
|
481
|
+
command: "gh --version",
|
|
482
|
+
versionRegex: /gh version ([\d.]+)/,
|
|
483
|
+
required: false,
|
|
484
|
+
installHint: "Install GitHub CLI: https://cli.github.com",
|
|
485
|
+
docs: "https://cli.github.com/manual"
|
|
486
|
+
},
|
|
487
|
+
npm: {
|
|
488
|
+
name: "npm",
|
|
489
|
+
command: "npm --version",
|
|
490
|
+
versionRegex: /([\d.]+)/,
|
|
491
|
+
required: false,
|
|
492
|
+
installHint: "npm comes with Node.js: https://nodejs.org"
|
|
493
|
+
},
|
|
494
|
+
claude: {
|
|
495
|
+
name: "claude",
|
|
496
|
+
command: "claude --version",
|
|
497
|
+
versionRegex: /claude ([\d.]+)/,
|
|
498
|
+
required: false,
|
|
499
|
+
installHint: "Install Claude Code: npm install -g @anthropic-ai/claude-code",
|
|
500
|
+
docs: "https://docs.anthropic.com/claude-code"
|
|
501
|
+
},
|
|
502
|
+
gemini: {
|
|
503
|
+
name: "gemini",
|
|
504
|
+
command: "gemini --version",
|
|
505
|
+
versionRegex: /gemini ([\d.]+)/,
|
|
506
|
+
required: false,
|
|
507
|
+
installHint: "Install Gemini CLI: npm install -g @google/gemini-cli",
|
|
508
|
+
docs: "https://ai.google.dev/gemini-api/docs"
|
|
509
|
+
}
|
|
510
|
+
};
|
|
511
|
+
var DependencyValidator = class {
|
|
512
|
+
static {
|
|
513
|
+
__name(this, "DependencyValidator");
|
|
514
|
+
}
|
|
515
|
+
cache = /* @__PURE__ */ new Map();
|
|
516
|
+
cacheTimeout = 6e4;
|
|
517
|
+
// 1 minute cache
|
|
518
|
+
cacheTimestamps = /* @__PURE__ */ new Map();
|
|
519
|
+
/**
|
|
520
|
+
* Check if a tool is available
|
|
521
|
+
* Uses caching to avoid repeated execSync calls
|
|
522
|
+
*/
|
|
523
|
+
checkTool(toolName) {
|
|
524
|
+
const cached = this.getCached(toolName);
|
|
525
|
+
if (cached) return cached;
|
|
526
|
+
const definition = TOOLS[toolName];
|
|
527
|
+
if (!definition) {
|
|
528
|
+
return this.checkUnknownTool(toolName);
|
|
529
|
+
}
|
|
530
|
+
const status = this.executeCheck(definition);
|
|
531
|
+
this.setCache(toolName, status);
|
|
532
|
+
return status;
|
|
533
|
+
}
|
|
534
|
+
/**
|
|
535
|
+
* Ensure a tool is available, throw helpful error if not
|
|
536
|
+
* Use this before operations that require a specific tool
|
|
537
|
+
*/
|
|
538
|
+
ensureTool(toolName) {
|
|
539
|
+
const status = this.checkTool(toolName);
|
|
540
|
+
if (!status.available) {
|
|
541
|
+
const definition = TOOLS[toolName];
|
|
542
|
+
const error = status.error || {
|
|
543
|
+
message: `${toolName} is not available`,
|
|
544
|
+
hint: definition?.installHint || `Install ${toolName} and try again`,
|
|
545
|
+
docs: definition?.docs
|
|
546
|
+
};
|
|
547
|
+
throw new DependencyError(error);
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
/**
|
|
551
|
+
* Ensure multiple tools are available
|
|
552
|
+
*/
|
|
553
|
+
ensureTools(toolNames) {
|
|
554
|
+
const missing = [];
|
|
555
|
+
for (const name of toolNames) {
|
|
556
|
+
const status = this.checkTool(name);
|
|
557
|
+
if (!status.available) {
|
|
558
|
+
missing.push(name);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (missing.length > 0) {
|
|
562
|
+
const hints = missing.map((name) => {
|
|
563
|
+
const def = TOOLS[name];
|
|
564
|
+
return def ? ` ${name}: ${def.installHint}` : ` ${name}: Install and try again`;
|
|
565
|
+
}).join("\n");
|
|
566
|
+
throw new DependencyError({
|
|
567
|
+
message: `Missing required tools: ${missing.join(", ")}`,
|
|
568
|
+
hint: `Install the following:
|
|
569
|
+
${hints}`
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Check if tool is available (boolean convenience method)
|
|
575
|
+
*/
|
|
576
|
+
isAvailable(toolName) {
|
|
577
|
+
return this.checkTool(toolName).available;
|
|
578
|
+
}
|
|
579
|
+
/**
|
|
580
|
+
* Get tool version if available
|
|
581
|
+
*/
|
|
582
|
+
getVersion(toolName) {
|
|
583
|
+
return this.checkTool(toolName).version;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Check multiple tools and return summary
|
|
587
|
+
*/
|
|
588
|
+
checkAll(toolNames) {
|
|
589
|
+
const names = toolNames || Object.keys(TOOLS);
|
|
590
|
+
const results = /* @__PURE__ */ new Map();
|
|
591
|
+
for (const name of names) {
|
|
592
|
+
results.set(name, this.checkTool(name));
|
|
593
|
+
}
|
|
594
|
+
return results;
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* Clear the cache (useful for tests or after installations)
|
|
598
|
+
*/
|
|
599
|
+
clearCache() {
|
|
600
|
+
this.cache.clear();
|
|
601
|
+
this.cacheTimestamps.clear();
|
|
602
|
+
}
|
|
603
|
+
// ==========================================================================
|
|
604
|
+
// PRIVATE METHODS
|
|
605
|
+
// ==========================================================================
|
|
606
|
+
executeCheck(definition) {
|
|
607
|
+
try {
|
|
608
|
+
const output = (0, import_node_child_process.execSync)(definition.command, {
|
|
609
|
+
encoding: "utf-8",
|
|
610
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
611
|
+
timeout: 5e3
|
|
612
|
+
// 5 second timeout
|
|
613
|
+
});
|
|
614
|
+
let version;
|
|
615
|
+
if (definition.versionRegex) {
|
|
616
|
+
const match = output.match(definition.versionRegex);
|
|
617
|
+
version = match ? match[1] : void 0;
|
|
618
|
+
}
|
|
619
|
+
return { available: true, version };
|
|
620
|
+
} catch {
|
|
621
|
+
return {
|
|
622
|
+
available: false,
|
|
623
|
+
error: createError(
|
|
624
|
+
`${definition.name} is not installed or not in PATH`,
|
|
625
|
+
definition.installHint,
|
|
626
|
+
{ docs: definition.docs }
|
|
627
|
+
)
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
checkUnknownTool(toolName) {
|
|
632
|
+
try {
|
|
633
|
+
(0, import_node_child_process.execSync)(`${toolName} --version`, {
|
|
634
|
+
encoding: "utf-8",
|
|
635
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
636
|
+
timeout: 5e3
|
|
637
|
+
});
|
|
638
|
+
return { available: true };
|
|
639
|
+
} catch {
|
|
640
|
+
try {
|
|
641
|
+
(0, import_node_child_process.execSync)(`${toolName} -v`, {
|
|
642
|
+
encoding: "utf-8",
|
|
643
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
644
|
+
timeout: 5e3
|
|
645
|
+
});
|
|
646
|
+
return { available: true };
|
|
647
|
+
} catch {
|
|
648
|
+
return {
|
|
649
|
+
available: false,
|
|
650
|
+
error: createError(
|
|
651
|
+
`${toolName} is not installed or not in PATH`,
|
|
652
|
+
`Install ${toolName} and try again`
|
|
653
|
+
)
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
getCached(toolName) {
|
|
659
|
+
const timestamp = this.cacheTimestamps.get(toolName);
|
|
660
|
+
if (!timestamp) return null;
|
|
661
|
+
if (Date.now() - timestamp > this.cacheTimeout) {
|
|
662
|
+
this.cache.delete(toolName);
|
|
663
|
+
this.cacheTimestamps.delete(toolName);
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
return this.cache.get(toolName) || null;
|
|
667
|
+
}
|
|
668
|
+
setCache(toolName, status) {
|
|
669
|
+
this.cache.set(toolName, status);
|
|
670
|
+
this.cacheTimestamps.set(toolName, Date.now());
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
var DependencyError = class extends Error {
|
|
674
|
+
static {
|
|
675
|
+
__name(this, "DependencyError");
|
|
676
|
+
}
|
|
677
|
+
hint;
|
|
678
|
+
docs;
|
|
679
|
+
constructor(error) {
|
|
680
|
+
super(error.message);
|
|
681
|
+
this.name = "DependencyError";
|
|
682
|
+
this.hint = error.hint;
|
|
683
|
+
this.docs = error.docs;
|
|
684
|
+
}
|
|
685
|
+
};
|
|
686
|
+
var dependencyValidator = new DependencyValidator();
|
|
687
|
+
|
|
412
688
|
// core/types/fs.ts
|
|
413
689
|
function isNotFoundError(error) {
|
|
414
690
|
return error?.code === "ENOENT";
|
|
@@ -1119,19 +1395,49 @@ var DIM = "\x1B[2m";
|
|
|
1119
1395
|
var NC = "\x1B[0m";
|
|
1120
1396
|
async function installAICLI(provider) {
|
|
1121
1397
|
const packageName = provider.name === "claude" ? "@anthropic-ai/claude-code" : "@google/gemini-cli";
|
|
1398
|
+
if (!dependencyValidator.isAvailable("npm")) {
|
|
1399
|
+
console.log(`${YELLOW}\u26A0\uFE0F npm is not available${NC}`);
|
|
1400
|
+
console.log("");
|
|
1401
|
+
console.log(`${DIM}Install ${provider.displayName} using one of:${NC}`);
|
|
1402
|
+
console.log(`${DIM} \u2022 Install Node.js: https://nodejs.org${NC}`);
|
|
1403
|
+
console.log(
|
|
1404
|
+
`${DIM} \u2022 Use Homebrew: brew install ${provider.name === "claude" ? "claude" : "gemini"}${NC}`
|
|
1405
|
+
);
|
|
1406
|
+
console.log(`${DIM} \u2022 Use npx directly: npx ${packageName}${NC}`);
|
|
1407
|
+
console.log("");
|
|
1408
|
+
return false;
|
|
1409
|
+
}
|
|
1122
1410
|
try {
|
|
1123
1411
|
console.log(`${YELLOW}\u{1F4E6} ${provider.displayName} not found. Installing...${NC}`);
|
|
1124
1412
|
console.log("");
|
|
1125
|
-
(0,
|
|
1413
|
+
(0, import_node_child_process3.execSync)(`npm install -g ${packageName}`, {
|
|
1414
|
+
stdio: "inherit",
|
|
1415
|
+
timeout: getTimeout("NPM_INSTALL")
|
|
1416
|
+
});
|
|
1126
1417
|
console.log("");
|
|
1127
1418
|
console.log(`${GREEN}\u2713${NC} ${provider.displayName} installed successfully`);
|
|
1128
1419
|
console.log("");
|
|
1129
1420
|
return true;
|
|
1130
1421
|
} catch (error) {
|
|
1422
|
+
const err = error;
|
|
1423
|
+
const isTimeout = err.killed && err.signal === "SIGTERM";
|
|
1424
|
+
if (isTimeout) {
|
|
1425
|
+
console.log(`${YELLOW}\u26A0\uFE0F Installation timed out for ${provider.displayName}${NC}`);
|
|
1426
|
+
console.log("");
|
|
1427
|
+
console.log(`${DIM}The npm install took too long. Try:${NC}`);
|
|
1428
|
+
console.log(`${DIM} \u2022 Set PRJCT_TIMEOUT_NPM_INSTALL=300000 for 5 minutes${NC}`);
|
|
1429
|
+
console.log(`${DIM} \u2022 Run manually: npm install -g ${packageName}${NC}`);
|
|
1430
|
+
} else {
|
|
1431
|
+
console.log(`${YELLOW}\u26A0\uFE0F Failed to install ${provider.displayName}: ${err.message}${NC}`);
|
|
1432
|
+
}
|
|
1433
|
+
console.log("");
|
|
1434
|
+
console.log(`${DIM}Alternative installation methods:${NC}`);
|
|
1435
|
+
console.log(`${DIM} \u2022 npm: npm install -g ${packageName}${NC}`);
|
|
1436
|
+
console.log(`${DIM} \u2022 yarn: yarn global add ${packageName}${NC}`);
|
|
1437
|
+
console.log(`${DIM} \u2022 pnpm: pnpm add -g ${packageName}${NC}`);
|
|
1131
1438
|
console.log(
|
|
1132
|
-
`${
|
|
1439
|
+
`${DIM} \u2022 brew: brew install ${provider.name === "claude" ? "claude" : "gemini"}${NC}`
|
|
1133
1440
|
);
|
|
1134
|
-
console.log(`${DIM}Please install manually: npm install -g ${packageName}${NC}`);
|
|
1135
1441
|
console.log("");
|
|
1136
1442
|
return false;
|
|
1137
1443
|
}
|