@minko-ai/minko 0.1.0 → 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/README.md +577 -2
- package/assets/mascot-preview.png +0 -0
- package/assets/mascot-preview.txt +46 -0
- package/assets/mascot-source.png +0 -0
- package/assets/skills/README.md +21 -0
- package/assets/skills/brainstorming/LICENSE +21 -0
- package/assets/skills/brainstorming/SKILL.md +316 -0
- package/assets/skills/buddhist-method/LICENSE +21 -0
- package/assets/skills/buddhist-method/SKILL.md +137 -0
- package/assets/skills/buddhist-method/references/ariyasacca-debug.md +57 -0
- package/assets/skills/buddhist-method/references/extended-principles.md +100 -0
- package/assets/skills/code-humanizer/LICENSE +21 -0
- package/assets/skills/code-humanizer/SKILL.md +174 -0
- package/assets/skills/code-review/LICENSE +21 -0
- package/assets/skills/code-review/SKILL.md +232 -0
- package/assets/skills/code-review/reference/angular.md +768 -0
- package/assets/skills/code-review/reference/architecture-review-guide.md +472 -0
- package/assets/skills/code-review/reference/c.md +890 -0
- package/assets/skills/code-review/reference/code-quality-universal.md +488 -0
- package/assets/skills/code-review/reference/code-review-best-practices.md +136 -0
- package/assets/skills/code-review/reference/common-bugs-checklist.md +302 -0
- package/assets/skills/code-review/reference/cpp.md +893 -0
- package/assets/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
- package/assets/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
- package/assets/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
- package/assets/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
- package/assets/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
- package/assets/skills/code-review/reference/csharp.md +525 -0
- package/assets/skills/code-review/reference/css-less-sass.md +661 -0
- package/assets/skills/code-review/reference/dart.md +670 -0
- package/assets/skills/code-review/reference/django.md +985 -0
- package/assets/skills/code-review/reference/fastapi.md +580 -0
- package/assets/skills/code-review/reference/go.md +993 -0
- package/assets/skills/code-review/reference/java.md +409 -0
- package/assets/skills/code-review/reference/java8.md +586 -0
- package/assets/skills/code-review/reference/kotlin.md +1018 -0
- package/assets/skills/code-review/reference/nestjs.md +593 -0
- package/assets/skills/code-review/reference/performance-review-guide.md +816 -0
- package/assets/skills/code-review/reference/php.md +684 -0
- package/assets/skills/code-review/reference/python.md +1073 -0
- package/assets/skills/code-review/reference/qt.md +757 -0
- package/assets/skills/code-review/reference/react.md +871 -0
- package/assets/skills/code-review/reference/ruby.md +964 -0
- package/assets/skills/code-review/reference/rust.md +846 -0
- package/assets/skills/code-review/reference/security-review-guide.md +494 -0
- package/assets/skills/code-review/reference/svelte.md +1064 -0
- package/assets/skills/code-review/reference/swift.md +936 -0
- package/assets/skills/code-review/reference/typescript.md +1016 -0
- package/assets/skills/code-review/reference/vue.md +924 -0
- package/assets/skills/code-review/reference/zig.md +440 -0
- package/assets/skills/cyclomatic-complexity/LICENSE +201 -0
- package/assets/skills/cyclomatic-complexity/SKILL.md +69 -0
- package/assets/skills/five-whys/LICENSE +21 -0
- package/assets/skills/five-whys/SKILL.md +247 -0
- package/assets/skills/five-whys/examples/performance-regression.md +161 -0
- package/assets/skills/five-whys/examples/production-incident.md +134 -0
- package/assets/skills/five-whys/references/software-patterns.md +284 -0
- package/assets/skills/five-whys/references/toyota-origins.md +257 -0
- package/assets/skills/systematic-debugging/LICENSE +21 -0
- package/assets/skills/systematic-debugging/SKILL.md +283 -0
- package/assets/skills/test-driven-development/LICENSE +21 -0
- package/assets/skills/test-driven-development/SKILL.md +330 -0
- package/assets/skills/verification-before-completion/LICENSE +21 -0
- package/assets/skills/verification-before-completion/SKILL.md +120 -0
- package/assets/skills/vibe-sec/LICENSE +201 -0
- package/assets/skills/vibe-sec/SKILL.md +758 -0
- package/assets/skills/writing-plans/LICENSE +21 -0
- package/assets/skills/writing-plans/SKILL.md +204 -0
- package/bin/minko.js +2 -2
- package/dist/agent/approvalTokens.js +224 -0
- package/dist/agent/branchLock.js +315 -0
- package/dist/agent/compact.js +182 -0
- package/dist/agent/context.js +183 -0
- package/dist/agent/cron.js +326 -0
- package/dist/agent/hooks.js +211 -0
- package/dist/agent/lanes.js +511 -0
- package/dist/agent/loop.js +337 -0
- package/dist/agent/memory.js +159 -0
- package/dist/agent/permissions.js +71 -0
- package/dist/agent/plan.js +118 -0
- package/dist/agent/prompt.js +120 -0
- package/dist/agent/subagent.js +67 -0
- package/dist/agent/tasks.js +449 -0
- package/dist/agent/transcript.js +62 -0
- package/dist/agent/workerStore.js +172 -0
- package/dist/agent/workerTypes.js +1 -0
- package/dist/agent/workers.js +503 -0
- package/dist/cli/app.js +261 -0
- package/dist/cli/argv.js +206 -0
- package/dist/cli/commandsCommand.js +12 -0
- package/dist/cli/configCommand.js +170 -0
- package/dist/cli/doctorCommand.js +112 -0
- package/dist/cli/exportCommand.js +85 -0
- package/dist/cli/forkCommand.js +24 -0
- package/dist/cli/linkCommand.js +272 -0
- package/dist/cli/loginCommand.js +69 -0
- package/dist/cli/mcpLoginCommand.js +68 -0
- package/dist/cli/pluginCommand.js +113 -0
- package/dist/cli/powerCommand.js +174 -0
- package/dist/cli/promptFooter.js +74 -0
- package/dist/cli/relayCommand.js +38 -0
- package/dist/cli/sandboxCommand.js +31 -0
- package/dist/cli/serveCommand.js +104 -0
- package/dist/cli/statusCommand.js +120 -0
- package/dist/cli/tui.js +1104 -0
- package/dist/commands/registry.js +99 -0
- package/dist/config/paths.js +81 -0
- package/dist/config/store.js +605 -0
- package/dist/index.js +199 -0
- package/dist/link/protocol.js +159 -0
- package/dist/link/relay.js +184 -0
- package/dist/link/server.js +566 -0
- package/dist/link/tunnel.js +90 -0
- package/dist/lsp/client.js +340 -0
- package/dist/lsp/tool.js +177 -0
- package/dist/mcp/client.js +469 -0
- package/dist/mcp/httpClient.js +191 -0
- package/dist/mcp/server.js +206 -0
- package/dist/mcp/tools.js +173 -0
- package/dist/oauth/device.js +111 -0
- package/dist/oauth/store.js +56 -0
- package/dist/plugins/loader.js +291 -0
- package/dist/plugins/market.js +98 -0
- package/dist/providers/anthropic.js +279 -0
- package/dist/providers/factory.js +109 -0
- package/dist/providers/openai.js +182 -0
- package/dist/providers/retry.js +95 -0
- package/dist/providers/sse.js +67 -0
- package/dist/providers/types.js +10 -0
- package/dist/rag/embeddings.js +171 -0
- package/dist/rag/index.js +373 -0
- package/dist/rag/server.js +167 -0
- package/dist/rag/tool.js +148 -0
- package/dist/sandbox/index.js +55 -0
- package/dist/sdk/server.js +150 -0
- package/dist/sdk/session.js +104 -0
- package/dist/session/liveness.js +148 -0
- package/dist/session/recorder.js +49 -0
- package/dist/session/store.js +171 -0
- package/dist/skills/index.js +428 -0
- package/dist/theme.js +30 -0
- package/dist/tools/fs.js +195 -0
- package/dist/tools/notebook.js +109 -0
- package/dist/tools/registry.js +32 -0
- package/dist/tools/search.js +130 -0
- package/dist/tools/shell.js +200 -0
- package/dist/tools/structured.js +83 -0
- package/dist/tools/types.js +1 -0
- package/dist/ui/ansi.js +135 -0
- package/dist/ui/banner.js +103 -0
- package/dist/ui/input.js +49 -0
- package/dist/ui/inputModel.js +113 -0
- package/dist/ui/keyboard.js +142 -0
- package/dist/ui/mascot.js +34 -0
- package/dist/ui/mascotData.js +96 -0
- package/dist/ui/md.js +183 -0
- package/dist/ui/screen.js +49 -0
- package/dist/ui/spinner.js +52 -0
- package/dist/ui/transcript.js +287 -0
- package/dist/util/clipboard.js +53 -0
- package/dist/util/errors.js +50 -0
- package/dist/util/version.js +12 -0
- package/dist/web/fetch.js +168 -0
- package/dist/web/search.js +97 -0
- package/package.json +45 -36
- package/src/agent/loop.ts +0 -159
- package/src/agent/prompt.ts +0 -45
- package/src/cli/app.ts +0 -396
- package/src/cli/argv.ts +0 -139
- package/src/cli/configCommand.ts +0 -175
- package/src/config/paths.ts +0 -20
- package/src/config/store.ts +0 -149
- package/src/index.ts +0 -120
- package/src/providers/anthropic.ts +0 -215
- package/src/providers/openai.ts +0 -158
- package/src/providers/sse.ts +0 -57
- package/src/providers/types.ts +0 -59
- package/src/session/store.ts +0 -125
- package/src/theme.ts +0 -41
- package/src/tools/fs.ts +0 -143
- package/src/tools/registry.ts +0 -19
- package/src/tools/shell.ts +0 -120
- package/src/tools/types.ts +0 -26
- package/src/ui/ansi.ts +0 -110
- package/src/ui/banner.ts +0 -39
- package/src/ui/input.ts +0 -54
- package/src/ui/md.ts +0 -160
- package/src/ui/spinner.ts +0 -58
- package/src/util/errors.ts +0 -48
package/src/cli/argv.ts
DELETED
|
@@ -1,139 +0,0 @@
|
|
|
1
|
-
export type ConfigArgs = {
|
|
2
|
-
command: 'config';
|
|
3
|
-
preset?: string;
|
|
4
|
-
apiKey?: string;
|
|
5
|
-
model?: string;
|
|
6
|
-
baseUrl?: string;
|
|
7
|
-
name?: string;
|
|
8
|
-
use?: string;
|
|
9
|
-
list: boolean;
|
|
10
|
-
};
|
|
11
|
-
|
|
12
|
-
export type ParsedArgs =
|
|
13
|
-
| { command: 'help' }
|
|
14
|
-
| { command: 'version' }
|
|
15
|
-
| { command: 'link' }
|
|
16
|
-
| { command: 'sessions'; json: boolean }
|
|
17
|
-
| ConfigArgs
|
|
18
|
-
| {
|
|
19
|
-
command: 'run';
|
|
20
|
-
prompt?: string;
|
|
21
|
-
print: boolean;
|
|
22
|
-
continue: boolean;
|
|
23
|
-
resume?: string;
|
|
24
|
-
auto: boolean;
|
|
25
|
-
model?: string;
|
|
26
|
-
};
|
|
27
|
-
|
|
28
|
-
const BOOLEAN = new Set([
|
|
29
|
-
'-p', '--print', '-c', '--continue', '--auto',
|
|
30
|
-
'-h', '--help', '-V', '--version', '--json', '--list', '-l', '--link', '-link',
|
|
31
|
-
]);
|
|
32
|
-
|
|
33
|
-
const VALUED = new Set([
|
|
34
|
-
'-r', '--resume', '--model', '--base-url', '--api-key',
|
|
35
|
-
'--provider', '--preset', '--name', '--use',
|
|
36
|
-
]);
|
|
37
|
-
|
|
38
|
-
export function parseArgs(argv: string[]): ParsedArgs {
|
|
39
|
-
const flags = new Map<string, string | true>();
|
|
40
|
-
const positional: string[] = [];
|
|
41
|
-
|
|
42
|
-
for (let index = 0; index < argv.length; index += 1) {
|
|
43
|
-
const token = argv[index];
|
|
44
|
-
if (token === '--') {
|
|
45
|
-
positional.push(...argv.slice(index + 1));
|
|
46
|
-
break;
|
|
47
|
-
}
|
|
48
|
-
if (token.startsWith('-') && token !== '-') {
|
|
49
|
-
const equals = token.indexOf('=');
|
|
50
|
-
if (equals > 0) {
|
|
51
|
-
const key = token.slice(0, equals);
|
|
52
|
-
if (!VALUED.has(key)) { throw new Error('未知选项:' + key); }
|
|
53
|
-
flags.set(key, token.slice(equals + 1));
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
if (VALUED.has(token)) {
|
|
57
|
-
const value = argv[index + 1];
|
|
58
|
-
if (value === undefined) { throw new Error('选项 ' + token + ' 缺少值'); }
|
|
59
|
-
flags.set(token, value);
|
|
60
|
-
index += 1;
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
if (BOOLEAN.has(token)) {
|
|
64
|
-
flags.set(token, true);
|
|
65
|
-
continue;
|
|
66
|
-
}
|
|
67
|
-
throw new Error('未知选项:' + token + '(运行 minko --help 查看用法)');
|
|
68
|
-
}
|
|
69
|
-
positional.push(token);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (flag(flags, '-h', '--help')) { return { command: 'help' }; }
|
|
73
|
-
if (flag(flags, '-V', '--version')) { return { command: 'version' }; }
|
|
74
|
-
if (flag(flags, '--link', '-link')) { return { command: 'link' }; }
|
|
75
|
-
|
|
76
|
-
const sub = positional[0];
|
|
77
|
-
if (sub === 'config') {
|
|
78
|
-
return {
|
|
79
|
-
command: 'config',
|
|
80
|
-
preset: text(flags, '--preset'),
|
|
81
|
-
apiKey: text(flags, '--api-key'),
|
|
82
|
-
model: text(flags, '--model'),
|
|
83
|
-
baseUrl: text(flags, '--base-url'),
|
|
84
|
-
name: text(flags, '--name'),
|
|
85
|
-
use: text(flags, '--use'),
|
|
86
|
-
list: Boolean(flag(flags, '--list', '-l')),
|
|
87
|
-
};
|
|
88
|
-
}
|
|
89
|
-
if (sub === 'sessions') {
|
|
90
|
-
return { command: 'sessions', json: Boolean(flag(flags, '--json')) };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const prompt = positional.length > 0 ? positional.join(' ') : undefined;
|
|
94
|
-
return {
|
|
95
|
-
command: 'run',
|
|
96
|
-
prompt,
|
|
97
|
-
print: Boolean(flag(flags, '-p', '--print')),
|
|
98
|
-
continue: Boolean(flags.has('-c') || flags.has('--continue')),
|
|
99
|
-
resume: text(flags, '-r') ?? text(flags, '--resume'),
|
|
100
|
-
auto: Boolean(flags.has('--auto')),
|
|
101
|
-
model: text(flags, '--model'),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function flag(flags: Map<string, string | true>, ...names: string[]): boolean {
|
|
106
|
-
return names.some((name) => flags.has(name));
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
function text(flags: Map<string, string | true>, ...names: string[]): string | undefined {
|
|
110
|
-
for (const name of names) {
|
|
111
|
-
const value = flags.get(name);
|
|
112
|
-
if (typeof value === 'string') { return value; }
|
|
113
|
-
}
|
|
114
|
-
return undefined;
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
export const HELP_TEXT = [
|
|
118
|
-
'用法:minko [选项] [提示词]',
|
|
119
|
-
'',
|
|
120
|
-
'直接运行 minko 进入交互模式;带提示词则执行一轮后退出。',
|
|
121
|
-
'',
|
|
122
|
-
'选项',
|
|
123
|
-
' -p, --print 一次性输出,不进入交互',
|
|
124
|
-
' -c, --continue 继续当前目录最近一次会话',
|
|
125
|
-
' -r, --resume <id> 继续指定会话',
|
|
126
|
-
' --model <id> 本次使用指定模型',
|
|
127
|
-
' --auto 不再逐条确认写文件和命令(危险)',
|
|
128
|
-
' -h, --help 显示帮助',
|
|
129
|
-
' -V, --version 显示版本',
|
|
130
|
-
'',
|
|
131
|
-
'命令',
|
|
132
|
-
' minko config 配置服务商与 API Key',
|
|
133
|
-
' minko config --list 查看已配置的模型',
|
|
134
|
-
' minko sessions 列出保存的会话',
|
|
135
|
-
' minko -link 手机远控配对(规划中)',
|
|
136
|
-
'',
|
|
137
|
-
'会话内命令',
|
|
138
|
-
' /help /new /clear /model <id> /sessions /exit',
|
|
139
|
-
].join('\n');
|
package/src/cli/configCommand.ts
DELETED
|
@@ -1,175 +0,0 @@
|
|
|
1
|
-
import * as readline from 'node:readline/promises';
|
|
2
|
-
import { Painter } from '../ui/ansi.ts';
|
|
3
|
-
import { askHidden } from '../ui/input.ts';
|
|
4
|
-
import {
|
|
5
|
-
loadConfig, saveConfig, upsertProvider, presetById, PRESETS,
|
|
6
|
-
} from '../config/store.ts';
|
|
7
|
-
import type { Config, ProviderConfig } from '../config/store.ts';
|
|
8
|
-
import type { ConfigArgs } from './argv.ts';
|
|
9
|
-
|
|
10
|
-
export async function runConfigCommand(args: ConfigArgs): Promise<number> {
|
|
11
|
-
const painter = new Painter();
|
|
12
|
-
const config = loadConfig();
|
|
13
|
-
|
|
14
|
-
if (args.list) {
|
|
15
|
-
printProviders(painter, config);
|
|
16
|
-
return 0;
|
|
17
|
-
}
|
|
18
|
-
if (args.use) {
|
|
19
|
-
if (!config.providers[args.use]) {
|
|
20
|
-
process.stderr.write(painter.error('没有名为 ' + args.use + ' 的配置。\n'));
|
|
21
|
-
return 1;
|
|
22
|
-
}
|
|
23
|
-
saveConfig({ ...config, active: args.use });
|
|
24
|
-
process.stdout.write(painter.ok('✓ ') + '已切换到 ' + painter.soft(args.use) + '\n');
|
|
25
|
-
return 0;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const hasFlags = Boolean(args.preset || args.apiKey || args.model || args.baseUrl || args.name);
|
|
29
|
-
if (hasFlags) {
|
|
30
|
-
return applyFlags(painter, config, args);
|
|
31
|
-
}
|
|
32
|
-
if (!process.stdin.isTTY) {
|
|
33
|
-
process.stderr.write(painter.error('非交互环境:请用 minko config --preset deepseek --api-key <key>\n'));
|
|
34
|
-
return 1;
|
|
35
|
-
}
|
|
36
|
-
return await wizard(painter, config);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function applyFlags(painter: Painter, config: Config, args: ConfigArgs): number {
|
|
40
|
-
let base: ProviderConfig | undefined;
|
|
41
|
-
let name = args.name ?? '';
|
|
42
|
-
|
|
43
|
-
if (args.preset) {
|
|
44
|
-
const preset = presetById(args.preset);
|
|
45
|
-
if (!preset) {
|
|
46
|
-
const ids = PRESETS.map((item) => item.id).join('、');
|
|
47
|
-
process.stderr.write(painter.error('未知预设:' + args.preset + '(可用:' + ids + ')\n'));
|
|
48
|
-
return 1;
|
|
49
|
-
}
|
|
50
|
-
name = name || preset.id;
|
|
51
|
-
base = {
|
|
52
|
-
kind: preset.kind,
|
|
53
|
-
baseUrl: args.baseUrl ?? preset.baseUrl,
|
|
54
|
-
model: args.model ?? preset.model,
|
|
55
|
-
apiKey: '',
|
|
56
|
-
};
|
|
57
|
-
} else {
|
|
58
|
-
const active = config.providers[config.active];
|
|
59
|
-
if (!active) {
|
|
60
|
-
process.stderr.write(painter.error('还没有配置。先用 minko config --preset deepseek --api-key <key>\n'));
|
|
61
|
-
return 1;
|
|
62
|
-
}
|
|
63
|
-
name = name || config.active;
|
|
64
|
-
base = { ...active };
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
base.baseUrl = args.baseUrl ?? base.baseUrl;
|
|
68
|
-
base.model = args.model ?? base.model;
|
|
69
|
-
base.apiKey = args.apiKey ?? base.apiKey;
|
|
70
|
-
|
|
71
|
-
if (!/^https?:\/\//i.test(base.baseUrl)) {
|
|
72
|
-
process.stderr.write(painter.error('--base-url 必须以 http:// 或 https:// 开头\n'));
|
|
73
|
-
return 1;
|
|
74
|
-
}
|
|
75
|
-
if (base.apiKey.length === 0) {
|
|
76
|
-
process.stderr.write(painter.error('还缺 API Key:加 --api-key <key>\n'));
|
|
77
|
-
return 1;
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const updated = upsertProvider(config, name, base);
|
|
81
|
-
saveConfig(updated);
|
|
82
|
-
process.stdout.write(painter.ok('✓ ') + '已保存 ' + painter.soft(name) + ' · '
|
|
83
|
-
+ painter.muted(base.baseUrl + ' · ' + base.model) + '\n');
|
|
84
|
-
return 0;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
async function wizard(painter: Painter, config: Config): Promise<number> {
|
|
88
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
89
|
-
try {
|
|
90
|
-
process.stdout.write('\n');
|
|
91
|
-
printProviders(painter, config);
|
|
92
|
-
process.stdout.write(' ' + painter.bold('选择一个服务商') + '\n');
|
|
93
|
-
PRESETS.forEach((preset, index) => {
|
|
94
|
-
process.stdout.write(' ' + painter.primary(String(index + 1) + ') ') + painter.soft(preset.label)
|
|
95
|
-
+ painter.dimmed(' ' + preset.baseUrl + ' ' + preset.model) + '\n');
|
|
96
|
-
});
|
|
97
|
-
process.stdout.write(' ' + painter.primary('3) ') + painter.soft('自定义') + '\n');
|
|
98
|
-
const choice = (await rl.question(' ' + painter.muted('序号') + ' › ')).trim();
|
|
99
|
-
|
|
100
|
-
let kind: 'openai' | 'anthropic' = 'openai';
|
|
101
|
-
let baseUrl = '';
|
|
102
|
-
let model = '';
|
|
103
|
-
let name = '';
|
|
104
|
-
|
|
105
|
-
if (choice === '' && config.active) {
|
|
106
|
-
const active = config.providers[config.active];
|
|
107
|
-
if (!active) { return 1; }
|
|
108
|
-
name = config.active;
|
|
109
|
-
kind = active.kind;
|
|
110
|
-
baseUrl = active.baseUrl;
|
|
111
|
-
model = active.model;
|
|
112
|
-
} else if (choice === '1') {
|
|
113
|
-
name = 'deepseek';
|
|
114
|
-
kind = 'openai';
|
|
115
|
-
baseUrl = 'https://api.deepseek.com';
|
|
116
|
-
model = 'deepseek-flash';
|
|
117
|
-
} else if (choice === '2') {
|
|
118
|
-
name = 'anthropic';
|
|
119
|
-
kind = 'anthropic';
|
|
120
|
-
baseUrl = 'https://api.anthropic.com';
|
|
121
|
-
model = 'claude-sonnet-5-5';
|
|
122
|
-
} else if (choice === '3' || choice === '') {
|
|
123
|
-
name = (await rl.question(' 名称(如 my-model) › ')).trim() || 'custom';
|
|
124
|
-
const kindAnswer = (await rl.question(' 接口格式 [openai/anthropic](默认 openai) › ')).trim().toLowerCase();
|
|
125
|
-
kind = kindAnswer === 'anthropic' ? 'anthropic' : 'openai';
|
|
126
|
-
baseUrl = (await rl.question(' 服务地址(如 https://api.example.com/v1) › ')).trim();
|
|
127
|
-
model = (await rl.question(' 模型名 › ')).trim();
|
|
128
|
-
} else {
|
|
129
|
-
process.stderr.write(painter.error('没有这个选项。\n'));
|
|
130
|
-
return 1;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
baseUrl = (await rl.question(' 服务地址 [' + baseUrl + '] › ')).trim() || baseUrl;
|
|
134
|
-
model = (await rl.question(' 模型 [' + model + '] › ')).trim() || model;
|
|
135
|
-
if (!/^https?:\/\//i.test(baseUrl) || model.length === 0) {
|
|
136
|
-
process.stderr.write(painter.error('地址或模型不合法,未保存。\n'));
|
|
137
|
-
return 1;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const existingKey = config.providers[name]?.apiKey ?? '';
|
|
141
|
-
process.stdout.write(' ' + painter.muted('API Key(输入不显示' + (existingKey ? ',直接回车保留原 Key' : '') + ')') + '\n');
|
|
142
|
-
const typed = (await askHidden(rl, ' › ')).trim();
|
|
143
|
-
const apiKey = typed.length > 0 ? typed : existingKey;
|
|
144
|
-
if (apiKey.length === 0) {
|
|
145
|
-
process.stderr.write(painter.error('没有 API Key,未保存。\n'));
|
|
146
|
-
return 1;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
const updated = upsertProvider(config, name, { kind, baseUrl, model, apiKey });
|
|
150
|
-
saveConfig(updated);
|
|
151
|
-
process.stdout.write('\n' + painter.ok('✓ ') + '已保存并启用 ' + painter.soft(name)
|
|
152
|
-
+ painter.dimmed(' ' + baseUrl + ' · ' + model) + '\n\n');
|
|
153
|
-
return 0;
|
|
154
|
-
} catch (error) {
|
|
155
|
-
process.stderr.write(painter.error('配置取消:' + (error instanceof Error ? error.message : String(error)) + '\n'));
|
|
156
|
-
return 1;
|
|
157
|
-
} finally {
|
|
158
|
-
rl.close();
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
export function printProviders(painter: Painter, config: Config): void {
|
|
163
|
-
const entries = Object.entries(config.providers);
|
|
164
|
-
if (entries.length === 0) {
|
|
165
|
-
process.stdout.write(' ' + painter.muted('还没有配置任何服务商。') + '\n');
|
|
166
|
-
return;
|
|
167
|
-
}
|
|
168
|
-
for (const [name, provider] of entries) {
|
|
169
|
-
const active = name === config.active;
|
|
170
|
-
const mark = active ? painter.primary('●') : painter.dimmed('○');
|
|
171
|
-
const keyHint = provider.apiKey.length > 0 ? 'Key 已保存' : '缺少 Key';
|
|
172
|
-
process.stdout.write(' ' + mark + ' ' + painter.soft(name.padEnd(12))
|
|
173
|
-
+ painter.muted(provider.model.padEnd(22)) + painter.dimmed(provider.baseUrl + ' · ' + keyHint) + '\n');
|
|
174
|
-
}
|
|
175
|
-
}
|
package/src/config/paths.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import os from 'node:os';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export function minkoHome(): string {
|
|
5
|
-
const custom = process.env.MINKO_HOME;
|
|
6
|
-
if (custom && custom.trim().length > 0) { return path.resolve(custom); }
|
|
7
|
-
return path.join(os.homedir(), '.minko');
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function configPath(): string {
|
|
11
|
-
return path.join(minkoHome(), 'config.json');
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export function sessionsDir(): string {
|
|
15
|
-
return path.join(minkoHome(), 'sessions');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export function historyFile(): string {
|
|
19
|
-
return path.join(minkoHome(), 'history.jsonl');
|
|
20
|
-
}
|
package/src/config/store.ts
DELETED
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { configPath } from './paths.ts';
|
|
4
|
-
|
|
5
|
-
export type ProviderKind = 'openai' | 'anthropic';
|
|
6
|
-
|
|
7
|
-
export type ProviderConfig = {
|
|
8
|
-
kind: ProviderKind;
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
model: string;
|
|
11
|
-
apiKey: string;
|
|
12
|
-
maxTokens?: number;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export type Config = {
|
|
16
|
-
active: string;
|
|
17
|
-
providers: Record<string, ProviderConfig>;
|
|
18
|
-
approval: 'ask' | 'auto';
|
|
19
|
-
maxTokens: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export type Preset = {
|
|
23
|
-
id: string;
|
|
24
|
-
label: string;
|
|
25
|
-
kind: ProviderKind;
|
|
26
|
-
baseUrl: string;
|
|
27
|
-
model: string;
|
|
28
|
-
hint: string;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export const PRESETS: Preset[] = [
|
|
32
|
-
{
|
|
33
|
-
id: 'deepseek',
|
|
34
|
-
label: 'DeepSeek',
|
|
35
|
-
kind: 'openai',
|
|
36
|
-
baseUrl: 'https://api.deepseek.com',
|
|
37
|
-
model: 'deepseek-flash',
|
|
38
|
-
hint: 'DeepSeek 开放平台,OpenAI 兼容',
|
|
39
|
-
},
|
|
40
|
-
{
|
|
41
|
-
id: 'anthropic',
|
|
42
|
-
label: 'Claude',
|
|
43
|
-
kind: 'anthropic',
|
|
44
|
-
baseUrl: 'https://api.anthropic.com',
|
|
45
|
-
model: 'claude-sonnet-5-5',
|
|
46
|
-
hint: 'Anthropic 官方 API',
|
|
47
|
-
},
|
|
48
|
-
];
|
|
49
|
-
|
|
50
|
-
export function emptyConfig(): Config {
|
|
51
|
-
return { active: '', providers: {}, approval: 'ask', maxTokens: 8192 };
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function loadConfig(file: string = configPath()): Config {
|
|
55
|
-
if (!fs.existsSync(file)) { return emptyConfig(); }
|
|
56
|
-
const raw = fs.readFileSync(file, 'utf8');
|
|
57
|
-
let parsed: unknown;
|
|
58
|
-
try {
|
|
59
|
-
parsed = JSON.parse(raw);
|
|
60
|
-
} catch {
|
|
61
|
-
throw new Error('配置文件不是合法的 JSON:' + file);
|
|
62
|
-
}
|
|
63
|
-
return normalizeConfig(parsed);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export function normalizeConfig(input: unknown): Config {
|
|
67
|
-
const base = emptyConfig();
|
|
68
|
-
if (input === null || typeof input !== 'object') { return base; }
|
|
69
|
-
const record = input as Record<string, unknown>;
|
|
70
|
-
const providers: Record<string, ProviderConfig> = {};
|
|
71
|
-
const rawProviders = record.providers;
|
|
72
|
-
if (rawProviders && typeof rawProviders === 'object') {
|
|
73
|
-
for (const [name, value] of Object.entries(rawProviders as Record<string, unknown>)) {
|
|
74
|
-
const entry = normalizeProvider(value);
|
|
75
|
-
if (entry) { providers[name] = entry; }
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
const active = typeof record.active === 'string' ? record.active : '';
|
|
79
|
-
const approval = record.approval === 'auto' ? 'auto' : 'ask';
|
|
80
|
-
const maxTokens = typeof record.maxTokens === 'number' && record.maxTokens > 0
|
|
81
|
-
? Math.floor(record.maxTokens)
|
|
82
|
-
: base.maxTokens;
|
|
83
|
-
return { active, providers, approval, maxTokens };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function normalizeProvider(value: unknown): ProviderConfig | null {
|
|
87
|
-
if (value === null || typeof value !== 'object') { return null; }
|
|
88
|
-
const record = value as Record<string, unknown>;
|
|
89
|
-
const kind: ProviderKind = record.kind === 'anthropic' ? 'anthropic' : 'openai';
|
|
90
|
-
const baseUrl = typeof record.baseUrl === 'string' ? record.baseUrl.trim() : '';
|
|
91
|
-
const model = typeof record.model === 'string' ? record.model.trim() : '';
|
|
92
|
-
const apiKey = typeof record.apiKey === 'string' ? record.apiKey.trim() : '';
|
|
93
|
-
if (baseUrl.length === 0 || model.length === 0) { return null; }
|
|
94
|
-
const maxTokens = typeof record.maxTokens === 'number' && record.maxTokens > 0
|
|
95
|
-
? Math.floor(record.maxTokens)
|
|
96
|
-
: undefined;
|
|
97
|
-
return { kind, baseUrl: normalizeBaseUrl(baseUrl), model, apiKey, maxTokens };
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
export function normalizeBaseUrl(url: string): string {
|
|
101
|
-
return url.trim().replace(/\/+$/, '');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export function saveConfig(config: Config, file: string = configPath()): void {
|
|
105
|
-
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
106
|
-
fs.writeFileSync(file, JSON.stringify(config, null, 2) + '\n', { encoding: 'utf8', mode: 0o600 });
|
|
107
|
-
try {
|
|
108
|
-
fs.chmodSync(file, 0o600);
|
|
109
|
-
} catch {
|
|
110
|
-
// Windows ignores POSIX modes; the file still lives under the user profile.
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
export function activeProvider(config: Config): { name: string; provider: ProviderConfig } | null {
|
|
115
|
-
const name = config.active;
|
|
116
|
-
if (!name) { return null; }
|
|
117
|
-
const provider = config.providers[name];
|
|
118
|
-
if (!provider) { return null; }
|
|
119
|
-
return { name, provider };
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
export function presetById(id: string): Preset | undefined {
|
|
123
|
-
return PRESETS.find((preset) => preset.id === id);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
export function upsertProvider(config: Config, name: string, provider: ProviderConfig): Config {
|
|
127
|
-
return {
|
|
128
|
-
...config,
|
|
129
|
-
active: name,
|
|
130
|
-
providers: { ...config.providers, [name]: provider },
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/** Problems that must be fixed before the first request is sent. */
|
|
135
|
-
export function configProblems(config: Config): string[] {
|
|
136
|
-
const problems: string[] = [];
|
|
137
|
-
const active = activeProvider(config);
|
|
138
|
-
if (!active) {
|
|
139
|
-
problems.push('还没有配置模型:运行 minko config 选择服务商并填入 API Key');
|
|
140
|
-
return problems;
|
|
141
|
-
}
|
|
142
|
-
if (active.provider.apiKey.length === 0) {
|
|
143
|
-
problems.push('模型 ' + active.name + ' 还没有 API Key:运行 minko config --api-key <key>');
|
|
144
|
-
}
|
|
145
|
-
if (!/^https?:\/\//i.test(active.provider.baseUrl)) {
|
|
146
|
-
problems.push('服务地址必须以 http:// 或 https:// 开头');
|
|
147
|
-
}
|
|
148
|
-
return problems;
|
|
149
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import fs from 'node:fs';
|
|
2
|
-
import { parseArgs, HELP_TEXT } from './cli/argv.ts';
|
|
3
|
-
import { runConfigCommand } from './cli/configCommand.ts';
|
|
4
|
-
import { runApp } from './cli/app.ts';
|
|
5
|
-
import {
|
|
6
|
-
activeProvider, configProblems, loadConfig, saveConfig,
|
|
7
|
-
} from './config/store.ts';
|
|
8
|
-
import type { ProviderConfig } from './config/store.ts';
|
|
9
|
-
import { createOpenAiProvider } from './providers/openai.ts';
|
|
10
|
-
import { createAnthropicProvider } from './providers/anthropic.ts';
|
|
11
|
-
import type { Provider } from './providers/types.ts';
|
|
12
|
-
import { SessionStore } from './session/store.ts';
|
|
13
|
-
import { Painter } from './ui/ansi.ts';
|
|
14
|
-
|
|
15
|
-
export async function main(argv: string[]): Promise<void> {
|
|
16
|
-
const painter = new Painter();
|
|
17
|
-
let parsed;
|
|
18
|
-
try {
|
|
19
|
-
parsed = parseArgs(argv);
|
|
20
|
-
} catch (error) {
|
|
21
|
-
process.stderr.write(painter.error((error instanceof Error ? error.message : String(error)) + '\n'));
|
|
22
|
-
process.exitCode = 1;
|
|
23
|
-
return;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
switch (parsed.command) {
|
|
27
|
-
case 'help':
|
|
28
|
-
process.stdout.write(HELP_TEXT + '\n');
|
|
29
|
-
return;
|
|
30
|
-
case 'version':
|
|
31
|
-
process.stdout.write('minko ' + readVersion() + '\n');
|
|
32
|
-
return;
|
|
33
|
-
case 'link':
|
|
34
|
-
process.stdout.write('\n ' + painter.primary('▌ ') + painter.bold('手机远控,正在路上')
|
|
35
|
-
+ '\n ' + painter.muted('下一步:minko -link 生成二维码,手机扫码配对,之后随时接管这台电脑上的会话。')
|
|
36
|
-
+ '\n\n');
|
|
37
|
-
return;
|
|
38
|
-
case 'sessions':
|
|
39
|
-
printSessions(painter, parsed.json);
|
|
40
|
-
return;
|
|
41
|
-
case 'config': {
|
|
42
|
-
process.exitCode = await runConfigCommand(parsed);
|
|
43
|
-
return;
|
|
44
|
-
}
|
|
45
|
-
case 'run':
|
|
46
|
-
break;
|
|
47
|
-
default:
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const config = loadConfig();
|
|
52
|
-
const problems = configProblems(config);
|
|
53
|
-
if (problems.length > 0) {
|
|
54
|
-
for (const problem of problems) {
|
|
55
|
-
process.stderr.write(painter.error('· ' + problem + '\n'));
|
|
56
|
-
}
|
|
57
|
-
process.exitCode = 1;
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
const active = activeProvider(config);
|
|
61
|
-
if (!active) { process.exitCode = 1; return; }
|
|
62
|
-
|
|
63
|
-
const model = parsed.model ?? active.provider.model;
|
|
64
|
-
const provider = createProvider({ ...active.provider, model, maxTokens: config.maxTokens });
|
|
65
|
-
|
|
66
|
-
process.exitCode = await runApp({
|
|
67
|
-
config,
|
|
68
|
-
provider,
|
|
69
|
-
providerName: active.name,
|
|
70
|
-
model,
|
|
71
|
-
cwd: process.cwd(),
|
|
72
|
-
auto: parsed.auto,
|
|
73
|
-
print: parsed.print,
|
|
74
|
-
prompt: parsed.prompt,
|
|
75
|
-
continueSession: parsed.continue,
|
|
76
|
-
resumeId: parsed.resume,
|
|
77
|
-
setModel: (next: string) => {
|
|
78
|
-
const fresh = loadConfig();
|
|
79
|
-
const entry = fresh.providers[fresh.active];
|
|
80
|
-
if (!entry) { throw new Error('当前没有可用的模型配置'); }
|
|
81
|
-
const updated: ProviderConfig = { ...entry, model: next };
|
|
82
|
-
saveConfig({ ...fresh, providers: { ...fresh.providers, [fresh.active]: updated } });
|
|
83
|
-
return createProvider(updated);
|
|
84
|
-
},
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function createProvider(config: ProviderConfig): Provider {
|
|
89
|
-
return config.kind === 'anthropic'
|
|
90
|
-
? createAnthropicProvider(config)
|
|
91
|
-
: createOpenAiProvider(config);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function readVersion(): string {
|
|
95
|
-
try {
|
|
96
|
-
const file = new URL('../package.json', import.meta.url);
|
|
97
|
-
const pkg = JSON.parse(fs.readFileSync(file, 'utf8')) as { version?: string };
|
|
98
|
-
return pkg.version ?? '0.0.0';
|
|
99
|
-
} catch {
|
|
100
|
-
return '0.0.0';
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function printSessions(painter: Painter, json: boolean): void {
|
|
105
|
-
const store = new SessionStore();
|
|
106
|
-
const metas = store.list();
|
|
107
|
-
if (json) {
|
|
108
|
-
process.stdout.write(JSON.stringify(metas, null, 2) + '\n');
|
|
109
|
-
return;
|
|
110
|
-
}
|
|
111
|
-
if (metas.length === 0) {
|
|
112
|
-
process.stdout.write(' ' + painter.muted('还没有保存的会话。') + '\n');
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
for (const meta of metas.slice(0, 30)) {
|
|
116
|
-
process.stdout.write(' ' + painter.primary('●') + ' ' + painter.soft(meta.id) + ' '
|
|
117
|
-
+ painter.muted(meta.title) + '\n '
|
|
118
|
-
+ painter.dimmed(meta.cwd + ' · ' + meta.updatedAt.slice(0, 16).replace('T', ' ')) + '\n');
|
|
119
|
-
}
|
|
120
|
-
}
|