@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.
- package/LICENSE +674 -0
- package/README.md +445 -0
- package/package.json +46 -0
- package/src/agent/agent.mjs +170 -0
- package/src/agent/custom-agent-loader.mjs +158 -0
- package/src/agent/generator.mjs +115 -0
- package/src/agent/prompt/architect.txt +36 -0
- package/src/agent/prompt/build-fixer.txt +71 -0
- package/src/agent/prompt/build.txt +101 -0
- package/src/agent/prompt/compaction.txt +12 -0
- package/src/agent/prompt/explore.txt +29 -0
- package/src/agent/prompt/guide.txt +40 -0
- package/src/agent/prompt/longagent.txt +178 -0
- package/src/agent/prompt/plan.txt +50 -0
- package/src/agent/prompt/researcher.txt +23 -0
- package/src/agent/prompt/reviewer.txt +44 -0
- package/src/agent/prompt/security-reviewer.txt +62 -0
- package/src/agent/prompt/tdd-guide.txt +84 -0
- package/src/agent/prompt/title.txt +8 -0
- package/src/command/custom-commands.mjs +57 -0
- package/src/commands/agent.mjs +71 -0
- package/src/commands/audit.mjs +77 -0
- package/src/commands/background.mjs +86 -0
- package/src/commands/chat.mjs +114 -0
- package/src/commands/command.mjs +41 -0
- package/src/commands/config.mjs +44 -0
- package/src/commands/doctor.mjs +148 -0
- package/src/commands/hook.mjs +29 -0
- package/src/commands/init.mjs +141 -0
- package/src/commands/longagent.mjs +100 -0
- package/src/commands/mcp.mjs +89 -0
- package/src/commands/permission.mjs +36 -0
- package/src/commands/prompt.mjs +42 -0
- package/src/commands/review.mjs +266 -0
- package/src/commands/rule.mjs +34 -0
- package/src/commands/session.mjs +235 -0
- package/src/commands/theme.mjs +98 -0
- package/src/commands/usage.mjs +91 -0
- package/src/config/defaults.mjs +195 -0
- package/src/config/import-config.mjs +76 -0
- package/src/config/load-config.mjs +76 -0
- package/src/config/schema.mjs +509 -0
- package/src/context.mjs +40 -0
- package/src/core/constants.mjs +46 -0
- package/src/core/errors.mjs +57 -0
- package/src/core/events.mjs +29 -0
- package/src/core/types.mjs +57 -0
- package/src/github/api.mjs +78 -0
- package/src/github/auth.mjs +286 -0
- package/src/github/flow.mjs +298 -0
- package/src/github/workspace.mjs +212 -0
- package/src/index.mjs +82 -0
- package/src/knowledge/api-design.txt +9 -0
- package/src/knowledge/cpp.txt +10 -0
- package/src/knowledge/docker.txt +10 -0
- package/src/knowledge/dotnet.txt +9 -0
- package/src/knowledge/electron.txt +10 -0
- package/src/knowledge/flutter.txt +10 -0
- package/src/knowledge/go.txt +9 -0
- package/src/knowledge/graphql.txt +10 -0
- package/src/knowledge/java.txt +9 -0
- package/src/knowledge/kotlin.txt +10 -0
- package/src/knowledge/loader.mjs +125 -0
- package/src/knowledge/next.txt +8 -0
- package/src/knowledge/node.txt +8 -0
- package/src/knowledge/nuxt.txt +9 -0
- package/src/knowledge/php.txt +10 -0
- package/src/knowledge/python.txt +10 -0
- package/src/knowledge/react-native.txt +10 -0
- package/src/knowledge/react.txt +9 -0
- package/src/knowledge/ruby.txt +11 -0
- package/src/knowledge/rust.txt +9 -0
- package/src/knowledge/svelte.txt +9 -0
- package/src/knowledge/swift.txt +10 -0
- package/src/knowledge/tailwind.txt +10 -0
- package/src/knowledge/testing.txt +8 -0
- package/src/knowledge/typescript.txt +8 -0
- package/src/knowledge/vue.txt +9 -0
- package/src/mcp/client-http.mjs +157 -0
- package/src/mcp/client-sse.mjs +286 -0
- package/src/mcp/client-stdio.mjs +451 -0
- package/src/mcp/registry.mjs +394 -0
- package/src/mcp/stdio-framing.mjs +127 -0
- package/src/orchestration/background-manager.mjs +358 -0
- package/src/orchestration/background-worker.mjs +245 -0
- package/src/orchestration/longagent-manager.mjs +116 -0
- package/src/orchestration/stage-scheduler.mjs +489 -0
- package/src/orchestration/subagent-router.mjs +62 -0
- package/src/orchestration/task-scheduler.mjs +74 -0
- package/src/permission/engine.mjs +92 -0
- package/src/permission/exec-policy.mjs +372 -0
- package/src/permission/prompt.mjs +39 -0
- package/src/permission/rules.mjs +120 -0
- package/src/permission/workspace-trust.mjs +44 -0
- package/src/plugin/builtin-hooks/console-warn.mjs +41 -0
- package/src/plugin/builtin-hooks/extract-patterns.mjs +75 -0
- package/src/plugin/builtin-hooks/post-edit-format.mjs +57 -0
- package/src/plugin/builtin-hooks/post-edit-typecheck.mjs +61 -0
- package/src/plugin/builtin-hooks/strategic-compaction.mjs +38 -0
- package/src/plugin/hook-bus.mjs +154 -0
- package/src/provider/anthropic.mjs +389 -0
- package/src/provider/ollama.mjs +236 -0
- package/src/provider/openai-compatible.mjs +1 -0
- package/src/provider/openai.mjs +339 -0
- package/src/provider/retry-policy.mjs +68 -0
- package/src/provider/router.mjs +228 -0
- package/src/provider/sse.mjs +91 -0
- package/src/repl.mjs +2929 -0
- package/src/review/diff-parser.mjs +36 -0
- package/src/review/rejection-queue.mjs +62 -0
- package/src/review/review-store.mjs +21 -0
- package/src/review/risk-score.mjs +61 -0
- package/src/rules/load-rules.mjs +64 -0
- package/src/runtime.mjs +1 -0
- package/src/session/checkpoint.mjs +239 -0
- package/src/session/compaction.mjs +276 -0
- package/src/session/engine.mjs +225 -0
- package/src/session/instinct-manager.mjs +172 -0
- package/src/session/instruction-loader.mjs +25 -0
- package/src/session/longagent-plan.mjs +329 -0
- package/src/session/longagent-scaffold.mjs +100 -0
- package/src/session/longagent.mjs +1462 -0
- package/src/session/loop.mjs +905 -0
- package/src/session/memory-loader.mjs +75 -0
- package/src/session/project-context.mjs +367 -0
- package/src/session/prompt/anthropic.txt +151 -0
- package/src/session/prompt/beast.txt +37 -0
- package/src/session/prompt/max-steps.txt +6 -0
- package/src/session/prompt/plan.txt +9 -0
- package/src/session/prompt/qwen.txt +46 -0
- package/src/session/prompt-loader.mjs +18 -0
- package/src/session/recovery.mjs +52 -0
- package/src/session/store.mjs +503 -0
- package/src/session/system-prompt.mjs +260 -0
- package/src/session/task-validator.mjs +266 -0
- package/src/session/usability-gates.mjs +379 -0
- package/src/skill/builtin/backend-patterns.mjs +123 -0
- package/src/skill/builtin/commit.mjs +64 -0
- package/src/skill/builtin/debug.mjs +45 -0
- package/src/skill/builtin/frontend-patterns.mjs +120 -0
- package/src/skill/builtin/frontend.mjs +188 -0
- package/src/skill/builtin/init.mjs +220 -0
- package/src/skill/builtin/review.mjs +49 -0
- package/src/skill/builtin/security-checklist.mjs +80 -0
- package/src/skill/builtin/tdd.mjs +54 -0
- package/src/skill/generator.mjs +113 -0
- package/src/skill/registry.mjs +336 -0
- package/src/storage/audit-store.mjs +83 -0
- package/src/storage/event-log.mjs +82 -0
- package/src/storage/ghost-commit-store.mjs +235 -0
- package/src/storage/json-store.mjs +53 -0
- package/src/storage/paths.mjs +148 -0
- package/src/theme/color.mjs +64 -0
- package/src/theme/default-theme.mjs +29 -0
- package/src/theme/load-theme.mjs +71 -0
- package/src/theme/markdown.mjs +135 -0
- package/src/theme/schema.mjs +45 -0
- package/src/theme/status-bar.mjs +158 -0
- package/src/tool/audit-wrapper.mjs +38 -0
- package/src/tool/edit-transaction.mjs +126 -0
- package/src/tool/executor.mjs +109 -0
- package/src/tool/file-lock-manager.mjs +85 -0
- package/src/tool/git-auto.mjs +545 -0
- package/src/tool/git-full-auto.mjs +478 -0
- package/src/tool/image-util.mjs +276 -0
- package/src/tool/prompt/background_cancel.txt +1 -0
- package/src/tool/prompt/background_output.txt +1 -0
- package/src/tool/prompt/bash.txt +71 -0
- package/src/tool/prompt/codesearch.txt +18 -0
- package/src/tool/prompt/edit.txt +27 -0
- package/src/tool/prompt/enter_plan.txt +74 -0
- package/src/tool/prompt/exit_plan.txt +62 -0
- package/src/tool/prompt/glob.txt +33 -0
- package/src/tool/prompt/grep.txt +43 -0
- package/src/tool/prompt/list.txt +8 -0
- package/src/tool/prompt/multiedit.txt +20 -0
- package/src/tool/prompt/notebookedit.txt +21 -0
- package/src/tool/prompt/patch.txt +24 -0
- package/src/tool/prompt/question.txt +44 -0
- package/src/tool/prompt/read.txt +40 -0
- package/src/tool/prompt/task.txt +83 -0
- package/src/tool/prompt/todowrite.txt +117 -0
- package/src/tool/prompt/webfetch.txt +38 -0
- package/src/tool/prompt/websearch.txt +43 -0
- package/src/tool/prompt/write.txt +38 -0
- package/src/tool/prompt-loader.mjs +18 -0
- package/src/tool/question-prompt.mjs +86 -0
- package/src/tool/registry.mjs +1309 -0
- package/src/tool/task-tool.mjs +28 -0
- package/src/ui/activity-renderer.mjs +410 -0
- package/src/ui/repl-dashboard.mjs +357 -0
- package/src/usage/pricing.mjs +121 -0
- package/src/usage/usage-meter.mjs +113 -0
- package/src/util/git.mjs +496 -0
- package/src/util/template.mjs +10 -0
- package/src/util/yaml.mjs +100 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { Command } from "commander"
|
|
2
|
+
import { BackgroundManager } from "../orchestration/background-manager.mjs"
|
|
3
|
+
import { buildContext, printContextWarnings } from "../context.mjs"
|
|
4
|
+
|
|
5
|
+
async function withContext(action) {
|
|
6
|
+
const ctx = await buildContext()
|
|
7
|
+
printContextWarnings(ctx)
|
|
8
|
+
await BackgroundManager.tick(ctx.configState.config)
|
|
9
|
+
return action(ctx)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function createBackgroundCommand() {
|
|
13
|
+
const cmd = new Command("background").description("inspect background delegated tasks")
|
|
14
|
+
|
|
15
|
+
cmd
|
|
16
|
+
.command("list")
|
|
17
|
+
.description("list background tasks")
|
|
18
|
+
.action(async () => {
|
|
19
|
+
await withContext(async () => {
|
|
20
|
+
const list = await BackgroundManager.list()
|
|
21
|
+
console.log(JSON.stringify(list, null, 2))
|
|
22
|
+
})
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
cmd
|
|
26
|
+
.command("show")
|
|
27
|
+
.description("show one background task")
|
|
28
|
+
.requiredOption("--id <id>", "task id")
|
|
29
|
+
.action(async (options) => {
|
|
30
|
+
await withContext(async () => {
|
|
31
|
+
const task = await BackgroundManager.get(options.id)
|
|
32
|
+
if (!task) {
|
|
33
|
+
console.error(`not found: ${options.id}`)
|
|
34
|
+
process.exitCode = 1
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
console.log(JSON.stringify(task, null, 2))
|
|
38
|
+
})
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
cmd
|
|
42
|
+
.command("cancel")
|
|
43
|
+
.description("cancel one background task")
|
|
44
|
+
.requiredOption("--id <id>", "task id")
|
|
45
|
+
.action(async (options) => {
|
|
46
|
+
await withContext(async () => {
|
|
47
|
+
const ok = await BackgroundManager.cancel(options.id)
|
|
48
|
+
if (!ok) {
|
|
49
|
+
console.error(`not found: ${options.id}`)
|
|
50
|
+
process.exitCode = 1
|
|
51
|
+
return
|
|
52
|
+
}
|
|
53
|
+
console.log(`cancel requested: ${options.id}`)
|
|
54
|
+
})
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
cmd
|
|
58
|
+
.command("retry")
|
|
59
|
+
.description("retry one interrupted/error background task")
|
|
60
|
+
.requiredOption("--id <id>", "task id")
|
|
61
|
+
.action(async (options) => {
|
|
62
|
+
await withContext(async (ctx) => {
|
|
63
|
+
const task = await BackgroundManager.retry(options.id, ctx.configState.config)
|
|
64
|
+
if (!task) {
|
|
65
|
+
console.error(`task not retryable or not found: ${options.id}`)
|
|
66
|
+
process.exitCode = 1
|
|
67
|
+
return
|
|
68
|
+
}
|
|
69
|
+
console.log(`retry queued: ${task.id} (attempt=${task.attempt})`)
|
|
70
|
+
})
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
cmd
|
|
74
|
+
.command("clean")
|
|
75
|
+
.description("remove old completed/cancelled/error/interrupted tasks")
|
|
76
|
+
.option("--max-age <days>", "max age in days", "7")
|
|
77
|
+
.action(async (options) => {
|
|
78
|
+
await withContext(async () => {
|
|
79
|
+
const maxAge = Number(options.maxAge || 7) * 24 * 60 * 60 * 1000
|
|
80
|
+
const removed = await BackgroundManager.clean({ maxAge })
|
|
81
|
+
console.log(`removed ${removed.length} task(s)`)
|
|
82
|
+
})
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
return cmd
|
|
86
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Command } from "commander"
|
|
2
|
+
import { buildContext, printContextWarnings } from "../context.mjs"
|
|
3
|
+
import { executeTurn, newSessionId, resolveMode } from "../session/engine.mjs"
|
|
4
|
+
import { renderStatusBar } from "../theme/status-bar.mjs"
|
|
5
|
+
import { applyCommandTemplate, loadCustomCommands } from "../command/custom-commands.mjs"
|
|
6
|
+
import { ToolRegistry } from "../tool/registry.mjs"
|
|
7
|
+
import { HookBus, initHookBus } from "../plugin/hook-bus.mjs"
|
|
8
|
+
import { listProviders } from "../provider/router.mjs"
|
|
9
|
+
|
|
10
|
+
export function createChatCommand() {
|
|
11
|
+
const providers = listProviders()
|
|
12
|
+
return new Command("chat")
|
|
13
|
+
.description("run one prompt in ask/plan/agent/longagent mode")
|
|
14
|
+
.argument("<prompt...>", "prompt text")
|
|
15
|
+
.option("--mode <mode>", "ask|plan|agent|longagent", "agent")
|
|
16
|
+
.option("--model <model>", "model id")
|
|
17
|
+
.option("--provider-type <type>", `provider type (${providers.join("|")})`)
|
|
18
|
+
.option("--base-url <url>", "provider base url override")
|
|
19
|
+
.option("--api-key-env <name>", "api key env override")
|
|
20
|
+
.option("--max-iterations <n>", "longagent max iterations (0 = unlimited)")
|
|
21
|
+
.option("--session <id>", "session id")
|
|
22
|
+
.action(async (promptParts, options) => {
|
|
23
|
+
const ctx = await buildContext()
|
|
24
|
+
printContextWarnings(ctx)
|
|
25
|
+
let prompt = promptParts.join(" ").trim()
|
|
26
|
+
if (prompt.startsWith("/")) {
|
|
27
|
+
const commands = await loadCustomCommands(process.cwd())
|
|
28
|
+
const [name, ...argTokens] = prompt.slice(1).split(/\s+/)
|
|
29
|
+
const custom = commands.find((item) => item.name === name)
|
|
30
|
+
if (custom) {
|
|
31
|
+
const args = argTokens.join(" ").trim()
|
|
32
|
+
prompt = applyCommandTemplate(custom.template, args, {
|
|
33
|
+
path: process.cwd()
|
|
34
|
+
})
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const mode = resolveMode(options.mode)
|
|
39
|
+
const providerType = options.providerType ?? ctx.configState.config.provider.default
|
|
40
|
+
const providerDefaults = ctx.configState.config.provider[providerType]
|
|
41
|
+
if (!providerDefaults) {
|
|
42
|
+
throw new Error(`unknown provider type: ${providerType}`)
|
|
43
|
+
}
|
|
44
|
+
const model = options.model ?? providerDefaults.default_model
|
|
45
|
+
const sessionId = options.session || newSessionId()
|
|
46
|
+
|
|
47
|
+
await ToolRegistry.initialize({
|
|
48
|
+
config: ctx.configState.config,
|
|
49
|
+
cwd: process.cwd()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
await initHookBus()
|
|
53
|
+
const chatParams = await HookBus.chatParams({
|
|
54
|
+
prompt,
|
|
55
|
+
mode,
|
|
56
|
+
model,
|
|
57
|
+
providerType,
|
|
58
|
+
sessionId,
|
|
59
|
+
baseUrl: options.baseUrl ?? null,
|
|
60
|
+
apiKeyEnv: options.apiKeyEnv ?? null
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const result = await executeTurn({
|
|
64
|
+
prompt: chatParams.prompt ?? prompt,
|
|
65
|
+
mode: chatParams.mode ?? mode,
|
|
66
|
+
model: chatParams.model ?? model,
|
|
67
|
+
sessionId,
|
|
68
|
+
configState: ctx.configState,
|
|
69
|
+
providerType: chatParams.providerType ?? providerType,
|
|
70
|
+
baseUrl: chatParams.baseUrl ?? options.baseUrl ?? null,
|
|
71
|
+
apiKeyEnv: chatParams.apiKeyEnv ?? options.apiKeyEnv ?? null,
|
|
72
|
+
maxIterations: options.maxIterations !== undefined ? Number(options.maxIterations) : null,
|
|
73
|
+
output: ctx.configState.config.provider[chatParams.providerType ?? providerType]?.stream !== false
|
|
74
|
+
? { write: (chunk) => process.stdout.write(String(chunk || "")) }
|
|
75
|
+
: null
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const status = renderStatusBar({
|
|
79
|
+
mode,
|
|
80
|
+
model: result.model,
|
|
81
|
+
permission: ctx.configState.config.permission.default_policy,
|
|
82
|
+
tokenMeter: result.tokenMeter,
|
|
83
|
+
aggregation: ctx.configState.config.usage.aggregation,
|
|
84
|
+
cost: result.cost,
|
|
85
|
+
showCost: ctx.configState.config.ui.status.show_cost,
|
|
86
|
+
showTokenMeter: ctx.configState.config.ui.status.show_token_meter,
|
|
87
|
+
theme: ctx.themeState.theme,
|
|
88
|
+
layout: ctx.configState.config.ui.layout,
|
|
89
|
+
longagentState: mode === "longagent" ? result.longagent : null
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
console.log(status)
|
|
93
|
+
console.log("")
|
|
94
|
+
const streamEnabled = ctx.configState.config.provider[chatParams.providerType ?? providerType]?.stream !== false
|
|
95
|
+
if (!streamEnabled || !result.emittedText) {
|
|
96
|
+
console.log(result.reply)
|
|
97
|
+
}
|
|
98
|
+
console.log("")
|
|
99
|
+
if (result.toolEvents.length) {
|
|
100
|
+
console.log(`tool events: ${result.toolEvents.length}`)
|
|
101
|
+
}
|
|
102
|
+
if (result.pricingWarnings.length) {
|
|
103
|
+
for (const warning of result.pricingWarnings) {
|
|
104
|
+
console.log(`pricing warning: ${warning}`)
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (result.budgetWarnings.length) {
|
|
108
|
+
for (const warning of result.budgetWarnings) {
|
|
109
|
+
console.log(`budget warning: ${warning}`)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
console.log(`session: ${sessionId}`)
|
|
113
|
+
})
|
|
114
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Command } from "commander"
|
|
2
|
+
import { applyCommandTemplate, loadCustomCommands } from "../command/custom-commands.mjs"
|
|
3
|
+
|
|
4
|
+
export function createCommandCommand() {
|
|
5
|
+
const cmd = new Command("command").description("inspect custom slash commands")
|
|
6
|
+
|
|
7
|
+
cmd
|
|
8
|
+
.command("list")
|
|
9
|
+
.description("list discovered custom commands")
|
|
10
|
+
.action(async () => {
|
|
11
|
+
const list = await loadCustomCommands(process.cwd())
|
|
12
|
+
if (!list.length) {
|
|
13
|
+
console.log("no custom commands found")
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
for (const item of list) {
|
|
17
|
+
console.log(`/${item.name} (${item.scope}) -> ${item.source}`)
|
|
18
|
+
}
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
cmd
|
|
22
|
+
.command("preview")
|
|
23
|
+
.description("preview command template expansion")
|
|
24
|
+
.requiredOption("--name <name>", "command name")
|
|
25
|
+
.option("--args <text>", "arguments text", "")
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
const list = await loadCustomCommands(process.cwd())
|
|
28
|
+
const item = list.find((cmd) => cmd.name === options.name)
|
|
29
|
+
if (!item) {
|
|
30
|
+
console.error(`command not found: ${options.name}`)
|
|
31
|
+
process.exitCode = 1
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
const expanded = applyCommandTemplate(item.template, options.args, {
|
|
35
|
+
path: process.cwd()
|
|
36
|
+
})
|
|
37
|
+
console.log(expanded)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
return cmd
|
|
41
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
import { readFile, writeFile } from "node:fs/promises"
|
|
3
|
+
import { Command } from "commander"
|
|
4
|
+
import YAML from "yaml"
|
|
5
|
+
import { importConfig } from "../config/import-config.mjs"
|
|
6
|
+
import { validateConfig } from "../config/schema.mjs"
|
|
7
|
+
|
|
8
|
+
function parseInput(file, raw) {
|
|
9
|
+
if (file.endsWith(".json")) return JSON.parse(raw)
|
|
10
|
+
return YAML.parse(raw)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function stringifyOutput(file, data) {
|
|
14
|
+
if (file.endsWith(".json")) return JSON.stringify(data, null, 2) + "\n"
|
|
15
|
+
return YAML.stringify(data)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createConfigCommand() {
|
|
19
|
+
const cmd = new Command("config").description("manage kkcode config")
|
|
20
|
+
|
|
21
|
+
cmd
|
|
22
|
+
.command("import")
|
|
23
|
+
.description("import external config into kkcode format")
|
|
24
|
+
.requiredOption("--from <file>", "source config file path")
|
|
25
|
+
.option("--to <file>", "output file path", "kkcode.config.yaml")
|
|
26
|
+
.action(async (options) => {
|
|
27
|
+
const from = path.resolve(options.from)
|
|
28
|
+
const to = path.resolve(options.to)
|
|
29
|
+
const raw = await readFile(from, "utf8")
|
|
30
|
+
const parsed = parseInput(from, raw)
|
|
31
|
+
const imported = importConfig(parsed)
|
|
32
|
+
const check = validateConfig(imported)
|
|
33
|
+
if (!check.valid) {
|
|
34
|
+
console.error("import produced invalid config:")
|
|
35
|
+
for (const error of check.errors) console.error(`- ${error}`)
|
|
36
|
+
process.exitCode = 1
|
|
37
|
+
return
|
|
38
|
+
}
|
|
39
|
+
await writeFile(to, stringifyOutput(to, imported), "utf8")
|
|
40
|
+
console.log(`imported config written: ${to}`)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
return cmd
|
|
44
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { Command } from "commander"
|
|
2
|
+
import { exec as execCb } from "node:child_process"
|
|
3
|
+
import { promisify } from "node:util"
|
|
4
|
+
import { buildContext } from "../context.mjs"
|
|
5
|
+
import { listProviders } from "../provider/router.mjs"
|
|
6
|
+
import { eventLogStats } from "../storage/event-log.mjs"
|
|
7
|
+
import { auditStats } from "../storage/audit-store.mjs"
|
|
8
|
+
import { fsckSessionStore, flushNow } from "../session/store.mjs"
|
|
9
|
+
import { BackgroundManager } from "../orchestration/background-manager.mjs"
|
|
10
|
+
import { McpRegistry } from "../mcp/registry.mjs"
|
|
11
|
+
|
|
12
|
+
const exec = promisify(execCb)
|
|
13
|
+
|
|
14
|
+
async function hasCommand(cmd) {
|
|
15
|
+
const query = process.platform === "win32" ? `where ${cmd}` : `command -v ${cmd}`
|
|
16
|
+
try {
|
|
17
|
+
await exec(query)
|
|
18
|
+
return true
|
|
19
|
+
} catch {
|
|
20
|
+
return false
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function summarizeBackground(tasks) {
|
|
25
|
+
const counters = {
|
|
26
|
+
total: tasks.length,
|
|
27
|
+
pending: 0,
|
|
28
|
+
running: 0,
|
|
29
|
+
completed: 0,
|
|
30
|
+
error: 0,
|
|
31
|
+
cancelled: 0,
|
|
32
|
+
interrupted: 0
|
|
33
|
+
}
|
|
34
|
+
for (const task of tasks) {
|
|
35
|
+
if (counters[task.status] !== undefined) counters[task.status] += 1
|
|
36
|
+
}
|
|
37
|
+
return counters
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
async function buildDoctorReport() {
|
|
41
|
+
const ctx = await buildContext()
|
|
42
|
+
await flushNow()
|
|
43
|
+
await BackgroundManager.tick(ctx.configState.config)
|
|
44
|
+
|
|
45
|
+
const checks = {
|
|
46
|
+
node: true,
|
|
47
|
+
rg: await hasCommand("rg"),
|
|
48
|
+
git: await hasCommand("git")
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const config = ctx.configState.config
|
|
52
|
+
const providers = []
|
|
53
|
+
for (const [name, provider] of Object.entries(config.provider || {})) {
|
|
54
|
+
if (name === "default") continue
|
|
55
|
+
if (!provider || typeof provider !== "object") continue
|
|
56
|
+
const keyEnv = provider.api_key_env || ""
|
|
57
|
+
providers.push({
|
|
58
|
+
name,
|
|
59
|
+
type: provider.type || name,
|
|
60
|
+
model: provider.default_model || null,
|
|
61
|
+
baseUrl: provider.base_url || null,
|
|
62
|
+
apiKeyEnv: keyEnv || null,
|
|
63
|
+
apiKeyConfigured: keyEnv ? Boolean(process.env[keyEnv]) : true
|
|
64
|
+
})
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const events = await eventLogStats()
|
|
68
|
+
const audit = await auditStats()
|
|
69
|
+
const storage = await fsckSessionStore()
|
|
70
|
+
const backgroundTasks = await BackgroundManager.list()
|
|
71
|
+
await McpRegistry.initialize(config)
|
|
72
|
+
const mcpSnapshot = McpRegistry.healthSnapshot()
|
|
73
|
+
const mcpHealthy = mcpSnapshot.filter((item) => item.ok).length
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
ok: storage.ok,
|
|
77
|
+
timestamp: new Date().toISOString(),
|
|
78
|
+
cwd: process.cwd(),
|
|
79
|
+
themeWarnings: ctx.themeState.errors,
|
|
80
|
+
config: {
|
|
81
|
+
defaultProvider: config.provider?.default || null,
|
|
82
|
+
userPath: ctx.configState.source.userPath,
|
|
83
|
+
projectPath: ctx.configState.source.projectPath,
|
|
84
|
+
warnings: ctx.configState.errors
|
|
85
|
+
},
|
|
86
|
+
runtime: {
|
|
87
|
+
providersRegistered: listProviders(),
|
|
88
|
+
providersConfigured: providers
|
|
89
|
+
},
|
|
90
|
+
checks,
|
|
91
|
+
mcp: {
|
|
92
|
+
configured: mcpSnapshot.length,
|
|
93
|
+
healthy: mcpHealthy,
|
|
94
|
+
unhealthy: mcpSnapshot.length - mcpHealthy,
|
|
95
|
+
servers: mcpSnapshot
|
|
96
|
+
},
|
|
97
|
+
storage: {
|
|
98
|
+
sessions: storage,
|
|
99
|
+
eventLog: events,
|
|
100
|
+
audit
|
|
101
|
+
},
|
|
102
|
+
background: summarizeBackground(backgroundTasks)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function printTextReport(report, themeWarnings = []) {
|
|
107
|
+
console.log("kkcode doctor")
|
|
108
|
+
console.log(`time: ${report.timestamp}`)
|
|
109
|
+
console.log(`cwd: ${report.cwd}`)
|
|
110
|
+
console.log(`default provider: ${report.config.defaultProvider}`)
|
|
111
|
+
console.log(`config.user: ${report.config.userPath || "(none)"}`)
|
|
112
|
+
console.log(`config.project: ${report.config.projectPath || "(none)"}`)
|
|
113
|
+
if (report.config.warnings.length) {
|
|
114
|
+
for (const warning of report.config.warnings) {
|
|
115
|
+
console.log(`config warning: ${warning}`)
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const warning of themeWarnings) {
|
|
119
|
+
console.log(`theme warning: ${warning}`)
|
|
120
|
+
}
|
|
121
|
+
for (const p of report.runtime.providersConfigured) {
|
|
122
|
+
console.log(
|
|
123
|
+
`provider:${p.name} type=${p.type} model=${p.model || "?"} key=${p.apiKeyEnv || "-"} (${p.apiKeyConfigured ? "set" : "missing"})`
|
|
124
|
+
)
|
|
125
|
+
}
|
|
126
|
+
console.log(`check node=${report.checks.node ? "ok" : "missing"} rg=${report.checks.rg ? "ok" : "missing"} git=${report.checks.git ? "ok" : "missing"}`)
|
|
127
|
+
console.log(`mcp: configured=${report.mcp.configured} healthy=${report.mcp.healthy} unhealthy=${report.mcp.unhealthy}`)
|
|
128
|
+
console.log(`sessions: ok=${report.storage.sessions.ok} index=${report.storage.sessions.sessionsInIndex} files=${report.storage.sessions.filesOnDisk}`)
|
|
129
|
+
console.log(`events: active=${report.storage.eventLog.activeBytes} rotated=${report.storage.eventLog.rotatedFiles}`)
|
|
130
|
+
console.log(`audit: total=${report.storage.audit.total} error1h=${report.storage.audit.error1h} error24h=${report.storage.audit.error24h}`)
|
|
131
|
+
console.log(
|
|
132
|
+
`background: total=${report.background.total} running=${report.background.running} pending=${report.background.pending} interrupted=${report.background.interrupted} error=${report.background.error}`
|
|
133
|
+
)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
export function createDoctorCommand() {
|
|
137
|
+
return new Command("doctor")
|
|
138
|
+
.description("run environment diagnostics")
|
|
139
|
+
.option("--json", "print structured diagnostics", false)
|
|
140
|
+
.action(async (options) => {
|
|
141
|
+
const report = await buildDoctorReport()
|
|
142
|
+
if (options.json) {
|
|
143
|
+
console.log(JSON.stringify(report, null, 2))
|
|
144
|
+
return
|
|
145
|
+
}
|
|
146
|
+
printTextReport(report, report.themeWarnings || [])
|
|
147
|
+
})
|
|
148
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { Command } from "commander"
|
|
2
|
+
import { initHookBus, HookBus } from "../plugin/hook-bus.mjs"
|
|
3
|
+
|
|
4
|
+
export function createHookCommand() {
|
|
5
|
+
const cmd = new Command("hook").description("inspect loaded hooks")
|
|
6
|
+
|
|
7
|
+
cmd
|
|
8
|
+
.command("list")
|
|
9
|
+
.description("list loaded hooks and loading errors")
|
|
10
|
+
.action(async () => {
|
|
11
|
+
await initHookBus(process.cwd())
|
|
12
|
+
const hooks = HookBus.list()
|
|
13
|
+
const errors = HookBus.errors()
|
|
14
|
+
console.log(`supported events: ${HookBus.supportedEvents().join(", ")}`)
|
|
15
|
+
if (!hooks.length) {
|
|
16
|
+
console.log("no hooks loaded")
|
|
17
|
+
} else {
|
|
18
|
+
for (const hook of hooks) {
|
|
19
|
+
console.log(`- ${hook.name} (${hook.source})`)
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
if (errors.length) {
|
|
23
|
+
console.log("hook loading errors:")
|
|
24
|
+
for (const err of errors) console.log(`- ${err}`)
|
|
25
|
+
}
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
return cmd
|
|
29
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import { join } from "node:path"
|
|
2
|
+
import { mkdir, writeFile, access } from "node:fs/promises"
|
|
3
|
+
import { createInterface } from "node:readline/promises"
|
|
4
|
+
import { Command } from "commander"
|
|
5
|
+
import YAML from "yaml"
|
|
6
|
+
import { listProviders } from "../provider/router.mjs"
|
|
7
|
+
import { validateConfig } from "../config/schema.mjs"
|
|
8
|
+
|
|
9
|
+
async function exists(target) {
|
|
10
|
+
try { await access(target); return true } catch { return false }
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
async function askQuestion(rl, question, defaultValue = "") {
|
|
14
|
+
const suffix = defaultValue ? ` (${defaultValue})` : ""
|
|
15
|
+
const answer = await rl.question(`${question}${suffix}: `)
|
|
16
|
+
return answer.trim() || defaultValue
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function askChoice(rl, question, choices, defaultValue) {
|
|
20
|
+
const choiceStr = choices.map((c, i) => `${i + 1}. ${c}`).join("\n")
|
|
21
|
+
console.log(`\n${question}\n${choiceStr}`)
|
|
22
|
+
const answer = await rl.question(`choose [${defaultValue}]: `)
|
|
23
|
+
const trimmed = answer.trim()
|
|
24
|
+
if (!trimmed) return defaultValue
|
|
25
|
+
const idx = parseInt(trimmed, 10)
|
|
26
|
+
if (idx >= 1 && idx <= choices.length) return choices[idx - 1]
|
|
27
|
+
if (choices.includes(trimmed)) return trimmed
|
|
28
|
+
return defaultValue
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const PROVIDER_DEFAULTS = {
|
|
32
|
+
openai: { api_key_env: "OPENAI_API_KEY", default_model: "gpt-5.3-codex" },
|
|
33
|
+
anthropic: { api_key_env: "ANTHROPIC_API_KEY", default_model: "claude-opus-4-6" },
|
|
34
|
+
ollama: { base_url: "http://localhost:11434", api_key_env: "", default_model: "llama3.1" },
|
|
35
|
+
"openai-compatible": { base_url: "", api_key_env: "", default_model: "" }
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildConfig(answers) {
|
|
39
|
+
const config = {
|
|
40
|
+
provider: {
|
|
41
|
+
default: answers.provider
|
|
42
|
+
},
|
|
43
|
+
permission: {
|
|
44
|
+
default_policy: answers.permissionPolicy || "ask"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const block = {}
|
|
48
|
+
if (answers.baseUrl) block.base_url = answers.baseUrl
|
|
49
|
+
if (answers.apiKeyEnv) block.api_key_env = answers.apiKeyEnv
|
|
50
|
+
if (answers.model) block.default_model = answers.model
|
|
51
|
+
if (Object.keys(block).length) {
|
|
52
|
+
config.provider[answers.provider] = block
|
|
53
|
+
}
|
|
54
|
+
return config
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function runInteractive(rl) {
|
|
58
|
+
const providers = listProviders()
|
|
59
|
+
const provider = await askChoice(rl, "select default provider:", providers, "openai")
|
|
60
|
+
const defaults = PROVIDER_DEFAULTS[provider] || {}
|
|
61
|
+
|
|
62
|
+
let baseUrl = ""
|
|
63
|
+
let apiKeyEnv = ""
|
|
64
|
+
let model = ""
|
|
65
|
+
|
|
66
|
+
if (provider === "ollama") {
|
|
67
|
+
baseUrl = await askQuestion(rl, "ollama base URL", defaults.base_url)
|
|
68
|
+
model = await askQuestion(rl, "default model", defaults.default_model)
|
|
69
|
+
} else if (provider === "openai-compatible") {
|
|
70
|
+
baseUrl = await askQuestion(rl, "base URL", "")
|
|
71
|
+
apiKeyEnv = await askQuestion(rl, "API key env var", "")
|
|
72
|
+
model = await askQuestion(rl, "default model", "")
|
|
73
|
+
} else {
|
|
74
|
+
apiKeyEnv = await askQuestion(rl, "API key env var", defaults.api_key_env)
|
|
75
|
+
model = await askQuestion(rl, "default model", defaults.default_model)
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const permissionPolicy = await askChoice(
|
|
79
|
+
rl,
|
|
80
|
+
"default permission policy:",
|
|
81
|
+
["allow", "ask", "deny"],
|
|
82
|
+
"ask"
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return { provider, baseUrl, apiKeyEnv, model, permissionPolicy }
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export function createInitCommand() {
|
|
89
|
+
return new Command("init")
|
|
90
|
+
.description("initialize kkcode in current project")
|
|
91
|
+
.option("-y, --yes", "use defaults without prompting")
|
|
92
|
+
.action(async (options) => {
|
|
93
|
+
const cwd = process.cwd()
|
|
94
|
+
const configDir = join(cwd, ".kkcode")
|
|
95
|
+
const configFile = join(configDir, "config.yaml")
|
|
96
|
+
|
|
97
|
+
if (await exists(configFile)) {
|
|
98
|
+
console.log(`config already exists: ${configFile}`)
|
|
99
|
+
console.log("use 'kkcode config' to modify it")
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
let answers
|
|
104
|
+
if (options.yes) {
|
|
105
|
+
answers = {
|
|
106
|
+
provider: "openai",
|
|
107
|
+
baseUrl: "",
|
|
108
|
+
apiKeyEnv: "OPENAI_API_KEY",
|
|
109
|
+
model: "gpt-5.3-codex",
|
|
110
|
+
permissionPolicy: "ask"
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
113
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout })
|
|
114
|
+
try {
|
|
115
|
+
answers = await runInteractive(rl)
|
|
116
|
+
} finally {
|
|
117
|
+
rl.close()
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const config = buildConfig(answers)
|
|
122
|
+
const check = validateConfig(config)
|
|
123
|
+
if (!check.valid) {
|
|
124
|
+
console.error("generated config is invalid:")
|
|
125
|
+
for (const e of check.errors) console.error(` - ${e}`)
|
|
126
|
+
process.exitCode = 1
|
|
127
|
+
return
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
await mkdir(configDir, { recursive: true })
|
|
131
|
+
await writeFile(configFile, YAML.stringify(config), "utf8")
|
|
132
|
+
console.log(`created: ${configFile}`)
|
|
133
|
+
|
|
134
|
+
const defaultProvider = config.provider.default
|
|
135
|
+
const providerCfg = config.provider[defaultProvider] || {}
|
|
136
|
+
if (providerCfg.api_key_env && !process.env[providerCfg.api_key_env]) {
|
|
137
|
+
console.log(`note: ${providerCfg.api_key_env} is not set in environment`)
|
|
138
|
+
}
|
|
139
|
+
console.log("run 'kkcode doctor' for full diagnostics")
|
|
140
|
+
})
|
|
141
|
+
}
|