koneck 1.0.2 → 2.0.0
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/dist/auto-docs.d.ts +4 -0
- package/dist/auto-docs.d.ts.map +1 -0
- package/dist/auto-docs.js +57 -0
- package/dist/auto-docs.js.map +1 -0
- package/dist/chat.d.ts.map +1 -1
- package/dist/chat.js +676 -102
- package/dist/chat.js.map +1 -1
- package/dist/code-graph.d.ts +14 -0
- package/dist/code-graph.d.ts.map +1 -0
- package/dist/code-graph.js +167 -0
- package/dist/code-graph.js.map +1 -0
- package/dist/critique.d.ts +4 -0
- package/dist/critique.d.ts.map +1 -0
- package/dist/critique.js +36 -0
- package/dist/critique.js.map +1 -0
- package/dist/daemon.d.ts +3 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +111 -0
- package/dist/daemon.js.map +1 -0
- package/dist/engine.d.ts +5 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +17 -3
- package/dist/engine.js.map +1 -1
- package/dist/health.d.ts +2 -0
- package/dist/health.d.ts.map +1 -0
- package/dist/health.js +201 -0
- package/dist/health.js.map +1 -0
- package/dist/index.js +163 -0
- package/dist/index.js.map +1 -1
- package/dist/issues.d.ts +3 -0
- package/dist/issues.d.ts.map +1 -0
- package/dist/issues.js +77 -0
- package/dist/issues.js.map +1 -0
- package/dist/knowledge-graph.d.ts +3 -0
- package/dist/knowledge-graph.d.ts.map +1 -0
- package/dist/knowledge-graph.js +85 -0
- package/dist/knowledge-graph.js.map +1 -0
- package/dist/pipeline.d.ts +3 -0
- package/dist/pipeline.d.ts.map +1 -0
- package/dist/pipeline.js +79 -0
- package/dist/pipeline.js.map +1 -0
- package/dist/reasoning-cache.d.ts +9 -0
- package/dist/reasoning-cache.d.ts.map +1 -0
- package/dist/reasoning-cache.js +67 -0
- package/dist/reasoning-cache.js.map +1 -0
- package/dist/recon.d.ts +2 -0
- package/dist/recon.d.ts.map +1 -0
- package/dist/recon.js +121 -0
- package/dist/recon.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/chat.js
CHANGED
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import readline from 'readline';
|
|
2
2
|
import chalk from 'chalk';
|
|
3
|
-
import {
|
|
3
|
+
import { execa } from 'execa';
|
|
4
|
+
import { createAgentSession, runAgent } from './engine.js';
|
|
4
5
|
import { tui } from './tui.js';
|
|
5
6
|
import { saveSession, listSessions, generateSessionId } from './session.js';
|
|
6
7
|
import { estimateCost, formatCost } from './pricing.js';
|
|
7
8
|
import { PROVIDERS } from './providers.js';
|
|
8
9
|
import { loadMemory } from './memory.js';
|
|
10
|
+
import { detectProject } from './project.js';
|
|
11
|
+
import { runDoctor } from './doctor.js';
|
|
12
|
+
import { runGenerateKoneckMd } from './generate-koneck-md.js';
|
|
13
|
+
import { runHealth } from './health.js';
|
|
14
|
+
import { runPipeline } from './pipeline.js';
|
|
15
|
+
import { queryKnowledgeGraph } from './knowledge-graph.js';
|
|
16
|
+
import { buildCodeGraph, queryGraph, getCallGraph } from './code-graph.js';
|
|
17
|
+
import { runAutoDocs } from './auto-docs.js';
|
|
9
18
|
const MODES = ['auto', 'plan', 'code'];
|
|
10
19
|
const MODE_LABELS = {
|
|
11
20
|
auto: chalk.cyan('auto'),
|
|
@@ -14,55 +23,142 @@ const MODE_LABELS = {
|
|
|
14
23
|
};
|
|
15
24
|
const MODE_PREFIXES = {
|
|
16
25
|
auto: '',
|
|
17
|
-
plan: 'Think step-by-step
|
|
18
|
-
code: 'Be concise and direct.
|
|
26
|
+
plan: 'Think step-by-step. Create an explicit plan before touching any file. List every file you will change and why.\n\nTask: ',
|
|
27
|
+
code: 'Be concise and direct. Only code changes. Skip explanations unless non-obvious.\n\nTask: ',
|
|
19
28
|
};
|
|
20
|
-
|
|
29
|
+
const EFFORT_HINTS = {
|
|
30
|
+
low: '[Low effort: be brief, skip explanations, fastest answer]\n',
|
|
31
|
+
high: '[High effort: be thorough, cover edge cases, deep analysis]\n',
|
|
32
|
+
max: '[Maximum effort: most rigorous analysis possible, check everything]\n',
|
|
33
|
+
};
|
|
34
|
+
const PERSONALITY_HINTS = {
|
|
35
|
+
default: '',
|
|
36
|
+
friendly: '[Be conversational, encouraging, and explain your reasoning warmly]\n',
|
|
37
|
+
minimal: '[Be extremely terse. Numbers, filenames, one-liners only. No prose.]\n',
|
|
38
|
+
};
|
|
39
|
+
// ─── Command registry ─────────────────────────────────────────────────────────
|
|
21
40
|
const COMMANDS = [
|
|
22
|
-
{ cmd: '/help', desc: 'Show this
|
|
23
|
-
{ cmd: '/status', desc: '
|
|
24
|
-
{ cmd: '/model', args: '[name]', desc: 'Show or switch model
|
|
25
|
-
{ cmd: '/provider', args: '[name]', desc: 'Show or switch provider
|
|
41
|
+
{ cmd: '/help', desc: 'Show this help' },
|
|
42
|
+
{ cmd: '/status', desc: 'Provider, model, mode, tokens, cost, CWD' },
|
|
43
|
+
{ cmd: '/model', args: '[name]', desc: 'Show or switch model' },
|
|
44
|
+
{ cmd: '/provider', args: '[name]', desc: 'Show or switch provider' },
|
|
26
45
|
{ cmd: '/mode', args: '[auto|plan|code]', desc: 'Show or set mode' },
|
|
27
|
-
{ cmd: '/
|
|
28
|
-
{ cmd: '/
|
|
29
|
-
{ cmd: '/
|
|
30
|
-
{ cmd: '/
|
|
46
|
+
{ cmd: '/effort', args: '[low|medium|high|max]', desc: 'Set reasoning effort level' },
|
|
47
|
+
{ cmd: '/personality', args: '[default|friendly|minimal]', desc: 'Toggle response tone' },
|
|
48
|
+
{ cmd: '/permissions', args: '[on|off]', desc: 'Show or toggle require-approval' },
|
|
49
|
+
{ cmd: '/btw', args: '<question>', desc: 'Side question — not added to history' },
|
|
50
|
+
{ cmd: '/goal', args: '[text]', desc: 'Set a persistent goal prepended to every message' },
|
|
51
|
+
{ cmd: '/add-dir', args: '<path>', desc: 'Add a directory to agent context' },
|
|
52
|
+
{ cmd: '/diff', desc: 'Show uncommitted git changes' },
|
|
53
|
+
{ cmd: '/verify', desc: 'Run project test command' },
|
|
54
|
+
{ cmd: '/code-review', args: '[--fix]', desc: 'Review current git diff for bugs' },
|
|
55
|
+
{ cmd: '/security-review', desc: 'Security audit of current git diff' },
|
|
56
|
+
{ cmd: '/mcp', desc: 'Show MCP server connections' },
|
|
57
|
+
{ cmd: '/init', desc: 'Generate KONECK.md for this project' },
|
|
58
|
+
{ cmd: '/doctor', desc: 'Run setup and config health check' },
|
|
59
|
+
{ cmd: '/debug', desc: 'Show runtime diagnostics' },
|
|
60
|
+
{ cmd: '/rewind', desc: 'Undo last exchange (remove last turn from history)' },
|
|
61
|
+
{ cmd: '/fork', desc: 'Fork conversation into a fresh session with same history' },
|
|
62
|
+
{ cmd: '/branch', desc: 'Alias for /fork' },
|
|
63
|
+
{ cmd: '/subtask', args: '<task>', desc: 'Delegate task to an isolated subagent' },
|
|
64
|
+
{ cmd: '/ps', desc: 'List active background tasks' },
|
|
65
|
+
{ cmd: '/copy', desc: 'Copy last response to clipboard' },
|
|
66
|
+
{ cmd: '/rename', args: '<name>', desc: 'Rename current session' },
|
|
67
|
+
{ cmd: '/compact', args: '[focus]', desc: 'Summarise conversation to save context' },
|
|
68
|
+
{ cmd: '/memory', desc: 'Show .koneck/MEMORY.md' },
|
|
31
69
|
{ cmd: '/cwd', args: '[path]', desc: 'Show or change working directory' },
|
|
32
|
-
{ cmd: '/
|
|
70
|
+
{ cmd: '/cost', desc: 'Show session cost' },
|
|
71
|
+
{ cmd: '/tokens', desc: 'Show token usage breakdown' },
|
|
72
|
+
{ cmd: '/clear', desc: 'Clear conversation history' },
|
|
33
73
|
{ cmd: '/reset', desc: 'Alias for /clear' },
|
|
34
|
-
{ cmd: '/
|
|
74
|
+
{ cmd: '/new', desc: 'Alias for /clear' },
|
|
75
|
+
{ cmd: '/save', desc: 'Save session to disk' },
|
|
35
76
|
{ cmd: '/sessions', desc: 'List recent saved sessions' },
|
|
36
|
-
{ cmd: '/
|
|
77
|
+
{ cmd: '/feedback', desc: 'Submit feedback / report a bug' },
|
|
78
|
+
{ cmd: '/architect', desc: 'Analyze current architecture, show problems and suggestions' },
|
|
79
|
+
{ cmd: '/performance', desc: 'Find N+1 queries, memory leaks, blocking I/O, slow paths' },
|
|
80
|
+
{ cmd: '/security', desc: 'Full OWASP security audit of the codebase' },
|
|
81
|
+
{ cmd: '/pipeline', args: '<task>', desc: 'Run multi-agent pipeline (planner→coder→reviewer→tester)' },
|
|
82
|
+
{ cmd: '/knowledge', args: '[concept]', desc: 'Query knowledge graph — where is X implemented?' },
|
|
83
|
+
{ cmd: '/health', desc: 'Repository health scan (dead code, complexity, unused deps)' },
|
|
84
|
+
{ cmd: '/graph', args: '[symbol]', desc: 'Build or query semantic code graph' },
|
|
85
|
+
{ cmd: '/docs', desc: 'Regenerate docs for recent changes' },
|
|
86
|
+
{ cmd: '/exit', desc: 'Save and exit' },
|
|
37
87
|
{ cmd: '/quit', desc: 'Alias for /exit' },
|
|
38
88
|
];
|
|
39
89
|
const COMMAND_NAMES = COMMANDS.map(c => c.cmd);
|
|
40
|
-
function
|
|
90
|
+
function buildHelp() {
|
|
41
91
|
const lines = ['\n ' + chalk.bold('Slash commands') + '\n'];
|
|
42
|
-
const w = Math.max(...COMMANDS.map(c => c.cmd.length + (c.args
|
|
92
|
+
const w = Math.max(...COMMANDS.map(c => c.cmd.length + (c.args?.length ?? -1) + 1));
|
|
43
93
|
for (const { cmd, args, desc } of COMMANDS) {
|
|
44
94
|
const left = (cmd + (args ? ' ' + args : '')).padEnd(w + 2);
|
|
45
95
|
lines.push(' ' + chalk.cyan(left) + chalk.dim(desc));
|
|
46
96
|
}
|
|
47
97
|
lines.push('');
|
|
48
|
-
lines.push(chalk.dim(' Shift+Tab
|
|
49
|
-
lines.push(chalk.dim('
|
|
98
|
+
lines.push(chalk.dim(' Shift+Tab Cycle mode: auto → plan → code → auto'));
|
|
99
|
+
lines.push(chalk.dim(' Esc Esc Edit previous message'));
|
|
100
|
+
lines.push(chalk.dim(' Ctrl+C Cancel running task / exit if idle'));
|
|
101
|
+
lines.push(chalk.dim(' Ctrl+L Clear terminal screen'));
|
|
50
102
|
lines.push('');
|
|
51
103
|
return lines.join('\n');
|
|
52
104
|
}
|
|
53
105
|
// ─── Prompt renderer ─────────────────────────────────────────────────────────
|
|
54
|
-
function renderPrompt(mode) {
|
|
55
|
-
|
|
106
|
+
function renderPrompt(mode, effort, personality, goal) {
|
|
107
|
+
const extras = [];
|
|
108
|
+
if (effort !== 'medium')
|
|
109
|
+
extras.push(chalk.dim(effort));
|
|
110
|
+
if (personality !== 'default')
|
|
111
|
+
extras.push(chalk.dim(personality));
|
|
112
|
+
if (goal)
|
|
113
|
+
extras.push(chalk.dim('goal'));
|
|
114
|
+
const suffix = extras.length ? chalk.dim(' ' + extras.join('·')) : '';
|
|
115
|
+
return chalk.cyan('koneck') + chalk.dim('[') + MODE_LABELS[mode] + chalk.dim(']') + suffix + chalk.cyan('> ');
|
|
116
|
+
}
|
|
117
|
+
// ─── Clipboard helper ─────────────────────────────────────────────────────────
|
|
118
|
+
async function copyToClipboard(text) {
|
|
119
|
+
const cmds = [
|
|
120
|
+
['wl-copy'],
|
|
121
|
+
['xclip', '-selection', 'clipboard'],
|
|
122
|
+
['xsel', '--clipboard', '--input'],
|
|
123
|
+
['pbcopy'],
|
|
124
|
+
];
|
|
125
|
+
for (const [bin, ...args] of cmds) {
|
|
126
|
+
try {
|
|
127
|
+
await execa(bin, args, { input: text });
|
|
128
|
+
return true;
|
|
129
|
+
}
|
|
130
|
+
catch { /* try next */ }
|
|
131
|
+
}
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
// ─── MCP servers reader ───────────────────────────────────────────────────────
|
|
135
|
+
async function readMcpConfig(cwd) {
|
|
136
|
+
const { default: fs } = await import('fs/promises');
|
|
137
|
+
const { default: path } = await import('path');
|
|
138
|
+
try {
|
|
139
|
+
const raw = await fs.readFile(path.join(cwd, '.koneck', 'mcp.json'), 'utf-8');
|
|
140
|
+
return JSON.parse(raw);
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
56
145
|
}
|
|
57
146
|
// ─── Main chat function ───────────────────────────────────────────────────────
|
|
58
147
|
export async function runChatMode(config) {
|
|
59
148
|
tui.printBanner();
|
|
60
149
|
const providerStr = chalk.dim(`${config.provider} / ${config.model}`);
|
|
61
150
|
console.log(chalk.cyan(' KONECK chat ') + providerStr);
|
|
62
|
-
console.log(chalk.dim(' Shift+Tab: cycle mode │ /help: commands │ Ctrl+C: cancel/exit\n'));
|
|
151
|
+
console.log(chalk.dim(' Shift+Tab: cycle mode │ /help: all commands │ Ctrl+C: cancel/exit\n'));
|
|
63
152
|
const sessionId = generateSessionId();
|
|
64
153
|
let session = await createAgentSession(config);
|
|
65
154
|
let mode = 'auto';
|
|
155
|
+
let effort = 'medium';
|
|
156
|
+
let personality = 'default';
|
|
157
|
+
let goal = null;
|
|
158
|
+
let sessionName = null;
|
|
159
|
+
let addedDirs = [];
|
|
160
|
+
let lastAssistantMsg = '';
|
|
161
|
+
let prevInput = '';
|
|
66
162
|
let busy = false;
|
|
67
163
|
let currentAbort = null;
|
|
68
164
|
let cfg = { ...config };
|
|
@@ -79,30 +175,49 @@ export async function runChatMode(config) {
|
|
|
79
175
|
return [[], line];
|
|
80
176
|
},
|
|
81
177
|
});
|
|
82
|
-
// ── Shift+Tab
|
|
178
|
+
// ── Shift+Tab + Esc Esc + Ctrl+L ──────────────────────────────────────────
|
|
83
179
|
if (process.stdin.isTTY) {
|
|
84
180
|
readline.emitKeypressEvents(process.stdin, rl);
|
|
181
|
+
let lastEsc = 0;
|
|
85
182
|
process.stdin.on('keypress', (_char, key) => {
|
|
86
183
|
if (!key)
|
|
87
184
|
return;
|
|
185
|
+
// Shift+Tab
|
|
88
186
|
if (key.sequence === '\x1b[Z' || (key.shift && key.name === 'tab')) {
|
|
89
187
|
const idx = MODES.indexOf(mode);
|
|
90
188
|
mode = MODES[(idx + 1) % MODES.length];
|
|
91
|
-
process.stdout.write('\r\x1b[K' + renderPrompt(mode));
|
|
189
|
+
process.stdout.write('\r\x1b[K' + renderPrompt(mode, effort, personality, !!goal));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
// Ctrl+L — clear screen
|
|
193
|
+
if (key.name === 'l' && key.ctrl) {
|
|
194
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
195
|
+
process.stdout.write(renderPrompt(mode, effort, personality, !!goal));
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
// Esc Esc — recall previous input
|
|
199
|
+
if (key.name === 'escape') {
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
if (now - lastEsc < 400 && prevInput) {
|
|
202
|
+
// Clear line and inject previous input
|
|
203
|
+
process.stdout.write('\r\x1b[K' + renderPrompt(mode, effort, personality, !!goal));
|
|
204
|
+
process.stdout.write(prevInput);
|
|
205
|
+
rl.write(prevInput);
|
|
206
|
+
}
|
|
207
|
+
lastEsc = now;
|
|
92
208
|
}
|
|
93
209
|
});
|
|
94
210
|
}
|
|
95
211
|
function writePrompt() {
|
|
96
|
-
process.stdout.write(renderPrompt(mode));
|
|
212
|
+
process.stdout.write(renderPrompt(mode, effort, personality, !!goal));
|
|
97
213
|
}
|
|
98
214
|
async function doSave() {
|
|
99
215
|
const firstUser = session.messages.find(m => m.role === 'user');
|
|
100
|
-
const
|
|
101
|
-
? firstUser.content.slice(0, 120)
|
|
102
|
-
: '(chat session)';
|
|
216
|
+
const defaultTask = firstUser && typeof firstUser.content === 'string'
|
|
217
|
+
? firstUser.content.slice(0, 120) : '(chat session)';
|
|
103
218
|
return saveSession(cfg.cwd, sessionId, {
|
|
104
219
|
id: sessionId,
|
|
105
|
-
task,
|
|
220
|
+
task: sessionName ?? defaultTask,
|
|
106
221
|
provider: cfg.provider,
|
|
107
222
|
model: cfg.model,
|
|
108
223
|
cwd: cfg.cwd,
|
|
@@ -115,45 +230,52 @@ export async function runChatMode(config) {
|
|
|
115
230
|
const input = line.trim();
|
|
116
231
|
if (!input)
|
|
117
232
|
return;
|
|
118
|
-
|
|
233
|
+
if (!input.startsWith('/'))
|
|
234
|
+
prevInput = input;
|
|
119
235
|
if (input.startsWith('/')) {
|
|
120
|
-
const
|
|
121
|
-
const
|
|
236
|
+
const spaceIdx = input.indexOf(' ');
|
|
237
|
+
const cmd = spaceIdx === -1 ? input : input.slice(0, spaceIdx);
|
|
238
|
+
const arg = spaceIdx === -1 ? '' : input.slice(spaceIdx + 1).trim();
|
|
122
239
|
switch (cmd) {
|
|
123
|
-
// ──
|
|
240
|
+
// ── Help ────────────────────────────────────────────────────────────
|
|
241
|
+
case '/help':
|
|
242
|
+
console.log(buildHelp());
|
|
243
|
+
return;
|
|
244
|
+
// ── Exit ────────────────────────────────────────────────────────────
|
|
124
245
|
case '/exit':
|
|
125
246
|
case '/quit': {
|
|
126
247
|
const file = await doSave();
|
|
127
|
-
console.log(chalk.dim(`\n Session saved: ${file}`));
|
|
128
|
-
console.log(chalk.dim(' Goodbye.\n'));
|
|
248
|
+
console.log(chalk.dim(`\n Session saved: ${file}\n Goodbye.\n`));
|
|
129
249
|
rl.close();
|
|
130
250
|
process.exit(0);
|
|
131
251
|
}
|
|
132
|
-
// ──
|
|
133
|
-
case '/help':
|
|
134
|
-
console.log(buildHelpText());
|
|
135
|
-
return;
|
|
136
|
-
// ── Status ────────────────────────────────────────────────────────────
|
|
252
|
+
// ── Status ──────────────────────────────────────────────────────────
|
|
137
253
|
case '/status': {
|
|
138
254
|
const cost = estimateCost(cfg.model, session.stats.promptTokens, session.stats.completionTokens);
|
|
139
|
-
const costStr = cost.known ? formatCost(cost.total) : 'unknown';
|
|
140
255
|
console.log('');
|
|
141
|
-
console.log(` ${chalk.dim('Provider
|
|
142
|
-
console.log(` ${chalk.dim('Model
|
|
256
|
+
console.log(` ${chalk.dim('Provider :')} ${chalk.white(cfg.provider)}`);
|
|
257
|
+
console.log(` ${chalk.dim('Model :')} ${chalk.white(cfg.model)}`);
|
|
143
258
|
if (cfg.baseURL)
|
|
144
|
-
console.log(` ${chalk.dim('Base URL
|
|
145
|
-
console.log(` ${chalk.dim('Mode
|
|
146
|
-
console.log(` ${chalk.dim('
|
|
147
|
-
console.log(` ${chalk.dim('
|
|
148
|
-
console.log(` ${chalk.dim('
|
|
149
|
-
console.log(` ${chalk.dim('
|
|
259
|
+
console.log(` ${chalk.dim('Base URL :')} ${chalk.dim(cfg.baseURL)}`);
|
|
260
|
+
console.log(` ${chalk.dim('Mode :')} ${MODE_LABELS[mode]}`);
|
|
261
|
+
console.log(` ${chalk.dim('Effort :')} ${chalk.white(effort)}`);
|
|
262
|
+
console.log(` ${chalk.dim('Personality :')} ${chalk.white(personality)}`);
|
|
263
|
+
console.log(` ${chalk.dim('Approval :')} ${chalk.white(cfg.requireApproval ? 'on' : 'off')}`);
|
|
264
|
+
console.log(` ${chalk.dim('CWD :')} ${chalk.dim(cfg.cwd)}`);
|
|
265
|
+
if (addedDirs.length)
|
|
266
|
+
console.log(` ${chalk.dim('Extra dirs :')} ${chalk.dim(addedDirs.join(', '))}`);
|
|
267
|
+
if (goal)
|
|
268
|
+
console.log(` ${chalk.dim('Goal :')} ${chalk.white(goal)}`);
|
|
269
|
+
console.log(` ${chalk.dim('Turns :')} ${chalk.white(session.stats.turns)}`);
|
|
270
|
+
console.log(` ${chalk.dim('Tokens :')} ${chalk.white(session.stats.totalTokens.toLocaleString())}`);
|
|
271
|
+
console.log(` ${chalk.dim('Cost :')} ${chalk.white(cost.known ? formatCost(cost.total) : 'unknown')}`);
|
|
150
272
|
console.log('');
|
|
151
273
|
return;
|
|
152
274
|
}
|
|
153
|
-
// ── Model
|
|
275
|
+
// ── Model ───────────────────────────────────────────────────────────
|
|
154
276
|
case '/model': {
|
|
155
277
|
if (!arg) {
|
|
156
|
-
console.log(`\n Current model: ${chalk.cyan(cfg.model)}
|
|
278
|
+
console.log(`\n Current model: ${chalk.cyan(cfg.model)}`);
|
|
157
279
|
console.log(chalk.dim(' Usage: /model <name> e.g. /model claude-opus-4-5\n'));
|
|
158
280
|
return;
|
|
159
281
|
}
|
|
@@ -162,12 +284,12 @@ export async function runChatMode(config) {
|
|
|
162
284
|
console.log(chalk.green(`\n ✔ Model → ${chalk.white(arg)} (session reset)\n`));
|
|
163
285
|
return;
|
|
164
286
|
}
|
|
165
|
-
// ── Provider
|
|
287
|
+
// ── Provider ────────────────────────────────────────────────────────
|
|
166
288
|
case '/provider': {
|
|
167
289
|
if (!arg) {
|
|
168
290
|
const known = Object.keys(PROVIDERS).join(', ');
|
|
169
291
|
console.log(`\n Current provider: ${chalk.cyan(cfg.provider)}`);
|
|
170
|
-
console.log(chalk.dim(` Available: ${known}
|
|
292
|
+
console.log(chalk.dim(` Available: ${known}`));
|
|
171
293
|
console.log(chalk.dim(' Usage: /provider <name> e.g. /provider anthropic\n'));
|
|
172
294
|
return;
|
|
173
295
|
}
|
|
@@ -178,7 +300,7 @@ export async function runChatMode(config) {
|
|
|
178
300
|
console.log(chalk.green(`\n ✔ Provider → ${chalk.white(arg)} model → ${chalk.white(newModel)} (session reset)\n`));
|
|
179
301
|
return;
|
|
180
302
|
}
|
|
181
|
-
// ── Mode
|
|
303
|
+
// ── Mode ────────────────────────────────────────────────────────────
|
|
182
304
|
case '/mode':
|
|
183
305
|
case '/auto':
|
|
184
306
|
case '/plan':
|
|
@@ -197,71 +319,358 @@ export async function runChatMode(config) {
|
|
|
197
319
|
console.log(`\n Mode → ${MODE_LABELS[mode]}\n`);
|
|
198
320
|
return;
|
|
199
321
|
}
|
|
200
|
-
// ──
|
|
201
|
-
case '/
|
|
202
|
-
const
|
|
203
|
-
if (
|
|
204
|
-
console.log(`\n
|
|
322
|
+
// ── Effort ──────────────────────────────────────────────────────────
|
|
323
|
+
case '/effort': {
|
|
324
|
+
const levels = ['low', 'medium', 'high', 'max'];
|
|
325
|
+
if (!arg) {
|
|
326
|
+
console.log(`\n Current effort: ${chalk.cyan(effort)}`);
|
|
327
|
+
console.log(chalk.dim(' Usage: /effort low|medium|high|max\n'));
|
|
328
|
+
return;
|
|
205
329
|
}
|
|
206
|
-
|
|
207
|
-
console.log(chalk.dim(`\n
|
|
330
|
+
if (!levels.includes(arg)) {
|
|
331
|
+
console.log(chalk.dim(`\n Unknown level "${arg}". Use: low, medium, high, max\n`));
|
|
332
|
+
return;
|
|
208
333
|
}
|
|
334
|
+
effort = arg;
|
|
335
|
+
console.log(chalk.green(`\n ✔ Effort → ${effort}\n`));
|
|
209
336
|
return;
|
|
210
337
|
}
|
|
211
|
-
// ──
|
|
212
|
-
case '/
|
|
213
|
-
const
|
|
338
|
+
// ── Personality ─────────────────────────────────────────────────────
|
|
339
|
+
case '/personality': {
|
|
340
|
+
const opts = ['default', 'friendly', 'minimal'];
|
|
341
|
+
if (!arg) {
|
|
342
|
+
console.log(`\n Current personality: ${chalk.cyan(personality)}`);
|
|
343
|
+
console.log(chalk.dim(' Usage: /personality default|friendly|minimal\n'));
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
if (!opts.includes(arg)) {
|
|
347
|
+
console.log(chalk.dim(`\n Unknown: "${arg}". Use: default, friendly, minimal\n`));
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
personality = arg;
|
|
351
|
+
console.log(chalk.green(`\n ✔ Personality → ${personality}\n`));
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
// ── Permissions ─────────────────────────────────────────────────────
|
|
355
|
+
case '/permissions': {
|
|
356
|
+
if (!arg) {
|
|
357
|
+
const state = cfg.requireApproval ? chalk.yellow('on (manual approval)') : chalk.green('off (auto-approve)');
|
|
358
|
+
console.log(`\n Approval: ${state}`);
|
|
359
|
+
console.log(chalk.dim(' Usage: /permissions on|off\n'));
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
cfg = { ...cfg, requireApproval: arg === 'on' };
|
|
363
|
+
console.log(chalk.green(`\n ✔ Approval → ${arg}\n`));
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
// ── BTW (ephemeral side question) ────────────────────────────────────
|
|
367
|
+
case '/btw': {
|
|
368
|
+
if (!arg) {
|
|
369
|
+
console.log(chalk.dim('\n Usage: /btw <question> (not added to history)\n'));
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
const checkpoint = session.messages.length;
|
|
373
|
+
busy = true;
|
|
374
|
+
rl.pause();
|
|
375
|
+
const sideAbort = new AbortController();
|
|
376
|
+
try {
|
|
377
|
+
await session.send('[Side question — do not reference this in future replies] ' + arg, sideAbort.signal);
|
|
378
|
+
const last = session.messages.filter(m => m.role === 'assistant').at(-1);
|
|
379
|
+
if (last && typeof last.content === 'string')
|
|
380
|
+
lastAssistantMsg = last.content;
|
|
381
|
+
}
|
|
382
|
+
catch { /* ignore */ }
|
|
383
|
+
finally {
|
|
384
|
+
session.messages.splice(checkpoint);
|
|
385
|
+
busy = false;
|
|
386
|
+
rl.resume();
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
// ── Goal ────────────────────────────────────────────────────────────
|
|
391
|
+
case '/goal': {
|
|
392
|
+
if (!arg) {
|
|
393
|
+
if (goal)
|
|
394
|
+
console.log(`\n Current goal: ${chalk.cyan(goal)}\n Use /goal clear to remove.\n`);
|
|
395
|
+
else
|
|
396
|
+
console.log(chalk.dim('\n No goal set. Usage: /goal <description>\n'));
|
|
397
|
+
return;
|
|
398
|
+
}
|
|
399
|
+
if (arg === 'clear') {
|
|
400
|
+
goal = null;
|
|
401
|
+
console.log(chalk.green('\n ✔ Goal cleared.\n'));
|
|
402
|
+
return;
|
|
403
|
+
}
|
|
404
|
+
goal = arg;
|
|
405
|
+
console.log(chalk.green(`\n ✔ Goal set: ${goal}\n`));
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
// ── Add-dir ─────────────────────────────────────────────────────────
|
|
409
|
+
case '/add-dir': {
|
|
410
|
+
if (!arg) {
|
|
411
|
+
console.log(chalk.dim('\n Usage: /add-dir <path>\n'));
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
const { default: path } = await import('path');
|
|
415
|
+
const resolved = path.resolve(cfg.cwd, arg);
|
|
416
|
+
if (!addedDirs.includes(resolved))
|
|
417
|
+
addedDirs.push(resolved);
|
|
418
|
+
console.log(chalk.green(`\n ✔ Added to context: ${resolved}\n`));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
// ── Diff ────────────────────────────────────────────────────────────
|
|
422
|
+
case '/diff': {
|
|
423
|
+
try {
|
|
424
|
+
const result = await execa('git', ['diff'], { cwd: cfg.cwd, all: true, reject: false });
|
|
425
|
+
const out = result.all?.trim() ?? '';
|
|
426
|
+
if (!out) {
|
|
427
|
+
console.log(chalk.dim('\n No uncommitted changes.\n'));
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
console.log('');
|
|
431
|
+
const lines = out.split('\n');
|
|
432
|
+
for (const line of lines) {
|
|
433
|
+
if (line.startsWith('+'))
|
|
434
|
+
process.stdout.write(chalk.green(' ' + line) + '\n');
|
|
435
|
+
else if (line.startsWith('-'))
|
|
436
|
+
process.stdout.write(chalk.red(' ' + line) + '\n');
|
|
437
|
+
else
|
|
438
|
+
process.stdout.write(chalk.dim(' ' + line) + '\n');
|
|
439
|
+
}
|
|
440
|
+
console.log('');
|
|
441
|
+
}
|
|
442
|
+
catch {
|
|
443
|
+
console.log(chalk.dim('\n Not a git repository.\n'));
|
|
444
|
+
}
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
// ── Verify ──────────────────────────────────────────────────────────
|
|
448
|
+
case '/verify': {
|
|
449
|
+
const project = await detectProject(cfg.cwd);
|
|
450
|
+
const cmd2 = project.testCommand;
|
|
451
|
+
if (!cmd2) {
|
|
452
|
+
console.log(chalk.dim('\n No test command detected for this project.\n'));
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
console.log(chalk.dim(`\n Running: ${cmd2}\n`));
|
|
456
|
+
const r = await execa(cmd2, { shell: true, cwd: cfg.cwd, all: true, reject: false });
|
|
457
|
+
const output = r.all ?? '';
|
|
458
|
+
output.split('\n').forEach(l => console.log(chalk.dim(' ') + l));
|
|
459
|
+
if (r.exitCode === 0)
|
|
460
|
+
console.log(chalk.green('\n ✔ Tests passed.\n'));
|
|
461
|
+
else
|
|
462
|
+
console.log(chalk.red('\n ✗ Tests failed.\n'));
|
|
463
|
+
return;
|
|
464
|
+
}
|
|
465
|
+
// ── Code review ─────────────────────────────────────────────────────
|
|
466
|
+
case '/code-review': {
|
|
467
|
+
const doFix = arg === '--fix';
|
|
468
|
+
const diff = (await execa('git', ['diff'], { cwd: cfg.cwd, all: true, reject: false })).all?.trim() ?? '';
|
|
469
|
+
if (!diff) {
|
|
470
|
+
console.log(chalk.dim('\n No changes to review.\n'));
|
|
471
|
+
return;
|
|
472
|
+
}
|
|
473
|
+
busy = true;
|
|
474
|
+
rl.pause();
|
|
475
|
+
currentAbort = new AbortController();
|
|
476
|
+
try {
|
|
477
|
+
const prompt = doFix
|
|
478
|
+
? `Review and fix these code changes:\n\`\`\`diff\n${diff}\n\`\`\``
|
|
479
|
+
: `Review these code changes for bugs, logic errors, and quality issues. Be specific about line numbers:\n\`\`\`diff\n${diff}\n\`\`\``;
|
|
480
|
+
await session.send(prompt, currentAbort.signal);
|
|
481
|
+
}
|
|
482
|
+
finally {
|
|
483
|
+
currentAbort = null;
|
|
484
|
+
busy = false;
|
|
485
|
+
rl.resume();
|
|
486
|
+
}
|
|
487
|
+
return;
|
|
488
|
+
}
|
|
489
|
+
// ── Security review ─────────────────────────────────────────────────
|
|
490
|
+
case '/security-review': {
|
|
491
|
+
const diff = (await execa('git', ['diff'], { cwd: cfg.cwd, all: true, reject: false })).all?.trim() ?? '';
|
|
492
|
+
if (!diff) {
|
|
493
|
+
console.log(chalk.dim('\n No changes to review.\n'));
|
|
494
|
+
return;
|
|
495
|
+
}
|
|
496
|
+
busy = true;
|
|
497
|
+
rl.pause();
|
|
498
|
+
currentAbort = new AbortController();
|
|
499
|
+
try {
|
|
500
|
+
await session.send(`Perform a security audit of these changes. Check for: injection, auth bypass, secrets, XSS, CSRF, insecure deserialization, path traversal, and OWASP Top 10 issues.\n\`\`\`diff\n${diff}\n\`\`\``, currentAbort.signal);
|
|
501
|
+
}
|
|
502
|
+
finally {
|
|
503
|
+
currentAbort = null;
|
|
504
|
+
busy = false;
|
|
505
|
+
rl.resume();
|
|
506
|
+
}
|
|
507
|
+
return;
|
|
508
|
+
}
|
|
509
|
+
// ── MCP ─────────────────────────────────────────────────────────────
|
|
510
|
+
case '/mcp': {
|
|
511
|
+
const mcpCfg = await readMcpConfig(cfg.cwd);
|
|
512
|
+
if (!mcpCfg) {
|
|
513
|
+
console.log(chalk.dim('\n No .koneck/mcp.json found.\n'));
|
|
514
|
+
console.log(chalk.dim(' Example: { "servers": { "github": { "command": "npx", "args": ["-y", "@modelcontextprotocol/server-github"] } } }\n'));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const servers = (mcpCfg['servers'] ?? {});
|
|
518
|
+
const names = Object.keys(servers);
|
|
519
|
+
if (!names.length) {
|
|
520
|
+
console.log(chalk.dim('\n No MCP servers configured.\n'));
|
|
521
|
+
return;
|
|
522
|
+
}
|
|
523
|
+
console.log('\n ' + chalk.bold('MCP servers') + '\n');
|
|
524
|
+
for (const name of names) {
|
|
525
|
+
const s = servers[name];
|
|
526
|
+
const cmd2 = [s['command'], ...(s['args'] ?? [])].join(' ');
|
|
527
|
+
console.log(` ${chalk.cyan(name.padEnd(20))} ${chalk.dim(cmd2)}`);
|
|
528
|
+
}
|
|
529
|
+
console.log('');
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
// ── Init ────────────────────────────────────────────────────────────
|
|
533
|
+
case '/init':
|
|
534
|
+
await runGenerateKoneckMd(cfg);
|
|
535
|
+
return;
|
|
536
|
+
// ── Doctor ──────────────────────────────────────────────────────────
|
|
537
|
+
case '/doctor':
|
|
538
|
+
await runDoctor(cfg.cwd);
|
|
539
|
+
return;
|
|
540
|
+
// ── Debug ───────────────────────────────────────────────────────────
|
|
541
|
+
case '/debug': {
|
|
542
|
+
const mem = process.memoryUsage();
|
|
214
543
|
console.log('');
|
|
215
|
-
console.log(` ${chalk.dim('
|
|
216
|
-
console.log(` ${chalk.dim('
|
|
217
|
-
console.log(` ${chalk.dim('
|
|
218
|
-
console.log(` ${chalk.dim('
|
|
544
|
+
console.log(` ${chalk.dim('Node.js :')} ${process.version}`);
|
|
545
|
+
console.log(` ${chalk.dim('Platform :')} ${process.platform} ${process.arch}`);
|
|
546
|
+
console.log(` ${chalk.dim('PID :')} ${process.pid}`);
|
|
547
|
+
console.log(` ${chalk.dim('Heap used :')} ${Math.round(mem.heapUsed / 1024 / 1024)} MB`);
|
|
548
|
+
console.log(` ${chalk.dim('Heap total :')} ${Math.round(mem.heapTotal / 1024 / 1024)} MB`);
|
|
549
|
+
console.log(` ${chalk.dim('Messages :')} ${session.messages.length}`);
|
|
550
|
+
console.log(` ${chalk.dim('Turns :')} ${session.stats.turns}`);
|
|
551
|
+
console.log(` ${chalk.dim('Tokens :')} ${session.stats.totalTokens.toLocaleString()}`);
|
|
219
552
|
console.log('');
|
|
220
553
|
return;
|
|
221
554
|
}
|
|
222
|
-
// ──
|
|
555
|
+
// ── Rewind ──────────────────────────────────────────────────────────
|
|
556
|
+
case '/rewind': {
|
|
557
|
+
const msgs = session.messages;
|
|
558
|
+
const lastUser = [...msgs].reverse().findIndex(m => m.role === 'user');
|
|
559
|
+
if (lastUser === -1) {
|
|
560
|
+
console.log(chalk.dim('\n Nothing to rewind.\n'));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
const idx = msgs.length - 1 - lastUser;
|
|
564
|
+
msgs.splice(idx);
|
|
565
|
+
console.log(chalk.green(`\n ✔ Rewound — removed last ${msgs.length - idx + session.messages.length - msgs.length} message(s).\n`));
|
|
566
|
+
console.log(chalk.green(` ✔ History now has ${session.messages.length} message(s).\n`));
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
// ── Fork / Branch ───────────────────────────────────────────────────
|
|
570
|
+
case '/fork':
|
|
571
|
+
case '/branch': {
|
|
572
|
+
const forkedSession = await createAgentSession(cfg);
|
|
573
|
+
forkedSession.messages.push(...session.messages.map(m => ({ ...m })));
|
|
574
|
+
session = forkedSession;
|
|
575
|
+
console.log(chalk.green(`\n ✔ Forked. Continuing on a fresh copy of the conversation (${session.messages.length} messages carried over).\n`));
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
// ── Subtask ─────────────────────────────────────────────────────────
|
|
579
|
+
case '/subtask': {
|
|
580
|
+
if (!arg) {
|
|
581
|
+
console.log(chalk.dim('\n Usage: /subtask <task description>\n'));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
console.log(chalk.dim(`\n Delegating to subagent: "${arg}"\n`));
|
|
585
|
+
busy = true;
|
|
586
|
+
rl.pause();
|
|
587
|
+
try {
|
|
588
|
+
await runAgent(arg, { ...cfg, ci: true });
|
|
589
|
+
}
|
|
590
|
+
catch (e) {
|
|
591
|
+
console.log(chalk.red(` ✗ Subtask error: ${e instanceof Error ? e.message : String(e)}\n`));
|
|
592
|
+
}
|
|
593
|
+
finally {
|
|
594
|
+
busy = false;
|
|
595
|
+
rl.resume();
|
|
596
|
+
}
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
// ── PS ──────────────────────────────────────────────────────────────
|
|
600
|
+
case '/ps':
|
|
601
|
+
console.log(busy
|
|
602
|
+
? chalk.yellow('\n 1 task running (current)\n')
|
|
603
|
+
: chalk.dim('\n No active background tasks.\n'));
|
|
604
|
+
return;
|
|
605
|
+
// ── Copy ────────────────────────────────────────────────────────────
|
|
606
|
+
case '/copy': {
|
|
607
|
+
if (!lastAssistantMsg) {
|
|
608
|
+
console.log(chalk.dim('\n No response to copy yet.\n'));
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
const ok = await copyToClipboard(lastAssistantMsg);
|
|
612
|
+
if (ok)
|
|
613
|
+
console.log(chalk.green('\n ✔ Copied to clipboard.\n'));
|
|
614
|
+
else {
|
|
615
|
+
console.log(chalk.dim('\n Clipboard tool not found (wl-copy/xclip/xsel/pbcopy). Last response:\n'));
|
|
616
|
+
console.log(lastAssistantMsg.split('\n').map(l => ' ' + l).join('\n') + '\n');
|
|
617
|
+
}
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
// ── Rename ──────────────────────────────────────────────────────────
|
|
621
|
+
case '/rename':
|
|
622
|
+
if (!arg) {
|
|
623
|
+
console.log(chalk.dim('\n Usage: /rename <session name>\n'));
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
sessionName = arg;
|
|
627
|
+
console.log(chalk.green(`\n ✔ Session renamed: "${arg}"\n`));
|
|
628
|
+
return;
|
|
629
|
+
// ── Compact ─────────────────────────────────────────────────────────
|
|
223
630
|
case '/compact': {
|
|
224
631
|
if (session.messages.length < 4) {
|
|
225
632
|
console.log(chalk.dim('\n Nothing to compact yet.\n'));
|
|
226
633
|
return;
|
|
227
634
|
}
|
|
228
635
|
console.log(chalk.dim('\n Compacting conversation…'));
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
?
|
|
237
|
-
: '
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
636
|
+
busy = true;
|
|
637
|
+
rl.pause();
|
|
638
|
+
const compactAbort = new AbortController();
|
|
639
|
+
try {
|
|
640
|
+
const focus = arg ? `Focus on: ${arg}.` : '';
|
|
641
|
+
await session.send(`Summarise our entire conversation so far into a concise briefing. ${focus} Cover: what was asked, what was done, key decisions, current file states. This will replace full history so be thorough.`, compactAbort.signal);
|
|
642
|
+
const last = session.messages.filter(m => m.role === 'assistant').at(-1);
|
|
643
|
+
const summary = last && typeof last.content === 'string' ? last.content : 'Conversation summarised.';
|
|
644
|
+
session.messages.splice(0, session.messages.length, { role: 'user', content: '[Compacted history]\n\n' + summary }, { role: 'assistant', content: 'Understood. I have context from our previous exchange.' });
|
|
645
|
+
console.log(chalk.green(' ✔ Compacted. History replaced with summary.\n'));
|
|
646
|
+
}
|
|
647
|
+
finally {
|
|
648
|
+
busy = false;
|
|
649
|
+
rl.resume();
|
|
650
|
+
}
|
|
651
|
+
return;
|
|
652
|
+
}
|
|
653
|
+
// ── Memory ──────────────────────────────────────────────────────────
|
|
243
654
|
case '/memory': {
|
|
244
655
|
const mem = await loadMemory(cfg.cwd);
|
|
245
|
-
if (!mem
|
|
246
|
-
console.log(chalk.dim('\n No memory
|
|
656
|
+
if (!mem?.trim()) {
|
|
657
|
+
console.log(chalk.dim('\n No memory in .koneck/MEMORY.md\n'));
|
|
247
658
|
return;
|
|
248
659
|
}
|
|
249
660
|
console.log('\n' + chalk.bold(' Project memory') + '\n');
|
|
250
|
-
|
|
251
|
-
console.log(chalk.dim(' ') + line);
|
|
252
|
-
}
|
|
661
|
+
mem.split('\n').forEach(l => console.log(chalk.dim(' ') + l));
|
|
253
662
|
console.log('');
|
|
254
663
|
return;
|
|
255
664
|
}
|
|
256
|
-
// ── CWD
|
|
665
|
+
// ── CWD ─────────────────────────────────────────────────────────────
|
|
257
666
|
case '/cwd': {
|
|
258
667
|
if (!arg) {
|
|
259
668
|
console.log(`\n CWD: ${chalk.cyan(cfg.cwd)}\n`);
|
|
260
669
|
return;
|
|
261
670
|
}
|
|
262
671
|
const { default: path } = await import('path');
|
|
263
|
-
const newCwd = path.resolve(cfg.cwd, arg);
|
|
264
672
|
const { default: fs } = await import('fs/promises');
|
|
673
|
+
const newCwd = path.resolve(cfg.cwd, arg);
|
|
265
674
|
try {
|
|
266
675
|
await fs.access(newCwd);
|
|
267
676
|
cfg = { ...cfg, cwd: newCwd };
|
|
@@ -273,23 +682,45 @@ export async function runChatMode(config) {
|
|
|
273
682
|
}
|
|
274
683
|
return;
|
|
275
684
|
}
|
|
276
|
-
// ──
|
|
685
|
+
// ── Cost ────────────────────────────────────────────────────────────
|
|
686
|
+
case '/cost': {
|
|
687
|
+
const cost = estimateCost(cfg.model, session.stats.promptTokens, session.stats.completionTokens);
|
|
688
|
+
if (cost.known)
|
|
689
|
+
console.log(`\n Session cost: ${chalk.white(formatCost(cost.total))} (input ${formatCost(cost.inputCost)} + output ${formatCost(cost.outputCost)})\n`);
|
|
690
|
+
else
|
|
691
|
+
console.log(chalk.dim(`\n Cost unknown — no pricing data for "${cfg.model}"\n`));
|
|
692
|
+
return;
|
|
693
|
+
}
|
|
694
|
+
// ── Tokens ──────────────────────────────────────────────────────────
|
|
695
|
+
case '/tokens': {
|
|
696
|
+
const { promptTokens, completionTokens, totalTokens, turns } = session.stats;
|
|
697
|
+
console.log('');
|
|
698
|
+
console.log(` ${chalk.dim('Prompt :')} ${chalk.white(promptTokens.toLocaleString())}`);
|
|
699
|
+
console.log(` ${chalk.dim('Completion :')} ${chalk.white(completionTokens.toLocaleString())}`);
|
|
700
|
+
console.log(` ${chalk.dim('Total :')} ${chalk.white(totalTokens.toLocaleString())}`);
|
|
701
|
+
console.log(` ${chalk.dim('Turns :')} ${chalk.white(turns)}`);
|
|
702
|
+
console.log('');
|
|
703
|
+
return;
|
|
704
|
+
}
|
|
705
|
+
// ── Clear / Reset / New ─────────────────────────────────────────────
|
|
277
706
|
case '/clear':
|
|
278
|
-
case '/reset':
|
|
707
|
+
case '/reset':
|
|
708
|
+
case '/new':
|
|
279
709
|
session = await createAgentSession(cfg);
|
|
710
|
+
goal = null;
|
|
711
|
+
addedDirs = [];
|
|
280
712
|
console.log(chalk.green(' ✔ Conversation cleared.\n'));
|
|
281
713
|
return;
|
|
282
|
-
|
|
283
|
-
// ── Save ──────────────────────────────────────────────────────────────
|
|
714
|
+
// ── Save ────────────────────────────────────────────────────────────
|
|
284
715
|
case '/save': {
|
|
285
716
|
const file = await doSave();
|
|
286
717
|
console.log(chalk.green(` ✔ Saved: ${file}\n`));
|
|
287
718
|
return;
|
|
288
719
|
}
|
|
289
|
-
// ── Sessions
|
|
720
|
+
// ── Sessions ────────────────────────────────────────────────────────
|
|
290
721
|
case '/sessions': {
|
|
291
722
|
const sessions = await listSessions(cfg.cwd);
|
|
292
|
-
if (sessions.length
|
|
723
|
+
if (!sessions.length) {
|
|
293
724
|
console.log(chalk.dim('\n No sessions found.\n'));
|
|
294
725
|
return;
|
|
295
726
|
}
|
|
@@ -304,22 +735,165 @@ export async function runChatMode(config) {
|
|
|
304
735
|
console.log('');
|
|
305
736
|
return;
|
|
306
737
|
}
|
|
738
|
+
// ── Feedback ────────────────────────────────────────────────────────
|
|
739
|
+
case '/feedback':
|
|
740
|
+
console.log('\n Report issues at: ' + chalk.cyan('https://github.com/Gubevu/konech/issues'));
|
|
741
|
+
console.log(chalk.dim(' Include your koneck version (koneck --version) and the error message.\n'));
|
|
742
|
+
return;
|
|
743
|
+
// ── Architect ───────────────────────────────────────────────────────
|
|
744
|
+
case '/architect': {
|
|
745
|
+
busy = true;
|
|
746
|
+
rl.pause();
|
|
747
|
+
currentAbort = new AbortController();
|
|
748
|
+
try {
|
|
749
|
+
await session.send('Analyze the current project architecture. Produce:\n1. Overview of the architecture\n2. Current problems (coupling, violations, god objects)\n3. Concrete improvement suggestions\n4. A dependency diagram in ASCII\nBe specific with file paths and module names.', currentAbort.signal);
|
|
750
|
+
}
|
|
751
|
+
finally {
|
|
752
|
+
currentAbort = null;
|
|
753
|
+
busy = false;
|
|
754
|
+
rl.resume();
|
|
755
|
+
}
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
// ── Performance ─────────────────────────────────────────────────────
|
|
759
|
+
case '/performance': {
|
|
760
|
+
busy = true;
|
|
761
|
+
rl.pause();
|
|
762
|
+
currentAbort = new AbortController();
|
|
763
|
+
try {
|
|
764
|
+
await session.send('Perform a performance audit of this codebase. Find and report:\n1. N+1 query patterns\n2. Memory leaks or unbounded growth\n3. Blocking I/O in async contexts\n4. Slow rendering or re-render triggers (if React)\n5. Missing database indexes\n6. Unnecessary re-computation\nInclude file:line references for each finding.', currentAbort.signal);
|
|
765
|
+
}
|
|
766
|
+
finally {
|
|
767
|
+
currentAbort = null;
|
|
768
|
+
busy = false;
|
|
769
|
+
rl.resume();
|
|
770
|
+
}
|
|
771
|
+
return;
|
|
772
|
+
}
|
|
773
|
+
// ── Security ────────────────────────────────────────────────────────
|
|
774
|
+
case '/security': {
|
|
775
|
+
busy = true;
|
|
776
|
+
rl.pause();
|
|
777
|
+
currentAbort = new AbortController();
|
|
778
|
+
try {
|
|
779
|
+
await session.send('Perform a full security audit of this codebase. Check for:\n- OWASP Top 10 vulnerabilities\n- JWT/session security\n- XSS, CSRF, SSRF vectors\n- SQL/command injection\n- Exposed secrets or API keys\n- Unsafe regex (ReDoS)\n- Insecure dependencies\n- Path traversal\nReport each finding with file:line and severity (critical/high/medium/low).', currentAbort.signal);
|
|
780
|
+
}
|
|
781
|
+
finally {
|
|
782
|
+
currentAbort = null;
|
|
783
|
+
busy = false;
|
|
784
|
+
rl.resume();
|
|
785
|
+
}
|
|
786
|
+
return;
|
|
787
|
+
}
|
|
788
|
+
// ── Pipeline ────────────────────────────────────────────────────────
|
|
789
|
+
case '/pipeline': {
|
|
790
|
+
if (!arg) {
|
|
791
|
+
console.log(chalk.dim('\n Usage: /pipeline <task description>\n'));
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
busy = true;
|
|
795
|
+
rl.pause();
|
|
796
|
+
try {
|
|
797
|
+
await runPipeline(arg, cfg);
|
|
798
|
+
}
|
|
799
|
+
catch (e) {
|
|
800
|
+
console.log(chalk.red(` ✗ Pipeline error: ${e instanceof Error ? e.message : String(e)}\n`));
|
|
801
|
+
}
|
|
802
|
+
finally {
|
|
803
|
+
busy = false;
|
|
804
|
+
rl.resume();
|
|
805
|
+
}
|
|
806
|
+
return;
|
|
807
|
+
}
|
|
808
|
+
// ── Knowledge graph ──────────────────────────────────────────────────
|
|
809
|
+
case '/knowledge': {
|
|
810
|
+
if (!arg) {
|
|
811
|
+
console.log(chalk.dim('\n Usage: /knowledge <concept> e.g. /knowledge authentication\n'));
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
console.log(chalk.dim('\n Querying knowledge graph…\n'));
|
|
815
|
+
try {
|
|
816
|
+
const result = await queryKnowledgeGraph(arg, cfg.cwd);
|
|
817
|
+
console.log(result || chalk.dim(' No results found. Run /knowledge build to index the project first.\n'));
|
|
818
|
+
}
|
|
819
|
+
catch (e) {
|
|
820
|
+
console.log(chalk.red(` ✗ ${e instanceof Error ? e.message : String(e)}\n`));
|
|
821
|
+
}
|
|
822
|
+
return;
|
|
823
|
+
}
|
|
824
|
+
// ── Health ──────────────────────────────────────────────────────────
|
|
825
|
+
case '/health':
|
|
826
|
+
await runHealth(cfg.cwd);
|
|
827
|
+
return;
|
|
828
|
+
// ── Code graph ──────────────────────────────────────────────────────
|
|
829
|
+
case '/graph': {
|
|
830
|
+
if (arg === 'build') {
|
|
831
|
+
console.log(chalk.dim('\n Building code graph…\n'));
|
|
832
|
+
await buildCodeGraph(cfg.cwd);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
if (arg === 'calls') {
|
|
836
|
+
const callGraph = await getCallGraph(cfg.cwd);
|
|
837
|
+
console.log('\n' + callGraph.split('\n').map(l => ' ' + l).join('\n') + '\n');
|
|
838
|
+
return;
|
|
839
|
+
}
|
|
840
|
+
if (arg) {
|
|
841
|
+
const results = await queryGraph(arg, cfg.cwd);
|
|
842
|
+
if (!results.length) {
|
|
843
|
+
console.log(chalk.dim(`\n No symbols matching "${arg}"\n`));
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
console.log('');
|
|
847
|
+
for (const s of results) {
|
|
848
|
+
console.log(` ${chalk.cyan(s.name.padEnd(30))} ${chalk.dim(s.kind.padEnd(12))} ${s.file}:${s.line}`);
|
|
849
|
+
}
|
|
850
|
+
console.log('');
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
console.log(chalk.dim('\n Usage: /graph build | /graph calls | /graph <symbol-name>\n'));
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
// ── Docs ────────────────────────────────────────────────────────────
|
|
857
|
+
case '/docs': {
|
|
858
|
+
console.log(chalk.dim('\n Regenerating documentation…\n'));
|
|
859
|
+
try {
|
|
860
|
+
await runAutoDocs(cfg);
|
|
861
|
+
console.log(chalk.green(' ✔ Documentation updated.\n'));
|
|
862
|
+
}
|
|
863
|
+
catch (e) {
|
|
864
|
+
console.log(chalk.red(` ✗ ${e instanceof Error ? e.message : String(e)}\n`));
|
|
865
|
+
}
|
|
866
|
+
return;
|
|
867
|
+
}
|
|
307
868
|
default:
|
|
308
|
-
console.log(chalk.dim(`\n Unknown command: ${cmd}. Type /help for the list.\n`));
|
|
869
|
+
console.log(chalk.dim(`\n Unknown command: ${cmd}. Type /help for the full list.\n`));
|
|
309
870
|
return;
|
|
310
871
|
}
|
|
311
872
|
}
|
|
312
873
|
// ── Task execution ────────────────────────────────────────────────────────
|
|
313
874
|
busy = true;
|
|
314
875
|
rl.pause();
|
|
315
|
-
|
|
876
|
+
// Build prefix from mode + effort + personality + goal + extra dirs
|
|
877
|
+
let prefix = MODE_PREFIXES[mode];
|
|
878
|
+
if (effort !== 'medium' && EFFORT_HINTS[effort])
|
|
879
|
+
prefix = EFFORT_HINTS[effort] + prefix;
|
|
880
|
+
if (personality !== 'default')
|
|
881
|
+
prefix = PERSONALITY_HINTS[personality] + prefix;
|
|
882
|
+
if (goal)
|
|
883
|
+
prefix = `[Persistent goal: ${goal}]\n` + prefix;
|
|
884
|
+
if (addedDirs.length)
|
|
885
|
+
prefix = `[Extra context directories: ${addedDirs.join(', ')}]\n` + prefix;
|
|
886
|
+
const taskText = prefix + input;
|
|
316
887
|
currentAbort = new AbortController();
|
|
317
888
|
const { signal } = currentAbort;
|
|
318
889
|
const taskStart = Date.now();
|
|
319
890
|
try {
|
|
320
891
|
await session.send(taskText, signal);
|
|
321
|
-
|
|
322
|
-
|
|
892
|
+
// Capture last assistant message for /copy
|
|
893
|
+
const last = session.messages.filter(m => m.role === 'assistant').at(-1);
|
|
894
|
+
if (last && typeof last.content === 'string')
|
|
895
|
+
lastAssistantMsg = last.content;
|
|
896
|
+
tui.printStats(session.stats, cfg.provider, cfg.model, Date.now() - taskStart);
|
|
323
897
|
}
|
|
324
898
|
catch (err) {
|
|
325
899
|
if (err?.name !== 'AbortError') {
|
|
@@ -332,11 +906,11 @@ export async function runChatMode(config) {
|
|
|
332
906
|
rl.resume();
|
|
333
907
|
}
|
|
334
908
|
}
|
|
335
|
-
// ── Ctrl+C
|
|
909
|
+
// ── Ctrl+C ────────────────────────────────────────────────────────────────
|
|
336
910
|
process.on('SIGINT', async () => {
|
|
337
911
|
if (busy && currentAbort) {
|
|
338
912
|
currentAbort.abort();
|
|
339
|
-
process.stdout.write(chalk.yellow('\n ⚠ Interrupted
|
|
913
|
+
process.stdout.write(chalk.yellow('\n ⚠ Interrupted.\n\n'));
|
|
340
914
|
return;
|
|
341
915
|
}
|
|
342
916
|
process.stdout.write('\n');
|