@kkelly-offical/kkcode 0.1.2

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 (196) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +445 -0
  3. package/package.json +46 -0
  4. package/src/agent/agent.mjs +170 -0
  5. package/src/agent/custom-agent-loader.mjs +158 -0
  6. package/src/agent/generator.mjs +115 -0
  7. package/src/agent/prompt/architect.txt +36 -0
  8. package/src/agent/prompt/build-fixer.txt +71 -0
  9. package/src/agent/prompt/build.txt +101 -0
  10. package/src/agent/prompt/compaction.txt +12 -0
  11. package/src/agent/prompt/explore.txt +29 -0
  12. package/src/agent/prompt/guide.txt +40 -0
  13. package/src/agent/prompt/longagent.txt +178 -0
  14. package/src/agent/prompt/plan.txt +50 -0
  15. package/src/agent/prompt/researcher.txt +23 -0
  16. package/src/agent/prompt/reviewer.txt +44 -0
  17. package/src/agent/prompt/security-reviewer.txt +62 -0
  18. package/src/agent/prompt/tdd-guide.txt +84 -0
  19. package/src/agent/prompt/title.txt +8 -0
  20. package/src/command/custom-commands.mjs +57 -0
  21. package/src/commands/agent.mjs +71 -0
  22. package/src/commands/audit.mjs +77 -0
  23. package/src/commands/background.mjs +86 -0
  24. package/src/commands/chat.mjs +114 -0
  25. package/src/commands/command.mjs +41 -0
  26. package/src/commands/config.mjs +44 -0
  27. package/src/commands/doctor.mjs +148 -0
  28. package/src/commands/hook.mjs +29 -0
  29. package/src/commands/init.mjs +141 -0
  30. package/src/commands/longagent.mjs +100 -0
  31. package/src/commands/mcp.mjs +89 -0
  32. package/src/commands/permission.mjs +36 -0
  33. package/src/commands/prompt.mjs +42 -0
  34. package/src/commands/review.mjs +266 -0
  35. package/src/commands/rule.mjs +34 -0
  36. package/src/commands/session.mjs +235 -0
  37. package/src/commands/theme.mjs +98 -0
  38. package/src/commands/usage.mjs +91 -0
  39. package/src/config/defaults.mjs +195 -0
  40. package/src/config/import-config.mjs +76 -0
  41. package/src/config/load-config.mjs +76 -0
  42. package/src/config/schema.mjs +509 -0
  43. package/src/context.mjs +40 -0
  44. package/src/core/constants.mjs +46 -0
  45. package/src/core/errors.mjs +57 -0
  46. package/src/core/events.mjs +29 -0
  47. package/src/core/types.mjs +57 -0
  48. package/src/github/api.mjs +78 -0
  49. package/src/github/auth.mjs +286 -0
  50. package/src/github/flow.mjs +298 -0
  51. package/src/github/workspace.mjs +212 -0
  52. package/src/index.mjs +82 -0
  53. package/src/knowledge/api-design.txt +9 -0
  54. package/src/knowledge/cpp.txt +10 -0
  55. package/src/knowledge/docker.txt +10 -0
  56. package/src/knowledge/dotnet.txt +9 -0
  57. package/src/knowledge/electron.txt +10 -0
  58. package/src/knowledge/flutter.txt +10 -0
  59. package/src/knowledge/go.txt +9 -0
  60. package/src/knowledge/graphql.txt +10 -0
  61. package/src/knowledge/java.txt +9 -0
  62. package/src/knowledge/kotlin.txt +10 -0
  63. package/src/knowledge/loader.mjs +125 -0
  64. package/src/knowledge/next.txt +8 -0
  65. package/src/knowledge/node.txt +8 -0
  66. package/src/knowledge/nuxt.txt +9 -0
  67. package/src/knowledge/php.txt +10 -0
  68. package/src/knowledge/python.txt +10 -0
  69. package/src/knowledge/react-native.txt +10 -0
  70. package/src/knowledge/react.txt +9 -0
  71. package/src/knowledge/ruby.txt +11 -0
  72. package/src/knowledge/rust.txt +9 -0
  73. package/src/knowledge/svelte.txt +9 -0
  74. package/src/knowledge/swift.txt +10 -0
  75. package/src/knowledge/tailwind.txt +10 -0
  76. package/src/knowledge/testing.txt +8 -0
  77. package/src/knowledge/typescript.txt +8 -0
  78. package/src/knowledge/vue.txt +9 -0
  79. package/src/mcp/client-http.mjs +157 -0
  80. package/src/mcp/client-sse.mjs +286 -0
  81. package/src/mcp/client-stdio.mjs +451 -0
  82. package/src/mcp/registry.mjs +394 -0
  83. package/src/mcp/stdio-framing.mjs +127 -0
  84. package/src/orchestration/background-manager.mjs +358 -0
  85. package/src/orchestration/background-worker.mjs +245 -0
  86. package/src/orchestration/longagent-manager.mjs +116 -0
  87. package/src/orchestration/stage-scheduler.mjs +489 -0
  88. package/src/orchestration/subagent-router.mjs +62 -0
  89. package/src/orchestration/task-scheduler.mjs +74 -0
  90. package/src/permission/engine.mjs +92 -0
  91. package/src/permission/exec-policy.mjs +372 -0
  92. package/src/permission/prompt.mjs +39 -0
  93. package/src/permission/rules.mjs +120 -0
  94. package/src/permission/workspace-trust.mjs +44 -0
  95. package/src/plugin/builtin-hooks/console-warn.mjs +41 -0
  96. package/src/plugin/builtin-hooks/extract-patterns.mjs +75 -0
  97. package/src/plugin/builtin-hooks/post-edit-format.mjs +57 -0
  98. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +61 -0
  99. package/src/plugin/builtin-hooks/strategic-compaction.mjs +38 -0
  100. package/src/plugin/hook-bus.mjs +154 -0
  101. package/src/provider/anthropic.mjs +389 -0
  102. package/src/provider/ollama.mjs +236 -0
  103. package/src/provider/openai-compatible.mjs +1 -0
  104. package/src/provider/openai.mjs +339 -0
  105. package/src/provider/retry-policy.mjs +68 -0
  106. package/src/provider/router.mjs +228 -0
  107. package/src/provider/sse.mjs +91 -0
  108. package/src/repl.mjs +2929 -0
  109. package/src/review/diff-parser.mjs +36 -0
  110. package/src/review/rejection-queue.mjs +62 -0
  111. package/src/review/review-store.mjs +21 -0
  112. package/src/review/risk-score.mjs +61 -0
  113. package/src/rules/load-rules.mjs +64 -0
  114. package/src/runtime.mjs +1 -0
  115. package/src/session/checkpoint.mjs +239 -0
  116. package/src/session/compaction.mjs +276 -0
  117. package/src/session/engine.mjs +225 -0
  118. package/src/session/instinct-manager.mjs +172 -0
  119. package/src/session/instruction-loader.mjs +25 -0
  120. package/src/session/longagent-plan.mjs +329 -0
  121. package/src/session/longagent-scaffold.mjs +100 -0
  122. package/src/session/longagent.mjs +1462 -0
  123. package/src/session/loop.mjs +905 -0
  124. package/src/session/memory-loader.mjs +75 -0
  125. package/src/session/project-context.mjs +367 -0
  126. package/src/session/prompt/anthropic.txt +151 -0
  127. package/src/session/prompt/beast.txt +37 -0
  128. package/src/session/prompt/max-steps.txt +6 -0
  129. package/src/session/prompt/plan.txt +9 -0
  130. package/src/session/prompt/qwen.txt +46 -0
  131. package/src/session/prompt-loader.mjs +18 -0
  132. package/src/session/recovery.mjs +52 -0
  133. package/src/session/store.mjs +503 -0
  134. package/src/session/system-prompt.mjs +260 -0
  135. package/src/session/task-validator.mjs +266 -0
  136. package/src/session/usability-gates.mjs +379 -0
  137. package/src/skill/builtin/backend-patterns.mjs +123 -0
  138. package/src/skill/builtin/commit.mjs +64 -0
  139. package/src/skill/builtin/debug.mjs +45 -0
  140. package/src/skill/builtin/frontend-patterns.mjs +120 -0
  141. package/src/skill/builtin/frontend.mjs +188 -0
  142. package/src/skill/builtin/init.mjs +220 -0
  143. package/src/skill/builtin/review.mjs +49 -0
  144. package/src/skill/builtin/security-checklist.mjs +80 -0
  145. package/src/skill/builtin/tdd.mjs +54 -0
  146. package/src/skill/generator.mjs +113 -0
  147. package/src/skill/registry.mjs +336 -0
  148. package/src/storage/audit-store.mjs +83 -0
  149. package/src/storage/event-log.mjs +82 -0
  150. package/src/storage/ghost-commit-store.mjs +235 -0
  151. package/src/storage/json-store.mjs +53 -0
  152. package/src/storage/paths.mjs +148 -0
  153. package/src/theme/color.mjs +64 -0
  154. package/src/theme/default-theme.mjs +29 -0
  155. package/src/theme/load-theme.mjs +71 -0
  156. package/src/theme/markdown.mjs +135 -0
  157. package/src/theme/schema.mjs +45 -0
  158. package/src/theme/status-bar.mjs +158 -0
  159. package/src/tool/audit-wrapper.mjs +38 -0
  160. package/src/tool/edit-transaction.mjs +126 -0
  161. package/src/tool/executor.mjs +109 -0
  162. package/src/tool/file-lock-manager.mjs +85 -0
  163. package/src/tool/git-auto.mjs +545 -0
  164. package/src/tool/git-full-auto.mjs +478 -0
  165. package/src/tool/image-util.mjs +276 -0
  166. package/src/tool/prompt/background_cancel.txt +1 -0
  167. package/src/tool/prompt/background_output.txt +1 -0
  168. package/src/tool/prompt/bash.txt +71 -0
  169. package/src/tool/prompt/codesearch.txt +18 -0
  170. package/src/tool/prompt/edit.txt +27 -0
  171. package/src/tool/prompt/enter_plan.txt +74 -0
  172. package/src/tool/prompt/exit_plan.txt +62 -0
  173. package/src/tool/prompt/glob.txt +33 -0
  174. package/src/tool/prompt/grep.txt +43 -0
  175. package/src/tool/prompt/list.txt +8 -0
  176. package/src/tool/prompt/multiedit.txt +20 -0
  177. package/src/tool/prompt/notebookedit.txt +21 -0
  178. package/src/tool/prompt/patch.txt +24 -0
  179. package/src/tool/prompt/question.txt +44 -0
  180. package/src/tool/prompt/read.txt +40 -0
  181. package/src/tool/prompt/task.txt +83 -0
  182. package/src/tool/prompt/todowrite.txt +117 -0
  183. package/src/tool/prompt/webfetch.txt +38 -0
  184. package/src/tool/prompt/websearch.txt +43 -0
  185. package/src/tool/prompt/write.txt +38 -0
  186. package/src/tool/prompt-loader.mjs +18 -0
  187. package/src/tool/question-prompt.mjs +86 -0
  188. package/src/tool/registry.mjs +1309 -0
  189. package/src/tool/task-tool.mjs +28 -0
  190. package/src/ui/activity-renderer.mjs +410 -0
  191. package/src/ui/repl-dashboard.mjs +357 -0
  192. package/src/usage/pricing.mjs +121 -0
  193. package/src/usage/usage-meter.mjs +113 -0
  194. package/src/util/git.mjs +496 -0
  195. package/src/util/template.mjs +10 -0
  196. package/src/util/yaml.mjs +100 -0
@@ -0,0 +1,298 @@
1
+ import { createInterface } from "node:readline/promises"
2
+ import { stdin as input, stdout as output } from "node:process"
3
+ import { ensureGitHubAuth } from "./auth.mjs"
4
+ import { listUserRepos, searchRepos, listBranches } from "./api.mjs"
5
+ import { ensureRepo, isLocalRepo, listLocalRepos, localBranches, repoLocalPath, removeLocalRepo, syncRepo, hasLocalChanges, getChangedFiles, generateCommitMessage, commitAndPush } from "./workspace.mjs"
6
+
7
+ function timeAgo(dateStr) {
8
+ if (!dateStr) return ""
9
+ const diff = Date.now() - new Date(dateStr).getTime()
10
+ const mins = Math.floor(diff / 60000)
11
+ if (mins < 1) return "just now"
12
+ if (mins < 60) return `${mins}m ago`
13
+ const hours = Math.floor(mins / 60)
14
+ if (hours < 24) return `${hours}h ago`
15
+ const days = Math.floor(hours / 24)
16
+ if (days < 30) return `${days}d ago`
17
+ return `${Math.floor(days / 30)}mo ago`
18
+ }
19
+
20
+ function padEnd(str, len) {
21
+ return str.length >= len ? str : str + " ".repeat(len - str.length)
22
+ }
23
+
24
+ function printRepoList(repos, localSet) {
25
+ const maxNameLen = Math.min(40, Math.max(...repos.map((r) => r.full_name.length)))
26
+ for (let i = 0; i < repos.length; i++) {
27
+ const r = repos[i]
28
+ const idx = ` \x1b[2m${String(i + 1).padStart(2)}.\x1b[0m `
29
+ const name = padEnd(r.full_name, maxNameLen + 2)
30
+ const local = localSet.has(r.full_name) ? "\x1b[32m[local]\x1b[0m " : " "
31
+ const stars = r.stars > 0 ? `\x1b[33m★${r.stars}\x1b[0m` : ""
32
+ const priv = r.private ? " \x1b[31m●\x1b[0m" : ""
33
+ const time = `\x1b[2m${timeAgo(r.pushed_at)}\x1b[0m`
34
+ console.log(`${idx}${name}${local}${stars}${priv} ${time}`)
35
+ }
36
+ }
37
+
38
+ function printBranchList(branches, localSet, defaultBranch) {
39
+ for (let i = 0; i < branches.length; i++) {
40
+ const b = branches[i]
41
+ const idx = ` \x1b[2m${String(i + 1).padStart(2)}.\x1b[0m `
42
+ const isDefault = b.name === defaultBranch ? " \x1b[2m(default)\x1b[0m" : ""
43
+ const local = localSet.has(b.name) ? " \x1b[32m[local]\x1b[0m" : ""
44
+ const prot = b.protected ? " \x1b[33m🔒\x1b[0m" : ""
45
+ console.log(`${idx}${b.name}${isDefault}${local}${prot}`)
46
+ }
47
+ }
48
+
49
+ async function prompt(rl, message) {
50
+ const answer = await rl.question(`\x1b[36m > ${message}\x1b[0m`)
51
+ return answer.trim()
52
+ }
53
+
54
+ export async function runGitHubFlow() {
55
+ const { token, login } = await ensureGitHubAuth()
56
+
57
+ const rl = createInterface({ input, output })
58
+
59
+ try {
60
+ // --- Repo selection ---
61
+ const localRepoList = await listLocalRepos()
62
+ const localRepoSet = new Set(localRepoList)
63
+
64
+ console.log(`\x1b[2m 👤 @${login}\x1b[0m`)
65
+ console.log(`\n\x1b[1m 📦 你的仓库:\x1b[0m \x1b[2m(输入序号选择,或输入关键词搜索)\x1b[0m\n`)
66
+
67
+ let repos = await listUserRepos(token)
68
+ printRepoList(repos, localRepoSet)
69
+
70
+ let selectedRepo = null
71
+ while (!selectedRepo) {
72
+ const input = await prompt(rl, "")
73
+ if (!input) continue
74
+
75
+ const num = parseInt(input, 10)
76
+ if (!isNaN(num) && num >= 1 && num <= repos.length) {
77
+ selectedRepo = repos[num - 1]
78
+ } else {
79
+ // Search
80
+ console.log(`\n\x1b[2m 搜索 "${input}" ...\x1b[0m\n`)
81
+ repos = await searchRepos(token, input, login)
82
+ if (repos.length === 0) {
83
+ console.log(" \x1b[31m未找到匹配的仓库\x1b[0m\n")
84
+ repos = await listUserRepos(token)
85
+ printRepoList(repos, localRepoSet)
86
+ } else {
87
+ printRepoList(repos, localRepoSet)
88
+ }
89
+ }
90
+ }
91
+
92
+ // --- Branch selection ---
93
+ console.log(`\n\x1b[1m 🌿 分支:\x1b[0m \x1b[2m${selectedRepo.full_name}\x1b[0m\n`)
94
+
95
+ const branches = await listBranches(token, selectedRepo.owner, selectedRepo.name)
96
+ const localPath = repoLocalPath(selectedRepo.full_name)
97
+ const existsLocally = await isLocalRepo(selectedRepo.full_name)
98
+ const localBranchSet = new Set(existsLocally ? await localBranches(localPath) : [])
99
+
100
+ // Sort: default branch first, then local branches, then the rest
101
+ branches.sort((a, b) => {
102
+ if (a.name === selectedRepo.default_branch) return -1
103
+ if (b.name === selectedRepo.default_branch) return 1
104
+ const aLocal = localBranchSet.has(a.name) ? 0 : 1
105
+ const bLocal = localBranchSet.has(b.name) ? 0 : 1
106
+ return aLocal - bLocal
107
+ })
108
+
109
+ printBranchList(branches, localBranchSet, selectedRepo.default_branch)
110
+
111
+ let selectedBranch = null
112
+ while (!selectedBranch) {
113
+ const input = await prompt(rl, "选择分支: ")
114
+ if (!input) continue
115
+
116
+ const num = parseInt(input, 10)
117
+ if (!isNaN(num) && num >= 1 && num <= branches.length) {
118
+ selectedBranch = branches[num - 1].name
119
+ } else {
120
+ // Direct branch name input
121
+ const match = branches.find((b) => b.name === input)
122
+ if (match) {
123
+ selectedBranch = match.name
124
+ } else {
125
+ console.log(` \x1b[31m未找到分支 "${input}"\x1b[0m`)
126
+ }
127
+ }
128
+ }
129
+
130
+ // --- Local repo action selection ---
131
+ let action = "clone"
132
+ if (existsLocally) {
133
+ console.log(`\n\x1b[33m 📦 本地已存在仓库 ${selectedRepo.full_name}\x1b[0m\n`)
134
+ console.log(" 请选择操作:")
135
+ console.log(" \x1b[36m1.\x1b[0m 使用本地仓库(不更新)")
136
+ console.log(" \x1b[36m2.\x1b[0m 同步云端最新代码(git pull)")
137
+ console.log(" \x1b[36m3.\x1b[0m 强制重新克隆(删除本地,重新下载)")
138
+ console.log("")
139
+
140
+ while (true) {
141
+ const choice = await prompt(rl, "选择操作 (1-3): ")
142
+ if (choice === "1") {
143
+ action = "use-local"
144
+ break
145
+ } else if (choice === "2") {
146
+ action = "sync"
147
+ break
148
+ } else if (choice === "3") {
149
+ action = "reclone"
150
+ break
151
+ } else {
152
+ console.log(" \x1b[31m请输入 1、2 或 3\x1b[0m")
153
+ }
154
+ }
155
+ }
156
+
157
+ rl.close()
158
+
159
+ // --- Execute action ---
160
+ let result
161
+ if (action === "use-local") {
162
+ console.log(`\n\x1b[36m 📂 使用本地仓库 ${selectedRepo.full_name}@${selectedBranch} ...\x1b[0m`)
163
+ const localPath = repoLocalPath(selectedRepo.full_name)
164
+ // Just checkout the branch if needed
165
+ const localBranchList = await localBranches(localPath)
166
+ if (!localBranchList.includes(selectedBranch)) {
167
+ console.log(` \x1b[33m⚠ 本地没有分支 ${selectedBranch},切换到默认分支\x1b[0m`)
168
+ selectedBranch = selectedRepo.default_branch
169
+ }
170
+ result = { path: localPath, isNew: false, action: "use-local" }
171
+ } else if (action === "sync") {
172
+ console.log(`\n\x1b[36m 📥 同步云端代码 ${selectedRepo.full_name}@${selectedBranch} ...\x1b[0m`)
173
+ result = await syncRepo({
174
+ fullName: selectedRepo.full_name,
175
+ branch: selectedBranch,
176
+ token
177
+ })
178
+ result.action = "sync"
179
+ } else if (action === "reclone") {
180
+ console.log(`\n\x1b[36m 🗑️ 删除本地仓库...\x1b[0m`)
181
+ await removeLocalRepo(selectedRepo.full_name)
182
+ console.log(`\x1b[36m 📥 重新克隆 ${selectedRepo.full_name}@${selectedBranch} ...\x1b[0m`)
183
+ result = await ensureRepo({
184
+ fullName: selectedRepo.full_name,
185
+ branch: selectedBranch,
186
+ token
187
+ })
188
+ result.action = "reclone"
189
+ } else {
190
+ // Clone new repo
191
+ console.log(`\n\x1b[36m 📥 克隆仓库 ${selectedRepo.full_name}@${selectedBranch} ...\x1b[0m`)
192
+ result = await ensureRepo({
193
+ fullName: selectedRepo.full_name,
194
+ branch: selectedBranch,
195
+ token
196
+ })
197
+ result.action = "clone"
198
+ }
199
+
200
+ console.log(` \x1b[2m→ ${result.path}\x1b[0m`)
201
+ console.log(`\x1b[32m ✓ 就绪\x1b[0m\n`)
202
+
203
+ return { cwd: result.path }
204
+ } finally {
205
+ rl.close()
206
+ }
207
+ }
208
+
209
+ /**
210
+ * REPL 退出后询问用户是否推送代码到 GitHub
211
+ * @param {Object} flowResult - runGitHubFlow 返回的结果
212
+ */
213
+ export async function promptPushChanges(flowResult) {
214
+ const { cwd } = flowResult
215
+ if (!cwd) return
216
+
217
+ // Check if there are any changes
218
+ const hasChanges = await hasLocalChanges(cwd)
219
+ if (!hasChanges) {
220
+ console.log("\n\x1b[2m 没有检测到代码变更\x1b[0m")
221
+ return
222
+ }
223
+
224
+ // Get changed files for display
225
+ const changedFiles = await getChangedFiles(cwd)
226
+ console.log("\n\x1b[33m 📦 检测到代码变更:\x1b[0m\n")
227
+ for (const file of changedFiles.slice(0, 10)) {
228
+ console.log(` \x1b[36m${file}\x1b[0m`)
229
+ }
230
+ if (changedFiles.length > 10) {
231
+ console.log(` \x1b[2m... 还有 ${changedFiles.length - 10} 个文件\x1b[0m`)
232
+ }
233
+ console.log("")
234
+
235
+ // Create readline interface
236
+ const rl = createInterface({ input, output })
237
+
238
+ try {
239
+ // Ask user what to do
240
+ console.log("\x1b[1m 是否推送到 GitHub?\x1b[0m\n")
241
+ console.log(" \x1b[36m1.\x1b[0m 推送变更到云端 (commit & push)")
242
+ console.log(" \x1b[36m2.\x1b[0m 放弃变更,保持云端版本")
243
+ console.log(" \x1b[36m3.\x1b[0m 稍后手动处理\n")
244
+
245
+ let choice = null
246
+ while (!choice) {
247
+ const answer = await rl.question(`\x1b[36m > 选择 (1-3): \x1b[0m`)
248
+ const trimmed = answer.trim()
249
+ if (trimmed === "1" || trimmed === "2" || trimmed === "3") {
250
+ choice = trimmed
251
+ } else {
252
+ console.log(" \x1b[31m请输入 1、2 或 3\x1b[0m")
253
+ }
254
+ }
255
+
256
+ if (choice === "1") {
257
+ // Get current branch
258
+ const { execFile } = await import("node:child_process")
259
+ const currentBranch = await new Promise((resolve) => {
260
+ execFile("git", ["branch", "--show-current"], { cwd }, (err, stdout) => {
261
+ resolve(err ? "main" : stdout.trim())
262
+ })
263
+ })
264
+
265
+ // Get commit message (use default or ask user)
266
+ const defaultMessage = await generateCommitMessage(cwd)
267
+ const customMessage = await rl.question(`\x1b[36m > 提交信息 [${defaultMessage}]: \x1b[0m`)
268
+ const message = customMessage.trim() || defaultMessage
269
+
270
+ // Get token
271
+ const { getStoredToken: getToken } = await import("./auth.mjs")
272
+ const { token } = await getToken() || {}
273
+ if (!token) {
274
+ console.log("\n \x1b[31m错误: 未找到 GitHub Token,无法推送\x1b[0m")
275
+ return
276
+ }
277
+
278
+ console.log("\n \x1b[36m📤 正在推送...\x1b[0m")
279
+ try {
280
+ await commitAndPush({ repoPath: cwd, message, branch: currentBranch, token })
281
+ console.log(`\x1b[32m ✓ 已成功推送到 GitHub (${currentBranch})\x1b[0m\n`)
282
+ } catch (error) {
283
+ console.log(`\x1b[31m ✗ 推送失败: ${error.message}\x1b[0m\n`)
284
+ }
285
+ } else if (choice === "2") {
286
+ console.log("\n \x1b[33m⚠ 已放弃本地变更\x1b[0m\n")
287
+ // Optionally reset the repo
288
+ const { execFile } = await import("node:child_process")
289
+ await new Promise((resolve) => {
290
+ execFile("git", ["reset", "--hard", "HEAD"], { cwd }, () => resolve())
291
+ })
292
+ } else {
293
+ console.log("\n \x1b[2m已跳过推送,您稍后可以使用 git 命令手动处理\x1b[0m\n")
294
+ }
295
+ } finally {
296
+ rl.close()
297
+ }
298
+ }
@@ -0,0 +1,212 @@
1
+ import { execFile } from "node:child_process"
2
+ import { mkdir, readdir, stat, rm } from "node:fs/promises"
3
+ import path from "node:path"
4
+ import { githubReposDir } from "../storage/paths.mjs"
5
+
6
+ function exec(cmd, args, opts = {}) {
7
+ return new Promise((resolve, reject) => {
8
+ execFile(cmd, args, { timeout: 120000, ...opts }, (err, stdout, stderr) => {
9
+ if (err) {
10
+ err.stderr = stderr
11
+ reject(err)
12
+ } else {
13
+ resolve(stdout.trim())
14
+ }
15
+ })
16
+ })
17
+ }
18
+
19
+ export function repoLocalPath(fullName) {
20
+ const [owner, repo] = fullName.split("/")
21
+ return path.join(githubReposDir(), owner, repo)
22
+ }
23
+
24
+ export async function isLocalRepo(fullName) {
25
+ try {
26
+ const p = repoLocalPath(fullName)
27
+ const s = await stat(path.join(p, ".git"))
28
+ return s.isDirectory()
29
+ } catch {
30
+ return false
31
+ }
32
+ }
33
+
34
+ export async function ensureRepo({ fullName, branch, token }) {
35
+ const localPath = repoLocalPath(fullName)
36
+ const exists = await isLocalRepo(fullName)
37
+
38
+ if (!exists) {
39
+ // Clone
40
+ await mkdir(path.dirname(localPath), { recursive: true })
41
+ const cloneUrl = `https://${token}@github.com/${fullName}.git`
42
+ await exec("git", ["clone", "--depth", "1", "-b", branch, "--single-branch", cloneUrl, localPath])
43
+ // Remove token from remote URL
44
+ const cleanUrl = `https://github.com/${fullName}.git`
45
+ await exec("git", ["remote", "set-url", "origin", cleanUrl], { cwd: localPath })
46
+ // Store token in git credential for this repo
47
+ await configureCredential(localPath, token)
48
+ return { path: localPath, isNew: true }
49
+ }
50
+
51
+ // Existing repo — fetch & checkout
52
+ await configureCredential(localPath, token)
53
+ await exec("git", ["fetch", "origin"], { cwd: localPath })
54
+
55
+ // Check if branch exists locally
56
+ const localBranchList = await localBranches(localPath)
57
+ if (localBranchList.includes(branch)) {
58
+ await exec("git", ["checkout", branch], { cwd: localPath })
59
+ await exec("git", ["pull", "--ff-only", "origin", branch], { cwd: localPath }).catch(() => {
60
+ // pull may fail if diverged, that's ok
61
+ })
62
+ } else {
63
+ // Checkout remote branch
64
+ await exec("git", ["checkout", "-b", branch, `origin/${branch}`], { cwd: localPath })
65
+ }
66
+
67
+ return { path: localPath, isNew: false }
68
+ }
69
+
70
+ async function configureCredential(repoPath, token) {
71
+ // Use store credential helper scoped to this repo
72
+ const credentialPath = path.join(repoPath, ".git", "kkcode-credentials")
73
+ const { writeFile } = await import("node:fs/promises")
74
+ await writeFile(credentialPath, `https://x-access-token:${token}@github.com\n`, "utf8")
75
+ await exec("git", ["config", "credential.helper", `store --file="${credentialPath}"`], { cwd: repoPath })
76
+ }
77
+
78
+ export async function listLocalRepos() {
79
+ const root = githubReposDir()
80
+ const repos = []
81
+ try {
82
+ const owners = await readdir(root)
83
+ for (const owner of owners) {
84
+ const ownerPath = path.join(root, owner)
85
+ const ownerStat = await stat(ownerPath).catch(() => null)
86
+ if (!ownerStat || !ownerStat.isDirectory()) continue
87
+ const names = await readdir(ownerPath)
88
+ for (const name of names) {
89
+ const gitDir = path.join(ownerPath, name, ".git")
90
+ const gitStat = await stat(gitDir).catch(() => null)
91
+ if (gitStat && gitStat.isDirectory()) {
92
+ repos.push(`${owner}/${name}`)
93
+ }
94
+ }
95
+ }
96
+ } catch {
97
+ // repos dir doesn't exist yet
98
+ }
99
+ return repos
100
+ }
101
+
102
+ export async function localBranches(repoPath) {
103
+ try {
104
+ const out = await exec("git", ["branch", "--list", "--format=%(refname:short)"], { cwd: repoPath })
105
+ return out.split("\n").filter(Boolean)
106
+ } catch {
107
+ return []
108
+ }
109
+ }
110
+
111
+ export async function removeLocalRepo(fullName) {
112
+ const localPath = repoLocalPath(fullName)
113
+ try {
114
+ await rm(localPath, { recursive: true, force: true })
115
+ return true
116
+ } catch {
117
+ return false
118
+ }
119
+ }
120
+
121
+ export async function syncRepo({ fullName, branch, token }) {
122
+ const localPath = repoLocalPath(fullName)
123
+ await configureCredential(localPath, token)
124
+ await exec("git", ["fetch", "origin"], { cwd: localPath })
125
+
126
+ // Check if branch exists locally
127
+ const localBranchList = await localBranches(localPath)
128
+ if (localBranchList.includes(branch)) {
129
+ await exec("git", ["checkout", branch], { cwd: localPath })
130
+ await exec("git", ["pull", "--ff-only", "origin", branch], { cwd: localPath }).catch(() => {
131
+ // pull may fail if diverged, that's ok
132
+ })
133
+ } else {
134
+ // Checkout remote branch
135
+ await exec("git", ["checkout", "-b", branch, `origin/${branch}`], { cwd: localPath })
136
+ }
137
+
138
+ return { path: localPath }
139
+ }
140
+
141
+ // Check if there are uncommitted changes
142
+ export async function hasLocalChanges(repoPath) {
143
+ try {
144
+ const status = await exec("git", ["status", "--porcelain"], { cwd: repoPath })
145
+ return status.length > 0
146
+ } catch {
147
+ return false
148
+ }
149
+ }
150
+
151
+ // Get diff stats for display
152
+ export async function getDiffStats(repoPath) {
153
+ try {
154
+ const stats = await exec("git", ["diff", "--stat", "HEAD"], { cwd: repoPath })
155
+ return stats
156
+ } catch {
157
+ return ""
158
+ }
159
+ }
160
+
161
+ // Get changed files list
162
+ export async function getChangedFiles(repoPath) {
163
+ try {
164
+ const output = await exec("git", ["status", "--short"], { cwd: repoPath })
165
+ return output.split("\n").filter(Boolean)
166
+ } catch {
167
+ return []
168
+ }
169
+ }
170
+
171
+ // Commit and push changes
172
+ export async function commitAndPush({ repoPath, message, branch, token }) {
173
+ await configureCredential(repoPath, token)
174
+
175
+ // Add all changes
176
+ await exec("git", ["add", "-A"], { cwd: repoPath })
177
+
178
+ // Commit
179
+ await exec("git", ["commit", "-m", message], { cwd: repoPath })
180
+
181
+ // Push
182
+ await exec("git", ["push", "origin", branch], { cwd: repoPath })
183
+
184
+ return true
185
+ }
186
+
187
+ // Generate a commit message based on changes
188
+ export async function generateCommitMessage(repoPath) {
189
+ try {
190
+ const files = await getChangedFiles(repoPath)
191
+ if (files.length === 0) return "Update files"
192
+
193
+ // Count types of changes
194
+ const added = files.filter(f => f.startsWith("A") || f.startsWith("??")).length
195
+ const modified = files.filter(f => f.startsWith("M")).length
196
+ const deleted = files.filter(f => f.startsWith("D")).length
197
+
198
+ if (added > 0 && modified === 0 && deleted === 0) {
199
+ return added === 1 ? "Add file" : `Add ${added} files`
200
+ }
201
+ if (modified > 0 && added === 0 && deleted === 0) {
202
+ return modified === 1 ? "Update file" : `Update ${modified} files`
203
+ }
204
+ if (deleted > 0 && added === 0 && modified === 0) {
205
+ return deleted === 1 ? "Remove file" : `Remove ${deleted} files`
206
+ }
207
+
208
+ return `Update files (+${added} ~${modified} -${deleted})`
209
+ } catch {
210
+ return "Update files"
211
+ }
212
+ }
package/src/index.mjs ADDED
@@ -0,0 +1,82 @@
1
+ #!/usr/bin/env node
2
+ import { Command } from "commander"
3
+ import { createThemeCommand } from "./commands/theme.mjs"
4
+ import { createUsageCommand } from "./commands/usage.mjs"
5
+ import { createReviewCommand } from "./commands/review.mjs"
6
+ import { createSessionCommand } from "./commands/session.mjs"
7
+ import { createChatCommand } from "./commands/chat.mjs"
8
+ import { createAgentCommand } from "./commands/agent.mjs"
9
+ import { createMcpCommand } from "./commands/mcp.mjs"
10
+ import { createPermissionCommand } from "./commands/permission.mjs"
11
+ import { createDoctorCommand } from "./commands/doctor.mjs"
12
+ import { createConfigCommand } from "./commands/config.mjs"
13
+ import { createPromptCommand } from "./commands/prompt.mjs"
14
+ import { createLongagentCommand } from "./commands/longagent.mjs"
15
+ import { createHookCommand } from "./commands/hook.mjs"
16
+ import { createCommandCommand } from "./commands/command.mjs"
17
+ import { createRuleCommand } from "./commands/rule.mjs"
18
+ import { createBackgroundCommand } from "./commands/background.mjs"
19
+ import { createInitCommand } from "./commands/init.mjs"
20
+ import { createAuditCommand } from "./commands/audit.mjs"
21
+ import { startRepl } from "./repl.mjs"
22
+
23
+ async function main() {
24
+ const hasTrust = process.argv.includes("--trust")
25
+ const hasGithub = process.argv.includes("--github")
26
+
27
+ if (hasGithub) {
28
+ const githubArgIndex = process.argv.indexOf("--github")
29
+ const nextArg = process.argv[githubArgIndex + 1]
30
+
31
+ if (nextArg === "logout") {
32
+ const { logout } = await import("./github/auth.mjs")
33
+ const success = await logout()
34
+ if (success) {
35
+ console.log("✓ 已登出 GitHub 账户")
36
+ } else {
37
+ console.log("⚠ 没有已登录的 GitHub 账户")
38
+ }
39
+ return
40
+ }
41
+
42
+ const { runGitHubFlow, promptPushChanges } = await import("./github/flow.mjs")
43
+ const result = await runGitHubFlow()
44
+ process.chdir(result.cwd)
45
+ await startRepl({ trust: hasTrust })
46
+ // After REPL exits, ask user if they want to push changes
47
+ await promptPushChanges(result)
48
+ return
49
+ }
50
+
51
+ if (process.argv.length <= 2 || (process.argv.length === 3 && hasTrust)) {
52
+ await startRepl({ trust: hasTrust })
53
+ return
54
+ }
55
+
56
+ const program = new Command()
57
+ program.name("kkcode").description("kkcode CLI").version("0.1.2")
58
+ program.addCommand(createChatCommand())
59
+ program.addCommand(createThemeCommand())
60
+ program.addCommand(createUsageCommand())
61
+ program.addCommand(createReviewCommand())
62
+ program.addCommand(createAgentCommand())
63
+ program.addCommand(createMcpCommand())
64
+ program.addCommand(createPermissionCommand())
65
+ program.addCommand(createDoctorCommand())
66
+ program.addCommand(createConfigCommand())
67
+ program.addCommand(createSessionCommand())
68
+ program.addCommand(createPromptCommand())
69
+ program.addCommand(createLongagentCommand())
70
+ program.addCommand(createHookCommand())
71
+ program.addCommand(createCommandCommand())
72
+ program.addCommand(createRuleCommand())
73
+ program.addCommand(createBackgroundCommand())
74
+ program.addCommand(createAuditCommand())
75
+ program.addCommand(createInitCommand())
76
+ await program.parseAsync(process.argv)
77
+ }
78
+
79
+ main().catch((error) => {
80
+ console.error(error.message)
81
+ process.exit(1)
82
+ })
@@ -0,0 +1,9 @@
1
+ API design conventions:
2
+ - RESTful: resources as nouns, HTTP verbs for actions
3
+ - Consistent response format: { data, error, meta }
4
+ - Input validation at the boundary (controller/handler level)
5
+ - Service layer for business logic, separate from HTTP handling
6
+ - Error responses: appropriate status codes + error message + error code
7
+ - Authentication middleware, not inline checks
8
+ - Pagination: cursor-based or offset-based, consistent across endpoints
9
+ - Versioning: URL prefix (/api/v1/) or header-based
@@ -0,0 +1,10 @@
1
+ C/C++ conventions for this project:
2
+ - C++: prefer modern C++ (C++17/20), RAII for resource management
3
+ - Smart pointers: std::unique_ptr (default), std::shared_ptr (shared ownership)
4
+ - Containers: std::vector, std::unordered_map, std::string
5
+ - Error handling: exceptions for C++, error codes for C, std::expected (C++23)
6
+ - Build: CMake (CMakeLists.txt) or Makefile
7
+ - Headers: #pragma once or include guards, forward declarations to reduce includes
8
+ - Naming: snake_case (Google/STL style) or camelCase (project convention)
9
+ - Testing: Google Test (gtest) or Catch2
10
+ - Memory: avoid raw new/delete, use containers and smart pointers
@@ -0,0 +1,10 @@
1
+ Docker conventions for this project:
2
+ - Multi-stage builds: separate build and runtime stages
3
+ - Use specific base image tags, not :latest
4
+ - .dockerignore to exclude node_modules, .git, .env
5
+ - One process per container, use docker-compose for multi-service
6
+ - COPY package*.json first, then npm install (layer caching)
7
+ - Non-root user: USER node or create dedicated user
8
+ - Health checks: HEALTHCHECK CMD curl -f http://localhost:PORT/health
9
+ - Environment variables for configuration, not hardcoded values
10
+ - docker-compose.yml: services, volumes, networks, depends_on
@@ -0,0 +1,9 @@
1
+ ASP.NET Core conventions for this project:
2
+ - Minimal API or Controller-based endpoints
3
+ - Dependency injection via builder.Services
4
+ - Entity Framework Core for data access with migrations
5
+ - DTOs separate from domain models
6
+ - Middleware pipeline for cross-cutting concerns
7
+ - appsettings.json for configuration, IOptions<T> pattern
8
+ - xUnit or NUnit for testing, Moq for mocking
9
+ - Async/await throughout, CancellationToken for cancellation
@@ -0,0 +1,10 @@
1
+ Electron conventions for this project:
2
+ - Process model: main process (Node.js) + renderer process (Chromium)
3
+ - IPC: ipcMain/ipcRenderer for inter-process communication
4
+ - Preload scripts: contextBridge.exposeInMainWorld() for secure API exposure
5
+ - contextIsolation: true, nodeIntegration: false (security defaults)
6
+ - BrowserWindow for app windows, Menu/Tray for system integration
7
+ - Auto-update: electron-updater for release distribution
8
+ - Packaging: electron-builder or electron-forge
9
+ - File system access through main process, not renderer
10
+ - Store user data in app.getPath('userData')
@@ -0,0 +1,10 @@
1
+ Flutter/Dart conventions for this project:
2
+ - Widget composition: small, focused widgets over large build() methods
3
+ - StatelessWidget for UI without state, StatefulWidget only when needed
4
+ - State management: Provider, Riverpod, or Bloc pattern
5
+ - Navigation: GoRouter or Navigator 2.0 for declarative routing
6
+ - Naming: snake_case for files, PascalCase for classes, camelCase for variables
7
+ - Async: Future<T> and Stream<T> with async/await
8
+ - Null safety: use required, late, and ? operators properly
9
+ - Testing: widget tests with testWidgets(), unit tests with test()
10
+ - Folder structure: lib/screens/, lib/widgets/, lib/models/, lib/services/
@@ -0,0 +1,9 @@
1
+ Go conventions for this project:
2
+ - Standard project layout: cmd/, internal/, pkg/
3
+ - Error handling: return errors, don't panic. Wrap with fmt.Errorf("context: %w", err)
4
+ - Interfaces: small, defined by consumer not provider
5
+ - Concurrency: goroutines + channels, or sync.WaitGroup
6
+ - Testing: table-driven tests, testify for assertions
7
+ - Naming: MixedCaps, no underscores. Exported = uppercase first letter
8
+ - Context: pass context.Context as first parameter
9
+ - Dependencies: go mod, minimal external deps
@@ -0,0 +1,10 @@
1
+ GraphQL conventions:
2
+ - Schema-first or code-first design, consistent across project
3
+ - Types: Query for reads, Mutation for writes, Subscription for real-time
4
+ - Naming: camelCase for fields, PascalCase for types, UPPER_CASE for enums
5
+ - Input types for mutation arguments: input CreateUserInput { ... }
6
+ - Pagination: Relay-style connections (edges, nodes, pageInfo) or simple offset
7
+ - Error handling: use errors array with extensions for error codes
8
+ - N+1 prevention: DataLoader for batching and caching
9
+ - Resolvers: thin resolvers that delegate to service layer
10
+ - Authentication: context-based, not per-resolver checks