@kood/claude-code 0.6.5 → 0.6.6

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/index.js CHANGED
@@ -23,6 +23,7 @@ var banner = () => {
23
23
 
24
24
  // src/commands/init.ts
25
25
  import fs8 from "fs-extra";
26
+ import os from "os";
26
27
 
27
28
  // src/features/templates/template-path-resolver.ts
28
29
  import path2 from "path";
@@ -319,6 +320,7 @@ var createExtrasCopier = (extrasType) => {
319
320
  var copyCommands = createExtrasCopier("commands");
320
321
  var copyAgents = createExtrasCopier("agents");
321
322
  var copyInstructions = createExtrasCopier("instructions");
323
+ var copyScripts = createExtrasCopier("scripts");
322
324
  var getSkillsToInstall = async (skillsSrc, templates) => {
323
325
  const metadataMap = await loadAllSkillMetadata(skillsSrc);
324
326
  const isNonUITemplate = templates.some((t) => NON_UI_TEMPLATES.includes(t));
@@ -363,6 +365,7 @@ var checkExistingClaudeFiles = async (targetDir) => {
363
365
  const commandsDir = path8.join(targetDir, ".claude", "commands");
364
366
  const agentsDir = path8.join(targetDir, ".claude", "agents");
365
367
  const instructionsDir = path8.join(targetDir, ".claude", "instructions");
368
+ const scriptsDir = path8.join(targetDir, ".claude", "scripts");
366
369
  if (await fs6.pathExists(skillsDir)) {
367
370
  existingFiles.push(".claude/skills/");
368
371
  }
@@ -375,6 +378,9 @@ var checkExistingClaudeFiles = async (targetDir) => {
375
378
  if (await fs6.pathExists(instructionsDir)) {
376
379
  existingFiles.push(".claude/instructions/");
377
380
  }
381
+ if (await fs6.pathExists(scriptsDir)) {
382
+ existingFiles.push(".claude/scripts/");
383
+ }
378
384
  return existingFiles;
379
385
  };
380
386
  var checkAllExtrasExist = async (_templates) => {
@@ -383,13 +389,150 @@ var checkAllExtrasExist = async (_templates) => {
383
389
  const commandsSrc = path8.join(claudeDir, "commands");
384
390
  const agentsSrc = path8.join(claudeDir, "agents");
385
391
  const instructionsSrc = path8.join(claudeDir, "instructions");
392
+ const scriptsSrc = path8.join(claudeDir, "scripts");
386
393
  const hasSkills = await hasFiles(skillsSrc);
387
394
  const hasCommands = await hasFiles(commandsSrc);
388
395
  const hasAgents = await hasFiles(agentsSrc);
389
396
  const hasInstructions = await hasFiles(instructionsSrc);
390
- return { hasSkills, hasCommands, hasAgents, hasInstructions };
397
+ const hasScripts = await hasFiles(scriptsSrc);
398
+ return { hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts };
391
399
  };
392
400
 
401
+ // src/features/extras/extras-installer.ts
402
+ function logExistingFilesUpdate(existingClaudeFiles) {
403
+ if (existingClaudeFiles.length > 0) {
404
+ logger.info("Updating existing extras:");
405
+ existingClaudeFiles.forEach((f) => logger.step(f));
406
+ }
407
+ }
408
+ async function installSkillsIfNeeded(templates, targetDir, shouldInstall, hasSkills) {
409
+ if (!shouldInstall) {
410
+ return { files: 0, directories: 0 };
411
+ }
412
+ if (!hasSkills) {
413
+ logger.warn("No skills found in selected templates.");
414
+ return { files: 0, directories: 0 };
415
+ }
416
+ logger.blank();
417
+ logger.info("Installing skills...");
418
+ const skillsResult = await copySkills(templates, targetDir);
419
+ logger.success(
420
+ `Skills: ${skillsResult.files} files, ${skillsResult.directories} directories`
421
+ );
422
+ return skillsResult;
423
+ }
424
+ async function installCommandsIfNeeded(templates, targetDir, shouldInstall, hasCommands) {
425
+ if (!shouldInstall) {
426
+ return { files: 0, directories: 0 };
427
+ }
428
+ if (!hasCommands) {
429
+ logger.warn("No commands found in selected templates.");
430
+ return { files: 0, directories: 0 };
431
+ }
432
+ logger.blank();
433
+ logger.info("Installing commands...");
434
+ const commandsResult = await copyCommands(templates, targetDir);
435
+ logger.success(
436
+ `Commands: ${commandsResult.files} files, ${commandsResult.directories} directories`
437
+ );
438
+ return commandsResult;
439
+ }
440
+ async function installAgentsIfNeeded(templates, targetDir, shouldInstall, hasAgents) {
441
+ if (!shouldInstall) {
442
+ return { files: 0, directories: 0 };
443
+ }
444
+ if (!hasAgents) {
445
+ logger.warn("No agents found in selected templates.");
446
+ return { files: 0, directories: 0 };
447
+ }
448
+ logger.blank();
449
+ logger.info("Installing agents...");
450
+ const agentsResult = await copyAgents(templates, targetDir);
451
+ logger.success(
452
+ `Agents: ${agentsResult.files} files, ${agentsResult.directories} directories`
453
+ );
454
+ return agentsResult;
455
+ }
456
+ async function installScriptsIfNeeded(templates, targetDir, shouldInstall, hasScripts) {
457
+ if (!shouldInstall) {
458
+ return { files: 0, directories: 0 };
459
+ }
460
+ if (!hasScripts) {
461
+ logger.warn("No scripts found in selected templates.");
462
+ return { files: 0, directories: 0 };
463
+ }
464
+ logger.blank();
465
+ logger.info("Installing scripts...");
466
+ const scriptsResult = await copyScripts(templates, targetDir);
467
+ logger.success(
468
+ `Scripts: ${scriptsResult.files} files, ${scriptsResult.directories} directories`
469
+ );
470
+ return scriptsResult;
471
+ }
472
+ async function installInstructionsIfNeeded(templates, targetDir, shouldInstall, hasInstructions) {
473
+ if (!shouldInstall) {
474
+ return { files: 0, directories: 0 };
475
+ }
476
+ if (!hasInstructions) {
477
+ logger.warn("No instructions found in selected templates.");
478
+ return { files: 0, directories: 0 };
479
+ }
480
+ logger.blank();
481
+ logger.info("Installing instructions...");
482
+ const instructionsResult = await copyInstructions(templates, targetDir);
483
+ logger.success(
484
+ `Instructions: ${instructionsResult.files} files, ${instructionsResult.directories} directories`
485
+ );
486
+ return instructionsResult;
487
+ }
488
+ async function installExtras(templates, targetDir, flags, availability) {
489
+ const {
490
+ installSkills,
491
+ installCommands,
492
+ installAgents,
493
+ installInstructions,
494
+ installScripts
495
+ } = flags;
496
+ if (!installSkills && !installCommands && !installAgents && !installInstructions && !installScripts) {
497
+ return { files: 0, directories: 0 };
498
+ }
499
+ const existingClaudeFiles = await checkExistingClaudeFiles(targetDir);
500
+ logExistingFilesUpdate(existingClaudeFiles);
501
+ const skillsResult = await installSkillsIfNeeded(
502
+ templates,
503
+ targetDir,
504
+ installSkills,
505
+ availability.hasSkills
506
+ );
507
+ const commandsResult = await installCommandsIfNeeded(
508
+ templates,
509
+ targetDir,
510
+ installCommands,
511
+ availability.hasCommands
512
+ );
513
+ const agentsResult = await installAgentsIfNeeded(
514
+ templates,
515
+ targetDir,
516
+ installAgents,
517
+ availability.hasAgents
518
+ );
519
+ const instructionsResult = await installInstructionsIfNeeded(
520
+ templates,
521
+ targetDir,
522
+ installInstructions,
523
+ availability.hasInstructions
524
+ );
525
+ const scriptsResult = await installScriptsIfNeeded(
526
+ templates,
527
+ targetDir,
528
+ installScripts,
529
+ availability.hasScripts
530
+ );
531
+ const totalFiles = skillsResult.files + commandsResult.files + agentsResult.files + instructionsResult.files + scriptsResult.files;
532
+ const totalDirectories = skillsResult.directories + commandsResult.directories + agentsResult.directories + instructionsResult.directories + scriptsResult.directories;
533
+ return { files: totalFiles, directories: totalDirectories };
534
+ }
535
+
393
536
  // src/shared/prompts/prompt-helpers.ts
394
537
  import prompts from "prompts";
395
538
  async function promptConfirm(message, initial = false) {
@@ -401,6 +544,15 @@ async function promptConfirm(message, initial = false) {
401
544
  });
402
545
  return { confirmed: response.confirmed ?? false };
403
546
  }
547
+ async function promptSelect(message, choices) {
548
+ const response = await prompts({
549
+ type: "select",
550
+ name: "value",
551
+ message,
552
+ choices
553
+ });
554
+ return { value: response.value };
555
+ }
404
556
  async function promptMultiselect(message, choices, options) {
405
557
  const response = await prompts({
406
558
  type: "multiselect",
@@ -461,17 +613,20 @@ async function promptExtrasSelection(options) {
461
613
  commands,
462
614
  agents,
463
615
  instructions,
616
+ scripts,
464
617
  hasSkills,
465
618
  hasCommands,
466
619
  hasAgents,
467
- hasInstructions
620
+ hasInstructions,
621
+ hasScripts
468
622
  } = options;
469
623
  let installSkills = skills ?? false;
470
624
  let installCommands = commands ?? false;
471
625
  const installAgents = agents ?? hasAgents;
472
626
  const installInstructions = instructions ?? hasInstructions;
473
- const noOptionsProvided = skills === void 0 && commands === void 0 && agents === void 0 && instructions === void 0;
474
- if (noOptionsProvided && (hasSkills || hasCommands || hasAgents || hasInstructions)) {
627
+ const installScripts = scripts ?? hasScripts;
628
+ const noOptionsProvided = skills === void 0 && commands === void 0 && agents === void 0 && instructions === void 0 && scripts === void 0;
629
+ if (noOptionsProvided && (hasSkills || hasCommands || hasAgents || hasInstructions || hasScripts)) {
475
630
  logger.blank();
476
631
  if (hasSkills) {
477
632
  const result = await promptConfirm(
@@ -492,126 +647,41 @@ async function promptExtrasSelection(options) {
492
647
  installSkills,
493
648
  installCommands,
494
649
  installAgents,
495
- installInstructions
650
+ installInstructions,
651
+ installScripts
496
652
  };
497
653
  }
498
-
499
- // src/features/extras/extras-installer.ts
500
- async function handleDuplicateFiles(existingClaudeFiles, force) {
501
- if (existingClaudeFiles.length === 0 || force) {
502
- return true;
503
- }
504
- logger.warn("The following .claude files/folders already exist:");
505
- existingClaudeFiles.forEach((f) => logger.step(f));
506
- logger.blank();
507
- const response = await promptConfirm(
508
- "Overwrite existing .claude files?",
509
- false
510
- );
511
- return response.confirmed;
512
- }
513
- async function installSkillsIfNeeded(templates, targetDir, shouldInstall, hasSkills) {
514
- if (!shouldInstall) {
515
- return { files: 0, directories: 0 };
516
- }
517
- if (!hasSkills) {
518
- logger.warn("No skills found in selected templates.");
519
- return { files: 0, directories: 0 };
520
- }
521
- logger.blank();
522
- logger.info("Installing skills...");
523
- const skillsResult = await copySkills(templates, targetDir);
524
- logger.success(
525
- `Skills: ${skillsResult.files} files, ${skillsResult.directories} directories`
526
- );
527
- return skillsResult;
528
- }
529
- async function installCommandsIfNeeded(templates, targetDir, shouldInstall, hasCommands) {
530
- if (!shouldInstall) {
531
- return { files: 0, directories: 0 };
532
- }
533
- if (!hasCommands) {
534
- logger.warn("No commands found in selected templates.");
535
- return { files: 0, directories: 0 };
536
- }
537
- logger.blank();
538
- logger.info("Installing commands...");
539
- const commandsResult = await copyCommands(templates, targetDir);
540
- logger.success(
541
- `Commands: ${commandsResult.files} files, ${commandsResult.directories} directories`
542
- );
543
- return commandsResult;
544
- }
545
- async function installAgentsIfNeeded(templates, targetDir, shouldInstall, hasAgents) {
546
- if (!shouldInstall) {
547
- return { files: 0, directories: 0 };
548
- }
549
- if (!hasAgents) {
550
- logger.warn("No agents found in selected templates.");
551
- return { files: 0, directories: 0 };
552
- }
553
- logger.blank();
554
- logger.info("Installing agents...");
555
- const agentsResult = await copyAgents(templates, targetDir);
556
- logger.success(
557
- `Agents: ${agentsResult.files} files, ${agentsResult.directories} directories`
558
- );
559
- return agentsResult;
560
- }
561
- async function installInstructionsIfNeeded(templates, targetDir, shouldInstall, hasInstructions) {
562
- if (!shouldInstall) {
563
- return { files: 0, directories: 0 };
564
- }
565
- if (!hasInstructions) {
566
- logger.warn("No instructions found in selected templates.");
567
- return { files: 0, directories: 0 };
654
+ async function promptScopeSelection(options) {
655
+ const { providedScope } = options;
656
+ if (providedScope) {
657
+ if (providedScope !== "project" && providedScope !== "user") {
658
+ logger.error(
659
+ `Invalid scope: "${providedScope}". Use "project" or "user".`
660
+ );
661
+ process.exit(1);
662
+ }
663
+ return { scope: providedScope };
568
664
  }
569
- logger.blank();
570
- logger.info("Installing instructions...");
571
- const instructionsResult = await copyInstructions(templates, targetDir);
572
- logger.success(
573
- `Instructions: ${instructionsResult.files} files, ${instructionsResult.directories} directories`
665
+ const response = await promptSelect(
666
+ "Select installation scope:",
667
+ [
668
+ {
669
+ title: "Project",
670
+ description: "Install to current project (.claude/)",
671
+ value: "project"
672
+ },
673
+ {
674
+ title: "User",
675
+ description: "Install to user home (~/.claude/)",
676
+ value: "user"
677
+ }
678
+ ]
574
679
  );
575
- return instructionsResult;
576
- }
577
- async function installExtras(templates, targetDir, flags, availability, force) {
578
- const { installSkills, installCommands, installAgents, installInstructions } = flags;
579
- if (!installSkills && !installCommands && !installAgents && !installInstructions) {
580
- return { files: 0, directories: 0 };
581
- }
582
- const existingClaudeFiles = await checkExistingClaudeFiles(targetDir);
583
- const shouldProceed = await handleDuplicateFiles(existingClaudeFiles, force);
584
- if (!shouldProceed) {
585
- logger.info("Skipping extras installation.");
586
- return { files: 0, directories: 0 };
680
+ if (!response.value) {
681
+ logger.info("Operation cancelled.");
682
+ process.exit(0);
587
683
  }
588
- const skillsResult = await installSkillsIfNeeded(
589
- templates,
590
- targetDir,
591
- installSkills,
592
- availability.hasSkills
593
- );
594
- const commandsResult = await installCommandsIfNeeded(
595
- templates,
596
- targetDir,
597
- installCommands,
598
- availability.hasCommands
599
- );
600
- const agentsResult = await installAgentsIfNeeded(
601
- templates,
602
- targetDir,
603
- installAgents,
604
- availability.hasAgents
605
- );
606
- const instructionsResult = await installInstructionsIfNeeded(
607
- templates,
608
- targetDir,
609
- installInstructions,
610
- availability.hasInstructions
611
- );
612
- const totalFiles = skillsResult.files + commandsResult.files + agentsResult.files + instructionsResult.files;
613
- const totalDirectories = skillsResult.directories + commandsResult.directories + agentsResult.directories + instructionsResult.directories;
614
- return { files: totalFiles, directories: totalDirectories };
684
+ return { scope: response.value };
615
685
  }
616
686
 
617
687
  // src/shared/gitignore-manager.ts
@@ -756,29 +826,38 @@ async function installTemplates(templates, targetDir) {
756
826
  logger.success(`Total: ${totalFiles} files, ${totalDirectories} directories`);
757
827
  return { files: totalFiles, directories: totalDirectories };
758
828
  }
759
- async function promptForExtrasInstallation(options, hasSkills, hasCommands, hasAgents, hasInstructions) {
829
+ async function promptForExtrasInstallation(options, hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts) {
760
830
  return await promptExtrasSelection({
761
831
  skills: options.skills,
762
832
  commands: options.commands,
763
833
  agents: options.agents,
764
834
  instructions: options.instructions,
835
+ scripts: options.scripts,
765
836
  hasSkills,
766
837
  hasCommands,
767
838
  hasAgents,
768
- hasInstructions
839
+ hasInstructions,
840
+ hasScripts
769
841
  });
770
842
  }
771
- function showInstallationSummary(templates, flags, hasSkills, hasCommands, hasAgents, hasInstructions) {
772
- const { installSkills, installCommands, installAgents, installInstructions } = flags;
773
- const hasExtrasInstalled = installSkills && hasSkills || installCommands && hasCommands || installAgents && hasAgents || installInstructions && hasInstructions;
843
+ function showInstallationSummary(templates, flags, hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts, scope) {
844
+ const {
845
+ installSkills,
846
+ installCommands,
847
+ installAgents,
848
+ installInstructions,
849
+ installScripts
850
+ } = flags;
851
+ const hasExtrasInstalled = installSkills && hasSkills || installCommands && hasCommands || installAgents && hasAgents || installInstructions && hasInstructions || installScripts && hasScripts;
774
852
  if (templates.length === 0 && !hasExtrasInstalled) {
775
853
  logger.blank();
776
854
  logger.info("No templates or extras installed.");
777
855
  logger.blank();
778
856
  return;
779
857
  }
858
+ const scopeLabel = scope === "user" ? "(user scope \u2192 ~/.claude/)" : "(project scope)";
780
859
  logger.blank();
781
- logger.success("Claude Code documentation installed!");
860
+ logger.success(`Claude Code documentation installed! ${scopeLabel}`);
782
861
  if (templates.length > 0) {
783
862
  logger.blank();
784
863
  logger.info("Installed templates:");
@@ -799,6 +878,9 @@ function showInstallationSummary(templates, flags, hasSkills, hasCommands, hasAg
799
878
  if (installInstructions && hasInstructions) {
800
879
  logger.step("Instructions \u2192 .claude/instructions/");
801
880
  }
881
+ if (installScripts && hasScripts) {
882
+ logger.step("Scripts \u2192 .claude/scripts/");
883
+ }
802
884
  }
803
885
  logger.blank();
804
886
  logger.info("Next steps:");
@@ -812,48 +894,65 @@ function showInstallationSummary(templates, flags, hasSkills, hasCommands, hasAg
812
894
  logger.blank();
813
895
  }
814
896
  var init = async (options) => {
815
- const targetDir = options.cwd || process.cwd();
816
- await validateTargetDirectory(targetDir);
897
+ const projectDir = options.cwd || process.cwd();
898
+ await validateTargetDirectory(projectDir);
899
+ const { scope } = await promptScopeSelection({
900
+ providedScope: options.scope
901
+ });
902
+ const targetDir = scope === "user" ? os.homedir() : projectDir;
903
+ const isUserScope = scope === "user";
904
+ if (isUserScope) {
905
+ logger.blank();
906
+ logger.info(`Installing to ~/.claude/ (user scope)`);
907
+ }
817
908
  const availableTemplates = await listAvailableTemplates();
818
909
  if (availableTemplates.length === 0) {
819
910
  logger.error("No templates found. Package may be corrupted.");
820
911
  process.exit(1);
821
912
  }
822
- const templates = await selectTemplates(options, availableTemplates);
823
- if (templates.length > 0) {
824
- await confirmOverwriteIfNeeded(targetDir, options.force ?? false);
825
- await installTemplates(templates, targetDir);
913
+ let templates = [];
914
+ if (!isUserScope) {
915
+ templates = await selectTemplates(options, availableTemplates);
916
+ if (templates.length > 0) {
917
+ await confirmOverwriteIfNeeded(targetDir, options.force ?? false);
918
+ await installTemplates(templates, targetDir);
919
+ }
826
920
  }
827
921
  const templatesToCheck = templates.length > 0 ? templates : availableTemplates;
828
- const { hasSkills, hasCommands, hasAgents, hasInstructions } = await checkAllExtrasExist(templatesToCheck);
922
+ const { hasSkills, hasCommands, hasAgents, hasInstructions, hasScripts } = await checkAllExtrasExist(templatesToCheck);
829
923
  const flags = await promptForExtrasInstallation(
830
924
  options,
831
925
  hasSkills,
832
926
  hasCommands,
833
927
  hasAgents,
834
- hasInstructions
835
- );
836
- await installExtras(
837
- templatesToCheck,
838
- targetDir,
839
- flags,
840
- { hasSkills, hasCommands, hasAgents, hasInstructions },
841
- options.force ?? false
928
+ hasInstructions,
929
+ hasScripts
842
930
  );
931
+ await installExtras(templatesToCheck, targetDir, flags, {
932
+ hasSkills,
933
+ hasCommands,
934
+ hasAgents,
935
+ hasInstructions,
936
+ hasScripts
937
+ });
843
938
  showInstallationSummary(
844
939
  templates,
845
940
  flags,
846
941
  hasSkills,
847
942
  hasCommands,
848
943
  hasAgents,
849
- hasInstructions
944
+ hasInstructions,
945
+ hasScripts,
946
+ scope
850
947
  );
851
- try {
852
- await updateGitignore(targetDir);
853
- } catch (error) {
854
- logger.warn(
855
- `Failed to update .gitignore: ${error instanceof Error ? error.message : "Unknown error"}`
856
- );
948
+ if (!isUserScope) {
949
+ try {
950
+ await updateGitignore(targetDir);
951
+ } catch (error) {
952
+ logger.warn(
953
+ `Failed to update .gitignore: ${error instanceof Error ? error.message : "Unknown error"}`
954
+ );
955
+ }
857
956
  }
858
957
  };
859
958
 
@@ -863,7 +962,7 @@ program.name("claude-code").description("Claude Code documentation installer for
863
962
  program.option(
864
963
  "-t, --template <names>",
865
964
  "template names (comma-separated: tanstack-start,hono)"
866
- ).option("-f, --force", "overwrite existing files without prompting").option("--cwd <path>", "target directory (default: current directory)").option("--list", "list available templates").option("-s, --skills", "install skills to .claude/skills/").option("-c, --commands", "install commands to .claude/commands/").option("-a, --agents", "install agents to .claude/agents/").action(async (options) => {
965
+ ).option("-f, --force", "overwrite existing files without prompting").option("--cwd <path>", "target directory (default: current directory)").option("--list", "list available templates").option("-s, --skills", "install skills to .claude/skills/").option("-c, --commands", "install commands to .claude/commands/").option("-a, --agents", "install agents to .claude/agents/").option("--scope <scope>", "installation scope (project|user)").action(async (options) => {
867
966
  banner();
868
967
  if (options.list) {
869
968
  const templates = await listAvailableTemplates();
@@ -876,6 +975,7 @@ program.option(
876
975
  templates: options.template?.split(",").map((t) => t.trim()),
877
976
  force: options.force,
878
977
  cwd: options.cwd,
978
+ scope: options.scope,
879
979
  skills: options.skills,
880
980
  commands: options.commands,
881
981
  agents: options.agents
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kood/claude-code",
3
- "version": "0.6.5",
3
+ "version": "0.6.6",
4
4
  "description": "Claude Code documentation installer for projects",
5
5
  "type": "module",
6
6
  "bin": "./dist/index.js",
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env bash
2
+ # build-run.sh - Package manager 감지 후 build 실행
3
+ # Usage: ./build-run.sh
4
+
5
+ set -euo pipefail
6
+
7
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
8
+ PM_DIR="$SCRIPT_DIR/../pm"
9
+
10
+ # Package manager 감지
11
+ PM=$("$PM_DIR/pm-detect.sh")
12
+
13
+ echo "=== Building with $PM ==="
14
+
15
+ # Build 실행
16
+ case "$PM" in
17
+ bun)
18
+ bun run build
19
+ ;;
20
+ pnpm)
21
+ pnpm run build
22
+ ;;
23
+ yarn)
24
+ yarn build
25
+ ;;
26
+ npm)
27
+ npm run build
28
+ ;;
29
+ *)
30
+ echo "Error: Unknown package manager: $PM" >&2
31
+ exit 1
32
+ ;;
33
+ esac
34
+
35
+ echo ""
36
+ echo "✓ Build completed successfully"
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env bash
2
+ # deploy-check.sh - 배포 전 전체 검증 (tsc + eslint + build)
3
+ # Usage: ./deploy-check.sh
4
+ # 검사 실패 시 조기 종료
5
+
6
+ set -euo pipefail
7
+
8
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ LINT_DIR="$SCRIPT_DIR/../lint"
10
+
11
+ echo "=========================================="
12
+ echo " Pre-Deploy Verification"
13
+ echo "=========================================="
14
+
15
+ # 스크립트 존재 검증
16
+ if [ ! -x "$LINT_DIR/lint-check.sh" ]; then
17
+ echo "Error: lint-check.sh not found or not executable" >&2
18
+ exit 1
19
+ fi
20
+ if [ ! -x "$SCRIPT_DIR/build-run.sh" ]; then
21
+ echo "Error: build-run.sh not found or not executable" >&2
22
+ exit 1
23
+ fi
24
+
25
+ # Step 1: Lint 검사 (tsc + eslint 병렬)
26
+ echo ""
27
+ echo "[1/2] Running lint checks..."
28
+ "$LINT_DIR/lint-check.sh"
29
+
30
+ # Step 2: Build
31
+ echo ""
32
+ echo "[2/2] Running build..."
33
+ "$SCRIPT_DIR/build-run.sh"
34
+
35
+ echo ""
36
+ echo "=========================================="
37
+ echo " ✓ All checks passed - Ready to deploy"
38
+ echo "=========================================="
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env bash
2
+ # git-all.sh - 모든 변경사항 add + commit + push (단순 케이스)
3
+ # Usage: ./git-all.sh "커밋 메시지"
4
+ # WARNING: git add -A 사용 - 민감 파일(.env 등) 포함 주의
5
+
6
+ set -euo pipefail
7
+
8
+ # Git 저장소 확인
9
+ if ! git rev-parse --git-dir > /dev/null 2>&1; then
10
+ echo "Error: Not a git repository"
11
+ exit 1
12
+ fi
13
+
14
+ if [ -z "$1" ]; then
15
+ echo "Usage: $0 \"commit message\""
16
+ exit 1
17
+ fi
18
+
19
+ COMMIT_MSG="$1"
20
+
21
+ # 빈 메시지 체크
22
+ if [ -z "$(echo "$COMMIT_MSG" | xargs)" ]; then
23
+ echo "Error: Commit message cannot be empty" >&2
24
+ exit 1
25
+ fi
26
+
27
+ # 변경사항 확인
28
+ if [ -n "$(git status --porcelain)" ]; then
29
+ # 민감 파일 검증
30
+ SENSITIVE=$(git status --porcelain | grep -E '^\?\?.*\.(env|pem|key|secret|credentials)' || true)
31
+ if [ -n "$SENSITIVE" ]; then
32
+ echo "ERROR: Sensitive files detected:" >&2
33
+ echo "$SENSITIVE" >&2
34
+ echo "" >&2
35
+ echo "Please use git-commit.sh with specific files instead" >&2
36
+ exit 1
37
+ fi
38
+
39
+ echo "=== Adding all changes ==="
40
+ git add -A
41
+
42
+ echo "=== Committing ==="
43
+ git commit -m "$COMMIT_MSG"
44
+
45
+ echo "=== Pushing ==="
46
+ BRANCH=$(git branch --show-current)
47
+ if ! git rev-parse --abbrev-ref "@{upstream}" >/dev/null 2>&1; then
48
+ git push -u origin "$BRANCH"
49
+ else
50
+ git push
51
+ fi
52
+
53
+ echo "Done: All changes committed and pushed"
54
+ else
55
+ echo "No changes to commit"
56
+ exit 0
57
+ fi
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env bash
2
+ # git-clean-check.sh - Working directory clean 여부 확인
3
+ # Usage: ./git-clean-check.sh [--quiet]
4
+ # Exit 0: clean, Exit 1: dirty
5
+
6
+ set -uo pipefail
7
+
8
+ # Git 저장소 확인
9
+ if ! git rev-parse --git-dir > /dev/null 2>&1; then
10
+ echo "Error: Not a git repository"
11
+ exit 1
12
+ fi
13
+
14
+ QUIET=false
15
+ if [ "${1:-}" = "--quiet" ]; then
16
+ QUIET=true
17
+ fi
18
+
19
+ if [ -n "$(git status --porcelain)" ]; then
20
+ if [ "$QUIET" = false ]; then
21
+ echo "Working directory is DIRTY"
22
+ echo ""
23
+ git status --short
24
+ fi
25
+ exit 1
26
+ else
27
+ if [ "$QUIET" = false ]; then
28
+ echo "Working directory is CLEAN"
29
+ fi
30
+ exit 0
31
+ fi
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env bash
2
+ # git-commit.sh - 커밋 실행
3
+ # Usage: ./git-commit.sh "커밋 메시지" [files...]
4
+ # files 미지정 시 staged files만 커밋
5
+
6
+ set -euo pipefail
7
+
8
+ # Git 저장소 확인
9
+ if ! git rev-parse --git-dir > /dev/null 2>&1; then
10
+ echo "Error: Not a git repository"
11
+ exit 1
12
+ fi
13
+
14
+ if [ -z "$1" ]; then
15
+ echo "Usage: $0 \"commit message\" [files...]"
16
+ echo " files: 커밋할 파일들 (미지정 시 staged files만)"
17
+ exit 1
18
+ fi
19
+
20
+ COMMIT_MSG="$1"
21
+
22
+ # 빈 메시지 체크
23
+ if [ -z "$(echo "$COMMIT_MSG" | xargs)" ]; then
24
+ echo "Error: Commit message cannot be empty" >&2
25
+ exit 1
26
+ fi
27
+
28
+ shift
29
+ FILES_PROVIDED=$#
30
+
31
+ # 파일 지정된 경우 add
32
+ if [ $FILES_PROVIDED -gt 0 ]; then
33
+ git add "$@"
34
+ fi
35
+
36
+ # staged 파일 확인
37
+ if git diff --cached --quiet; then
38
+ if [ $FILES_PROVIDED -eq 0 ]; then
39
+ echo "Error: No staged changes to commit"
40
+ echo "Tip: Stage files with 'git add' or pass files as arguments"
41
+ else
42
+ echo "Error: No changes in specified files"
43
+ echo "Tip: Check if files exist and have modifications"
44
+ fi
45
+ exit 1
46
+ fi
47
+
48
+ # 커밋
49
+ git commit -m "$COMMIT_MSG"
50
+
51
+ echo "Committed successfully"
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env bash
2
+ # git-info.sh - Git 상태 및 diff 요약 출력
3
+ # 에이전트가 분석할 정보를 빠르게 수집
4
+
5
+ set -euo pipefail
6
+
7
+ # Git 저장소 확인
8
+ if ! git rev-parse --git-dir > /dev/null 2>&1; then
9
+ echo "Error: Not a git repository"
10
+ exit 1
11
+ fi
12
+
13
+ echo "=== Git Status ==="
14
+ if [ -n "$(git status --porcelain)" ]; then
15
+ git status --short
16
+ else
17
+ echo "(clean)"
18
+ fi
19
+
20
+ echo ""
21
+ echo "=== Changed Files ==="
22
+ git diff --stat
23
+
24
+ echo ""
25
+ echo "=== Staged Files ==="
26
+ git diff --cached --stat
27
+
28
+ echo ""
29
+ echo "=== Recent Commits (5) ==="
30
+ git log --oneline -5
31
+
32
+ echo ""
33
+ echo "=== Current Branch ==="
34
+ git branch --show-current
@@ -0,0 +1,50 @@
1
+ #!/usr/bin/env bash
2
+ # git-push.sh - 안전한 푸시 (상태 확인 후)
3
+ # Usage: ./git-push.sh [--force]
4
+
5
+ set -euo pipefail
6
+
7
+ # Git 저장소 확인
8
+ if ! git rev-parse --git-dir > /dev/null 2>&1; then
9
+ echo "Error: Not a git repository"
10
+ exit 1
11
+ fi
12
+
13
+ # 현재 브랜치
14
+ BRANCH=$(git branch --show-current)
15
+
16
+ if [ -z "$BRANCH" ]; then
17
+ echo "Error: Not on any branch (detached HEAD)"
18
+ exit 1
19
+ fi
20
+
21
+ # Force push 처리
22
+ USE_FORCE=false
23
+ if [ "${1:-}" = "--force" ]; then
24
+ # main/master 보호
25
+ if [ "$BRANCH" = "main" ] || [ "$BRANCH" = "master" ]; then
26
+ echo "ERROR: Cannot force push to $BRANCH"
27
+ echo "This operation is too dangerous on protected branches"
28
+ exit 1
29
+ fi
30
+ USE_FORCE=true
31
+ echo "Warning: Force push enabled (with lease) to $BRANCH"
32
+ fi
33
+
34
+ # upstream 확인 및 push
35
+ if ! git rev-parse --abbrev-ref "@{upstream}" >/dev/null 2>&1; then
36
+ echo "Setting upstream for branch: $BRANCH"
37
+ if [ "$USE_FORCE" = true ]; then
38
+ git push -u origin "$BRANCH" --force-with-lease
39
+ else
40
+ git push -u origin "$BRANCH"
41
+ fi
42
+ else
43
+ if [ "$USE_FORCE" = true ]; then
44
+ git push --force-with-lease
45
+ else
46
+ git push
47
+ fi
48
+ fi
49
+
50
+ echo "Pushed to origin/$BRANCH"
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env bash
2
+ # lint-check.sh - tsc + eslint 병렬 실행
3
+ # Usage: ./lint-check.sh
4
+ # 두 검사를 병렬로 실행하고 결과를 수집
5
+
6
+ set -uo pipefail
7
+
8
+ # 임시 파일
9
+ TSC_OUT=$(mktemp)
10
+ ESLINT_OUT=$(mktemp)
11
+ TSC_EXIT=0
12
+ ESLINT_EXIT=0
13
+
14
+ # Cleanup (ERR, INT, TERM도 처리)
15
+ trap "rm -f $TSC_OUT $ESLINT_OUT" EXIT ERR INT TERM
16
+
17
+ echo "=== Running TypeScript + ESLint in parallel ==="
18
+
19
+ # 병렬 실행
20
+ (npx tsc --noEmit > "$TSC_OUT" 2>&1) &
21
+ TSC_PID=$!
22
+
23
+ (npx eslint . > "$ESLINT_OUT" 2>&1) &
24
+ ESLINT_PID=$!
25
+
26
+ # 대기
27
+ wait $TSC_PID || TSC_EXIT=$?
28
+ wait $ESLINT_PID || ESLINT_EXIT=$?
29
+
30
+ # 결과 출력
31
+ echo ""
32
+ echo "=== TypeScript Results ==="
33
+ if [ $TSC_EXIT -eq 0 ]; then
34
+ echo "✓ No type errors"
35
+ else
36
+ cat "$TSC_OUT"
37
+ fi
38
+
39
+ echo ""
40
+ echo "=== ESLint Results ==="
41
+ if [ $ESLINT_EXIT -eq 0 ]; then
42
+ echo "✓ No lint errors"
43
+ else
44
+ cat "$ESLINT_OUT"
45
+ fi
46
+
47
+ echo ""
48
+ echo "=== Summary ==="
49
+ if [ $TSC_EXIT -eq 0 ] && [ $ESLINT_EXIT -eq 0 ]; then
50
+ echo "✓ All checks passed"
51
+ exit 0
52
+ else
53
+ [ $TSC_EXIT -ne 0 ] && echo "✗ TypeScript: FAILED"
54
+ [ $ESLINT_EXIT -ne 0 ] && echo "✗ ESLint: FAILED"
55
+ exit 1
56
+ fi
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # lint-file.sh - 특정 파일/디렉토리만 검사
3
+ # Usage: ./lint-file.sh <file|directory> [file2...]
4
+ # Example: ./lint-file.sh src/utils/api.ts
5
+ # ./lint-file.sh src/components/
6
+
7
+ set -uo pipefail
8
+
9
+ if [ $# -eq 0 ]; then
10
+ echo "Usage: $0 <file|directory> [file2...]"
11
+ echo "Example: $0 src/utils/api.ts"
12
+ exit 1
13
+ fi
14
+
15
+ TSC_EXIT=0
16
+ ESLINT_EXIT=0
17
+
18
+ echo "=== Checking: $@ ==="
19
+
20
+ # TypeScript 검사 (전체 프로젝트 - tsc는 개별 파일 검사 미지원)
21
+ echo ""
22
+ echo "=== TypeScript ==="
23
+ echo "(Note: tsc checks entire project)"
24
+ npx tsc --noEmit || TSC_EXIT=$?
25
+
26
+ # ESLint 검사
27
+ echo ""
28
+ echo "=== ESLint ==="
29
+ npx eslint "$@" || ESLINT_EXIT=$?
30
+
31
+ # 결과
32
+ echo ""
33
+ echo "=== Summary ==="
34
+ if [ $TSC_EXIT -eq 0 ] && [ $ESLINT_EXIT -eq 0 ]; then
35
+ echo "✓ All checks passed"
36
+ exit 0
37
+ else
38
+ [ $TSC_EXIT -ne 0 ] && echo "✗ TypeScript: FAILED"
39
+ [ $ESLINT_EXIT -ne 0 ] && echo "✗ ESLint: FAILED"
40
+ exit 1
41
+ fi
@@ -0,0 +1,25 @@
1
+ #!/usr/bin/env bash
2
+ # pm-detect.sh - Package manager 자동 감지
3
+ # Usage: ./pm-detect.sh
4
+ # Output: npm | yarn | pnpm | bun
5
+ # Exit 1 if no lock file found
6
+
7
+ set -euo pipefail
8
+
9
+ # Lock 파일 기반 감지
10
+ if [ -f "bun.lockb" ]; then
11
+ echo "bun"
12
+ elif [ -f "pnpm-lock.yaml" ]; then
13
+ echo "pnpm"
14
+ elif [ -f "yarn.lock" ]; then
15
+ echo "yarn"
16
+ elif [ -f "package-lock.json" ]; then
17
+ echo "npm"
18
+ elif [ -f "package.json" ]; then
19
+ # Lock 파일 없으면 기본 npm
20
+ echo "Warning: No lock file found, defaulting to npm" >&2
21
+ echo "npm"
22
+ else
23
+ echo "Error: No package.json found" >&2
24
+ exit 1
25
+ fi
@@ -0,0 +1,41 @@
1
+ #!/usr/bin/env bash
2
+ # pm-run.sh - 감지된 package manager로 스크립트 실행
3
+ # Usage: ./pm-run.sh <script> [args...]
4
+ # Example: ./pm-run.sh build
5
+ # ./pm-run.sh test --watch
6
+
7
+ set -euo pipefail
8
+
9
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
10
+
11
+ if [ $# -eq 0 ] || [ -z "${1:-}" ]; then
12
+ echo "Usage: $0 <script> [args...]"
13
+ echo "Example: $0 build"
14
+ exit 1
15
+ fi
16
+
17
+ SCRIPT="$1"
18
+ shift
19
+
20
+ # Package manager 감지
21
+ PM=$("$SCRIPT_DIR/pm-detect.sh")
22
+
23
+ # 실행
24
+ case "$PM" in
25
+ bun)
26
+ bun run "$SCRIPT" "$@"
27
+ ;;
28
+ pnpm)
29
+ pnpm run "$SCRIPT" "$@"
30
+ ;;
31
+ yarn)
32
+ yarn "$SCRIPT" "$@"
33
+ ;;
34
+ npm)
35
+ npm run "$SCRIPT" "$@"
36
+ ;;
37
+ *)
38
+ echo "Error: Unknown package manager: $PM" >&2
39
+ exit 1
40
+ ;;
41
+ esac
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env bash
2
+ # version-bump.sh - 버전 계산
3
+ # Usage: ./version-bump.sh <current_version> <bump_type>
4
+ # bump_type: +1 (patch), +minor, +major, or direct version (x.x.x)
5
+ # Example: ./version-bump.sh 1.2.3 +1 → 1.2.4
6
+ # ./version-bump.sh 1.2.3 +minor → 1.3.0
7
+ # ./version-bump.sh 1.2.3 +major → 2.0.0
8
+ # ./version-bump.sh 1.2.3 2.0.0 → 2.0.0
9
+
10
+ set -euo pipefail
11
+
12
+ if [ $# -lt 2 ]; then
13
+ echo "Usage: $0 <current_version> <bump_type>"
14
+ echo "bump_type: +1, +minor, +major, or x.x.x"
15
+ exit 1
16
+ fi
17
+
18
+ CURRENT="$1"
19
+ BUMP="$2"
20
+
21
+ # 버전 형식 먼저 검증
22
+ if ! [[ "$CURRENT" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
23
+ echo "Error: Invalid version format: $CURRENT" >&2
24
+ echo "Expected: x.x.x (e.g., 1.2.3)" >&2
25
+ exit 1
26
+ fi
27
+
28
+ # 버전 파싱
29
+ IFS='.' read -r MAJOR MINOR PATCH <<< "$CURRENT"
30
+
31
+ # 새 버전 계산
32
+ case "$BUMP" in
33
+ +1|+patch)
34
+ NEW_VERSION="$MAJOR.$MINOR.$((PATCH + 1))"
35
+ ;;
36
+ +minor)
37
+ NEW_VERSION="$MAJOR.$((MINOR + 1)).0"
38
+ ;;
39
+ +major)
40
+ NEW_VERSION="$((MAJOR + 1)).0.0"
41
+ ;;
42
+ *)
43
+ # 직접 버전 지정 검증
44
+ if [[ "$BUMP" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
45
+ NEW_VERSION="$BUMP"
46
+ else
47
+ echo "Error: Invalid bump type: $BUMP"
48
+ echo "Use: +1, +minor, +major, or x.x.x"
49
+ exit 1
50
+ fi
51
+ ;;
52
+ esac
53
+
54
+ echo "$NEW_VERSION"
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env bash
2
+ # version-find.sh - 버전 파일 탐색
3
+ # Usage: ./version-find.sh
4
+ # package.json과 .version() 코드 파일을 병렬로 탐색
5
+
6
+ set -uo pipefail
7
+
8
+ echo "=== Searching version files ==="
9
+
10
+ # 임시 파일
11
+ PKG_OUT=$(mktemp)
12
+ CODE_OUT=$(mktemp)
13
+
14
+ trap "rm -f $PKG_OUT $CODE_OUT" EXIT ERR INT TERM
15
+
16
+ # 병렬 탐색 (fd 우선, 없으면 find)
17
+ if command -v fd &>/dev/null; then
18
+ fd -t f 'package.json' -E node_modules > "$PKG_OUT" 2>&1 &
19
+ else
20
+ find . -name 'package.json' -not -path '*/node_modules/*' > "$PKG_OUT" 2>&1 &
21
+ fi
22
+ PID1=$!
23
+
24
+ (rg "\.version\(['\"]" --type ts --type js -l 2>/dev/null || true) > "$CODE_OUT" &
25
+ PID2=$!
26
+
27
+ wait $PID1
28
+ wait $PID2
29
+
30
+ # 결과 출력
31
+ echo ""
32
+ echo "=== package.json files ==="
33
+ if [ -s "$PKG_OUT" ]; then
34
+ cat "$PKG_OUT"
35
+ else
36
+ echo "(none found)"
37
+ fi
38
+
39
+ echo ""
40
+ echo "=== Code files with .version() ==="
41
+ if [ -s "$CODE_OUT" ]; then
42
+ cat "$CODE_OUT"
43
+ else
44
+ echo "(none found)"
45
+ fi
46
+
47
+ echo ""
48
+ echo "=== All version files ==="
49
+ cat "$PKG_OUT" "$CODE_OUT" | sort -u