@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,9 +1,12 @@
1
1
  import path from "node:path"
2
- import { readFile, writeFile } from "node:fs/promises"
2
+ import { readFile, writeFile, mkdir } from "node:fs/promises"
3
3
  import { Command } from "commander"
4
4
  import YAML from "yaml"
5
5
  import { importConfig } from "../config/import-config.mjs"
6
6
  import { validateConfig } from "../config/schema.mjs"
7
+ import { loadConfig } from "../config/load-config.mjs"
8
+ import { DEFAULT_CONFIG } from "../config/defaults.mjs"
9
+ import { projectConfigCandidates } from "../storage/paths.mjs"
7
10
 
8
11
  function parseInput(file, raw) {
9
12
  if (file.endsWith(".json")) return JSON.parse(raw)
@@ -15,6 +18,72 @@ function stringifyOutput(file, data) {
15
18
  return YAML.stringify(data)
16
19
  }
17
20
 
21
+ function getByPath(obj, keyPath) {
22
+ const keys = keyPath.split(".")
23
+ let current = obj
24
+ for (const key of keys) {
25
+ if (current == null || typeof current !== "object") return undefined
26
+ current = current[key]
27
+ }
28
+ return current
29
+ }
30
+
31
+ const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype"])
32
+
33
+ function setByPath(obj, keyPath, value) {
34
+ const keys = keyPath.split(".")
35
+ if (keys.some(k => FORBIDDEN_KEYS.has(k))) {
36
+ throw new Error(`forbidden key in path: ${keyPath}`)
37
+ }
38
+ let current = obj
39
+ for (let i = 0; i < keys.length - 1; i++) {
40
+ const key = keys[i]
41
+ if (current[key] == null || typeof current[key] !== "object") {
42
+ current[key] = {}
43
+ }
44
+ current = current[key]
45
+ }
46
+ current[keys[keys.length - 1]] = value
47
+ }
48
+
49
+ function coerceValue(raw) {
50
+ if (raw === "true") return true
51
+ if (raw === "false") return false
52
+ if (raw === "null") return null
53
+ if (raw !== "" && /^-?\d+(\.\d+)?$/.test(raw)) return Number(raw)
54
+ return raw
55
+ }
56
+
57
+ function isNonDefault(merged, defaults, keyPath) {
58
+ const mergedVal = getByPath(merged, keyPath)
59
+ const defaultVal = getByPath(defaults, keyPath)
60
+ return JSON.stringify(mergedVal) !== JSON.stringify(defaultVal)
61
+ }
62
+
63
+ function printTree(obj, defaults, prefix = "", indent = "") {
64
+ if (obj == null || typeof obj !== "object") {
65
+ const marker = isNonDefault(obj, defaults, prefix) ? " *" : ""
66
+ console.log(`${indent}${formatValue(obj)}${marker}`)
67
+ return
68
+ }
69
+ for (const [key, value] of Object.entries(obj)) {
70
+ const fullPath = prefix ? `${prefix}.${key}` : key
71
+ if (value != null && typeof value === "object" && !Array.isArray(value)) {
72
+ console.log(`${indent}${key}:`)
73
+ printTree(value, defaults, fullPath, indent + " ")
74
+ } else {
75
+ const marker = isNonDefault(value, getByPath(defaults, fullPath), "") ? " *" : ""
76
+ console.log(`${indent}${key}: ${formatValue(value)}${marker}`)
77
+ }
78
+ }
79
+ }
80
+
81
+ function formatValue(v) {
82
+ if (v === null) return "null"
83
+ if (Array.isArray(v)) return JSON.stringify(v)
84
+ return String(v)
85
+ }
86
+
18
87
  export function createConfigCommand() {
19
88
  const cmd = new Command("config").description("manage kkcode config")
20
89
 
@@ -40,5 +109,83 @@ export function createConfigCommand() {
40
109
  console.log(`imported config written: ${to}`)
41
110
  })
42
111
 
112
+ cmd
113
+ .command("show")
114
+ .description("show current effective config (merged)")
115
+ .option("--section <name>", "show only a specific top-level section")
116
+ .action(async (options) => {
117
+ const { config, source } = await loadConfig(process.cwd())
118
+ console.log("# effective config (user + project + defaults)")
119
+ if (source.userPath) console.log(`# user: ${source.userPath}`)
120
+ if (source.projectPath) console.log(`# project: ${source.projectPath}`)
121
+ console.log("# entries marked with * differ from defaults\n")
122
+ const target = options.section ? { [options.section]: config[options.section] } : config
123
+ if (options.section && config[options.section] === undefined) {
124
+ console.error(`unknown section: ${options.section}`)
125
+ process.exitCode = 1
126
+ return
127
+ }
128
+ printTree(target, DEFAULT_CONFIG)
129
+ })
130
+
131
+ cmd
132
+ .command("get <key>")
133
+ .description("get a config value by dot-path (e.g. provider.default)")
134
+ .action(async (key) => {
135
+ const { config } = await loadConfig(process.cwd())
136
+ const value = getByPath(config, key)
137
+ if (value === undefined) {
138
+ console.error(`key not found: ${key}`)
139
+ process.exitCode = 1
140
+ return
141
+ }
142
+ if (value != null && typeof value === "object") {
143
+ console.log(YAML.stringify(value).trimEnd())
144
+ } else {
145
+ console.log(formatValue(value))
146
+ }
147
+ })
148
+
149
+ cmd
150
+ .command("set <key> <value>")
151
+ .description("set a config value in project config (e.g. provider.default anthropic)")
152
+ .action(async (key, rawValue) => {
153
+ const cwd = process.cwd()
154
+ const candidates = projectConfigCandidates(cwd)
155
+ let configPath = null
156
+ for (const c of candidates) {
157
+ try {
158
+ await readFile(c, "utf8")
159
+ configPath = c
160
+ break
161
+ } catch { /* not found */ }
162
+ }
163
+ if (!configPath) {
164
+ configPath = candidates[0]
165
+ await mkdir(path.dirname(configPath), { recursive: true })
166
+ }
167
+
168
+ let existing = {}
169
+ try {
170
+ const raw = await readFile(configPath, "utf8")
171
+ existing = parseInput(configPath, raw) || {}
172
+ } catch { /* new file */ }
173
+
174
+ const value = coerceValue(rawValue)
175
+ setByPath(existing, key, value)
176
+
177
+ const check = validateConfig(existing)
178
+ if (!check.valid) {
179
+ console.error("resulting config is invalid:")
180
+ for (const e of check.errors) console.error(` - ${e}`)
181
+ process.exitCode = 1
182
+ return
183
+ }
184
+
185
+ await writeFile(configPath, stringifyOutput(configPath, existing), "utf8")
186
+ console.log(`${key} = ${formatValue(value)}`)
187
+ console.log(`written: ${configPath}`)
188
+ })
189
+
43
190
  return cmd
44
191
  }
@@ -8,6 +8,7 @@ import { auditStats } from "../storage/audit-store.mjs"
8
8
  import { fsckSessionStore, flushNow } from "../session/store.mjs"
9
9
  import { BackgroundManager } from "../orchestration/background-manager.mjs"
10
10
  import { McpRegistry } from "../mcp/registry.mjs"
11
+ import { SkillRegistry } from "../skill/registry.mjs"
11
12
 
12
13
  const exec = promisify(execCb)
13
14
 
@@ -69,8 +70,19 @@ async function buildDoctorReport() {
69
70
  const storage = await fsckSessionStore()
70
71
  const backgroundTasks = await BackgroundManager.list()
71
72
  await McpRegistry.initialize(config)
73
+ await SkillRegistry.initialize({ ...config, skills: { ...(config.skills || {}), auto_seed: false } }, process.cwd())
72
74
  const mcpSnapshot = McpRegistry.healthSnapshot()
73
75
  const mcpHealthy = mcpSnapshot.filter((item) => item.ok).length
76
+ const skillList = SkillRegistry.list()
77
+ const skillSummary = {
78
+ enabled: config.skills?.enabled !== false,
79
+ autoSeed: config.skills?.auto_seed !== false,
80
+ total: skillList.length,
81
+ template: skillList.filter((s) => s.type === "template").length,
82
+ skillMd: skillList.filter((s) => s.type === "skill_md").length,
83
+ mcpPrompt: skillList.filter((s) => s.type === "mcp_prompt").length,
84
+ programmable: skillList.filter((s) => s.type === "mjs").length
85
+ }
74
86
 
75
87
  return {
76
88
  ok: storage.ok,
@@ -94,6 +106,7 @@ async function buildDoctorReport() {
94
106
  unhealthy: mcpSnapshot.length - mcpHealthy,
95
107
  servers: mcpSnapshot
96
108
  },
109
+ skills: skillSummary,
97
110
  storage: {
98
111
  sessions: storage,
99
112
  eventLog: events,
@@ -120,11 +133,18 @@ function printTextReport(report, themeWarnings = []) {
120
133
  }
121
134
  for (const p of report.runtime.providersConfigured) {
122
135
  console.log(
123
- `provider:${p.name} type=${p.type} model=${p.model || "?"} key=${p.apiKeyEnv || "-"} (${p.apiKeyConfigured ? "set" : "missing"})`
136
+ `provider:${p.name} type=${p.type} model=${p.model || "?"} env=${p.apiKeyEnv || "-"} (${p.apiKeyConfigured ? "set" : "missing"})`
124
137
  )
125
138
  }
126
139
  console.log(`check node=${report.checks.node ? "ok" : "missing"} rg=${report.checks.rg ? "ok" : "missing"} git=${report.checks.git ? "ok" : "missing"}`)
127
140
  console.log(`mcp: configured=${report.mcp.configured} healthy=${report.mcp.healthy} unhealthy=${report.mcp.unhealthy}`)
141
+ console.log(`skills: total=${report.skills.total} template=${report.skills.template + report.skills.skillMd} mcp=${report.skills.mcpPrompt} programmable=${report.skills.programmable}`)
142
+ if (report.mcp.configured === 0) {
143
+ console.log(" mcp quickstart: kkcode mcp init --project --with-skills")
144
+ }
145
+ if (report.skills.total === 0) {
146
+ console.log(" skills quickstart: kkcode skill init --project")
147
+ }
128
148
  console.log(`sessions: ok=${report.storage.sessions.ok} index=${report.storage.sessions.sessionsInIndex} files=${report.storage.sessions.filesOnDisk}`)
129
149
  console.log(`events: active=${report.storage.eventLog.activeBytes} rotated=${report.storage.eventLog.rotatedFiles}`)
130
150
  console.log(`audit: total=${report.storage.audit.total} error1h=${report.storage.audit.error1h} error24h=${report.storage.audit.error24h}`)
@@ -138,11 +158,15 @@ export function createDoctorCommand() {
138
158
  .description("run environment diagnostics")
139
159
  .option("--json", "print structured diagnostics", false)
140
160
  .action(async (options) => {
141
- const report = await buildDoctorReport()
142
- if (options.json) {
143
- console.log(JSON.stringify(report, null, 2))
144
- return
161
+ try {
162
+ const report = await buildDoctorReport()
163
+ if (options.json) {
164
+ console.log(JSON.stringify(report, null, 2))
165
+ return
166
+ }
167
+ printTextReport(report, report.themeWarnings || [])
168
+ } finally {
169
+ McpRegistry.shutdown()
145
170
  }
146
- printTextReport(report, report.themeWarnings || [])
147
171
  })
148
172
  }
@@ -35,6 +35,23 @@ const PROVIDER_DEFAULTS = {
35
35
  "openai-compatible": { base_url: "", api_key_env: "", default_model: "" }
36
36
  }
37
37
 
38
+ // Auto-detect available providers by checking environment variables
39
+ function detectProvider() {
40
+ const registered = new Set(listProviders())
41
+ const checks = [
42
+ { provider: "anthropic", env: "ANTHROPIC_API_KEY" },
43
+ { provider: "openai", env: "OPENAI_API_KEY" }
44
+ ]
45
+ const available = []
46
+ for (const { provider, env } of checks) {
47
+ if (process.env[env] && registered.has(provider)) available.push(provider)
48
+ }
49
+ // Prefer anthropic if both are set, otherwise first available, fallback to openai
50
+ if (available.includes("anthropic")) return { provider: "anthropic", detected: available }
51
+ if (available.length) return { provider: available[0], detected: available }
52
+ return { provider: "openai", detected: [] }
53
+ }
54
+
38
55
  function buildConfig(answers) {
39
56
  const config = {
40
57
  provider: {
@@ -56,7 +73,11 @@ function buildConfig(answers) {
56
73
 
57
74
  async function runInteractive(rl) {
58
75
  const providers = listProviders()
59
- const provider = await askChoice(rl, "select default provider:", providers, "openai")
76
+ const { provider: detectedProvider, detected } = detectProvider()
77
+ if (detected.length) {
78
+ console.log(`\nauto-detected API keys: ${detected.join(", ")}`)
79
+ }
80
+ const provider = await askChoice(rl, "select default provider:", providers, detectedProvider)
60
81
  const defaults = PROVIDER_DEFAULTS[provider] || {}
61
82
 
62
83
  let baseUrl = ""
@@ -102,11 +123,16 @@ export function createInitCommand() {
102
123
 
103
124
  let answers
104
125
  if (options.yes) {
126
+ const { provider, detected } = detectProvider()
127
+ const defaults = PROVIDER_DEFAULTS[provider] || {}
128
+ if (detected.length) {
129
+ console.log(`auto-detected: ${detected.join(", ")} → using ${provider}`)
130
+ }
105
131
  answers = {
106
- provider: "openai",
107
- baseUrl: "",
108
- apiKeyEnv: "OPENAI_API_KEY",
109
- model: "gpt-5.3-codex",
132
+ provider,
133
+ baseUrl: defaults.base_url || "",
134
+ apiKeyEnv: defaults.api_key_env || "",
135
+ model: defaults.default_model || "",
110
136
  permissionPolicy: "ask"
111
137
  }
112
138
  } else {
@@ -134,7 +160,7 @@ export function createInitCommand() {
134
160
  const defaultProvider = config.provider.default
135
161
  const providerCfg = config.provider[defaultProvider] || {}
136
162
  if (providerCfg.api_key_env && !process.env[providerCfg.api_key_env]) {
137
- console.log(`note: ${providerCfg.api_key_env} is not set in environment`)
163
+ console.log(`note: environment variable ${providerCfg.api_key_env} is not set`)
138
164
  }
139
165
  console.log("run 'kkcode doctor' for full diagnostics")
140
166
  })
@@ -1,5 +1,8 @@
1
1
  import { Command } from "commander"
2
+ import { readFile } from "node:fs/promises"
2
3
  import { LongAgentManager } from "../orchestration/longagent-manager.mjs"
4
+ import { loadConfig } from "../config/load-config.mjs"
5
+ import { eventLogPath } from "../storage/paths.mjs"
3
6
 
4
7
  export function createLongagentCommand() {
5
8
  const cmd = new Command("longagent").description("manage longagent sessions")
@@ -96,5 +99,119 @@ export function createLongagentCommand() {
96
99
  console.log(`stage retry requested: ${options.stage} (session=${out.sessionId})`)
97
100
  })
98
101
 
102
+ cmd
103
+ .command("logs")
104
+ .description("view longagent event logs")
105
+ .option("--session <id>", "filter by session id")
106
+ .option("-n, --lines <n>", "number of recent lines", "50")
107
+ .option("--json", "output raw JSON lines")
108
+ .action(async (options) => {
109
+ const logFile = eventLogPath()
110
+ let raw
111
+ try {
112
+ raw = await readFile(logFile, "utf8")
113
+ } catch {
114
+ console.error(`no event log found at ${logFile}`)
115
+ process.exitCode = 1
116
+ return
117
+ }
118
+ const allLines = raw.trim().split("\n").filter(Boolean)
119
+ let events = allLines.map((line) => {
120
+ try { return JSON.parse(line) } catch { return null }
121
+ }).filter(Boolean)
122
+
123
+ if (options.session) {
124
+ events = events.filter((e) => e.sessionId === options.session)
125
+ }
126
+ // filter longagent-related events
127
+ events = events.filter((e) =>
128
+ String(e.type || "").includes("longagent") ||
129
+ String(e.type || "").includes("stage") ||
130
+ String(e.type || "").includes("task")
131
+ )
132
+ const limit = Math.max(1, Number(options.lines) || 50)
133
+ events = events.slice(-limit)
134
+
135
+ if (options.json) {
136
+ for (const e of events) console.log(JSON.stringify(e))
137
+ return
138
+ }
139
+ if (!events.length) {
140
+ console.log("no longagent events found")
141
+ return
142
+ }
143
+ for (const e of events) {
144
+ const ts = e.timestamp ? new Date(e.timestamp).toISOString().slice(11, 19) : "??:??:??"
145
+ const sid = e.sessionId ? e.sessionId.slice(0, 12) : "????????????"
146
+ const payload = e.payload ? JSON.stringify(e.payload).slice(0, 120) : ""
147
+ console.log(`${ts} [${sid}] ${e.type || "unknown"}${payload ? " " + payload : ""}`)
148
+ }
149
+ })
150
+
151
+ cmd
152
+ .command("config")
153
+ .description("show effective longagent configuration")
154
+ .option("--full", "show full merged config (not just longagent section)")
155
+ .action(async (options) => {
156
+ const configState = await loadConfig()
157
+ if (options.full) {
158
+ console.log(JSON.stringify(configState.config, null, 2))
159
+ return
160
+ }
161
+ const la = configState.config.agent?.longagent || {}
162
+ console.log("## longagent config")
163
+ console.log(JSON.stringify(la, null, 2))
164
+ console.log("\n## sources")
165
+ console.log(` user: ${configState.source.userPath || "(none)"}`)
166
+ console.log(` project: ${configState.source.projectPath || "(none)"}`)
167
+ console.log(` env: ${configState.source.envPath || "(none)"}`)
168
+ if (configState.errors.length) {
169
+ console.log("\n## errors")
170
+ for (const e of configState.errors) console.log(` - ${e}`)
171
+ }
172
+ })
173
+
174
+ cmd
175
+ .command("start")
176
+ .description("launch a longagent session with a prompt")
177
+ .argument("<prompt>", "task description for longagent")
178
+ .option("--model <model>", "override model")
179
+ .option("--provider <type>", "override provider type")
180
+ .option("--max-iterations <n>", "max iterations (0=unlimited)", "0")
181
+ .action(async (prompt, options) => {
182
+ const configState = await loadConfig()
183
+ const providerKey = options.provider || configState.config.provider.default
184
+ const providerConf = configState.config.provider[providerKey] || {}
185
+ const model = options.model || providerConf.default_model
186
+ if (!model) {
187
+ console.error(`no model configured for provider "${providerKey}"`)
188
+ process.exitCode = 1
189
+ return
190
+ }
191
+ const { executeTurn } = await import("../session/engine.mjs")
192
+ const { newSessionId } = await import("../session/engine.mjs")
193
+ const sessionId = newSessionId()
194
+ console.log(`starting longagent session: ${sessionId}`)
195
+ console.log(`model: ${model}, provider: ${providerKey}`)
196
+ try {
197
+ const result = await executeTurn({
198
+ prompt,
199
+ mode: "longagent",
200
+ model,
201
+ sessionId,
202
+ configState: { config: configState.config, source: configState.source },
203
+ providerType: providerKey,
204
+ baseUrl: providerConf.base_url || null,
205
+ apiKeyEnv: providerConf.api_key_env || null,
206
+ maxIterations: Number(options.maxIterations) || 0,
207
+ output: { write: (t) => process.stdout.write(t) }
208
+ })
209
+ console.log(`\nsession ${sessionId} finished (status: ${result.status || "done"})`)
210
+ } catch (err) {
211
+ console.error(`longagent error: ${err.message}`)
212
+ process.exitCode = 1
213
+ }
214
+ })
215
+
99
216
  return cmd
100
217
  }