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.
@@ -60,7 +60,7 @@ __export(ai_provider_exports, {
60
60
  });
61
61
  function whichCommand(command) {
62
62
  try {
63
- const result = (0, import_node_child_process.execSync)(`which ${command}`, { stdio: "pipe", encoding: "utf-8" });
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, import_node_child_process.execSync)(`${command} --version`, { stdio: "pipe", encoding: "utf-8" });
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 import_node_child_process, import_node_fs2, import_node_os, import_node_path2, ClaudeProvider, GeminiProvider, AntigravityProvider, CursorProvider, WindsurfProvider, Providers, ai_provider_default;
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
- import_node_child_process = require("node:child_process");
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 import_node_child_process2 = require("node:child_process");
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, import_node_child_process2.execSync)(`npm install -g ${packageName}`, { stdio: "inherit" });
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
- `${YELLOW}\u26A0\uFE0F Failed to install ${provider.displayName}: ${error.message}${NC}`
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "prjct-cli",
3
- "version": "0.61.0",
3
+ "version": "0.63.0",
4
4
  "description": "Context layer for AI agents. Project context for Claude Code, Gemini CLI, and more.",
5
5
  "main": "core/index.ts",
6
6
  "bin": {