codemini-cli 0.1.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.
- package/LICENSE +21 -0
- package/OPERATIONS.md +202 -0
- package/README.md +138 -0
- package/bin/coder.js +7 -0
- package/deployment.md +205 -0
- package/package.json +54 -0
- package/skills/brainstorming-lite/SKILL.md +37 -0
- package/skills/executing-plan-lite/SKILL.md +41 -0
- package/skills/superpowers-lite/SKILL.md +44 -0
- package/souls/anime.md +3 -0
- package/souls/default.md +3 -0
- package/souls/playful.md +3 -0
- package/souls/professional.md +3 -0
- package/src/cli.js +62 -0
- package/src/commands/chat.js +106 -0
- package/src/commands/config.js +61 -0
- package/src/commands/doctor.js +87 -0
- package/src/commands/run.js +64 -0
- package/src/commands/skill.js +264 -0
- package/src/core/agent-loop.js +281 -0
- package/src/core/chat-runtime.js +2075 -0
- package/src/core/checkpoint-store.js +66 -0
- package/src/core/command-loader.js +201 -0
- package/src/core/command-policy.js +71 -0
- package/src/core/config-store.js +196 -0
- package/src/core/context-compact.js +90 -0
- package/src/core/default-system-prompt.js +5 -0
- package/src/core/fs-utils.js +16 -0
- package/src/core/input-history-store.js +48 -0
- package/src/core/input-parser.js +15 -0
- package/src/core/paths.js +109 -0
- package/src/core/provider/openai-compatible.js +228 -0
- package/src/core/session-store.js +178 -0
- package/src/core/shell-profile.js +122 -0
- package/src/core/shell.js +71 -0
- package/src/core/skill-registry.js +55 -0
- package/src/core/soul.js +55 -0
- package/src/core/task-store.js +116 -0
- package/src/core/tools.js +237 -0
- package/src/tui/chat-app.js +2007 -0
- package/src/tui/input-escape.js +21 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: superpowers-lite
|
|
3
|
+
description: Concise workflow skill tuned for 30B-class models: search first, keep context tight, use sub-agents for narrow tasks, and verify before claiming success.
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
Use this skill as the default lightweight operating style for coding work.
|
|
8
|
+
|
|
9
|
+
Core rules:
|
|
10
|
+
|
|
11
|
+
1. Search first.
|
|
12
|
+
Use `rg` for repo search before broad file reads. Prefer local context commands and read only the smallest useful slice.
|
|
13
|
+
|
|
14
|
+
2. Keep context tight.
|
|
15
|
+
Do not carry full conversation history into every step. Summarize, narrow scope, and work from the most recent relevant evidence.
|
|
16
|
+
|
|
17
|
+
3. Prefer narrow sub-agents.
|
|
18
|
+
When a task can be split cleanly, use sub-agents for bounded subtasks so the main thread keeps global focus. Give each sub-agent:
|
|
19
|
+
- one clear objective
|
|
20
|
+
- a tiny context summary
|
|
21
|
+
- a tiny file evidence packet
|
|
22
|
+
- a concrete expected output
|
|
23
|
+
|
|
24
|
+
4. Read and write with intent.
|
|
25
|
+
Use `read_file` only when shell output is not enough. Use `write_file` for edits. Avoid unnecessary tool calls and avoid rereading the same file without a reason.
|
|
26
|
+
|
|
27
|
+
5. Verify before claiming success.
|
|
28
|
+
Run the relevant test, check, or command before saying work is fixed or complete.
|
|
29
|
+
|
|
30
|
+
Default workflow:
|
|
31
|
+
- Search with `rg`
|
|
32
|
+
- Inspect local context
|
|
33
|
+
- Plan the next smallest step
|
|
34
|
+
- Delegate if the work is independent
|
|
35
|
+
- Edit
|
|
36
|
+
- Verify
|
|
37
|
+
- Summarize briefly
|
|
38
|
+
|
|
39
|
+
Sub-agent guidance:
|
|
40
|
+
- `planner`: break work into steps, risks, and checks
|
|
41
|
+
- `coder`: implement one bounded change
|
|
42
|
+
- `reviewer`: look for bugs, regressions, and missing verification
|
|
43
|
+
|
|
44
|
+
If the task is simple, stay lightweight. Do not expand into a large ceremony unless the problem actually needs it.
|
package/souls/anime.md
ADDED
package/souls/default.md
ADDED
package/souls/playful.md
ADDED
package/src/cli.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { handleChat } from './commands/chat.js';
|
|
2
|
+
import { handleRun } from './commands/run.js';
|
|
3
|
+
import { handleConfig } from './commands/config.js';
|
|
4
|
+
import { handleDoctor } from './commands/doctor.js';
|
|
5
|
+
import { handleSkill } from './commands/skill.js';
|
|
6
|
+
|
|
7
|
+
const VERSION = '0.1.0';
|
|
8
|
+
|
|
9
|
+
function printHelp() {
|
|
10
|
+
console.log(`codemini ${VERSION}
|
|
11
|
+
Usage:
|
|
12
|
+
codemini [prompt] [--plain]
|
|
13
|
+
codemini chat [prompt] [--plain]
|
|
14
|
+
codemini run <task> [--max-steps N]
|
|
15
|
+
codemini config set|get|list <key> [value]
|
|
16
|
+
codemini doctor
|
|
17
|
+
codemini skill list|install|enable|disable|inspect|reindex
|
|
18
|
+
codemini --version
|
|
19
|
+
codemini --help`);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export async function runCli(args) {
|
|
23
|
+
const [command, ...rest] = args;
|
|
24
|
+
const knownCommands = new Set(['chat', 'run', 'config', 'doctor', 'skill']);
|
|
25
|
+
|
|
26
|
+
if (!command || command === '--help' || command === '-h') {
|
|
27
|
+
if (!command) {
|
|
28
|
+
await handleChat([]);
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
printHelp();
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (command === '--version' || command === '-v' || command === 'version') {
|
|
36
|
+
console.log(VERSION);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!knownCommands.has(command)) {
|
|
41
|
+
await handleChat(args);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
switch (command) {
|
|
46
|
+
case 'chat':
|
|
47
|
+
await handleChat(rest);
|
|
48
|
+
return;
|
|
49
|
+
case 'run':
|
|
50
|
+
await handleRun(rest);
|
|
51
|
+
return;
|
|
52
|
+
case 'config':
|
|
53
|
+
await handleConfig(rest);
|
|
54
|
+
return;
|
|
55
|
+
case 'doctor':
|
|
56
|
+
await handleDoctor();
|
|
57
|
+
return;
|
|
58
|
+
case 'skill':
|
|
59
|
+
await handleSkill(rest);
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import readline from 'node:readline/promises';
|
|
2
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
3
|
+
import { loadConfig } from '../core/config-store.js';
|
|
4
|
+
import { createChatRuntime } from '../core/chat-runtime.js';
|
|
5
|
+
import { buildDefaultSystemPrompt } from '../core/default-system-prompt.js';
|
|
6
|
+
import { resolveSession } from '../core/session-store.js';
|
|
7
|
+
import pkg from '../../package.json' with { type: 'json' };
|
|
8
|
+
|
|
9
|
+
function parseChatArgs(args) {
|
|
10
|
+
const parsed = {
|
|
11
|
+
prompt: '',
|
|
12
|
+
sessionId: undefined,
|
|
13
|
+
model: undefined,
|
|
14
|
+
system: undefined,
|
|
15
|
+
plain: false
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
19
|
+
const arg = args[i];
|
|
20
|
+
if (arg === '--session') {
|
|
21
|
+
parsed.sessionId = args[i + 1];
|
|
22
|
+
i += 1;
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
if (arg === '--model') {
|
|
26
|
+
parsed.model = args[i + 1];
|
|
27
|
+
i += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (arg === '--system') {
|
|
31
|
+
parsed.system = args[i + 1];
|
|
32
|
+
i += 1;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (arg === '--plain') {
|
|
36
|
+
parsed.plain = true;
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
parsed.prompt += `${parsed.prompt ? ' ' : ''}${arg}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return parsed;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function runPlainLoop(runtime) {
|
|
46
|
+
console.log('CodeMini CLI plain mode. Use /help and /exit.');
|
|
47
|
+
const rl = readline.createInterface({ input, output });
|
|
48
|
+
try {
|
|
49
|
+
while (true) {
|
|
50
|
+
let line;
|
|
51
|
+
try {
|
|
52
|
+
line = await rl.question('codemini> ');
|
|
53
|
+
} catch {
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
const result = await runtime.submit(line);
|
|
57
|
+
if (result.type === 'exit') break;
|
|
58
|
+
if (result.type === 'noop') continue;
|
|
59
|
+
if (result.text) console.log(result.text);
|
|
60
|
+
}
|
|
61
|
+
} finally {
|
|
62
|
+
rl.close();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export async function handleChat(args) {
|
|
67
|
+
const parsed = parseChatArgs(args);
|
|
68
|
+
const config = await loadConfig();
|
|
69
|
+
const session = await resolveSession(parsed.sessionId);
|
|
70
|
+
const systemPrompt =
|
|
71
|
+
parsed.system ||
|
|
72
|
+
buildDefaultSystemPrompt(config);
|
|
73
|
+
|
|
74
|
+
const runtime = await createChatRuntime({
|
|
75
|
+
session,
|
|
76
|
+
config,
|
|
77
|
+
model: parsed.model,
|
|
78
|
+
systemPrompt
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
if (parsed.prompt) {
|
|
82
|
+
const result = await runtime.submit(parsed.prompt);
|
|
83
|
+
if (result.text) console.log(result.text);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (parsed.plain || !process.stdout.isTTY) {
|
|
88
|
+
await runPlainLoop(runtime);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const React = (await import('react')).default;
|
|
93
|
+
const { render } = await import('ink');
|
|
94
|
+
const { ChatApp } = await import('../tui/chat-app.js');
|
|
95
|
+
const instance = render(
|
|
96
|
+
React.createElement(ChatApp, {
|
|
97
|
+
runtime,
|
|
98
|
+
sessionId: session.id,
|
|
99
|
+
model: parsed.model || config.model.name,
|
|
100
|
+
language: config.ui?.language || 'zh',
|
|
101
|
+
shellName: config.shell?.default || 'powershell',
|
|
102
|
+
version: pkg.version
|
|
103
|
+
})
|
|
104
|
+
);
|
|
105
|
+
await instance.waitUntilExit();
|
|
106
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { getConfigValue, loadConfig, resetConfig, setConfigValue } from '../core/config-store.js';
|
|
2
|
+
|
|
3
|
+
function usage() {
|
|
4
|
+
console.log(`Usage:
|
|
5
|
+
codemini config set <key> <value>
|
|
6
|
+
codemini config get <key>
|
|
7
|
+
codemini config list
|
|
8
|
+
codemini config reset`);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export async function handleConfig(args) {
|
|
12
|
+
const [sub, ...rest] = args;
|
|
13
|
+
|
|
14
|
+
if (!sub) {
|
|
15
|
+
usage();
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
if (sub === 'set') {
|
|
20
|
+
const [key, ...valueParts] = rest;
|
|
21
|
+
if (!key || valueParts.length === 0) {
|
|
22
|
+
throw new Error('config set requires <key> <value>');
|
|
23
|
+
}
|
|
24
|
+
const value = valueParts.join(' ');
|
|
25
|
+
await setConfigValue(key, value);
|
|
26
|
+
console.log(`Set ${key}=${value}`);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (sub === 'get') {
|
|
31
|
+
const [key] = rest;
|
|
32
|
+
if (!key) {
|
|
33
|
+
throw new Error('config get requires <key>');
|
|
34
|
+
}
|
|
35
|
+
const value = await getConfigValue(key);
|
|
36
|
+
if (value === undefined) {
|
|
37
|
+
console.log('undefined');
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
if (typeof value === 'object') {
|
|
41
|
+
console.log(JSON.stringify(value, null, 2));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
console.log(String(value));
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (sub === 'list') {
|
|
49
|
+
const config = await loadConfig();
|
|
50
|
+
console.log(JSON.stringify(config, null, 2));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (sub === 'reset') {
|
|
55
|
+
await resetConfig();
|
|
56
|
+
console.log('Config reset complete');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
usage();
|
|
61
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { getConfigFilePath, getSessionsDir, getSkillsDir } from '../core/paths.js';
|
|
4
|
+
import { loadConfig } from '../core/config-store.js';
|
|
5
|
+
|
|
6
|
+
async function checkPathWritable(targetPath) {
|
|
7
|
+
try {
|
|
8
|
+
await fs.mkdir(targetPath, { recursive: true });
|
|
9
|
+
const tmp = `${targetPath}/.doctor-write-test`;
|
|
10
|
+
await fs.writeFile(tmp, 'ok', 'utf8');
|
|
11
|
+
await fs.unlink(tmp);
|
|
12
|
+
return true;
|
|
13
|
+
} catch {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function checkGateway(config) {
|
|
19
|
+
if (!config.gateway.base_url || !config.gateway.api_key) {
|
|
20
|
+
return { ok: false, reason: 'gateway.base_url or gateway.api_key missing' };
|
|
21
|
+
}
|
|
22
|
+
const url = `${config.gateway.base_url.replace(/\/$/, '')}/models`;
|
|
23
|
+
try {
|
|
24
|
+
const res = await fetch(url, {
|
|
25
|
+
method: 'GET',
|
|
26
|
+
headers: { Authorization: `Bearer ${config.gateway.api_key}` }
|
|
27
|
+
});
|
|
28
|
+
if (!res.ok) {
|
|
29
|
+
return { ok: false, reason: `HTTP ${res.status}` };
|
|
30
|
+
}
|
|
31
|
+
return { ok: true, reason: 'reachable' };
|
|
32
|
+
} catch (err) {
|
|
33
|
+
return { ok: false, reason: err.message };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function handleDoctor() {
|
|
38
|
+
const config = await loadConfig();
|
|
39
|
+
const checks = [];
|
|
40
|
+
|
|
41
|
+
checks.push({
|
|
42
|
+
name: 'Node.js version',
|
|
43
|
+
ok: Number(process.versions.node.split('.')[0]) >= 20,
|
|
44
|
+
detail: process.versions.node
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
checks.push({
|
|
48
|
+
name: 'Platform',
|
|
49
|
+
ok: process.platform === 'win32',
|
|
50
|
+
detail: process.platform === 'win32' ? 'win32' : `non-win32 (${process.platform})`
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
checks.push({
|
|
54
|
+
name: 'Config file writable',
|
|
55
|
+
ok: await checkPathWritable(path.dirname(getConfigFilePath())),
|
|
56
|
+
detail: getConfigFilePath()
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
checks.push({
|
|
60
|
+
name: 'Sessions dir writable',
|
|
61
|
+
ok: await checkPathWritable(getSessionsDir()),
|
|
62
|
+
detail: getSessionsDir()
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
checks.push({
|
|
66
|
+
name: 'Skills dir writable',
|
|
67
|
+
ok: await checkPathWritable(getSkillsDir()),
|
|
68
|
+
detail: getSkillsDir()
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
const gateway = await checkGateway(config);
|
|
72
|
+
checks.push({
|
|
73
|
+
name: 'Gateway connectivity',
|
|
74
|
+
ok: gateway.ok,
|
|
75
|
+
detail: gateway.reason
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
for (const check of checks) {
|
|
79
|
+
const mark = check.ok ? 'OK' : 'FAIL';
|
|
80
|
+
console.log(`[${mark}] ${check.name}: ${check.detail}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
const failed = checks.filter((c) => !c.ok).length;
|
|
84
|
+
if (failed > 0) {
|
|
85
|
+
process.exitCode = 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { loadConfig } from '../core/config-store.js';
|
|
2
|
+
import { buildDefaultSystemPrompt } from '../core/default-system-prompt.js';
|
|
3
|
+
import { runAgentLoop } from '../core/agent-loop.js';
|
|
4
|
+
import { createChatCompletion } from '../core/provider/openai-compatible.js';
|
|
5
|
+
import { buildSystemPromptWithSoul } from '../core/soul.js';
|
|
6
|
+
import { getBuiltinTools } from '../core/tools.js';
|
|
7
|
+
|
|
8
|
+
function parseRunArgs(args) {
|
|
9
|
+
const parsed = {
|
|
10
|
+
task: '',
|
|
11
|
+
model: undefined,
|
|
12
|
+
maxSteps: 8
|
|
13
|
+
};
|
|
14
|
+
for (let i = 0; i < args.length; i += 1) {
|
|
15
|
+
const arg = args[i];
|
|
16
|
+
if (arg === '--model') {
|
|
17
|
+
parsed.model = args[i + 1];
|
|
18
|
+
i += 1;
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
if (arg === '--max-steps') {
|
|
22
|
+
parsed.maxSteps = Number(args[i + 1] || 8);
|
|
23
|
+
i += 1;
|
|
24
|
+
continue;
|
|
25
|
+
}
|
|
26
|
+
parsed.task += `${parsed.task ? ' ' : ''}${arg}`;
|
|
27
|
+
}
|
|
28
|
+
return parsed;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export async function handleRun(args) {
|
|
32
|
+
const parsed = parseRunArgs(args);
|
|
33
|
+
if (!parsed.task) {
|
|
34
|
+
throw new Error('run requires <task>');
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const config = await loadConfig();
|
|
38
|
+
const { definitions, handlers } = getBuiltinTools({
|
|
39
|
+
workspaceRoot: process.cwd(),
|
|
40
|
+
config
|
|
41
|
+
});
|
|
42
|
+
const systemPrompt = await buildSystemPromptWithSoul(buildDefaultSystemPrompt(config), config);
|
|
43
|
+
|
|
44
|
+
const result = await runAgentLoop({
|
|
45
|
+
systemPrompt,
|
|
46
|
+
userPrompt: parsed.task,
|
|
47
|
+
model: parsed.model || config.model.name,
|
|
48
|
+
toolDefinitions: definitions,
|
|
49
|
+
toolHandlers: handlers,
|
|
50
|
+
maxSteps: parsed.maxSteps,
|
|
51
|
+
requestCompletion: async ({ messages, tools, model }) =>
|
|
52
|
+
createChatCompletion({
|
|
53
|
+
baseUrl: config.gateway.base_url,
|
|
54
|
+
apiKey: config.gateway.api_key,
|
|
55
|
+
model,
|
|
56
|
+
messages,
|
|
57
|
+
tools,
|
|
58
|
+
timeoutMs: config.gateway.timeout_ms || 90000,
|
|
59
|
+
maxRetries: config.gateway.max_retries ?? 2
|
|
60
|
+
})
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
console.log(result.text);
|
|
64
|
+
}
|