mulby-cli 1.1.5
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/PLUGIN_DEVELOP_PROMPT.md +1164 -0
- package/README.md +852 -0
- package/assets/default-icon.png +0 -0
- package/dist/commands/ai-session.js +44 -0
- package/dist/commands/build.js +111 -0
- package/dist/commands/config-ai.js +291 -0
- package/dist/commands/config.js +53 -0
- package/dist/commands/create/ai-create.js +183 -0
- package/dist/commands/create/assets.js +53 -0
- package/dist/commands/create/basic.js +72 -0
- package/dist/commands/create/index.js +73 -0
- package/dist/commands/create/react.js +136 -0
- package/dist/commands/create/templates/basic.js +383 -0
- package/dist/commands/create/templates/react/backend.js +72 -0
- package/dist/commands/create/templates/react/config.js +166 -0
- package/dist/commands/create/templates/react/docs.js +78 -0
- package/dist/commands/create/templates/react/hooks.js +469 -0
- package/dist/commands/create/templates/react/index.js +41 -0
- package/dist/commands/create/templates/react/types.js +1228 -0
- package/dist/commands/create/templates/react/ui.js +528 -0
- package/dist/commands/create/templates/react.js +1888 -0
- package/dist/commands/dev.js +141 -0
- package/dist/commands/pack.js +160 -0
- package/dist/commands/resume.js +97 -0
- package/dist/commands/test-ui.js +50 -0
- package/dist/index.js +71 -0
- package/dist/services/ai/PLUGIN_API.md +1102 -0
- package/dist/services/ai/PLUGIN_DEVELOP_PROMPT.md +1164 -0
- package/dist/services/ai/context-manager.js +639 -0
- package/dist/services/ai/index.js +88 -0
- package/dist/services/ai/knowledge.js +52 -0
- package/dist/services/ai/prompts.js +114 -0
- package/dist/services/ai/providers/base.js +38 -0
- package/dist/services/ai/providers/claude.js +284 -0
- package/dist/services/ai/providers/deepseek.js +28 -0
- package/dist/services/ai/providers/gemini.js +191 -0
- package/dist/services/ai/providers/glm.js +31 -0
- package/dist/services/ai/providers/minimax.js +27 -0
- package/dist/services/ai/providers/openai.js +177 -0
- package/dist/services/ai/tools.js +204 -0
- package/dist/services/ai-generator.js +968 -0
- package/dist/services/config-manager.js +117 -0
- package/dist/services/dependency-manager.js +236 -0
- package/dist/services/file-writer.js +66 -0
- package/dist/services/plan-adapter.js +244 -0
- package/dist/services/plan-command-handler.js +172 -0
- package/dist/services/plan-manager.js +502 -0
- package/dist/services/session-manager.js +113 -0
- package/dist/services/task-analyzer.js +136 -0
- package/dist/services/tui/index.js +57 -0
- package/dist/services/tui/store.js +123 -0
- package/dist/types/ai.js +172 -0
- package/dist/types/plan.js +2 -0
- package/dist/ui/Terminal.js +56 -0
- package/dist/ui/components/InputArea.js +176 -0
- package/dist/ui/components/LogArea.js +19 -0
- package/dist/ui/components/PlanPanel.js +69 -0
- package/dist/ui/components/SelectArea.js +13 -0
- package/package.json +45 -0
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.SessionManager = void 0;
|
|
37
|
+
const fs = __importStar(require("fs-extra"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const uuid_1 = require("uuid");
|
|
41
|
+
const SESSION_DIR = path.join(os.homedir(), '.mulby', 'ai-sessions');
|
|
42
|
+
class SessionManager {
|
|
43
|
+
constructor() {
|
|
44
|
+
fs.ensureDirSync(SESSION_DIR);
|
|
45
|
+
}
|
|
46
|
+
static getInstance() {
|
|
47
|
+
if (!SessionManager.instance) {
|
|
48
|
+
SessionManager.instance = new SessionManager();
|
|
49
|
+
}
|
|
50
|
+
return SessionManager.instance;
|
|
51
|
+
}
|
|
52
|
+
createSession(description, targetDir) {
|
|
53
|
+
const session = {
|
|
54
|
+
id: (0, uuid_1.v4)(),
|
|
55
|
+
pluginName: 'pending-name', // Will be updated after planning
|
|
56
|
+
description,
|
|
57
|
+
targetDir,
|
|
58
|
+
status: 'planning',
|
|
59
|
+
completedFiles: [],
|
|
60
|
+
conversationHistory: [],
|
|
61
|
+
createdAt: new Date().toISOString(),
|
|
62
|
+
updatedAt: new Date().toISOString()
|
|
63
|
+
};
|
|
64
|
+
this.saveSession(session);
|
|
65
|
+
return session;
|
|
66
|
+
}
|
|
67
|
+
saveSession(session) {
|
|
68
|
+
session.updatedAt = new Date().toISOString();
|
|
69
|
+
const filePath = path.join(SESSION_DIR, `${session.id}.json`);
|
|
70
|
+
fs.writeJsonSync(filePath, session, { spaces: 2 });
|
|
71
|
+
}
|
|
72
|
+
getSession(id) {
|
|
73
|
+
const filePath = path.join(SESSION_DIR, `${id}.json`);
|
|
74
|
+
if (fs.existsSync(filePath)) {
|
|
75
|
+
return fs.readJsonSync(filePath);
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
getRecentSession() {
|
|
80
|
+
const files = fs.readdirSync(SESSION_DIR)
|
|
81
|
+
.filter(f => f.endsWith('.json'))
|
|
82
|
+
.map(f => ({
|
|
83
|
+
name: f,
|
|
84
|
+
time: fs.statSync(path.join(SESSION_DIR, f)).mtime.getTime()
|
|
85
|
+
}))
|
|
86
|
+
.sort((a, b) => b.time - a.time); // Latest first
|
|
87
|
+
if (files.length > 0) {
|
|
88
|
+
return this.getSession(files[0].name.replace('.json', ''));
|
|
89
|
+
}
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
getLatestSessionForDir(dir) {
|
|
93
|
+
const files = fs.readdirSync(SESSION_DIR)
|
|
94
|
+
.filter(f => f.endsWith('.json'))
|
|
95
|
+
.map(f => ({
|
|
96
|
+
name: f,
|
|
97
|
+
time: fs.statSync(path.join(SESSION_DIR, f)).mtime.getTime()
|
|
98
|
+
}))
|
|
99
|
+
.sort((a, b) => b.time - a.time);
|
|
100
|
+
for (const file of files) {
|
|
101
|
+
const session = this.getSession(file.name.replace('.json', ''));
|
|
102
|
+
if (session && session.targetDir === dir) {
|
|
103
|
+
return session;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
listSessions() {
|
|
109
|
+
const files = fs.readdirSync(SESSION_DIR).filter(f => f.endsWith('.json'));
|
|
110
|
+
return files.map(f => fs.readJsonSync(path.join(SESSION_DIR, f)));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.SessionManager = SessionManager;
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TaskAnalyzer = void 0;
|
|
4
|
+
const ai_1 = require("./ai");
|
|
5
|
+
/**
|
|
6
|
+
* Task Analyzer - uses AI to determine task complexity
|
|
7
|
+
* and whether planning mode should be triggered
|
|
8
|
+
*/
|
|
9
|
+
class TaskAnalyzer {
|
|
10
|
+
/**
|
|
11
|
+
* Use AI to analyze task complexity
|
|
12
|
+
*/
|
|
13
|
+
static async analyze(userInput) {
|
|
14
|
+
const prompt = `分析以下用户任务,判断其复杂度。只返回 JSON,不要其他内容。
|
|
15
|
+
|
|
16
|
+
用户任务:
|
|
17
|
+
${userInput}
|
|
18
|
+
|
|
19
|
+
返回格式:
|
|
20
|
+
{
|
|
21
|
+
"complexity": "simple" | "medium" | "complex",
|
|
22
|
+
"shouldPlan": true | false,
|
|
23
|
+
"estimatedSteps": number,
|
|
24
|
+
"reason": "简短说明原因"
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
判断标准:
|
|
28
|
+
- simple: 单一明确的小任务,如修复一个 bug、改个文案、加个注释
|
|
29
|
+
- medium: 需要 2-4 个步骤的任务,如实现一个小功能、重构一个模块
|
|
30
|
+
- complex: 需要 5+ 个步骤或涉及多个模块/领域的任务,如设计系统架构、实现完整功能模块
|
|
31
|
+
|
|
32
|
+
注意:如果用户只是粘贴了错误信息让你修复,这通常是 simple 任务。`;
|
|
33
|
+
try {
|
|
34
|
+
const response = await this.aiService.chat([
|
|
35
|
+
{ role: 'user', content: prompt }
|
|
36
|
+
], {
|
|
37
|
+
maxTokens: 200,
|
|
38
|
+
temperature: 0
|
|
39
|
+
});
|
|
40
|
+
const content = response.content?.trim() || '';
|
|
41
|
+
// Extract JSON from response (handle potential markdown code blocks)
|
|
42
|
+
const jsonMatch = content.match(/\{[\s\S]*\}/);
|
|
43
|
+
if (jsonMatch) {
|
|
44
|
+
const result = JSON.parse(jsonMatch[0]);
|
|
45
|
+
return {
|
|
46
|
+
complexity: result.complexity || 'simple',
|
|
47
|
+
estimatedSteps: result.estimatedSteps || 1,
|
|
48
|
+
shouldPlan: result.shouldPlan ?? (result.complexity !== 'simple'),
|
|
49
|
+
requiredFiles: [],
|
|
50
|
+
dependencies: [],
|
|
51
|
+
risks: [],
|
|
52
|
+
reason: result.reason
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
// Fallback to simple if AI analysis fails
|
|
58
|
+
console.error('AI analysis failed:', error);
|
|
59
|
+
}
|
|
60
|
+
// Default fallback
|
|
61
|
+
return {
|
|
62
|
+
complexity: 'simple',
|
|
63
|
+
estimatedSteps: 1,
|
|
64
|
+
shouldPlan: false,
|
|
65
|
+
requiredFiles: [],
|
|
66
|
+
dependencies: [],
|
|
67
|
+
risks: []
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Quick check - skip analysis only for obvious non-task inputs
|
|
72
|
+
* We want AI to decide for most cases, so keep this minimal
|
|
73
|
+
*/
|
|
74
|
+
static shouldSkipAnalysis(input) {
|
|
75
|
+
const trimmed = input.trim();
|
|
76
|
+
// Skip for very short inputs (likely just a word or two)
|
|
77
|
+
if (trimmed.length < 10)
|
|
78
|
+
return true;
|
|
79
|
+
// Skip for greetings and simple commands
|
|
80
|
+
if (this.isSimpleCommand(trimmed))
|
|
81
|
+
return true;
|
|
82
|
+
// Skip for pure informational questions (not task requests)
|
|
83
|
+
// Only skip "what is X" type questions, not "how to implement X"
|
|
84
|
+
if (/^(什么是|是什么|.{1,5}是啥)/.test(trimmed))
|
|
85
|
+
return true;
|
|
86
|
+
if (/^what\s+(is|are)\s+/i.test(trimmed))
|
|
87
|
+
return true;
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Get a human-readable description of the analysis
|
|
92
|
+
*/
|
|
93
|
+
static getAnalysisDescription(analysis) {
|
|
94
|
+
const complexityMap = {
|
|
95
|
+
simple: '简单任务',
|
|
96
|
+
medium: '中等复杂度任务',
|
|
97
|
+
complex: '复杂任务'
|
|
98
|
+
};
|
|
99
|
+
let desc = `${complexityMap[analysis.complexity]}`;
|
|
100
|
+
if (analysis.estimatedSteps > 1) {
|
|
101
|
+
desc += `(预估 ${analysis.estimatedSteps} 个步骤)`;
|
|
102
|
+
}
|
|
103
|
+
if (analysis.reason) {
|
|
104
|
+
desc += `\n 原因:${analysis.reason}`;
|
|
105
|
+
}
|
|
106
|
+
return desc;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if the input looks like it's asking a question
|
|
110
|
+
*/
|
|
111
|
+
static isQuestion(input) {
|
|
112
|
+
const questionPatterns = [
|
|
113
|
+
/^(什么|怎么|如何|为什么|哪个|哪些|是否|能否|可以吗)/,
|
|
114
|
+
/\?$/,
|
|
115
|
+
/^(what|how|why|which|where|when|is|are|can|could|would|should)/i,
|
|
116
|
+
/吗[??]?$/,
|
|
117
|
+
/呢[??]?$/
|
|
118
|
+
];
|
|
119
|
+
return questionPatterns.some(p => p.test(input.trim()));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Check if the input is a simple command or greeting
|
|
123
|
+
*/
|
|
124
|
+
static isSimpleCommand(input) {
|
|
125
|
+
const simplePatterns = [
|
|
126
|
+
/^(你好|hi|hello|hey)/i,
|
|
127
|
+
/^(谢谢|thanks|thank you)/i,
|
|
128
|
+
/^(好的|ok|okay|sure)/i,
|
|
129
|
+
/^(继续|continue|go on)/i,
|
|
130
|
+
/^(是|否|yes|no)$/i
|
|
131
|
+
];
|
|
132
|
+
return simplePatterns.some(p => p.test(input.trim()));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
exports.TaskAnalyzer = TaskAnalyzer;
|
|
136
|
+
TaskAnalyzer.aiService = ai_1.AIServiceFactory.create();
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.tui = exports.TerminalService = void 0;
|
|
7
|
+
const react_1 = __importDefault(require("react"));
|
|
8
|
+
const ink_1 = require("ink");
|
|
9
|
+
const Terminal_1 = require("../../ui/Terminal");
|
|
10
|
+
const store_1 = require("./store");
|
|
11
|
+
class TerminalService {
|
|
12
|
+
constructor() {
|
|
13
|
+
this.inkInstance = null;
|
|
14
|
+
// Private constructor
|
|
15
|
+
}
|
|
16
|
+
static getInstance() {
|
|
17
|
+
if (!TerminalService.instance) {
|
|
18
|
+
TerminalService.instance = new TerminalService();
|
|
19
|
+
}
|
|
20
|
+
return TerminalService.instance;
|
|
21
|
+
}
|
|
22
|
+
start() {
|
|
23
|
+
if (!this.inkInstance) {
|
|
24
|
+
// Need to use createElement for React 17+ / Ink 3+
|
|
25
|
+
this.inkInstance = (0, ink_1.render)(react_1.default.createElement(Terminal_1.TerminalApp));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
stop() {
|
|
29
|
+
// 先清理 store 中可能还在等待的 Promise
|
|
30
|
+
store_1.terminalStore.cleanup();
|
|
31
|
+
if (this.inkInstance) {
|
|
32
|
+
try {
|
|
33
|
+
this.inkInstance.unmount();
|
|
34
|
+
}
|
|
35
|
+
catch (e) {
|
|
36
|
+
// 忽略 unmount 错误
|
|
37
|
+
}
|
|
38
|
+
this.inkInstance = null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
log(message) {
|
|
42
|
+
// Strip ANSI codes if needed, or keep them if Text supports it (Ink supports Chalk)
|
|
43
|
+
store_1.terminalStore.addLog(message);
|
|
44
|
+
}
|
|
45
|
+
async prompt(message) {
|
|
46
|
+
this.log(message); // Log the question
|
|
47
|
+
return await store_1.terminalStore.startPrompt();
|
|
48
|
+
}
|
|
49
|
+
async select(items) {
|
|
50
|
+
return await store_1.terminalStore.startSelect(items);
|
|
51
|
+
}
|
|
52
|
+
setStatus(message) {
|
|
53
|
+
store_1.terminalStore.setStatus(message);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.TerminalService = TerminalService;
|
|
57
|
+
exports.tui = TerminalService.getInstance();
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.terminalStore = void 0;
|
|
4
|
+
const events_1 = require("events");
|
|
5
|
+
class TerminalStore extends events_1.EventEmitter {
|
|
6
|
+
constructor() {
|
|
7
|
+
super(...arguments);
|
|
8
|
+
this.logs = [];
|
|
9
|
+
this.isPrompting = false;
|
|
10
|
+
this.statusMessage = '';
|
|
11
|
+
this.isSelecting = false;
|
|
12
|
+
this.selectItems = [];
|
|
13
|
+
this.selectResolver = null;
|
|
14
|
+
this.inputResolver = null;
|
|
15
|
+
}
|
|
16
|
+
addLog(msg) {
|
|
17
|
+
this.logs.push(msg);
|
|
18
|
+
this.emit('change');
|
|
19
|
+
}
|
|
20
|
+
setStatus(msg) {
|
|
21
|
+
this.statusMessage = msg;
|
|
22
|
+
this.emit('change');
|
|
23
|
+
}
|
|
24
|
+
startPrompt() {
|
|
25
|
+
return new Promise((resolve) => {
|
|
26
|
+
// 如果已有未完成的 prompt,先用空字符串 resolve 它
|
|
27
|
+
if (this.inputResolver) {
|
|
28
|
+
const oldResolver = this.inputResolver;
|
|
29
|
+
this.inputResolver = null;
|
|
30
|
+
oldResolver('');
|
|
31
|
+
}
|
|
32
|
+
// 如果正在选择,取消选择并 resolve
|
|
33
|
+
if (this.isSelecting && this.selectResolver) {
|
|
34
|
+
const oldResolver = this.selectResolver;
|
|
35
|
+
this.selectResolver = null;
|
|
36
|
+
this.isSelecting = false;
|
|
37
|
+
this.selectItems = [];
|
|
38
|
+
oldResolver('');
|
|
39
|
+
}
|
|
40
|
+
this.isPrompting = true;
|
|
41
|
+
this.inputResolver = resolve;
|
|
42
|
+
this.emit('change');
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
startSelect(items) {
|
|
46
|
+
return new Promise((resolve) => {
|
|
47
|
+
// 如果已有未完成的 select,先用空字符串 resolve 它
|
|
48
|
+
if (this.selectResolver) {
|
|
49
|
+
const oldResolver = this.selectResolver;
|
|
50
|
+
this.selectResolver = null;
|
|
51
|
+
oldResolver('');
|
|
52
|
+
}
|
|
53
|
+
// 如果正在 prompt,取消 prompt 并 resolve
|
|
54
|
+
if (this.isPrompting && this.inputResolver) {
|
|
55
|
+
const oldResolver = this.inputResolver;
|
|
56
|
+
this.inputResolver = null;
|
|
57
|
+
this.isPrompting = false;
|
|
58
|
+
oldResolver('');
|
|
59
|
+
}
|
|
60
|
+
this.isSelecting = true;
|
|
61
|
+
this.selectItems = items;
|
|
62
|
+
this.selectResolver = resolve;
|
|
63
|
+
this.emit('change');
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
submitInput(value) {
|
|
67
|
+
if (this.inputResolver) {
|
|
68
|
+
// Echo input to logs to simulate terminal history
|
|
69
|
+
// We use a specific style to indicate it's user input (e.g. green arrow)
|
|
70
|
+
// But since 'chalk' might not be imported, let's just use raw string or rely on the caller?
|
|
71
|
+
// Actually, best to import chalk here.
|
|
72
|
+
// Since we cannot easily add imports at the top with this tool without rewriting the whole file or using multi_replace (which is fine),
|
|
73
|
+
// I will assume chalk is available or use a simple string if I can't import easily.
|
|
74
|
+
// Wait, I can rewrite the import section too if I include it in the range or just rewrite the file.
|
|
75
|
+
// But 'chalk' is used comprehensively in this project.
|
|
76
|
+
// To avoid complex partial replace issues with imports, I'll just rewrite the whole file
|
|
77
|
+
// or use multi_replace to add import.
|
|
78
|
+
// LIMITATION: replace_file_content works on a contiguous block.
|
|
79
|
+
// I will use `replace_file_content` to replace the methods, but I need `chalk`.
|
|
80
|
+
// Let's check if I can just use ANSI codes? No, better use chalk.
|
|
81
|
+
// I'll rewrite the whole file to add the import safely.
|
|
82
|
+
this.addLog(`\x1b[32m✔\x1b[0m ${value}`); // Green checkmark + value using raw ANSI for simplicity if avoiding full rewrite, or just do full rewrite.
|
|
83
|
+
this.isPrompting = false;
|
|
84
|
+
const resolver = this.inputResolver;
|
|
85
|
+
this.inputResolver = null;
|
|
86
|
+
this.emit('change');
|
|
87
|
+
resolver(value);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
submitSelect(value) {
|
|
91
|
+
if (this.selectResolver) {
|
|
92
|
+
const selectedItem = this.selectItems.find(i => i.value === value);
|
|
93
|
+
const label = selectedItem ? selectedItem.label : value;
|
|
94
|
+
this.addLog(`\x1b[32m✔\x1b[0m \x1b[36m${label}\x1b[0m`); // Green check + Cyan label
|
|
95
|
+
this.isSelecting = false;
|
|
96
|
+
this.selectItems = [];
|
|
97
|
+
const resolver = this.selectResolver;
|
|
98
|
+
this.selectResolver = null;
|
|
99
|
+
this.emit('change');
|
|
100
|
+
resolver(value);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 清理所有等待中的 Promise,防止进程挂起
|
|
105
|
+
*/
|
|
106
|
+
cleanup() {
|
|
107
|
+
if (this.inputResolver) {
|
|
108
|
+
const resolver = this.inputResolver;
|
|
109
|
+
this.inputResolver = null;
|
|
110
|
+
this.isPrompting = false;
|
|
111
|
+
resolver('');
|
|
112
|
+
}
|
|
113
|
+
if (this.selectResolver) {
|
|
114
|
+
const resolver = this.selectResolver;
|
|
115
|
+
this.selectResolver = null;
|
|
116
|
+
this.isSelecting = false;
|
|
117
|
+
this.selectItems = [];
|
|
118
|
+
resolver('');
|
|
119
|
+
}
|
|
120
|
+
this.removeAllListeners();
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
exports.terminalStore = new TerminalStore();
|
package/dist/types/ai.js
ADDED
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PROVIDER_ENDPOINTS = exports.MODEL_CONTEXT_WINDOWS = exports.PROVIDER_MODELS = exports.DEFAULT_PROVIDER_CONFIG = void 0;
|
|
4
|
+
exports.getModelContextWindow = getModelContextWindow;
|
|
5
|
+
exports.DEFAULT_PROVIDER_CONFIG = {
|
|
6
|
+
maxRetries: 3,
|
|
7
|
+
timeout: 60,
|
|
8
|
+
streaming: true
|
|
9
|
+
};
|
|
10
|
+
// 预设的供应商模型 (2026年1月最新)
|
|
11
|
+
// 注意: maxTokens 在 OpenAI/Anthropic SDK 中都表示"最大输出 token 数"
|
|
12
|
+
exports.PROVIDER_MODELS = {
|
|
13
|
+
openai: [
|
|
14
|
+
// GPT-5.2 系列 (2026年最新, 400K上下文)
|
|
15
|
+
'gpt-5.2', 'gpt-5.2-pro', 'gpt-5.2-chat-latest',
|
|
16
|
+
// GPT-5.1 系列
|
|
17
|
+
'gpt-5.1', 'gpt-5.1-mini',
|
|
18
|
+
// GPT-5 系列
|
|
19
|
+
'gpt-5', 'gpt-5-mini',
|
|
20
|
+
// o 系列推理模型
|
|
21
|
+
'o3', 'o3-mini', 'o4-mini',
|
|
22
|
+
],
|
|
23
|
+
claude: [
|
|
24
|
+
// Claude 4.5 系列 (2025年最新)
|
|
25
|
+
'claude-opus-4-5-20250514', 'claude-sonnet-4-5-20250514',
|
|
26
|
+
// Claude 4 系列
|
|
27
|
+
'claude-sonnet-4-20250514',
|
|
28
|
+
// Claude 3.5 系列 (仍然广泛使用)
|
|
29
|
+
'claude-3-5-sonnet-20241022', 'claude-3-5-haiku-20241022',
|
|
30
|
+
],
|
|
31
|
+
deepseek: [
|
|
32
|
+
// DeepSeek V3.1 (2025年8月)
|
|
33
|
+
'deepseek-chat', // V3/V3.1
|
|
34
|
+
'deepseek-reasoner', // R1 推理模型
|
|
35
|
+
],
|
|
36
|
+
gemini: [
|
|
37
|
+
// Gemini 3 系列 (2026年最新)
|
|
38
|
+
'gemini-3-pro-preview', 'gemini-3-flash-preview',
|
|
39
|
+
// Gemini 2.5 系列
|
|
40
|
+
'gemini-2.5-pro', 'gemini-2.5-flash',
|
|
41
|
+
// Gemini 2.0 系列
|
|
42
|
+
'gemini-2.0-flash',
|
|
43
|
+
],
|
|
44
|
+
glm: [
|
|
45
|
+
// GLM 4.7 (2025年最新)
|
|
46
|
+
'glm-4.7', 'glm-4.6',
|
|
47
|
+
// GLM 4 系列
|
|
48
|
+
'glm-4-plus', 'glm-4', 'glm-4-air', 'glm-4-flash', 'glm-4-long',
|
|
49
|
+
],
|
|
50
|
+
minimax: [
|
|
51
|
+
// MiniMax M2 系列 (2025年12月)
|
|
52
|
+
'MiniMax-M2.1', 'MiniMax-M2.1-lightning', 'MiniMax-M2',
|
|
53
|
+
],
|
|
54
|
+
custom: []
|
|
55
|
+
};
|
|
56
|
+
// 各供应商模型的上下文窗口配置 (2026年1月最新)
|
|
57
|
+
// contextWindow: 上下文窗口大小 (输入+输出总限制)
|
|
58
|
+
// maxOutput: 最大输出 token 数 (OpenAI/Anthropic SDK 中 max_tokens 参数的值)
|
|
59
|
+
exports.MODEL_CONTEXT_WINDOWS = {
|
|
60
|
+
// ============ OpenAI ============
|
|
61
|
+
// GPT-5.2 系列 (2026年最新, 400K上下文)
|
|
62
|
+
'gpt-5.2': { contextWindow: 400000, maxOutput: 128000 },
|
|
63
|
+
'gpt-5.2-pro': { contextWindow: 400000, maxOutput: 128000 },
|
|
64
|
+
'gpt-5.2-chat-latest': { contextWindow: 128000, maxOutput: 16384 },
|
|
65
|
+
// GPT-5.1 系列
|
|
66
|
+
'gpt-5.1': { contextWindow: 400000, maxOutput: 128000 },
|
|
67
|
+
'gpt-5.1-mini': { contextWindow: 128000, maxOutput: 32768 },
|
|
68
|
+
// GPT-5 系列
|
|
69
|
+
'gpt-5': { contextWindow: 400000, maxOutput: 128000 },
|
|
70
|
+
'gpt-5-mini': { contextWindow: 128000, maxOutput: 32768 },
|
|
71
|
+
// o 系列推理模型
|
|
72
|
+
'o3': { contextWindow: 200000, maxOutput: 100000 },
|
|
73
|
+
'o3-mini': { contextWindow: 200000, maxOutput: 100000 },
|
|
74
|
+
'o4-mini': { contextWindow: 200000, maxOutput: 100000 },
|
|
75
|
+
// 旧模型 (兼容)
|
|
76
|
+
'gpt-4o': { contextWindow: 128000, maxOutput: 16384 },
|
|
77
|
+
'gpt-4o-mini': { contextWindow: 128000, maxOutput: 16384 },
|
|
78
|
+
'gpt-4': { contextWindow: 128000, maxOutput: 8192 },
|
|
79
|
+
'gpt-4-turbo': { contextWindow: 128000, maxOutput: 4096 },
|
|
80
|
+
// ============ Claude (Anthropic) ============
|
|
81
|
+
// Claude 4.5 系列 (2025年最新)
|
|
82
|
+
'claude-opus-4-5-20250514': { contextWindow: 200000, maxOutput: 32000 },
|
|
83
|
+
'claude-sonnet-4-5-20250514': { contextWindow: 200000, maxOutput: 16000 },
|
|
84
|
+
// Claude 4 系列
|
|
85
|
+
'claude-sonnet-4-20250514': { contextWindow: 200000, maxOutput: 64000 },
|
|
86
|
+
// Claude 3.5 系列
|
|
87
|
+
'claude-3-5-sonnet-20241022': { contextWindow: 200000, maxOutput: 8192 },
|
|
88
|
+
'claude-3-5-haiku-20241022': { contextWindow: 200000, maxOutput: 8192 },
|
|
89
|
+
// Claude 别名 (用于前缀匹配)
|
|
90
|
+
'claude-opus-4-5': { contextWindow: 200000, maxOutput: 32000 },
|
|
91
|
+
'claude-sonnet-4-5': { contextWindow: 200000, maxOutput: 16000 },
|
|
92
|
+
'claude-sonnet-4': { contextWindow: 200000, maxOutput: 64000 },
|
|
93
|
+
'claude-3-5-sonnet': { contextWindow: 200000, maxOutput: 8192 },
|
|
94
|
+
'claude-3-5-haiku': { contextWindow: 200000, maxOutput: 8192 },
|
|
95
|
+
// ============ DeepSeek ============
|
|
96
|
+
'deepseek-chat': { contextWindow: 128000, maxOutput: 8192 },
|
|
97
|
+
'deepseek-reasoner': { contextWindow: 128000, maxOutput: 64000 },
|
|
98
|
+
// ============ Gemini (Google) ============
|
|
99
|
+
// Gemini 3 系列 (2026年最新)
|
|
100
|
+
'gemini-3-pro-preview': { contextWindow: 1000000, maxOutput: 65536 },
|
|
101
|
+
'gemini-3-flash-preview': { contextWindow: 1000000, maxOutput: 32768 },
|
|
102
|
+
// Gemini 2.5 系列
|
|
103
|
+
'gemini-2.5-pro': { contextWindow: 1000000, maxOutput: 65536 },
|
|
104
|
+
'gemini-2.5-flash': { contextWindow: 1000000, maxOutput: 32768 },
|
|
105
|
+
// Gemini 2.0 系列
|
|
106
|
+
'gemini-2.0-flash': { contextWindow: 1000000, maxOutput: 8192 },
|
|
107
|
+
// 旧模型 (兼容)
|
|
108
|
+
'gemini-1.5-pro': { contextWindow: 2097152, maxOutput: 8192 },
|
|
109
|
+
'gemini-1.5-flash': { contextWindow: 1048576, maxOutput: 8192 },
|
|
110
|
+
// ============ GLM (智谱) ============
|
|
111
|
+
'glm-4.7': { contextWindow: 200000, maxOutput: 128000 },
|
|
112
|
+
'glm-4.6': { contextWindow: 200000, maxOutput: 8192 },
|
|
113
|
+
'glm-4-plus': { contextWindow: 128000, maxOutput: 4096 },
|
|
114
|
+
'glm-4': { contextWindow: 128000, maxOutput: 4096 },
|
|
115
|
+
'glm-4-air': { contextWindow: 128000, maxOutput: 4096 },
|
|
116
|
+
'glm-4-flash': { contextWindow: 128000, maxOutput: 4096 },
|
|
117
|
+
'glm-4-long': { contextWindow: 1000000, maxOutput: 4096 },
|
|
118
|
+
// ============ MiniMax ============
|
|
119
|
+
// MiniMax M2 系列 (2025年12月, 200K上下文可扩展1M)
|
|
120
|
+
'MiniMax-M2.1': { contextWindow: 200000, maxOutput: 8192 },
|
|
121
|
+
'MiniMax-M2.1-lightning': { contextWindow: 200000, maxOutput: 8192 },
|
|
122
|
+
'MiniMax-M2': { contextWindow: 200000, maxOutput: 8192 },
|
|
123
|
+
};
|
|
124
|
+
/**
|
|
125
|
+
* Get context window for a model
|
|
126
|
+
* @param modelName - Full model name or partial match
|
|
127
|
+
* @param provider - Provider type for fallback
|
|
128
|
+
* @returns ModelInfo with context window and max output
|
|
129
|
+
*/
|
|
130
|
+
function getModelContextWindow(modelName, provider) {
|
|
131
|
+
// Exact match
|
|
132
|
+
if (exports.MODEL_CONTEXT_WINDOWS[modelName]) {
|
|
133
|
+
return exports.MODEL_CONTEXT_WINDOWS[modelName];
|
|
134
|
+
}
|
|
135
|
+
// Partial match (e.g., "gpt-4-0125-preview" matches "gpt-4")
|
|
136
|
+
for (const [key, info] of Object.entries(exports.MODEL_CONTEXT_WINDOWS)) {
|
|
137
|
+
if (modelName.startsWith(key)) {
|
|
138
|
+
return info;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
// Fallback based on provider (使用各供应商最常用模型的默认值)
|
|
142
|
+
if (provider) {
|
|
143
|
+
switch (provider) {
|
|
144
|
+
case 'openai':
|
|
145
|
+
return { contextWindow: 400000, maxOutput: 128000 }; // gpt-5.2 默认值
|
|
146
|
+
case 'claude':
|
|
147
|
+
return { contextWindow: 200000, maxOutput: 8192 }; // claude-3-5-sonnet 默认值
|
|
148
|
+
case 'deepseek':
|
|
149
|
+
return { contextWindow: 128000, maxOutput: 8192 }; // deepseek-chat 默认值
|
|
150
|
+
case 'gemini':
|
|
151
|
+
return { contextWindow: 1000000, maxOutput: 65536 }; // gemini-3-pro 默认值
|
|
152
|
+
case 'glm':
|
|
153
|
+
return { contextWindow: 128000, maxOutput: 4096 }; // glm-4 默认值
|
|
154
|
+
case 'minimax':
|
|
155
|
+
return { contextWindow: 200000, maxOutput: 8192 }; // MiniMax-M2.1 默认值
|
|
156
|
+
default:
|
|
157
|
+
return { contextWindow: 128000, maxOutput: 8192 };
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Ultimate fallback
|
|
161
|
+
return { contextWindow: 128000, maxOutput: 4096 };
|
|
162
|
+
}
|
|
163
|
+
// 供应商默认端点
|
|
164
|
+
exports.PROVIDER_ENDPOINTS = {
|
|
165
|
+
openai: 'https://api.openai.com/v1',
|
|
166
|
+
claude: undefined, // 使用 SDK 默认
|
|
167
|
+
deepseek: 'https://api.deepseek.com',
|
|
168
|
+
gemini: 'https://generativelanguage.googleapis.com/v1beta',
|
|
169
|
+
glm: 'https://open.bigmodel.cn/api/paas/v4',
|
|
170
|
+
minimax: 'https://api.minimaxi.com/anthropic',
|
|
171
|
+
custom: undefined
|
|
172
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TerminalApp = void 0;
|
|
4
|
+
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
+
// @ts-nocheck
|
|
6
|
+
const react_1 = require("react");
|
|
7
|
+
const ink_1 = require("ink");
|
|
8
|
+
const InputArea_1 = require("./components/InputArea");
|
|
9
|
+
const TerminalApp = () => {
|
|
10
|
+
// This state management is tricky because we need to bridge external events (logs)
|
|
11
|
+
// to React state. We'll use a simple event emitter pattern or strictly control it via Props
|
|
12
|
+
// if we were rendering repeatedly.
|
|
13
|
+
// However, Ink is best used when it controls the render loop.
|
|
14
|
+
// We will need a way to inject "logs" from the outside.
|
|
15
|
+
// For now, let's assume we use a module-level event emitter or store.
|
|
16
|
+
// NOTE: In a real implementation, we'd use a Context or a Global Store (like Zustand outside React)
|
|
17
|
+
// to feed data into this component, because the "Service" layer needs to call `log()`.
|
|
18
|
+
// See `src/services/tui/index.ts` for how we bridge this.
|
|
19
|
+
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", padding: 1, children: (0, jsx_runtime_1.jsx)(TerminalUI, {}) }));
|
|
20
|
+
};
|
|
21
|
+
exports.TerminalApp = TerminalApp;
|
|
22
|
+
// Internal component that actually hooks into the store/events
|
|
23
|
+
const store_1 = require("../services/tui/store");
|
|
24
|
+
const SelectArea_1 = require("./components/SelectArea");
|
|
25
|
+
const TerminalUI = () => {
|
|
26
|
+
const [logs, setLogs] = (0, react_1.useState)([]);
|
|
27
|
+
const [isPrompting, setIsPrompting] = (0, react_1.useState)(false);
|
|
28
|
+
const [isSelecting, setIsSelecting] = (0, react_1.useState)(false);
|
|
29
|
+
const [selectItems, setSelectItems] = (0, react_1.useState)([]);
|
|
30
|
+
const [statusMessage, setStatusMessage] = (0, react_1.useState)('');
|
|
31
|
+
(0, react_1.useEffect)(() => {
|
|
32
|
+
const updateState = () => {
|
|
33
|
+
// Important: We must create a new array reference for React to detect change,
|
|
34
|
+
// but for Static, passing the whole growing array works.
|
|
35
|
+
setLogs([...store_1.terminalStore.logs]);
|
|
36
|
+
setIsPrompting(store_1.terminalStore.isPrompting);
|
|
37
|
+
setStatusMessage(store_1.terminalStore.statusMessage);
|
|
38
|
+
setIsSelecting(store_1.terminalStore.isSelecting);
|
|
39
|
+
setSelectItems(store_1.terminalStore.selectItems);
|
|
40
|
+
};
|
|
41
|
+
// Initial sync
|
|
42
|
+
updateState();
|
|
43
|
+
// Subscribe
|
|
44
|
+
store_1.terminalStore.addListener('change', updateState);
|
|
45
|
+
return () => {
|
|
46
|
+
store_1.terminalStore.removeListener('change', updateState);
|
|
47
|
+
};
|
|
48
|
+
}, []);
|
|
49
|
+
const handleInput = (value) => {
|
|
50
|
+
store_1.terminalStore.submitInput(value);
|
|
51
|
+
};
|
|
52
|
+
const handleSelect = (value) => {
|
|
53
|
+
store_1.terminalStore.submitSelect(value);
|
|
54
|
+
};
|
|
55
|
+
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", justifyContent: "space-between", children: [(0, jsx_runtime_1.jsx)(ink_1.Static, { items: logs, children: (log, index) => ((0, jsx_runtime_1.jsx)(ink_1.Box, { paddingBottom: 0, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { color: "white", children: log }) }, index)) }), (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: isSelecting ? ((0, jsx_runtime_1.jsx)(SelectArea_1.SelectArea, { items: selectItems, onSelect: handleSelect })) : ((0, jsx_runtime_1.jsx)(InputArea_1.InputArea, { isPrompting: isPrompting, statusMessage: statusMessage, onSubmit: handleInput })) })] }));
|
|
56
|
+
};
|