@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.
Files changed (189) hide show
  1. package/README.md +577 -2
  2. package/assets/mascot-preview.png +0 -0
  3. package/assets/mascot-preview.txt +46 -0
  4. package/assets/mascot-source.png +0 -0
  5. package/assets/skills/README.md +21 -0
  6. package/assets/skills/brainstorming/LICENSE +21 -0
  7. package/assets/skills/brainstorming/SKILL.md +316 -0
  8. package/assets/skills/buddhist-method/LICENSE +21 -0
  9. package/assets/skills/buddhist-method/SKILL.md +137 -0
  10. package/assets/skills/buddhist-method/references/ariyasacca-debug.md +57 -0
  11. package/assets/skills/buddhist-method/references/extended-principles.md +100 -0
  12. package/assets/skills/code-humanizer/LICENSE +21 -0
  13. package/assets/skills/code-humanizer/SKILL.md +174 -0
  14. package/assets/skills/code-review/LICENSE +21 -0
  15. package/assets/skills/code-review/SKILL.md +232 -0
  16. package/assets/skills/code-review/reference/angular.md +768 -0
  17. package/assets/skills/code-review/reference/architecture-review-guide.md +472 -0
  18. package/assets/skills/code-review/reference/c.md +890 -0
  19. package/assets/skills/code-review/reference/code-quality-universal.md +488 -0
  20. package/assets/skills/code-review/reference/code-review-best-practices.md +136 -0
  21. package/assets/skills/code-review/reference/common-bugs-checklist.md +302 -0
  22. package/assets/skills/code-review/reference/cpp.md +893 -0
  23. package/assets/skills/code-review/reference/cross-cutting/async-concurrency-patterns.md +515 -0
  24. package/assets/skills/code-review/reference/cross-cutting/error-handling-principles.md +492 -0
  25. package/assets/skills/code-review/reference/cross-cutting/n-plus-one-queries.md +309 -0
  26. package/assets/skills/code-review/reference/cross-cutting/sql-injection-prevention.md +308 -0
  27. package/assets/skills/code-review/reference/cross-cutting/xss-prevention.md +264 -0
  28. package/assets/skills/code-review/reference/csharp.md +525 -0
  29. package/assets/skills/code-review/reference/css-less-sass.md +661 -0
  30. package/assets/skills/code-review/reference/dart.md +670 -0
  31. package/assets/skills/code-review/reference/django.md +985 -0
  32. package/assets/skills/code-review/reference/fastapi.md +580 -0
  33. package/assets/skills/code-review/reference/go.md +993 -0
  34. package/assets/skills/code-review/reference/java.md +409 -0
  35. package/assets/skills/code-review/reference/java8.md +586 -0
  36. package/assets/skills/code-review/reference/kotlin.md +1018 -0
  37. package/assets/skills/code-review/reference/nestjs.md +593 -0
  38. package/assets/skills/code-review/reference/performance-review-guide.md +816 -0
  39. package/assets/skills/code-review/reference/php.md +684 -0
  40. package/assets/skills/code-review/reference/python.md +1073 -0
  41. package/assets/skills/code-review/reference/qt.md +757 -0
  42. package/assets/skills/code-review/reference/react.md +871 -0
  43. package/assets/skills/code-review/reference/ruby.md +964 -0
  44. package/assets/skills/code-review/reference/rust.md +846 -0
  45. package/assets/skills/code-review/reference/security-review-guide.md +494 -0
  46. package/assets/skills/code-review/reference/svelte.md +1064 -0
  47. package/assets/skills/code-review/reference/swift.md +936 -0
  48. package/assets/skills/code-review/reference/typescript.md +1016 -0
  49. package/assets/skills/code-review/reference/vue.md +924 -0
  50. package/assets/skills/code-review/reference/zig.md +440 -0
  51. package/assets/skills/cyclomatic-complexity/LICENSE +201 -0
  52. package/assets/skills/cyclomatic-complexity/SKILL.md +69 -0
  53. package/assets/skills/five-whys/LICENSE +21 -0
  54. package/assets/skills/five-whys/SKILL.md +247 -0
  55. package/assets/skills/five-whys/examples/performance-regression.md +161 -0
  56. package/assets/skills/five-whys/examples/production-incident.md +134 -0
  57. package/assets/skills/five-whys/references/software-patterns.md +284 -0
  58. package/assets/skills/five-whys/references/toyota-origins.md +257 -0
  59. package/assets/skills/systematic-debugging/LICENSE +21 -0
  60. package/assets/skills/systematic-debugging/SKILL.md +283 -0
  61. package/assets/skills/test-driven-development/LICENSE +21 -0
  62. package/assets/skills/test-driven-development/SKILL.md +330 -0
  63. package/assets/skills/verification-before-completion/LICENSE +21 -0
  64. package/assets/skills/verification-before-completion/SKILL.md +120 -0
  65. package/assets/skills/vibe-sec/LICENSE +201 -0
  66. package/assets/skills/vibe-sec/SKILL.md +758 -0
  67. package/assets/skills/writing-plans/LICENSE +21 -0
  68. package/assets/skills/writing-plans/SKILL.md +204 -0
  69. package/bin/minko.js +2 -2
  70. package/dist/agent/approvalTokens.js +224 -0
  71. package/dist/agent/branchLock.js +315 -0
  72. package/dist/agent/compact.js +182 -0
  73. package/dist/agent/context.js +183 -0
  74. package/dist/agent/cron.js +326 -0
  75. package/dist/agent/hooks.js +211 -0
  76. package/dist/agent/lanes.js +511 -0
  77. package/dist/agent/loop.js +337 -0
  78. package/dist/agent/memory.js +159 -0
  79. package/dist/agent/permissions.js +71 -0
  80. package/dist/agent/plan.js +118 -0
  81. package/dist/agent/prompt.js +120 -0
  82. package/dist/agent/subagent.js +67 -0
  83. package/dist/agent/tasks.js +449 -0
  84. package/dist/agent/transcript.js +62 -0
  85. package/dist/agent/workerStore.js +172 -0
  86. package/dist/agent/workerTypes.js +1 -0
  87. package/dist/agent/workers.js +503 -0
  88. package/dist/cli/app.js +261 -0
  89. package/dist/cli/argv.js +206 -0
  90. package/dist/cli/commandsCommand.js +12 -0
  91. package/dist/cli/configCommand.js +170 -0
  92. package/dist/cli/doctorCommand.js +112 -0
  93. package/dist/cli/exportCommand.js +85 -0
  94. package/dist/cli/forkCommand.js +24 -0
  95. package/dist/cli/linkCommand.js +272 -0
  96. package/dist/cli/loginCommand.js +69 -0
  97. package/dist/cli/mcpLoginCommand.js +68 -0
  98. package/dist/cli/pluginCommand.js +113 -0
  99. package/dist/cli/powerCommand.js +174 -0
  100. package/dist/cli/promptFooter.js +74 -0
  101. package/dist/cli/relayCommand.js +38 -0
  102. package/dist/cli/sandboxCommand.js +31 -0
  103. package/dist/cli/serveCommand.js +104 -0
  104. package/dist/cli/statusCommand.js +120 -0
  105. package/dist/cli/tui.js +1104 -0
  106. package/dist/commands/registry.js +99 -0
  107. package/dist/config/paths.js +81 -0
  108. package/dist/config/store.js +605 -0
  109. package/dist/index.js +199 -0
  110. package/dist/link/protocol.js +159 -0
  111. package/dist/link/relay.js +184 -0
  112. package/dist/link/server.js +566 -0
  113. package/dist/link/tunnel.js +90 -0
  114. package/dist/lsp/client.js +340 -0
  115. package/dist/lsp/tool.js +177 -0
  116. package/dist/mcp/client.js +469 -0
  117. package/dist/mcp/httpClient.js +191 -0
  118. package/dist/mcp/server.js +206 -0
  119. package/dist/mcp/tools.js +173 -0
  120. package/dist/oauth/device.js +111 -0
  121. package/dist/oauth/store.js +56 -0
  122. package/dist/plugins/loader.js +291 -0
  123. package/dist/plugins/market.js +98 -0
  124. package/dist/providers/anthropic.js +279 -0
  125. package/dist/providers/factory.js +109 -0
  126. package/dist/providers/openai.js +182 -0
  127. package/dist/providers/retry.js +95 -0
  128. package/dist/providers/sse.js +67 -0
  129. package/dist/providers/types.js +10 -0
  130. package/dist/rag/embeddings.js +171 -0
  131. package/dist/rag/index.js +373 -0
  132. package/dist/rag/server.js +167 -0
  133. package/dist/rag/tool.js +148 -0
  134. package/dist/sandbox/index.js +55 -0
  135. package/dist/sdk/server.js +150 -0
  136. package/dist/sdk/session.js +104 -0
  137. package/dist/session/liveness.js +148 -0
  138. package/dist/session/recorder.js +49 -0
  139. package/dist/session/store.js +171 -0
  140. package/dist/skills/index.js +428 -0
  141. package/dist/theme.js +30 -0
  142. package/dist/tools/fs.js +195 -0
  143. package/dist/tools/notebook.js +109 -0
  144. package/dist/tools/registry.js +32 -0
  145. package/dist/tools/search.js +130 -0
  146. package/dist/tools/shell.js +200 -0
  147. package/dist/tools/structured.js +83 -0
  148. package/dist/tools/types.js +1 -0
  149. package/dist/ui/ansi.js +135 -0
  150. package/dist/ui/banner.js +103 -0
  151. package/dist/ui/input.js +49 -0
  152. package/dist/ui/inputModel.js +113 -0
  153. package/dist/ui/keyboard.js +142 -0
  154. package/dist/ui/mascot.js +34 -0
  155. package/dist/ui/mascotData.js +96 -0
  156. package/dist/ui/md.js +183 -0
  157. package/dist/ui/screen.js +49 -0
  158. package/dist/ui/spinner.js +52 -0
  159. package/dist/ui/transcript.js +287 -0
  160. package/dist/util/clipboard.js +53 -0
  161. package/dist/util/errors.js +50 -0
  162. package/dist/util/version.js +12 -0
  163. package/dist/web/fetch.js +168 -0
  164. package/dist/web/search.js +97 -0
  165. package/package.json +45 -36
  166. package/src/agent/loop.ts +0 -159
  167. package/src/agent/prompt.ts +0 -45
  168. package/src/cli/app.ts +0 -396
  169. package/src/cli/argv.ts +0 -139
  170. package/src/cli/configCommand.ts +0 -175
  171. package/src/config/paths.ts +0 -20
  172. package/src/config/store.ts +0 -149
  173. package/src/index.ts +0 -120
  174. package/src/providers/anthropic.ts +0 -215
  175. package/src/providers/openai.ts +0 -158
  176. package/src/providers/sse.ts +0 -57
  177. package/src/providers/types.ts +0 -59
  178. package/src/session/store.ts +0 -125
  179. package/src/theme.ts +0 -41
  180. package/src/tools/fs.ts +0 -143
  181. package/src/tools/registry.ts +0 -19
  182. package/src/tools/shell.ts +0 -120
  183. package/src/tools/types.ts +0 -26
  184. package/src/ui/ansi.ts +0 -110
  185. package/src/ui/banner.ts +0 -39
  186. package/src/ui/input.ts +0 -54
  187. package/src/ui/md.ts +0 -160
  188. package/src/ui/spinner.ts +0 -58
  189. package/src/util/errors.ts +0 -48
@@ -0,0 +1,113 @@
1
+ import fs from 'node:fs';
2
+ import { loadConfig, saveConfig } from "../config/store.js";
3
+ import { installPlugin, listPlugins, pluginsDir, uninstallPlugin, } from "../plugins/loader.js";
4
+ import { fetchMarket, installFromGit, searchMarket } from "../plugins/market.js";
5
+ import { Painter } from "../ui/ansi.js";
6
+ export async function runPluginCommand(action, target) {
7
+ const painter = new Painter();
8
+ const config = loadConfig();
9
+ try {
10
+ if (action === 'list') {
11
+ const result = listPlugins(pluginsDir(), config.plugins.disabled);
12
+ for (const error of result.errors) {
13
+ process.stderr.write(painter.error('· ' + error + '\n'));
14
+ }
15
+ if (result.plugins.length === 0) {
16
+ process.stdout.write(' ' + painter.muted('还没有安装插件。用 minko plugin install <目录>') + '\n');
17
+ return 0;
18
+ }
19
+ for (const plugin of result.plugins) {
20
+ const state = plugin.enabled ? painter.ok('启用') : painter.muted('停用');
21
+ process.stdout.write(' ' + painter.primary('●') + ' ' + painter.soft(plugin.name)
22
+ + (plugin.version ? painter.dimmed(' v' + plugin.version) : '')
23
+ + ' [' + state + painter.dimmed('] · ' + plugin.toolCount + ' 个工具')
24
+ + (plugin.description ? '\n ' + painter.dimmed(plugin.description) : '')
25
+ + '\n');
26
+ }
27
+ return 0;
28
+ }
29
+ if (action === 'search') {
30
+ const registryUrl = config.plugins.registryUrl;
31
+ if (!registryUrl) {
32
+ process.stderr.write(painter.error('还没有配置 plugins.registryUrl') + '\n');
33
+ return 1;
34
+ }
35
+ const entries = await fetchMarket(registryUrl);
36
+ const results = searchMarket(entries, target ?? '');
37
+ if (results.length === 0) {
38
+ process.stdout.write(painter.muted('没有匹配的插件。') + '\n');
39
+ return 0;
40
+ }
41
+ for (const entry of results) {
42
+ process.stdout.write(' ' + painter.soft(entry.name)
43
+ + (entry.version ? painter.dimmed(' v' + entry.version) : '')
44
+ + (entry.description ? ' ' + painter.muted(entry.description) : '') + '\n'
45
+ + ' ' + painter.dimmed(entry.git) + '\n');
46
+ }
47
+ return 0;
48
+ }
49
+ if (!target || target.trim().length === 0) {
50
+ const usage = {
51
+ install: 'minko plugin install <名字|git 地址|本地目录>',
52
+ uninstall: 'minko plugin uninstall <名字>',
53
+ enable: 'minko plugin enable <名字>',
54
+ disable: 'minko plugin disable <名字>',
55
+ };
56
+ process.stderr.write(painter.error('用法:' + usage[action] + '\n'));
57
+ return 1;
58
+ }
59
+ if (action === 'install') {
60
+ const source = target.trim();
61
+ if (/^(https?:\/\/|git@|ssh:\/\/)/i.test(source) || /\.git$/i.test(source)) {
62
+ const installed = installFromGit(source);
63
+ process.stdout.write(painter.ok('✓ ') + '已安装插件 ' + painter.soft(installed.name)
64
+ + painter.dimmed(' → ' + installed.path) + '\n');
65
+ return 0;
66
+ }
67
+ if (fs.existsSync(source)) {
68
+ const installed = installPlugin(source);
69
+ process.stdout.write(painter.ok('✓ ') + '已安装插件 ' + painter.soft(installed.name)
70
+ + painter.dimmed(' → ' + installed.path) + '\n');
71
+ return 0;
72
+ }
73
+ if (config.plugins.registryUrl) {
74
+ const entries = await fetchMarket(config.plugins.registryUrl);
75
+ const entry = entries.find((candidate) => candidate.name === source);
76
+ if (!entry) {
77
+ process.stderr.write(painter.error('插件市场里没有:' + source) + '\n');
78
+ return 1;
79
+ }
80
+ const installed = installFromGit(entry.git);
81
+ process.stdout.write(painter.ok('✓ ') + '已安装插件 ' + painter.soft(installed.name)
82
+ + painter.dimmed(' → ' + installed.path) + '\n');
83
+ return 0;
84
+ }
85
+ process.stderr.write(painter.error('不是本地目录,也没有配置 plugins.registryUrl') + '\n');
86
+ return 1;
87
+ }
88
+ if (action === 'uninstall') {
89
+ const removed = uninstallPlugin(target);
90
+ if (!removed) {
91
+ process.stderr.write(painter.error('没有找到插件:' + target + '\n'));
92
+ return 1;
93
+ }
94
+ process.stdout.write(painter.ok('✓ ') + '已卸载插件 ' + painter.soft(target) + '\n');
95
+ return 0;
96
+ }
97
+ const disabled = new Set(config.plugins.disabled);
98
+ if (action === 'disable') {
99
+ disabled.add(target);
100
+ }
101
+ else {
102
+ disabled.delete(target);
103
+ }
104
+ saveConfig({ ...config, plugins: { disabled: [...disabled].sort() } });
105
+ process.stdout.write(painter.ok('✓ ') + (action === 'disable' ? '已停用插件 ' : '已启用插件 ')
106
+ + painter.soft(target) + '\n');
107
+ return 0;
108
+ }
109
+ catch (error) {
110
+ process.stderr.write(painter.error((error instanceof Error ? error.message : String(error)) + '\n'));
111
+ return 1;
112
+ }
113
+ }
@@ -0,0 +1,174 @@
1
+ import { Painter, visibleWidth } from "../ui/ansi.js";
2
+ import { KeyParser } from "../ui/keyboard.js";
3
+ import { loadConfig, saveConfig, POWER_LABELS } from "../config/store.js";
4
+ import { configPath } from "../config/paths.js";
5
+ const OPTIONS = [
6
+ { key: 'a', mode: 'full', detail: '读取和修改电脑上的所有文件,不再询问' },
7
+ { key: 'b', mode: 'workspace', detail: '只能在当前工作目录内读写,不再询问' },
8
+ { key: 'c', mode: 'restricted', detail: '需要更多权限时请求确认(默认)' },
9
+ { key: 'd', mode: 'readonly', detail: '不允许读取或修改任何文件' },
10
+ ];
11
+ export async function runPowerCommand(mode) {
12
+ const painter = new Painter();
13
+ const config = loadConfig();
14
+ if (mode && mode.trim().length > 0) {
15
+ const target = resolveMode(mode.trim().toLowerCase());
16
+ if (!target) {
17
+ process.stderr.write(painter.error('未知权限模式:' + mode + '(可用 a/b/c/d)\n'));
18
+ return 1;
19
+ }
20
+ return apply(painter, config, target);
21
+ }
22
+ if (!process.stdin.isTTY) {
23
+ process.stdout.write('当前权限模式:' + POWER_LABELS[config.power] + '\n');
24
+ return 0;
25
+ }
26
+ const picked = await pick(painter, config.power);
27
+ if (!picked) {
28
+ process.stdout.write(painter.dimmed(' 已取消,权限未修改\n'));
29
+ return 0;
30
+ }
31
+ return apply(painter, config, picked);
32
+ }
33
+ function resolveMode(input) {
34
+ if (input === '1') {
35
+ return 'full';
36
+ }
37
+ if (input === '2') {
38
+ return 'workspace';
39
+ }
40
+ if (input === '3') {
41
+ return 'restricted';
42
+ }
43
+ if (input === '4') {
44
+ return 'readonly';
45
+ }
46
+ const found = OPTIONS.find((option) => option.key === input || option.mode === input);
47
+ return found ? found.mode : null;
48
+ }
49
+ function apply(painter, config, mode) {
50
+ saveConfig({ ...config, power: mode });
51
+ process.stdout.write('\n ' + painter.ok('✓ ') + '权限模式已切换为 ' + painter.soft(POWER_LABELS[mode]) + '\n');
52
+ process.stdout.write(' ' + painter.dimmed('已写入 ' + configPath() + ',对所有工作区生效') + '\n\n');
53
+ return 0;
54
+ }
55
+ /** Arrow keys move, Enter confirms, Esc cancels. */
56
+ async function pick(painter, current) {
57
+ let index = Math.max(0, OPTIONS.findIndex((option) => option.mode === current));
58
+ const lines = renderMenu(painter, index, current);
59
+ process.stdout.write(lines.join('\n') + '\n');
60
+ if (process.stdin.isTTY) {
61
+ process.stdin.setRawMode(true);
62
+ }
63
+ process.stdin.resume();
64
+ process.stdin.setEncoding('utf8');
65
+ const parser = new KeyParser();
66
+ return await new Promise((resolve) => {
67
+ const cleanup = () => {
68
+ process.stdin.removeListener('data', onData);
69
+ if (process.stdin.isTTY) {
70
+ process.stdin.setRawMode(false);
71
+ }
72
+ process.stdin.pause();
73
+ };
74
+ const redraw = () => {
75
+ const content = renderMenu(painter, index, current);
76
+ process.stdout.write('\x1b[' + content.length + 'A');
77
+ for (const line of content) {
78
+ process.stdout.write('\x1b[2K' + line + '\n');
79
+ }
80
+ };
81
+ const onData = (chunk) => {
82
+ for (const key of parser.push(chunk)) {
83
+ const move = handleKey(key);
84
+ if (move === 'up') {
85
+ index = Math.max(0, index - 1);
86
+ redraw();
87
+ }
88
+ else if (move === 'down') {
89
+ index = Math.min(OPTIONS.length - 1, index + 1);
90
+ redraw();
91
+ }
92
+ else if (move === 'pick') {
93
+ cleanup();
94
+ process.stdout.write('\n');
95
+ resolve(OPTIONS[index].mode);
96
+ return;
97
+ }
98
+ else if (move === 'cancel') {
99
+ cleanup();
100
+ process.stdout.write('\n');
101
+ resolve(null);
102
+ return;
103
+ }
104
+ else if (move !== 'none') {
105
+ const target = OPTIONS.findIndex((option) => option.key === move);
106
+ if (target >= 0) {
107
+ index = target;
108
+ redraw();
109
+ }
110
+ }
111
+ }
112
+ };
113
+ process.stdin.on('data', onData);
114
+ });
115
+ }
116
+ function handleKey(key) {
117
+ if (key.type === 'up' || key.type === 'left') {
118
+ return 'up';
119
+ }
120
+ if (key.type === 'down' || key.type === 'right') {
121
+ return 'down';
122
+ }
123
+ if (key.type === 'enter') {
124
+ return 'pick';
125
+ }
126
+ if (key.type === 'escape') {
127
+ return 'cancel';
128
+ }
129
+ if (key.type === 'ctrl' && key.key === 'c') {
130
+ return 'cancel';
131
+ }
132
+ if (key.type === 'text') {
133
+ const text = key.text.trim().toLowerCase();
134
+ if (text === 'a' || text === 'b' || text === 'c' || text === 'd') {
135
+ return text;
136
+ }
137
+ if (text === '1') {
138
+ return 'a';
139
+ }
140
+ if (text === '2') {
141
+ return 'b';
142
+ }
143
+ if (text === '3') {
144
+ return 'c';
145
+ }
146
+ if (text === '4') {
147
+ return 'd';
148
+ }
149
+ }
150
+ return 'none';
151
+ }
152
+ function renderMenu(painter, selected, current) {
153
+ const rows = [];
154
+ rows.push(painter.bold(painter.soft('权限模式')));
155
+ rows.push('');
156
+ OPTIONS.forEach((option, index) => {
157
+ const active = index === selected;
158
+ const marker = active ? painter.primary('❯ ') : ' ';
159
+ const title = (index + 1) + '. ' + POWER_LABELS[option.mode] + ' (' + option.key + ')';
160
+ rows.push(marker + (active ? painter.primary(title) : painter.soft(title)));
161
+ rows.push(' ' + painter.dim(option.detail)
162
+ + (option.mode === current ? painter.dimmed(' ← 当前') : ''));
163
+ });
164
+ rows.push('');
165
+ const width = Math.max(50, ...rows.map((row) => visibleWidth(row))) + 4;
166
+ const border = (text) => painter.fg('#8b6fd6', text);
167
+ const lines = ['', border('╭' + '─'.repeat(width - 2) + '╮')];
168
+ for (const row of rows) {
169
+ const padding = ' '.repeat(Math.max(0, width - 4 - visibleWidth(row)));
170
+ lines.push(border('│') + ' ' + row + padding + ' ' + border('│'));
171
+ }
172
+ lines.push(border('╰' + '─'.repeat(width - 2) + '╯'));
173
+ return lines;
174
+ }
@@ -0,0 +1,74 @@
1
+ import { terminalWidth } from "../ui/ansi.js";
2
+ /**
3
+ * Keeps an input box pinned to the bottom of the window.
4
+ *
5
+ * The terminal's scrolling region is limited to the area above the box, so
6
+ * streamed replies scroll there while the border rows stay put.
7
+ */
8
+ export class PromptFooter {
9
+ painter;
10
+ enabled;
11
+ topRule = '';
12
+ ruleWidth = 0;
13
+ constructor(painter) {
14
+ this.painter = painter;
15
+ this.enabled = Boolean(process.stdout.isTTY) && process.env.MINKO_NO_PINNED !== '1';
16
+ }
17
+ get active() {
18
+ return this.enabled;
19
+ }
20
+ rows() {
21
+ const rows = process.stdout.rows;
22
+ return typeof rows === 'number' && rows >= 6 ? rows : 24;
23
+ }
24
+ width() {
25
+ return Math.max(20, Math.min(terminalWidth() - 1, 120));
26
+ }
27
+ rule() {
28
+ const width = this.width();
29
+ if (width !== this.ruleWidth) {
30
+ this.ruleWidth = width;
31
+ this.topRule = this.painter.dimmed('─'.repeat(width));
32
+ }
33
+ return this.topRule;
34
+ }
35
+ get ruleRow() { return this.rows() - 2; }
36
+ get promptRow() { return this.rows() - 1; }
37
+ get bottomRow() { return this.rows(); }
38
+ get regionBottom() { return Math.max(1, this.rows() - 3); }
39
+ /** Reserve the bottom three rows; call before printing the welcome block. */
40
+ enter() {
41
+ if (!this.enabled) {
42
+ return;
43
+ }
44
+ process.stdout.write('\x1b[1;' + this.regionBottom + 'r');
45
+ }
46
+ /** Redraw the empty input box and park the cursor on the prompt row. */
47
+ park() {
48
+ if (!this.enabled) {
49
+ return;
50
+ }
51
+ const rule = this.rule();
52
+ process.stdout.write('\x1b[' + this.ruleRow + ';1H\x1b[2K' + rule);
53
+ process.stdout.write('\x1b[' + this.promptRow + ';1H\x1b[2K');
54
+ process.stdout.write('\x1b[' + this.bottomRow + ';1H\x1b[2K' + rule);
55
+ process.stdout.write('\x1b[' + this.promptRow + ';1H');
56
+ }
57
+ /** Leave the footer and continue writing into the scrolling area. */
58
+ beginOutput() {
59
+ if (!this.enabled) {
60
+ return;
61
+ }
62
+ process.stdout.write('\x1b[' + this.promptRow + ';1H\x1b[2K');
63
+ process.stdout.write('\x1b[' + this.regionBottom + ';1H\n');
64
+ }
65
+ /** Restore the whole window for the shell. */
66
+ leave() {
67
+ if (!this.enabled) {
68
+ return;
69
+ }
70
+ this.enabled = false;
71
+ process.stdout.write('\x1b[r');
72
+ process.stdout.write('\x1b[' + this.rows() + ';1H\n');
73
+ }
74
+ }
@@ -0,0 +1,38 @@
1
+ import { Painter } from "../ui/ansi.js";
2
+ import { RelayServer } from "../link/relay.js";
3
+ import { readVersion } from "../util/version.js";
4
+ const DEFAULT_PORT = 8787;
5
+ export async function runRelayCommand(portArg) {
6
+ const painter = new Painter();
7
+ const relay = new RelayServer();
8
+ let port;
9
+ try {
10
+ port = await relay.start('0.0.0.0', portArg ?? DEFAULT_PORT);
11
+ }
12
+ catch (error) {
13
+ process.stderr.write(painter.error('无法监听端口:'
14
+ + (error instanceof Error ? error.message : String(error)) + '\n'));
15
+ return 1;
16
+ }
17
+ const lines = [
18
+ '',
19
+ ' ' + painter.bold(painter.soft('Minko 中转')) + painter.dimmed(' v' + readVersion()),
20
+ ' ' + painter.dimmed('─'.repeat(46)),
21
+ ' ' + painter.muted('监听 ') + painter.primary('0.0.0.0:' + port),
22
+ ' ' + painter.muted('电脑 ') + painter.dimmed('minko -link --relay https://你的域名'),
23
+ ' ' + painter.muted('手机 ') + painter.dimmed('扫电脑上的二维码即可,无需改地址'),
24
+ '',
25
+ ' ' + painter.dimmed('公网访问请在前面套 HTTPS 反向代理(nginx/Caddy),手机要求 https。'),
26
+ ' ' + painter.dimmed('Ctrl+C 结束'),
27
+ '',
28
+ ];
29
+ process.stdout.write(lines.join('\n') + '\n');
30
+ return await new Promise((resolve) => {
31
+ const onSigint = () => {
32
+ process.removeListener('SIGINT', onSigint);
33
+ process.stdout.write('\n ' + painter.dimmed('已停止中转') + '\n');
34
+ void relay.stop().then(() => resolve(0));
35
+ };
36
+ process.on('SIGINT', onSigint);
37
+ });
38
+ }
@@ -0,0 +1,31 @@
1
+ import { loadConfig } from "../config/store.js";
2
+ import { dockerAvailable, resolveSandbox } from "../sandbox/index.js";
3
+ import { Painter } from "../ui/ansi.js";
4
+ export async function runSandboxCommand(json) {
5
+ const painter = new Painter();
6
+ const options = resolveSandbox(loadConfig());
7
+ const docker = dockerAvailable();
8
+ const report = {
9
+ mode: options?.mode ?? 'off',
10
+ image: options?.image ?? null,
11
+ network: options?.network ?? false,
12
+ allowFallback: options?.allowFallback ?? false,
13
+ docker,
14
+ };
15
+ if (json) {
16
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
17
+ return 0;
18
+ }
19
+ process.stdout.write(' ' + painter.muted('sandbox ') + report.mode + '\n');
20
+ if (options) {
21
+ process.stdout.write(' ' + painter.muted('image ') + report.image + '\n');
22
+ process.stdout.write(' ' + painter.muted('network ') + (report.network ? '允许' : '禁止') + '\n');
23
+ process.stdout.write(' ' + painter.muted('fallback ') + (report.allowFallback ? '允许普通 shell' : '关闭') + '\n');
24
+ }
25
+ process.stdout.write(' ' + painter.muted('docker ')
26
+ + (docker ? painter.ok('可用') : painter.error('不可用')) + '\n');
27
+ if (options && !docker && !report.allowFallback) {
28
+ process.stdout.write(' ' + painter.dimmed('Docker 不可用时 run_shell 会拒绝执行;可启动 Docker 或设置 allowFallback。') + '\n');
29
+ }
30
+ return 0;
31
+ }
@@ -0,0 +1,104 @@
1
+ import fs from 'node:fs';
2
+ import os from 'node:os';
3
+ import path from 'node:path';
4
+ import { activeProvider, configProblems, loadConfig } from "../config/store.js";
5
+ import { providerChain } from "../providers/factory.js";
6
+ import { SessionStore } from "../session/store.js";
7
+ import { LinkServer } from "../link/server.js";
8
+ import { connectMcpServers } from "../mcp/tools.js";
9
+ import { TunnelClient } from "../link/tunnel.js";
10
+ import { loadOrCreateDevice, pidFilePath, recordPhone } from "../link/protocol.js";
11
+ import { minkoHome } from "../config/paths.js";
12
+ import { readVersion } from "../util/version.js";
13
+ export const SERVE_PORT = 7337;
14
+ function serveLog(line) {
15
+ try {
16
+ fs.appendFileSync(path.join(minkoHome(), 'serve.log'), new Date().toISOString() + ' ' + line + '\n', 'utf8');
17
+ }
18
+ catch {
19
+ // logging must never break the daemon
20
+ }
21
+ }
22
+ /**
23
+ * Background daemon: keeps the tunnel to the relay open and hosts sessions.
24
+ * Started detached by `minko -link` and at login; the phone talks to it.
25
+ */
26
+ export async function runServeCommand() {
27
+ process.on('uncaughtException', (error) => {
28
+ serveLog('uncaught ' + (error instanceof Error ? error.stack ?? error.message : String(error)));
29
+ process.exit(1);
30
+ });
31
+ serveLog('start v' + readVersion() + ' node=' + process.execPath);
32
+ const device = loadOrCreateDevice();
33
+ if (!device.relay || !device.room || !device.token) {
34
+ process.stderr.write('还没有配对:先运行 minko -link\n');
35
+ return 1;
36
+ }
37
+ const config = loadConfig();
38
+ const problems = configProblems(config);
39
+ if (problems.length > 0) {
40
+ process.stderr.write(problems.join('\n') + '\n');
41
+ return 1;
42
+ }
43
+ const active = activeProvider(config);
44
+ if (!active) {
45
+ return 1;
46
+ }
47
+ const chain = providerChain(config);
48
+ if (!chain) {
49
+ return 1;
50
+ }
51
+ const mcp = await connectMcpServers(config.mcpServers);
52
+ for (const error of mcp.errors) {
53
+ serveLog('mcp ' + error);
54
+ }
55
+ const server = new LinkServer({
56
+ provider: chain.provider,
57
+ power: config.power,
58
+ permissions: config.permissions,
59
+ toolHooks: config.hooks,
60
+ mcpTools: mcp.tools,
61
+ config,
62
+ store: new SessionStore(),
63
+ cwd: os.homedir(),
64
+ token: device.token,
65
+ deviceId: device.id,
66
+ name: device.name,
67
+ version: readVersion(),
68
+ getPhones: () => device.phones ?? [],
69
+ onPair: (name) => recordPhone(device, name),
70
+ });
71
+ let port;
72
+ try {
73
+ port = await server.start('0.0.0.0', SERVE_PORT);
74
+ }
75
+ catch {
76
+ // Another instance is already serving; that is fine.
77
+ await mcp.close();
78
+ return 0;
79
+ }
80
+ const pidFile = pidFilePath();
81
+ fs.mkdirSync(path.dirname(pidFile), { recursive: true });
82
+ fs.writeFileSync(pidFile, String(process.pid), 'utf8');
83
+ const tunnel = new TunnelClient(device.relay, device.room, device.token, port);
84
+ tunnel.onLog = (line) => {
85
+ serveLog(line);
86
+ process.stdout.write('[relay] ' + line + '\n');
87
+ };
88
+ tunnel.start();
89
+ serveLog('listening on 0.0.0.0:' + port);
90
+ const shutdown = () => {
91
+ try {
92
+ fs.unlinkSync(pidFile);
93
+ }
94
+ catch { /* already gone */ }
95
+ tunnel.stop();
96
+ void mcp.close().finally(() => {
97
+ void server.stop().finally(() => process.exit(0));
98
+ });
99
+ };
100
+ process.on('SIGINT', shutdown);
101
+ process.on('SIGTERM', shutdown);
102
+ await new Promise(() => { });
103
+ return 0;
104
+ }
@@ -0,0 +1,120 @@
1
+ import { activeProvider } from "../config/store.js";
2
+ import { configPath, minkoHome } from "../config/paths.js";
3
+ import { SessionStore } from "../session/store.js";
4
+ import { defaultMemoryDir, loadMemoryContext } from "../agent/memory.js";
5
+ import { listPlugins, pluginsDir } from "../plugins/loader.js";
6
+ import { readVersion } from "../util/version.js";
7
+ import { Painter } from "../ui/ansi.js";
8
+ export function buildStatus(config, store = new SessionStore(), deps = {}) {
9
+ const active = activeProvider(config);
10
+ const sessions = store.list();
11
+ const plugins = listPlugins(pluginsDir(), config.plugins.disabled);
12
+ return {
13
+ version: deps.version ?? readVersion(),
14
+ node: deps.node ?? process.version,
15
+ home: deps.home ?? minkoHome(),
16
+ configPath: deps.configPath ?? configPath(),
17
+ provider: active
18
+ ? {
19
+ name: active.name,
20
+ kind: active.provider.kind,
21
+ model: active.provider.model,
22
+ baseUrl: active.provider.baseUrl,
23
+ hasKey: active.provider.apiKey.length > 0,
24
+ }
25
+ : null,
26
+ power: config.power,
27
+ approval: config.approval,
28
+ fallbacks: config.fallbackProviders,
29
+ modelAliases: Object.keys(config.modelAliases).length,
30
+ sessions: {
31
+ count: sessions.length,
32
+ latest: sessions[0]?.id ?? null,
33
+ },
34
+ plugins: {
35
+ enabled: plugins.plugins.filter((plugin) => plugin.enabled).length,
36
+ disabled: plugins.plugins.filter((plugin) => !plugin.enabled).length,
37
+ errors: plugins.errors.length,
38
+ },
39
+ mcpServers: Object.keys(config.mcpServers).length,
40
+ lspServers: Object.keys(config.lsp.servers ?? {}).length,
41
+ rag: {
42
+ embeddings: Boolean(config.rag.embeddings && config.rag.embeddings.enabled !== false),
43
+ remote: Boolean(config.rag.remote?.baseUrl),
44
+ },
45
+ memory: Boolean(loadMemoryContext(defaultMemoryDir())),
46
+ permissions: {
47
+ deny: config.permissions.deny.length,
48
+ ask: config.permissions.ask.length,
49
+ allow: config.permissions.allow.length,
50
+ },
51
+ };
52
+ }
53
+ export function formatStatusPlain(status) {
54
+ const lines = [
55
+ 'Minko ' + status.version + ' · Node ' + status.node,
56
+ 'home: ' + status.home,
57
+ 'config: ' + status.configPath,
58
+ ];
59
+ if (status.provider) {
60
+ lines.push('provider: ' + status.provider.name + ' · ' + status.provider.kind
61
+ + ' · ' + status.provider.model + (status.provider.hasKey ? ' · key ✓' : ' · key 缺失'));
62
+ }
63
+ else {
64
+ lines.push('provider: 未配置');
65
+ }
66
+ lines.push('power: ' + status.power + ' · approval ' + status.approval);
67
+ if (status.fallbacks.length > 0) {
68
+ lines.push('fallback: ' + status.fallbacks.join(' → ')
69
+ + ' · ' + status.modelAliases + ' 个模型别名');
70
+ }
71
+ lines.push('sessions: ' + status.sessions.count
72
+ + (status.sessions.latest ? ' · latest ' + status.sessions.latest : ''));
73
+ lines.push('plugins: ' + status.plugins.enabled + ' 启用 / ' + status.plugins.disabled
74
+ + ' 停用 / ' + status.plugins.errors + ' 错误');
75
+ lines.push('MCP / LSP: ' + status.mcpServers + ' / ' + status.lspServers);
76
+ lines.push('RAG: embeddings ' + (status.rag.embeddings ? '开' : '关')
77
+ + ' · remote ' + (status.rag.remote ? '开' : '关'));
78
+ lines.push('memory: ' + (status.memory ? '有索引' : '空'));
79
+ lines.push('rules: deny ' + status.permissions.deny + ' / ask ' + status.permissions.ask
80
+ + ' / allow ' + status.permissions.allow);
81
+ return lines.join('\n');
82
+ }
83
+ export async function runStatusCommand(json) {
84
+ const { loadConfig } = await import("../config/store.js");
85
+ const painter = new Painter();
86
+ const status = buildStatus(loadConfig());
87
+ if (json) {
88
+ process.stdout.write(JSON.stringify(status, null, 2) + '\n');
89
+ return 0;
90
+ }
91
+ process.stdout.write(' ' + painter.bold('Minko ') + painter.soft(status.version)
92
+ + painter.dimmed(' · Node ' + status.node) + '\n');
93
+ process.stdout.write(' ' + painter.muted('home ') + status.home + '\n');
94
+ process.stdout.write(' ' + painter.muted('config ') + status.configPath + '\n');
95
+ if (status.provider) {
96
+ process.stdout.write(' ' + painter.muted('provider ') + painter.primary(status.provider.name)
97
+ + painter.dimmed(' · ' + status.provider.kind + ' · ' + status.provider.model)
98
+ + (status.provider.hasKey ? painter.ok(' · key ✓') : painter.error(' · key 缺失')) + '\n');
99
+ }
100
+ else {
101
+ process.stdout.write(' ' + painter.muted('provider ') + painter.error('未配置') + '\n');
102
+ }
103
+ process.stdout.write(' ' + painter.muted('power ') + status.power
104
+ + painter.dimmed(' · approval ' + status.approval) + '\n');
105
+ if (status.fallbacks.length > 0) {
106
+ process.stdout.write(' ' + painter.muted('fallback ') + status.fallbacks.join(' → ')
107
+ + painter.dimmed(' · ' + status.modelAliases + ' 个模型别名') + '\n');
108
+ }
109
+ process.stdout.write(' ' + painter.muted('sessions ') + status.sessions.count
110
+ + (status.sessions.latest ? painter.dimmed(' · latest ' + status.sessions.latest) : '') + '\n');
111
+ process.stdout.write(' ' + painter.muted('plugins ') + status.plugins.enabled + ' 启用 / '
112
+ + status.plugins.disabled + ' 停用 / ' + status.plugins.errors + ' 错误' + '\n');
113
+ process.stdout.write(' ' + painter.muted('MCP / LSP ') + status.mcpServers + ' / ' + status.lspServers + '\n');
114
+ process.stdout.write(' ' + painter.muted('RAG ') + 'embeddings '
115
+ + (status.rag.embeddings ? '开' : '关') + ' · remote ' + (status.rag.remote ? '开' : '关') + '\n');
116
+ process.stdout.write(' ' + painter.muted('memory ') + (status.memory ? '有索引' : '空') + '\n');
117
+ process.stdout.write(' ' + painter.muted('rules ') + 'deny ' + status.permissions.deny
118
+ + ' / ask ' + status.permissions.ask + ' / allow ' + status.permissions.allow + '\n');
119
+ return 0;
120
+ }