@einja/dev-cli 0.1.40 → 0.1.44

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.
Files changed (207) hide show
  1. package/README.md +89 -1
  2. package/dist/cli.js +1 -0
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/init.d.ts.map +1 -1
  5. package/dist/commands/init.js +71 -1
  6. package/dist/commands/init.js.map +1 -1
  7. package/dist/commands/list.js.map +1 -1
  8. package/dist/commands/sync.d.ts.map +1 -1
  9. package/dist/commands/sync.js +187 -13
  10. package/dist/commands/sync.js.map +1 -1
  11. package/dist/commands/task-loop/lib/github-client.test.js.map +1 -1
  12. package/dist/commands/task-loop/lib/vibe-kanban-rest-client.js +2 -2
  13. package/dist/commands/task-loop/lib/vibe-kanban-rest-client.js.map +1 -1
  14. package/dist/lib/dependency-checker.d.ts.map +1 -1
  15. package/dist/lib/merger.d.ts +12 -0
  16. package/dist/lib/merger.d.ts.map +1 -1
  17. package/dist/lib/merger.js +28 -0
  18. package/dist/lib/merger.js.map +1 -1
  19. package/dist/lib/preset-update/cli-repo-detector.d.ts.map +1 -1
  20. package/dist/lib/preset-update/file-copier.d.ts.map +1 -1
  21. package/dist/lib/preset-update/file-copier.js +3 -3
  22. package/dist/lib/preset-update/file-copier.js.map +1 -1
  23. package/dist/lib/preset-update/preset-finder.d.ts.map +1 -1
  24. package/dist/lib/preset.d.ts.map +1 -1
  25. package/dist/lib/sync/category-validator.d.ts +1 -1
  26. package/dist/lib/sync/category-validator.d.ts.map +1 -1
  27. package/dist/lib/sync/category-validator.js +2 -1
  28. package/dist/lib/sync/category-validator.js.map +1 -1
  29. package/dist/lib/sync/category-validator.test.js +3 -1
  30. package/dist/lib/sync/category-validator.test.js.map +1 -1
  31. package/dist/lib/sync/conflict-reporter.d.ts.map +1 -1
  32. package/dist/lib/sync/diff-engine.d.ts.map +1 -1
  33. package/dist/lib/sync/file-filter.d.ts.map +1 -1
  34. package/dist/lib/sync/file-filter.js +1 -0
  35. package/dist/lib/sync/file-filter.js.map +1 -1
  36. package/dist/lib/sync/integration.test.js +255 -69
  37. package/dist/lib/sync/integration.test.js.map +1 -1
  38. package/dist/lib/sync/json-processor.d.ts +4 -4
  39. package/dist/lib/sync/json-processor.d.ts.map +1 -1
  40. package/dist/lib/sync/json-processor.js +11 -11
  41. package/dist/lib/sync/json-processor.js.map +1 -1
  42. package/dist/lib/sync/marker-processor.d.ts +60 -8
  43. package/dist/lib/sync/marker-processor.d.ts.map +1 -1
  44. package/dist/lib/sync/marker-processor.js +117 -26
  45. package/dist/lib/sync/marker-processor.js.map +1 -1
  46. package/dist/lib/sync/marker-processor.test.js +261 -40
  47. package/dist/lib/sync/marker-processor.test.js.map +1 -1
  48. package/dist/lib/sync/metadata-manager.d.ts +4 -0
  49. package/dist/lib/sync/metadata-manager.d.ts.map +1 -1
  50. package/dist/lib/sync/metadata-manager.js +15 -0
  51. package/dist/lib/sync/metadata-manager.js.map +1 -1
  52. package/dist/lib/sync/metadata-manager.test.js +69 -0
  53. package/dist/lib/sync/metadata-manager.test.js.map +1 -1
  54. package/dist/lib/sync/orphan-cleaner.d.ts +29 -0
  55. package/dist/lib/sync/orphan-cleaner.d.ts.map +1 -0
  56. package/dist/lib/sync/orphan-cleaner.js +80 -0
  57. package/dist/lib/sync/orphan-cleaner.js.map +1 -0
  58. package/dist/lib/sync/orphan-cleaner.test.d.ts +2 -0
  59. package/dist/lib/sync/orphan-cleaner.test.d.ts.map +1 -0
  60. package/dist/lib/sync/orphan-cleaner.test.js +169 -0
  61. package/dist/lib/sync/orphan-cleaner.test.js.map +1 -0
  62. package/dist/lib/sync/project-private-synchronizer.d.ts +52 -0
  63. package/dist/lib/sync/project-private-synchronizer.d.ts.map +1 -0
  64. package/dist/lib/sync/project-private-synchronizer.js +110 -0
  65. package/dist/lib/sync/project-private-synchronizer.js.map +1 -0
  66. package/dist/lib/sync/project-private-synchronizer.test.d.ts +2 -0
  67. package/dist/lib/sync/project-private-synchronizer.test.d.ts.map +1 -0
  68. package/dist/lib/sync/project-private-synchronizer.test.js +348 -0
  69. package/dist/lib/sync/project-private-synchronizer.test.js.map +1 -0
  70. package/dist/types/index.d.ts +1 -0
  71. package/dist/types/index.d.ts.map +1 -1
  72. package/dist/types/sync.d.ts +36 -6
  73. package/dist/types/sync.d.ts.map +1 -1
  74. package/dist/types/sync.js +2 -2
  75. package/dist/types/sync.js.map +1 -1
  76. package/package.json +5 -4
  77. package/presets/default/.claude/agents/einja/Explore.md +140 -0
  78. package/presets/default/.claude/agents/einja/backend-architect.md +21 -1
  79. package/presets/default/.claude/agents/einja/codex-agent.md +5 -1
  80. package/presets/default/.claude/agents/einja/design-engineer.md +5 -1
  81. package/presets/default/.claude/agents/einja/docs/docs-updater.md +7 -93
  82. package/presets/default/.claude/agents/einja/frontend-architect.md +21 -1
  83. package/presets/default/.claude/agents/einja/frontend-coder.md +5 -1
  84. package/presets/default/.claude/agents/einja/{specs/spec-design-generator.md → issue-specs/design-generator.md} +16 -8
  85. package/presets/default/.claude/agents/einja/{specs/spec-qa-generator.md → issue-specs/qa-generator.md} +10 -4
  86. package/presets/default/.claude/agents/einja/{specs/spec-requirements-generator.md → issue-specs/requirements-generator.md} +9 -6
  87. package/presets/default/.claude/agents/einja/{specs/spec-tasks-generator.md → issue-specs/tasks-generator.md} +19 -16
  88. package/presets/default/.claude/agents/einja/{specs/spec-tasks-validator.md → issue-specs/tasks-validator.md} +13 -9
  89. package/presets/default/.claude/agents/einja/issue-specs/ui-design-generator.md +114 -0
  90. package/presets/default/.claude/agents/einja/task/task-executer.md +64 -116
  91. package/presets/default/.claude/agents/einja/task/task-modification-analyzer.md +6 -2
  92. package/presets/default/.claude/agents/einja/task/task-qa.md +7 -3
  93. package/presets/default/.claude/agents/einja/task/task-reviewer.md +17 -1
  94. package/presets/default/.claude/commands/einja/einja-sync.md +124 -45
  95. package/presets/default/.claude/commands/einja/frontend-implement.md +3 -1
  96. package/presets/default/.claude/commands/einja/issue-exec.md +413 -0
  97. package/presets/default/.claude/commands/einja/start-dev.md +4 -0
  98. package/presets/default/.claude/commands/einja/sync-cursor-commands.md +10 -6
  99. package/presets/default/.claude/commands/einja/{update-docs-by-task-specs.md → update-docs-by-issue-specs.md} +61 -57
  100. package/presets/default/.claude/hooks/einja/plan-mode-skill-loader.sh +27 -0
  101. package/presets/default/.claude/settings.json +29 -5
  102. package/presets/default/.claude/skills/{einja-general-context-loader → _einja-general-context-loader}/SKILL.md +6 -2
  103. package/presets/default/.claude/skills/{einja-output-format → _einja-output-format}/SKILL.md +5 -1
  104. package/presets/default/.claude/skills/_einja-project-overview/SKILL.md +29 -0
  105. package/presets/default/.claude/skills/{einja-spec-context-loader → _einja-spec-context-loader}/SKILL.md +9 -5
  106. package/presets/default/.claude/skills/einja-coding-standards/references/testing-strategy.md +899 -0
  107. package/presets/default/.claude/skills/einja-conflict-resolver/SKILL.md +5 -1
  108. package/presets/default/.claude/skills/einja-create-pr/SKILL.md +138 -0
  109. package/presets/default/.claude/skills/einja-infra-maintenance/SKILL.md +779 -0
  110. package/presets/default/.claude/{commands/einja/spec-create.md → skills/einja-issue-spec-create/SKILL.md} +60 -23
  111. package/presets/default/.claude/skills/einja-issue-spec-generator/SKILL.md +105 -0
  112. package/presets/default/.claude/skills/einja-issue-spec-generator/references/format-rules.md +35 -0
  113. package/presets/default/.claude/skills/einja-issue-spec-validator/SKILL.md +130 -0
  114. package/presets/default/.claude/skills/einja-issue-spec-validator/references/validation-rules.md +52 -0
  115. package/presets/default/.claude/skills/einja-npm-release/SKILL.md +242 -0
  116. package/presets/default/.claude/skills/einja-skill-creator/SKILL.md +311 -263
  117. package/presets/default/.claude/skills/einja-skill-creator/agents/analyzer.md +274 -0
  118. package/presets/default/.claude/skills/einja-skill-creator/agents/comparator.md +202 -0
  119. package/presets/default/.claude/skills/einja-skill-creator/agents/grader.md +195 -0
  120. package/presets/default/.claude/skills/einja-skill-creator/assets/eval_review.html +146 -0
  121. package/presets/default/.claude/skills/einja-skill-creator/eval-viewer/generate_review.py +471 -0
  122. package/presets/default/.claude/skills/einja-skill-creator/eval-viewer/viewer.html +1325 -0
  123. package/presets/default/.claude/skills/einja-skill-creator/references/schemas.md +430 -0
  124. package/presets/default/.claude/skills/einja-skill-creator/scripts/aggregate_benchmark.py +401 -0
  125. package/presets/default/.claude/skills/einja-skill-creator/scripts/compare_runs.py +154 -0
  126. package/presets/default/.claude/skills/einja-skill-creator/scripts/generate_report.py +272 -0
  127. package/presets/default/.claude/skills/einja-skill-creator/scripts/improve_description.py +247 -0
  128. package/presets/default/.claude/skills/einja-skill-creator/scripts/init_skill.py +13 -19
  129. package/presets/default/.claude/skills/einja-skill-creator/scripts/package_skill.py +36 -7
  130. package/presets/default/.claude/skills/einja-skill-creator/scripts/run_eval.py +310 -0
  131. package/presets/default/.claude/skills/einja-skill-creator/scripts/run_loop.py +375 -0
  132. package/presets/default/.claude/skills/einja-skill-creator/scripts/utils.py +48 -0
  133. package/presets/default/.claude/skills/einja-skill-first/SKILL.md +265 -0
  134. package/presets/default/.claude/skills/einja-subagent-question-protocol/SKILL.md +98 -0
  135. package/presets/default/.claude/skills/einja-task-commit/SKILL.md +11 -7
  136. package/presets/default/.claude/{commands/einja/task-exec.md → skills/einja-task-exec/SKILL.md} +106 -89
  137. package/presets/default/.claude/skills/einja-task-qa/SKILL.md +8 -4
  138. package/presets/default/.claude/skills/einja-task-qa/references/troubleshooting.md +1 -1
  139. package/presets/default/.claude/skills/einja-task-qa/references/usage-patterns.md +2 -2
  140. package/presets/default/.claude/skills/einja-team-exec/SKILL.md +165 -0
  141. package/presets/default/.envrc +5 -0
  142. package/presets/default/.mcp.json +2 -12
  143. package/presets/default/CLAUDE.md.template +45 -8
  144. package/presets/default/docs/einja/example/specs/issues/issue999-example-task/tasks.md +1 -1
  145. package/presets/default/docs/einja/instructions/deployment-setup.md +4 -9
  146. package/presets/default/docs/einja/instructions/environment-setup.md +3 -8
  147. package/presets/default/docs/einja/instructions/issue-exec-workflow.md +276 -0
  148. package/presets/default/docs/einja/instructions/local-server-environment-and-worktree.md +71 -9
  149. package/presets/default/docs/einja/instructions/neon-cli-reference.md +3 -8
  150. package/presets/default/docs/einja/instructions/setup-flow.md +279 -0
  151. package/presets/default/docs/einja/instructions/task-execute.md +63 -68
  152. package/presets/default/docs/einja/instructions/vercel-cli-reference.md +17 -10
  153. package/presets/default/docs/einja/steering/README.md +11 -11
  154. package/presets/default/docs/einja/steering/acceptance-criteria-and-qa-guide.md +4 -9
  155. package/presets/default/docs/einja/steering/architecture.md +3 -8
  156. package/presets/default/docs/einja/steering/branch-strategy.md +63 -70
  157. package/presets/default/docs/einja/steering/commit-rules.md +3 -8
  158. package/presets/default/docs/einja/steering/db-schema-design.md +3 -8
  159. package/presets/default/docs/einja/steering/development/api-development.md +3 -8
  160. package/presets/default/docs/einja/steering/development/backend-architecture.md +3 -8
  161. package/presets/default/docs/einja/steering/development/coding-standards.md +723 -0
  162. package/presets/default/docs/einja/steering/development/component-design.md +502 -0
  163. package/presets/default/docs/einja/steering/development/database-guidelines.md +2 -2
  164. package/presets/default/docs/einja/steering/development/frontend-development.md +3 -8
  165. package/presets/default/docs/einja/steering/development/playwright-guidelines.md +59 -0
  166. package/presets/default/docs/einja/steering/development/review-guidelines.md +3 -8
  167. package/presets/default/docs/einja/steering/development/testing-strategy.md +3 -8
  168. package/presets/default/docs/einja/steering/development-workflow.md +155 -140
  169. package/presets/default/docs/einja/steering/infrastructure/deployment.md +156 -55
  170. package/presets/default/docs/einja/steering/infrastructure/environment-variables.md +4 -8
  171. package/presets/default/docs/einja/steering/product.md +3 -8
  172. package/presets/default/docs/einja/steering/task-management.md +22 -110
  173. package/presets/default/scripts/ensure-serena.sh +75 -0
  174. package/presets/default/scripts/env-rotate-secrets.ts +396 -0
  175. package/presets/default/scripts/env-show.ts +130 -0
  176. package/presets/default/scripts/env.ts +479 -0
  177. package/presets/default/scripts/init-github.ts +363 -0
  178. package/presets/default/scripts/init.sh +98 -0
  179. package/presets/default/scripts/lib/env-common.ts +108 -0
  180. package/presets/default/scripts/lib/worktree-config.ts +64 -0
  181. package/presets/default/scripts/setup-dev.ts +655 -0
  182. package/presets/default/scripts/stop-serena.sh +25 -0
  183. package/presets/default/scripts/worktree/dev.ts +872 -0
  184. package/dist/lib/sync/seed-synchronizer.d.ts +0 -27
  185. package/dist/lib/sync/seed-synchronizer.d.ts.map +0 -1
  186. package/dist/lib/sync/seed-synchronizer.js +0 -72
  187. package/dist/lib/sync/seed-synchronizer.js.map +0 -1
  188. package/dist/lib/sync/seed-synchronizer.test.d.ts +0 -2
  189. package/dist/lib/sync/seed-synchronizer.test.d.ts.map +0 -1
  190. package/dist/lib/sync/seed-synchronizer.test.js +0 -147
  191. package/dist/lib/sync/seed-synchronizer.test.js.map +0 -1
  192. package/presets/default/.claude/agents/einja/git/conflict-resolver.md +0 -148
  193. package/presets/default/.claude/hooks/einja/validate-git-commit.sh +0 -239
  194. package/presets/default/.claude/skills/einja-api-development/SKILL.md +0 -14
  195. package/presets/default/.claude/skills/einja-backend-architecture/SKILL.md +0 -18
  196. package/presets/default/.claude/skills/einja-coding-standards/SKILL.md +0 -132
  197. package/presets/default/.claude/skills/einja-coding-standards/references/import-conventions.md +0 -69
  198. package/presets/default/.claude/skills/einja-coding-standards/references/naming-conventions.md +0 -107
  199. package/presets/default/.claude/skills/einja-coding-standards/references/prohibited-patterns.md +0 -169
  200. package/presets/default/.claude/skills/einja-coding-standards/references/typescript-rules.md +0 -247
  201. package/presets/default/.claude/skills/einja-component-design/SKILL.md +0 -109
  202. package/presets/default/.claude/skills/einja-component-design/references/directory-structure.md +0 -117
  203. package/presets/default/.claude/skills/einja-component-design/references/props-patterns.md +0 -159
  204. package/presets/default/.claude/skills/einja-component-design/references/styling-guide.md +0 -122
  205. package/presets/default/.claude/skills/einja-frontend-development/SKILL.md +0 -14
  206. package/presets/default/.claude/skills/einja-project-overview/SKILL.md +0 -35
  207. package/presets/default/docs/einja/instructions/task-vibe-kanban-loop.md +0 -565
@@ -0,0 +1,479 @@
1
+ /**
2
+ * 環境変数設定ウィザード
3
+ *
4
+ * 対話的に環境変数を設定・更新するCLIツール
5
+ * 使用方法: pnpm env
6
+ */
7
+
8
+ import { execSync } from "node:child_process";
9
+ import fs from "node:fs";
10
+ import path from "node:path";
11
+ import * as p from "@clack/prompts";
12
+ import {
13
+ type EnvironmentConfig,
14
+ ENVIRONMENTS,
15
+ parseEnvFile,
16
+ getPrivateKey,
17
+ ENV_KEYS_PATH,
18
+ } from "./lib/env-common.js";
19
+
20
+ const cwd = process.cwd();
21
+
22
+ // ファイルパス
23
+ const ENV_PATH = path.join(cwd, ".env");
24
+ const ENV_LOCAL_PATH = path.join(cwd, ".env.local");
25
+ const ENV_PERSONAL_PATH = path.join(cwd, ".env.personal");
26
+ const ENV_PERSONAL_EXAMPLE_PATH = path.join(cwd, ".env.personal.example");
27
+
28
+ /**
29
+ * 環境変数ファイルに値を設定
30
+ */
31
+ function setEnvValue(filePath: string, key: string, value: string): void {
32
+ let content = "";
33
+ if (fs.existsSync(filePath)) {
34
+ content = fs.readFileSync(filePath, "utf-8");
35
+ }
36
+
37
+ const regex = new RegExp(`^${key}=.*$`, "m");
38
+ if (regex.test(content)) {
39
+ content = content.replace(regex, `${key}=${value}`);
40
+ } else {
41
+ content = content.trim() + `\n${key}=${value}\n`;
42
+ }
43
+
44
+ fs.writeFileSync(filePath, content);
45
+ }
46
+
47
+ /**
48
+ * 現在の環境変数の状態を表示
49
+ */
50
+ function showStatus(): void {
51
+ p.note(
52
+ [
53
+ "📁 ファイル状態:",
54
+ ` .env : ${fs.existsSync(ENV_PATH) ? "✅ 存在" : "❌ なし"}`,
55
+ ` .env.local : ${fs.existsSync(ENV_LOCAL_PATH) ? "✅ 存在" : "❌ なし"}`,
56
+ ` .env.personal : ${fs.existsSync(ENV_PERSONAL_PATH) ? "✅ 存在" : "❌ なし"}`,
57
+ ` .env.keys : ${fs.existsSync(ENV_KEYS_PATH) ? "✅ 存在" : "❌ なし"}`,
58
+ "",
59
+ "🔑 主要な環境変数:",
60
+ ].join("\n"),
61
+ "環境変数の状態"
62
+ );
63
+
64
+ const envPersonal = parseEnvFile(ENV_PERSONAL_PATH);
65
+ const env = parseEnvFile(ENV_PATH);
66
+
67
+ const checkVars = [
68
+ { key: "GITHUB_TOKEN", file: ".env.personal" },
69
+ { key: "DATABASE_URL", file: ".env" },
70
+ { key: "AUTH_SECRET", file: ".env" },
71
+ ];
72
+
73
+ for (const { key, file } of checkVars) {
74
+ const value = file === ".env.personal" ? envPersonal[key] : env[key];
75
+ const status = value ? "✅ 設定済み" : "❌ 未設定";
76
+ const masked = value ? `${value.slice(0, 8)}...` : "-";
77
+ console.log(` ${key}: ${status} (${masked})`);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * 個人トークンを設定
83
+ */
84
+ async function setupPersonalTokens(): Promise<void> {
85
+ // .env.personal がなければ作成
86
+ if (!fs.existsSync(ENV_PERSONAL_PATH)) {
87
+ if (fs.existsSync(ENV_PERSONAL_EXAMPLE_PATH)) {
88
+ fs.copyFileSync(ENV_PERSONAL_EXAMPLE_PATH, ENV_PERSONAL_PATH);
89
+ p.log.success(".env.personal を作成しました");
90
+ } else {
91
+ fs.writeFileSync(ENV_PERSONAL_PATH, "# 個人用トークン\n");
92
+ p.log.success(".env.personal を新規作成しました");
93
+ }
94
+ }
95
+
96
+ const current = parseEnvFile(ENV_PERSONAL_PATH);
97
+
98
+ // GITHUB_TOKEN
99
+ const currentGithubToken = current.GITHUB_TOKEN;
100
+ const githubTokenStatus = currentGithubToken
101
+ ? `現在: ${currentGithubToken.slice(0, 8)}...`
102
+ : "未設定";
103
+
104
+ const updateGithubToken = await p.confirm({
105
+ message: `GITHUB_TOKEN を設定しますか? (${githubTokenStatus})`,
106
+ initialValue: !currentGithubToken,
107
+ });
108
+
109
+ if (p.isCancel(updateGithubToken)) {
110
+ p.cancel("キャンセルしました");
111
+ process.exit(0);
112
+ }
113
+
114
+ if (updateGithubToken) {
115
+ p.log.info("GitHub Personal Access Token を入力してください");
116
+ p.log.info("取得方法: https://github.com/settings/tokens/new");
117
+ p.log.info("必要なスコープ: repo, read:org");
118
+
119
+ const token = await p.password({
120
+ message: "GITHUB_TOKEN:",
121
+ });
122
+
123
+ if (p.isCancel(token)) {
124
+ p.cancel("キャンセルしました");
125
+ process.exit(0);
126
+ }
127
+
128
+ if (token) {
129
+ setEnvValue(ENV_PERSONAL_PATH, "GITHUB_TOKEN", token);
130
+ p.log.success("GITHUB_TOKEN を設定しました");
131
+
132
+ // トークンの検証を試みる
133
+ try {
134
+ const result = execSync(
135
+ `curl -s -o /dev/null -w "%{http_code}" -H "Authorization: token ${token}" https://api.github.com/user`,
136
+ { encoding: "utf-8" }
137
+ ).trim();
138
+ if (result === "200") {
139
+ p.log.success("✅ トークンの検証に成功しました");
140
+ } else {
141
+ p.log.warn(`⚠️ トークンの検証に失敗しました (HTTP ${result})`);
142
+ }
143
+ } catch {
144
+ p.log.warn("⚠️ トークンの検証をスキップしました");
145
+ }
146
+ }
147
+ }
148
+
149
+ // 他のトークン(オプション)
150
+ const addMore = await p.confirm({
151
+ message: "他のトークンも設定しますか? (OPENAI_API_KEY, ANTHROPIC_API_KEY等)",
152
+ initialValue: false,
153
+ });
154
+
155
+ if (p.isCancel(addMore)) {
156
+ p.cancel("キャンセルしました");
157
+ process.exit(0);
158
+ }
159
+
160
+ if (addMore) {
161
+ const keyName = await p.text({
162
+ message: "環境変数名を入力:",
163
+ placeholder: "OPENAI_API_KEY",
164
+ });
165
+
166
+ if (p.isCancel(keyName) || !keyName) {
167
+ return;
168
+ }
169
+
170
+ const keyValue = await p.password({
171
+ message: `${keyName}:`,
172
+ });
173
+
174
+ if (p.isCancel(keyValue)) {
175
+ return;
176
+ }
177
+
178
+ if (keyValue) {
179
+ setEnvValue(ENV_PERSONAL_PATH, keyName, keyValue);
180
+ p.log.success(`${keyName} を設定しました`);
181
+ }
182
+ }
183
+
184
+ p.note(
185
+ "direnv allow を実行するか、新しいターミナルを開くと反映されます",
186
+ "💡 次のステップ"
187
+ );
188
+ }
189
+
190
+
191
+ /**
192
+ * 環境設定を変更(汎用)
193
+ */
194
+ async function updateEnvironmentSettings(env: EnvironmentConfig): Promise<void> {
195
+ const envFilePath = path.join(cwd, env.file);
196
+
197
+ if (!fs.existsSync(envFilePath)) {
198
+ p.log.error(`${env.file} が見つかりません`);
199
+ return;
200
+ }
201
+
202
+ if (!fs.existsSync(ENV_KEYS_PATH)) {
203
+ p.log.error(".env.keys が見つかりません(秘密鍵が必要です)");
204
+ p.log.info("チームから .env.keys を共有してもらってください");
205
+ return;
206
+ }
207
+
208
+ const privateKey = getPrivateKey(env.privateKeyEnv);
209
+ if (!privateKey) {
210
+ p.log.error(`.env.keys に ${env.privateKeyEnv} が見つかりません`);
211
+ return;
212
+ }
213
+
214
+ // 本番環境の場合は追加確認
215
+ if (env.name === "production") {
216
+ p.log.warn("⚠️ 本番環境の設定を変更しようとしています");
217
+ const confirmProd = await p.confirm({
218
+ message: "本当に本番環境の設定を変更しますか?",
219
+ initialValue: false,
220
+ });
221
+ if (p.isCancel(confirmProd) || !confirmProd) {
222
+ p.cancel("キャンセルしました");
223
+ return;
224
+ }
225
+ }
226
+
227
+ p.note(
228
+ [
229
+ `${env.description}(${env.file})を変更します。`,
230
+ "",
231
+ "手順:",
232
+ `1. ${env.file} を復号`,
233
+ "2. エディタで編集",
234
+ "3. 再暗号化",
235
+ "4. git commit & push",
236
+ ].join("\n"),
237
+ `📝 ${env.description}の変更`
238
+ );
239
+
240
+ const proceed = await p.confirm({
241
+ message: "続行しますか?",
242
+ initialValue: true,
243
+ });
244
+
245
+ if (p.isCancel(proceed) || !proceed) {
246
+ p.cancel("キャンセルしました");
247
+ return;
248
+ }
249
+
250
+ const spinner = p.spinner();
251
+ const tmpPath = path.join(cwd, `${env.file}.tmp`);
252
+ const backupPath = path.join(cwd, `${env.file}.bak`);
253
+
254
+ // dotenvx実行時の環境変数
255
+ const dotenvxEnv = { ...process.env, [env.privateKeyEnv]: privateKey };
256
+
257
+ try {
258
+ // 1. 復号
259
+ spinner.start(`${env.file} を復号中...`);
260
+ const decrypted = execSync(`dotenvx decrypt -f ${env.file} --stdout`, {
261
+ cwd,
262
+ encoding: "utf-8",
263
+ env: dotenvxEnv,
264
+ });
265
+ fs.writeFileSync(tmpPath, decrypted);
266
+ spinner.stop(`${env.file} を復号しました`);
267
+
268
+ // 2. エディタで開く
269
+ const editor = process.env.EDITOR || "vi";
270
+
271
+ // vi/vimの場合はファイル先頭に使い方ヘルプをコメントとして追加
272
+ const vimHelpMarker = "# === ↓↓↓ ここから下を編集(この行より上は保存時に自動削除)↓↓↓ ===";
273
+ if (editor === "vi" || editor === "vim") {
274
+ const vimHelp = `# ┌─────────────────────────────────────────────────┐
275
+ # │ vi/vim の基本操作 │
276
+ # ├─────────────────────────────────────────────────┤
277
+ # │ 【編集モードに入る】 │
278
+ # │ i ... カーソル位置から入力開始 │
279
+ # │ a ... カーソルの次の位置から入力開始 │
280
+ # │ o ... 次の行に新しい行を挿入して入力開始 │
281
+ # │ │
282
+ # │ 【編集モードから出る】 │
283
+ # │ Esc ... ノーマルモードに戻る │
284
+ # │ │
285
+ # │ 【保存・終了】(Escを押してから) │
286
+ # │ :wq ... 保存して終了 │
287
+ # │ :w ... 保存のみ │
288
+ # │ :q! ... 保存せず強制終了 │
289
+ # │ │
290
+ # │ 【カーソル移動】 │
291
+ # │ h j k l または 矢印キー │
292
+ # └─────────────────────────────────────────────────┘
293
+ ${vimHelpMarker}
294
+
295
+ `;
296
+ const currentContent = fs.readFileSync(tmpPath, "utf-8");
297
+ fs.writeFileSync(tmpPath, vimHelp + currentContent);
298
+ }
299
+
300
+ p.log.info(`${editor} で ${env.file}.tmp を開きます...`);
301
+
302
+ execSync(`${editor} ${env.file}.tmp`, {
303
+ cwd,
304
+ stdio: "inherit",
305
+ });
306
+
307
+ // 3. 編集確認
308
+ const confirmSave = await p.confirm({
309
+ message: "変更を保存しますか?",
310
+ initialValue: true,
311
+ });
312
+
313
+ if (p.isCancel(confirmSave) || !confirmSave) {
314
+ fs.unlinkSync(tmpPath);
315
+ p.log.info("変更をキャンセルしました");
316
+ return;
317
+ }
318
+
319
+ // 4. バックアップ作成 → リネーム → 再暗号化
320
+ spinner.start("再暗号化中...");
321
+
322
+ // vi/vimヘルプコメントを削除(ファイル先頭から)
323
+ if (editor === "vi" || editor === "vim") {
324
+ let content = fs.readFileSync(tmpPath, "utf-8");
325
+ const markerIndex = content.indexOf(vimHelpMarker);
326
+ if (markerIndex !== -1) {
327
+ // マーカー行の次の改行以降を残す
328
+ const afterMarker = markerIndex + vimHelpMarker.length;
329
+ content = content.substring(afterMarker).replace(/^\n+/, "");
330
+ }
331
+ fs.writeFileSync(tmpPath, content);
332
+ }
333
+
334
+ // 元のファイルをバックアップ
335
+ fs.copyFileSync(envFilePath, backupPath);
336
+
337
+ try {
338
+ // テンポラリファイルを元のファイルにリネーム
339
+ fs.unlinkSync(envFilePath);
340
+ fs.renameSync(tmpPath, envFilePath);
341
+
342
+ // 暗号化
343
+ execSync(`dotenvx encrypt -f ${env.file}`, {
344
+ cwd,
345
+ stdio: "pipe",
346
+ env: dotenvxEnv,
347
+ });
348
+
349
+ // 成功したらバックアップを削除
350
+ fs.unlinkSync(backupPath);
351
+ spinner.stop("再暗号化が完了しました");
352
+ } catch (encryptError) {
353
+ // 暗号化失敗時はバックアップから復元
354
+ spinner.stop("暗号化に失敗しました");
355
+ if (fs.existsSync(backupPath)) {
356
+ fs.copyFileSync(backupPath, envFilePath);
357
+ fs.unlinkSync(backupPath);
358
+ p.log.info("元のファイルを復元しました");
359
+ }
360
+ throw encryptError;
361
+ }
362
+
363
+ p.note(
364
+ [
365
+ `git add ${env.file}`,
366
+ `git commit -m "chore: ${env.description}設定を更新"`,
367
+ "git push",
368
+ "",
369
+ env.name === "local"
370
+ ? "チームメンバーは git pull 後に pnpm dev:setup で反映"
371
+ : "変更はデプロイ時に反映されます",
372
+ ].join("\n"),
373
+ "💡 次のステップ"
374
+ );
375
+ } catch (error) {
376
+ spinner.stop("エラーが発生しました");
377
+ // テンポラリファイルを削除
378
+ if (fs.existsSync(tmpPath)) {
379
+ fs.unlinkSync(tmpPath);
380
+ }
381
+ // バックアップも削除
382
+ if (fs.existsSync(backupPath)) {
383
+ fs.unlinkSync(backupPath);
384
+ }
385
+ throw error;
386
+ }
387
+ }
388
+
389
+ /**
390
+ * 環境選択メニューを表示
391
+ */
392
+ async function selectEnvironment(): Promise<void> {
393
+ // 利用可能な環境をチェック
394
+ const availableEnvs = ENVIRONMENTS.filter((env) => {
395
+ const envFilePath = path.join(cwd, env.file);
396
+ const hasFile = fs.existsSync(envFilePath);
397
+ const hasKey = getPrivateKey(env.privateKeyEnv) !== null;
398
+ return hasFile && hasKey;
399
+ });
400
+
401
+ if (availableEnvs.length === 0) {
402
+ p.log.error("編集可能な環境がありません");
403
+ p.log.info("環境ファイルと秘密鍵が必要です");
404
+ return;
405
+ }
406
+
407
+ const envOptions = availableEnvs.map((env) => ({
408
+ value: env.name,
409
+ label: env.description,
410
+ hint: env.file,
411
+ }));
412
+
413
+ const selectedEnv = await p.select({
414
+ message: "編集する環境を選択してください",
415
+ options: envOptions,
416
+ });
417
+
418
+ if (p.isCancel(selectedEnv)) {
419
+ p.cancel("キャンセルしました");
420
+ return;
421
+ }
422
+
423
+ const env = ENVIRONMENTS.find((e) => e.name === selectedEnv);
424
+ if (env) {
425
+ await updateEnvironmentSettings(env);
426
+ }
427
+ }
428
+
429
+ /**
430
+ * メイン処理
431
+ */
432
+ async function main(): Promise<void> {
433
+ p.intro("🔧 環境変数設定ウィザード");
434
+
435
+ const action = await p.select({
436
+ message: "何をしますか?",
437
+ options: [
438
+ {
439
+ value: "personal",
440
+ label: "個人トークンを設定",
441
+ hint: "GITHUB_TOKEN, API_KEY等",
442
+ },
443
+ {
444
+ value: "environment",
445
+ label: "環境設定を変更",
446
+ hint: "local, staging, production, ci 等",
447
+ },
448
+ {
449
+ value: "status",
450
+ label: "現在の状態を確認",
451
+ hint: "環境変数の設定状況を表示",
452
+ },
453
+ ],
454
+ });
455
+
456
+ if (p.isCancel(action)) {
457
+ p.cancel("キャンセルしました");
458
+ process.exit(0);
459
+ }
460
+
461
+ switch (action) {
462
+ case "personal":
463
+ await setupPersonalTokens();
464
+ break;
465
+ case "environment":
466
+ await selectEnvironment();
467
+ break;
468
+ case "status":
469
+ showStatus();
470
+ break;
471
+ }
472
+
473
+ p.outro("✅ 完了");
474
+ }
475
+
476
+ main().catch((error: unknown) => {
477
+ p.log.error(`エラーが発生しました: ${error}`);
478
+ process.exit(1);
479
+ });