@kkelly-offical/kkcode 0.1.7 → 0.2.3-preview.1

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 (166) hide show
  1. package/LICENSE +674 -674
  2. package/README.md +474 -387
  3. package/package.json +50 -46
  4. package/src/agent/agent.mjs +228 -220
  5. package/src/agent/custom-agent-loader.mjs +6 -3
  6. package/src/agent/generator.mjs +2 -2
  7. package/src/agent/prompt/assistant.txt +12 -0
  8. package/src/agent/prompt/bug-hunter.txt +89 -89
  9. package/src/agent/prompt/frontend-designer.txt +58 -58
  10. package/src/agent/prompt/guide.txt +1 -1
  11. package/src/agent/prompt/longagent-blueprint-agent.txt +83 -83
  12. package/src/agent/prompt/longagent-coding-agent.txt +37 -37
  13. package/src/agent/prompt/longagent-debugging-agent.txt +46 -46
  14. package/src/agent/prompt/longagent-preview-agent.txt +63 -63
  15. package/src/command/custom-commands.mjs +2 -2
  16. package/src/commands/agent.mjs +1 -1
  17. package/src/commands/background.mjs +145 -4
  18. package/src/commands/chat.mjs +117 -76
  19. package/src/commands/config.mjs +148 -1
  20. package/src/commands/doctor.mjs +30 -6
  21. package/src/commands/init.mjs +32 -6
  22. package/src/commands/longagent.mjs +117 -0
  23. package/src/commands/mcp.mjs +275 -43
  24. package/src/commands/permission.mjs +1 -1
  25. package/src/commands/session.mjs +195 -140
  26. package/src/commands/skill.mjs +63 -0
  27. package/src/commands/theme.mjs +1 -1
  28. package/src/commands/update.mjs +32 -0
  29. package/src/config/defaults.mjs +289 -260
  30. package/src/config/import-config.mjs +1 -1
  31. package/src/config/load-config.mjs +61 -4
  32. package/src/config/schema.mjs +604 -574
  33. package/src/context.mjs +4 -1
  34. package/src/core/constants.mjs +97 -91
  35. package/src/core/types.mjs +1 -1
  36. package/src/github/api.mjs +78 -78
  37. package/src/github/auth.mjs +294 -286
  38. package/src/github/flow.mjs +298 -298
  39. package/src/github/workspace.mjs +225 -212
  40. package/src/index.mjs +87 -82
  41. package/src/knowledge/frontend-aesthetics.txt +38 -38
  42. package/src/mcp/client-http.mjs +139 -141
  43. package/src/mcp/client-sse.mjs +297 -288
  44. package/src/mcp/client-stdio.mjs +534 -533
  45. package/src/mcp/constants.mjs +4 -2
  46. package/src/mcp/registry.mjs +498 -479
  47. package/src/mcp/stdio-framing.mjs +135 -133
  48. package/src/mcp/tool-result.mjs +24 -24
  49. package/src/observability/edit-diagnostics.mjs +449 -0
  50. package/src/observability/index.mjs +42 -42
  51. package/src/observability/metrics.mjs +165 -137
  52. package/src/observability/tracer.mjs +137 -137
  53. package/src/onboarding.mjs +209 -0
  54. package/src/orchestration/background-manager.mjs +567 -372
  55. package/src/orchestration/background-worker.mjs +419 -305
  56. package/src/orchestration/interruption-reason.mjs +21 -0
  57. package/src/orchestration/longagent-manager.mjs +197 -171
  58. package/src/orchestration/stage-scheduler.mjs +733 -728
  59. package/src/orchestration/subagent-router.mjs +7 -1
  60. package/src/orchestration/task-scheduler.mjs +219 -7
  61. package/src/permission/engine.mjs +1 -1
  62. package/src/permission/exec-policy.mjs +370 -370
  63. package/src/permission/file-edit-policy.mjs +108 -0
  64. package/src/permission/prompt.mjs +1 -1
  65. package/src/permission/rules.mjs +116 -7
  66. package/src/plugin/builtin-hooks/post-edit-format.mjs +2 -1
  67. package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +104 -40
  68. package/src/plugin/hook-bus.mjs +19 -5
  69. package/src/plugin/manifest-loader.mjs +222 -0
  70. package/src/provider/anthropic.mjs +396 -390
  71. package/src/provider/ollama.mjs +7 -1
  72. package/src/provider/openai.mjs +382 -340
  73. package/src/provider/retry-policy.mjs +74 -68
  74. package/src/provider/router.mjs +242 -241
  75. package/src/provider/sse.mjs +104 -104
  76. package/src/provider/wizard.mjs +556 -0
  77. package/src/repl/capability-facade.mjs +30 -0
  78. package/src/repl/command-surface.mjs +23 -0
  79. package/src/repl/controller-entry.mjs +40 -0
  80. package/src/repl/core-shell.mjs +208 -0
  81. package/src/repl/dialog-router.mjs +87 -0
  82. package/src/repl/input-engine.mjs +76 -0
  83. package/src/repl/keymap.mjs +7 -0
  84. package/src/repl/operator-surface.mjs +15 -0
  85. package/src/repl/permission-flow.mjs +49 -0
  86. package/src/repl/runtime-facade.mjs +36 -0
  87. package/src/repl/slash-router.mjs +62 -0
  88. package/src/repl/state-store.mjs +29 -0
  89. package/src/repl/turn-controller.mjs +58 -0
  90. package/src/repl/verification.mjs +23 -0
  91. package/src/repl.mjs +3371 -2981
  92. package/src/rules/load-rules.mjs +3 -3
  93. package/src/runtime.mjs +1 -1
  94. package/src/session/agent-transaction.mjs +86 -0
  95. package/src/session/checkpoint.mjs +302 -302
  96. package/src/session/compaction.mjs +298 -298
  97. package/src/session/engine.mjs +417 -232
  98. package/src/session/longagent-4stage.mjs +467 -460
  99. package/src/session/longagent-hybrid.mjs +1344 -1097
  100. package/src/session/longagent-plan.mjs +376 -365
  101. package/src/session/longagent-project-memory.mjs +53 -53
  102. package/src/session/longagent-scaffold.mjs +291 -291
  103. package/src/session/longagent-task-bus.mjs +138 -54
  104. package/src/session/longagent-utils.mjs +828 -472
  105. package/src/session/longagent.mjs +911 -900
  106. package/src/session/loop.mjs +1005 -930
  107. package/src/session/prompt/agent.txt +25 -25
  108. package/src/session/prompt/anthropic.txt +150 -150
  109. package/src/session/prompt/beast.txt +1 -1
  110. package/src/session/prompt/plan.txt +31 -31
  111. package/src/session/prompt/qwen.txt +46 -46
  112. package/src/session/recovery.mjs +21 -0
  113. package/src/session/rollback.mjs +196 -195
  114. package/src/session/routing-observability.mjs +72 -0
  115. package/src/session/runtime-state.mjs +47 -0
  116. package/src/session/store.mjs +523 -519
  117. package/src/session/system-prompt.mjs +308 -273
  118. package/src/session/task-validator.mjs +267 -267
  119. package/src/session/usability-gates.mjs +2 -2
  120. package/src/skill/builtin/commit.mjs +64 -64
  121. package/src/skill/builtin/design.mjs +76 -76
  122. package/src/skill/generator.mjs +18 -2
  123. package/src/skill/registry.mjs +642 -390
  124. package/src/storage/audit-store.mjs +18 -11
  125. package/src/storage/event-log.mjs +7 -1
  126. package/src/storage/ghost-commit-store.mjs +243 -245
  127. package/src/storage/paths.mjs +17 -0
  128. package/src/theme/default-theme.mjs +1 -1
  129. package/src/theme/markdown.mjs +4 -0
  130. package/src/theme/schema.mjs +1 -1
  131. package/src/theme/status-bar.mjs +162 -158
  132. package/src/tool/audit-wrapper.mjs +18 -2
  133. package/src/tool/edit-transaction.mjs +23 -0
  134. package/src/tool/executor.mjs +26 -1
  135. package/src/tool/file-read-state.mjs +65 -0
  136. package/src/tool/git-auto.mjs +526 -526
  137. package/src/tool/git-full-auto.mjs +487 -478
  138. package/src/tool/mutation-guard.mjs +54 -0
  139. package/src/tool/prompt/edit.txt +3 -3
  140. package/src/tool/prompt/multiedit.txt +1 -0
  141. package/src/tool/prompt/notebookedit.txt +2 -1
  142. package/src/tool/prompt/patch.txt +25 -24
  143. package/src/tool/prompt/read.txt +3 -3
  144. package/src/tool/prompt/sysinfo.txt +29 -0
  145. package/src/tool/prompt/task.txt +66 -4
  146. package/src/tool/prompt/write.txt +2 -2
  147. package/src/tool/question-prompt.mjs +99 -93
  148. package/src/tool/registry.mjs +1701 -1343
  149. package/src/tool/task-tool.mjs +14 -6
  150. package/src/ui/activity-renderer.mjs +667 -664
  151. package/src/ui/repl-background-panel.mjs +7 -0
  152. package/src/ui/repl-capability-panel.mjs +9 -0
  153. package/src/ui/repl-dashboard.mjs +54 -4
  154. package/src/ui/repl-help.mjs +110 -0
  155. package/src/ui/repl-operator-panel.mjs +12 -0
  156. package/src/ui/repl-route-feedback.mjs +35 -0
  157. package/src/ui/repl-status-view.mjs +76 -0
  158. package/src/ui/repl-task-panel.mjs +5 -0
  159. package/src/ui/repl-transcript-panel.mjs +56 -0
  160. package/src/ui/repl-turn-summary.mjs +135 -0
  161. package/src/update/checker.mjs +184 -0
  162. package/src/usage/pricing.mjs +122 -121
  163. package/src/usage/usage-meter.mjs +1 -0
  164. package/src/util/git.mjs +562 -519
  165. package/src/util/template.mjs +6 -1
  166. package/src/version.mjs +3 -0
@@ -1,212 +1,225 @@
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
- }
1
+ import { execFile } from "node:child_process"
2
+ import { chmod, 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
+ try {
43
+ await exec("git", ["clone", "--depth", "1", "-b", branch, "--single-branch", cloneUrl, localPath])
44
+ } catch (err) {
45
+ sanitizeTokenFromError(err, token)
46
+ throw err
47
+ }
48
+ // Remove token from remote URL
49
+ const cleanUrl = `https://github.com/${fullName}.git`
50
+ await exec("git", ["remote", "set-url", "origin", cleanUrl], { cwd: localPath })
51
+ // Store token in git credential for this repo
52
+ await configureCredential(localPath, token)
53
+ return { path: localPath, isNew: true }
54
+ }
55
+
56
+ // Existing repo fetch & checkout
57
+ await configureCredential(localPath, token)
58
+ await exec("git", ["fetch", "origin"], { cwd: localPath })
59
+
60
+ // Check if branch exists locally
61
+ const localBranchList = await localBranches(localPath)
62
+ if (localBranchList.includes(branch)) {
63
+ await exec("git", ["checkout", branch], { cwd: localPath })
64
+ await exec("git", ["pull", "--ff-only", "origin", branch], { cwd: localPath }).catch(() => {
65
+ // pull may fail if diverged, that's ok
66
+ })
67
+ } else {
68
+ // Checkout remote branch
69
+ await exec("git", ["checkout", "-b", branch, `origin/${branch}`], { cwd: localPath })
70
+ }
71
+
72
+ return { path: localPath, isNew: false }
73
+ }
74
+
75
+ function sanitizeTokenFromError(err, token) {
76
+ const mask = (s) => s ? s.replace(/https:\/\/[^@\s]+@/g, "https://***@") : s
77
+ if (err.message) err.message = mask(err.message)
78
+ if (err.stderr) err.stderr = mask(err.stderr)
79
+ if (err.stdout) err.stdout = mask(err.stdout)
80
+ }
81
+
82
+ async function configureCredential(repoPath, token) {
83
+ // Use store credential helper scoped to this repo
84
+ const credentialPath = path.join(repoPath, ".git", "kkcode-credentials")
85
+ const { writeFile } = await import("node:fs/promises")
86
+ await writeFile(credentialPath, `https://x-access-token:${token}@github.com\n`, { encoding: "utf8", mode: 0o600 })
87
+ await chmod(credentialPath, 0o600)
88
+ await exec("git", ["config", "credential.helper", `store --file="${credentialPath}"`], { cwd: repoPath })
89
+ }
90
+
91
+ export async function listLocalRepos() {
92
+ const root = githubReposDir()
93
+ const repos = []
94
+ try {
95
+ const owners = await readdir(root)
96
+ for (const owner of owners) {
97
+ const ownerPath = path.join(root, owner)
98
+ const ownerStat = await stat(ownerPath).catch(() => null)
99
+ if (!ownerStat || !ownerStat.isDirectory()) continue
100
+ const names = await readdir(ownerPath)
101
+ for (const name of names) {
102
+ const gitDir = path.join(ownerPath, name, ".git")
103
+ const gitStat = await stat(gitDir).catch(() => null)
104
+ if (gitStat && gitStat.isDirectory()) {
105
+ repos.push(`${owner}/${name}`)
106
+ }
107
+ }
108
+ }
109
+ } catch {
110
+ // repos dir doesn't exist yet
111
+ }
112
+ return repos
113
+ }
114
+
115
+ export async function localBranches(repoPath) {
116
+ try {
117
+ const out = await exec("git", ["branch", "--list", "--format=%(refname:short)"], { cwd: repoPath })
118
+ return out.split("\n").filter(Boolean)
119
+ } catch {
120
+ return []
121
+ }
122
+ }
123
+
124
+ export async function removeLocalRepo(fullName) {
125
+ const localPath = repoLocalPath(fullName)
126
+ try {
127
+ await rm(localPath, { recursive: true, force: true })
128
+ return true
129
+ } catch {
130
+ return false
131
+ }
132
+ }
133
+
134
+ export async function syncRepo({ fullName, branch, token }) {
135
+ const localPath = repoLocalPath(fullName)
136
+ await configureCredential(localPath, token)
137
+ await exec("git", ["fetch", "origin"], { cwd: localPath })
138
+
139
+ // Check if branch exists locally
140
+ const localBranchList = await localBranches(localPath)
141
+ if (localBranchList.includes(branch)) {
142
+ await exec("git", ["checkout", branch], { cwd: localPath })
143
+ await exec("git", ["pull", "--ff-only", "origin", branch], { cwd: localPath }).catch(() => {
144
+ // pull may fail if diverged, that's ok
145
+ })
146
+ } else {
147
+ // Checkout remote branch
148
+ await exec("git", ["checkout", "-b", branch, `origin/${branch}`], { cwd: localPath })
149
+ }
150
+
151
+ return { path: localPath }
152
+ }
153
+
154
+ // Check if there are uncommitted changes
155
+ export async function hasLocalChanges(repoPath) {
156
+ try {
157
+ const status = await exec("git", ["status", "--porcelain"], { cwd: repoPath })
158
+ return status.length > 0
159
+ } catch {
160
+ return false
161
+ }
162
+ }
163
+
164
+ // Get diff stats for display
165
+ export async function getDiffStats(repoPath) {
166
+ try {
167
+ const stats = await exec("git", ["diff", "--stat", "HEAD"], { cwd: repoPath })
168
+ return stats
169
+ } catch {
170
+ return ""
171
+ }
172
+ }
173
+
174
+ // Get changed files list
175
+ export async function getChangedFiles(repoPath) {
176
+ try {
177
+ const output = await exec("git", ["status", "--short"], { cwd: repoPath })
178
+ return output.split("\n").filter(Boolean)
179
+ } catch {
180
+ return []
181
+ }
182
+ }
183
+
184
+ // Commit and push changes
185
+ export async function commitAndPush({ repoPath, message, branch, token }) {
186
+ await configureCredential(repoPath, token)
187
+
188
+ // Add all changes
189
+ await exec("git", ["add", "-A"], { cwd: repoPath })
190
+
191
+ // Commit
192
+ await exec("git", ["commit", "-m", message], { cwd: repoPath })
193
+
194
+ // Push
195
+ await exec("git", ["push", "origin", branch], { cwd: repoPath })
196
+
197
+ return true
198
+ }
199
+
200
+ // Generate a commit message based on changes
201
+ export async function generateCommitMessage(repoPath) {
202
+ try {
203
+ const files = await getChangedFiles(repoPath)
204
+ if (files.length === 0) return "Update files"
205
+
206
+ // Count types of changes
207
+ const added = files.filter(f => f.startsWith("A") || f.startsWith("??")).length
208
+ const modified = files.filter(f => f.startsWith("M")).length
209
+ const deleted = files.filter(f => f.startsWith("D")).length
210
+
211
+ if (added > 0 && modified === 0 && deleted === 0) {
212
+ return added === 1 ? "Add file" : `Add ${added} files`
213
+ }
214
+ if (modified > 0 && added === 0 && deleted === 0) {
215
+ return modified === 1 ? "Update file" : `Update ${modified} files`
216
+ }
217
+ if (deleted > 0 && added === 0 && modified === 0) {
218
+ return deleted === 1 ? "Remove file" : `Remove ${deleted} files`
219
+ }
220
+
221
+ return `Update files (+${added} ~${modified} -${deleted})`
222
+ } catch {
223
+ return "Update files"
224
+ }
225
+ }
package/src/index.mjs CHANGED
@@ -1,82 +1,87 @@
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.6")
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
- })
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 { createSkillCommand } from "./commands/skill.mjs"
22
+ import { startRepl } from "./repl.mjs"
23
+ import { PACKAGE_VERSION } from "./version.mjs"
24
+ import { createUpdateCommand } from "./commands/update.mjs"
25
+
26
+ async function main() {
27
+ const hasTrust = process.argv.includes("--trust")
28
+ const hasGithub = process.argv.includes("--github")
29
+
30
+ if (hasGithub) {
31
+ const githubArgIndex = process.argv.indexOf("--github")
32
+ const nextArg = process.argv[githubArgIndex + 1]
33
+
34
+ if (nextArg === "logout") {
35
+ const { logout } = await import("./github/auth.mjs")
36
+ const success = await logout()
37
+ if (success) {
38
+ console.log("✓ 已登出 GitHub 账户")
39
+ } else {
40
+ console.log("⚠ 没有已登录的 GitHub 账户")
41
+ }
42
+ return
43
+ }
44
+
45
+ const { runGitHubFlow, promptPushChanges } = await import("./github/flow.mjs")
46
+ const result = await runGitHubFlow()
47
+ process.chdir(result.cwd)
48
+ await startRepl({ trust: hasTrust })
49
+ // After REPL exits, ask user if they want to push changes
50
+ await promptPushChanges(result)
51
+ return
52
+ }
53
+
54
+ if (process.argv.length <= 2 || (process.argv.length === 3 && hasTrust)) {
55
+ await startRepl({ trust: hasTrust })
56
+ return
57
+ }
58
+
59
+ const program = new Command()
60
+ program.name("kkcode").description("kkcode CLI").version(PACKAGE_VERSION)
61
+ program.addCommand(createChatCommand())
62
+ program.addCommand(createThemeCommand())
63
+ program.addCommand(createUsageCommand())
64
+ program.addCommand(createReviewCommand())
65
+ program.addCommand(createAgentCommand())
66
+ program.addCommand(createMcpCommand())
67
+ program.addCommand(createPermissionCommand())
68
+ program.addCommand(createDoctorCommand())
69
+ program.addCommand(createConfigCommand())
70
+ program.addCommand(createSessionCommand())
71
+ program.addCommand(createPromptCommand())
72
+ program.addCommand(createLongagentCommand())
73
+ program.addCommand(createHookCommand())
74
+ program.addCommand(createCommandCommand())
75
+ program.addCommand(createRuleCommand())
76
+ program.addCommand(createBackgroundCommand())
77
+ program.addCommand(createAuditCommand())
78
+ program.addCommand(createInitCommand())
79
+ program.addCommand(createSkillCommand())
80
+ program.addCommand(createUpdateCommand())
81
+ await program.parseAsync(process.argv)
82
+ }
83
+
84
+ main().catch((error) => {
85
+ console.error(error.message)
86
+ process.exit(1)
87
+ })