flowmind 1.4.3 → 1.4.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/CHANGELOG.md +12 -0
- package/README.md +40 -72
- package/README_CN.md +50 -78
- package/bin/flowmind-codex.js +197 -0
- package/bin/flowmind.js +27 -6
- package/core/component-registry.js +24 -0
- package/core/config-manager.js +1 -1
- package/core/honor-engine.js +10 -4
- package/core/index.js +84 -31
- package/core/learning-engine.js +13 -5
- package/package.json +2 -1
- package/skills/learning-feedback/index.js +2 -2
- package/skills/log-audit/index.js +24 -23
- package/skills/resource-bind/index.js +11 -2
- package/tui/app.jsx +1 -1
- package/tui/components/ChatPanel.jsx +11 -4
- package/tui/components/Sidebar.jsx +7 -2
- package/tui/components/StatusBar.jsx +6 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.4.5] - 2026-06-29
|
|
4
|
+
|
|
5
|
+
### Fixed
|
|
6
|
+
- Skill routing now prefers explicit skill execution before treating input as generic learning feedback
|
|
7
|
+
- `learning-feedback` now receives the active FlowMind runtime and current skill context for proper feedback injection
|
|
8
|
+
- `resource-bind list` now reads registry data through a stable `getAll()` API instead of a broken internal path
|
|
9
|
+
- `log-audit` now executes real adapter log queries when an adapter is configured, instead of returning a query plan only
|
|
10
|
+
- TUI focus handling now keeps sidebar/chat input behavior and status display aligned
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Core regression tests for routing order, learning bindings, registry listing, and executable skill behavior
|
|
14
|
+
|
|
3
15
|
## [1.3.0] - 2026-06-26
|
|
4
16
|
|
|
5
17
|
### Fixed
|
package/README.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
### **The AI Agent That Learns How You Work**
|
|
6
6
|
|
|
7
|
-
*
|
|
7
|
+
*An adaptive memory and workflow layer for MCP-based developer tools.*
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](CONTRIBUTING.md)
|
|
11
|
-
[](CHANGELOG.md)
|
|
12
12
|
|
|
13
13
|
[中文](README_CN.md) | [Quick Start](#-quick-start) | [How It Works](#-how-it-works) | [Use Cases](#-use-cases) | [Architecture](#-architecture)
|
|
14
14
|
|
|
@@ -16,101 +16,69 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## One Core Value
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
FlowMind helps you **teach a developer workflow once and reuse it later**.
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
❌ Same instructions every single time:
|
|
26
|
-
"Format output as table..."
|
|
27
|
-
"Use sequential list..."
|
|
28
|
-
"Check errors first then..."
|
|
29
|
-
"Connect using source_id..."
|
|
30
|
-
```
|
|
23
|
+
Today, the most reliable path in this repository is:
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
25
|
+
1. Route a request to a skill
|
|
26
|
+
2. Execute that skill through a configured adapter or MCP-compatible provider
|
|
27
|
+
3. Capture explicit user feedback
|
|
28
|
+
4. Re-apply that preference on the next similar run
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
❌ Valuable experience cannot be preserved:
|
|
41
|
-
Architect's design thinking → Lost with project end
|
|
42
|
-
Debugging paths → Have to摸索 again next time
|
|
43
|
-
Best practices → Cannot be reused or传承
|
|
44
|
-
```
|
|
30
|
+
If you want one sentence:
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
❌ Repetitive waiting and inefficient operations:
|
|
49
|
-
Configure database connection every time
|
|
50
|
-
Enter complete conditions for every log query
|
|
51
|
-
Manually execute multiple steps for each deployment
|
|
52
|
-
```
|
|
32
|
+
> FlowMind is a memory layer for repetitive MCP-based developer operations.
|
|
53
33
|
|
|
54
|
-
|
|
34
|
+
## A Runnable Example
|
|
55
35
|
|
|
56
|
-
|
|
36
|
+
```bash
|
|
37
|
+
# 1. Install
|
|
38
|
+
npm install -g flowmind
|
|
57
39
|
|
|
58
|
-
|
|
40
|
+
# 2. Inspect available skills
|
|
41
|
+
flowmind skills --json
|
|
59
42
|
|
|
60
|
-
|
|
43
|
+
# 3. Run a real workflow through the log-audit skill
|
|
44
|
+
flowmind process --skill log-audit "查询 traceId abc123 的日志"
|
|
61
45
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
✅ Every time after: FlowMind remembers
|
|
65
|
-
✅ Smarter with use: AI self-evolution
|
|
66
|
-
```
|
|
46
|
+
# 4. Give explicit feedback
|
|
47
|
+
flowmind "下次用表格格式"
|
|
67
48
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
```
|
|
71
|
-
Code Locate → Data Verify → Problem Analyze → One-Click Fix → Auto Deploy → Archive
|
|
72
|
-
↓ ↓ ↓ ↓ ↓ ↓
|
|
73
|
-
Local+MCP RDS Read SLS Analysis Code Modify Pipeline OpenSpec+Yuque
|
|
49
|
+
# 5. Programmatic / Codex-friendly access
|
|
50
|
+
flowmind-codex --skill log-audit "查询 traceId abc123 的日志"
|
|
74
51
|
```
|
|
75
52
|
|
|
76
|
-
|
|
53
|
+
What you get today:
|
|
54
|
+
- Skill routing
|
|
55
|
+
- MCP/provider-aware execution contracts
|
|
56
|
+
- Explicit feedback capture
|
|
57
|
+
- Local persistence for preferences and learning
|
|
77
58
|
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
- 🎓 **Experience Retention** - Architect and senior developer design thinking, permanently preserved
|
|
83
|
-
|
|
84
|
-
---
|
|
59
|
+
What this project is not yet:
|
|
60
|
+
- A full autonomous coding agent
|
|
61
|
+
- A complete SSH/remote code execution platform
|
|
62
|
+
- A one-click deploy system for every workflow
|
|
85
63
|
|
|
86
|
-
##
|
|
87
|
-
|
|
88
|
-
### Installation
|
|
64
|
+
## Quick Start
|
|
89
65
|
|
|
90
66
|
```bash
|
|
91
67
|
npm install -g flowmind
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### Initialize
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
68
|
flowmind init
|
|
69
|
+
flowmind skills --json
|
|
70
|
+
flowmind process --skill log-audit "查询 traceId abc123 的日志"
|
|
98
71
|
```
|
|
99
72
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
### Start Using
|
|
73
|
+
If you are integrating with Codex or scripts:
|
|
103
74
|
|
|
104
75
|
```bash
|
|
105
|
-
|
|
106
|
-
flowmind "查询 traceId
|
|
107
|
-
FlowMind: [Executes and learns your preference]
|
|
108
|
-
|
|
109
|
-
# Next time - FlowMind remembers!
|
|
110
|
-
flowmind "查询 traceId abc123 的日志"
|
|
111
|
-
FlowMind: [Automatically uses sequential list format] ✓
|
|
76
|
+
flowmind-codex skills
|
|
77
|
+
flowmind-codex --skill log-audit "查询 traceId abc123 的日志"
|
|
112
78
|
```
|
|
113
79
|
|
|
80
|
+
FlowMind stores learning data locally and uses that state to apply explicit feedback on future runs.
|
|
81
|
+
|
|
114
82
|
---
|
|
115
83
|
|
|
116
84
|
## 🧠 How It Works
|
package/README_CN.md
CHANGED
|
@@ -4,11 +4,11 @@
|
|
|
4
4
|
|
|
5
5
|
### **学习你工作方式的 AI 智能体**
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
*一个面向 MCP 开发工作流的可学习记忆层。*
|
|
8
8
|
|
|
9
9
|
[](LICENSE)
|
|
10
10
|
[](CONTRIBUTING.md)
|
|
11
|
-
[](CHANGELOG.md)
|
|
12
12
|
|
|
13
13
|
[English](README.md) | [快速开始](#-快速开始) | [工作原理](#-工作原理) | [使用场景](#-使用场景) | [架构](#-架构)
|
|
14
14
|
|
|
@@ -16,107 +16,69 @@
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## 一个核心价值
|
|
20
20
|
|
|
21
|
-
|
|
21
|
+
FlowMind 的目标不是“什么都做”,而是把**重复出现的开发工作流记住并复用**。
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
```
|
|
25
|
-
❌ 每次都要重复相同的指令:
|
|
26
|
-
"输出格式用表格..."
|
|
27
|
-
"用顺序列表..."
|
|
28
|
-
"先检查错误再..."
|
|
29
|
-
"用 source_id 连接..."
|
|
30
|
-
```
|
|
23
|
+
当前仓库里最可靠、最能跑通的一条主链路是:
|
|
31
24
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
```
|
|
25
|
+
1. 把请求路由到合适的 skill
|
|
26
|
+
2. 通过已配置的 adapter / MCP 兼容提供者执行 skill
|
|
27
|
+
3. 捕获用户的显式反馈
|
|
28
|
+
4. 在下次相似请求里复用这份偏好
|
|
37
29
|
|
|
38
|
-
|
|
39
|
-
```
|
|
40
|
-
❌ 宝贵的经验无法沉淀:
|
|
41
|
-
架构师的设计思路 → 随项目结束而丢失
|
|
42
|
-
调试的排查路径 → 下次又要重新摸索
|
|
43
|
-
最佳实践 → 无法复用和传承
|
|
44
|
-
```
|
|
30
|
+
一句话概括:
|
|
45
31
|
|
|
46
|
-
|
|
47
|
-
```
|
|
48
|
-
❌ 重复等待和低效操作:
|
|
49
|
-
每次连接数据库都要配置
|
|
50
|
-
每次查日志都要输完整条件
|
|
51
|
-
每次部署都要手动执行多个步骤
|
|
52
|
-
```
|
|
32
|
+
> FlowMind 是一个给 MCP 开发工具使用的记忆层。
|
|
53
33
|
|
|
54
|
-
|
|
34
|
+
## 一个可跑通的例子
|
|
55
35
|
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
36
|
+
```bash
|
|
37
|
+
# 1. 安装
|
|
38
|
+
npm install -g flowmind
|
|
59
39
|
|
|
60
|
-
|
|
40
|
+
# 2. 看看有哪些技能
|
|
41
|
+
flowmind skills --json
|
|
61
42
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
✅ 之后每次:FlowMind 自动记住
|
|
65
|
-
✅ 越用越懂你:AI 自我进化
|
|
66
|
-
```
|
|
43
|
+
# 3. 走真正的日志技能链路
|
|
44
|
+
flowmind process --skill log-audit "查询 traceId abc123 的日志"
|
|
67
45
|
|
|
68
|
-
|
|
46
|
+
# 4. 给出显式反馈
|
|
47
|
+
flowmind "下次用表格格式"
|
|
69
48
|
|
|
49
|
+
# 5. 如果你在 Codex / 脚本里集成
|
|
50
|
+
flowmind-codex --skill log-audit "查询 traceId abc123 的日志"
|
|
70
51
|
```
|
|
71
|
-
代码定位 → 数据验证 → 问题分析 → 一键修复 → 自动部署 → 归档记录
|
|
72
|
-
↓ ↓ ↓ ↓ ↓ ↓
|
|
73
|
-
本地+MCP RDS读取 SLS分析 代码修改 流水线执行 OpenSpec+语雀
|
|
74
|
-
```
|
|
75
|
-
|
|
76
|
-
### 越用越智能
|
|
77
52
|
|
|
78
|
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
82
|
-
-
|
|
53
|
+
现在已经具备的能力:
|
|
54
|
+
- skill 路由
|
|
55
|
+
- 面向 MCP/provider 的执行协议
|
|
56
|
+
- 显式反馈学习
|
|
57
|
+
- 本地持久化学习记录和偏好
|
|
83
58
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
59
|
+
当前还不应该过度承诺的部分:
|
|
60
|
+
- 不是完整的自治编码 Agent
|
|
61
|
+
- 不是通用 SSH 远程执行平台
|
|
62
|
+
- 不是所有工作流都已实现一键自动化
|
|
87
63
|
|
|
88
|
-
|
|
64
|
+
## 快速开始
|
|
89
65
|
|
|
90
66
|
```bash
|
|
91
67
|
npm install -g flowmind
|
|
92
|
-
```
|
|
93
|
-
|
|
94
|
-
### 初始化
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
# 基础初始化
|
|
98
68
|
flowmind init
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
flowmind init --ai openai
|
|
102
|
-
flowmind init --ai anthropic
|
|
103
|
-
flowmind init --ai ollama
|
|
69
|
+
flowmind skills --json
|
|
70
|
+
flowmind process --skill log-audit "查询 traceId abc123 的日志"
|
|
104
71
|
```
|
|
105
72
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
### 开始使用
|
|
73
|
+
如果你要集成到 Codex 或脚本:
|
|
109
74
|
|
|
110
75
|
```bash
|
|
111
|
-
|
|
112
|
-
flowmind "查询 traceId
|
|
113
|
-
FlowMind: [执行并学习你的偏好]
|
|
114
|
-
|
|
115
|
-
# 下次 - FlowMind 自动记住!
|
|
116
|
-
flowmind "查询 traceId abc123 的日志"
|
|
117
|
-
FlowMind: [自动使用顺序列表格式] ✓
|
|
76
|
+
flowmind-codex skills
|
|
77
|
+
flowmind-codex --skill log-audit "查询 traceId abc123 的日志"
|
|
118
78
|
```
|
|
119
79
|
|
|
80
|
+
FlowMind 会把学习数据保存在本地,并在后续运行中应用这些显式反馈。
|
|
81
|
+
|
|
120
82
|
---
|
|
121
83
|
|
|
122
84
|
## 📖 使用方式
|
|
@@ -319,6 +281,16 @@ Claude:我来使用 FlowMind 的代码审查技能...
|
|
|
319
281
|
通过 JSON 输出与 Codex 集成。
|
|
320
282
|
|
|
321
283
|
```bash
|
|
284
|
+
# 推荐:使用 Codex 包装命令
|
|
285
|
+
flowmind-codex "查询最近1小时的错误日志"
|
|
286
|
+
flowmind-codex skills
|
|
287
|
+
flowmind-codex skill log-audit
|
|
288
|
+
flowmind-codex doctor
|
|
289
|
+
|
|
290
|
+
# 默认使用当前工作区的 .flowmind-codex/
|
|
291
|
+
# 也可以自定义目录
|
|
292
|
+
# FLOWMIND_CODEX_HOME=/your/path flowmind-codex skills
|
|
293
|
+
|
|
322
294
|
# 获取技能列表 (JSON 格式)
|
|
323
295
|
flowmind skills --json
|
|
324
296
|
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { program } = require('commander');
|
|
4
|
+
const fs = require('fs-extra');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const FlowMind = require('../core');
|
|
7
|
+
const { version } = require('../package.json');
|
|
8
|
+
|
|
9
|
+
function printJson(data) {
|
|
10
|
+
process.stdout.write(`${JSON.stringify(data, null, 2)}\n`);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function fail(message, extra = {}) {
|
|
14
|
+
printJson({
|
|
15
|
+
ok: false,
|
|
16
|
+
error: {
|
|
17
|
+
message,
|
|
18
|
+
...extra
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
async function createFlowMind() {
|
|
25
|
+
const codexHome = process.env.FLOWMIND_CODEX_HOME || path.join(process.cwd(), '.flowmind-codex');
|
|
26
|
+
process.env.FLOWMIND_HOME = process.env.FLOWMIND_HOME || codexHome;
|
|
27
|
+
|
|
28
|
+
const configPath = path.join(codexHome, 'config.json');
|
|
29
|
+
await fs.ensureDir(codexHome);
|
|
30
|
+
|
|
31
|
+
if (!await fs.pathExists(configPath)) {
|
|
32
|
+
await fs.writeJson(configPath, {
|
|
33
|
+
version: '1.0.0',
|
|
34
|
+
name: 'FlowMind Codex Workspace',
|
|
35
|
+
storagePath: codexHome,
|
|
36
|
+
learning: {
|
|
37
|
+
storagePath: path.join(codexHome, 'learning')
|
|
38
|
+
}
|
|
39
|
+
}, { spaces: 2 });
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const flowmind = new FlowMind({ configPath });
|
|
43
|
+
await flowmind.init();
|
|
44
|
+
return flowmind;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
program
|
|
48
|
+
.name('flowmind-codex')
|
|
49
|
+
.description('Codex-friendly JSON wrapper for FlowMind')
|
|
50
|
+
.version(version);
|
|
51
|
+
|
|
52
|
+
program
|
|
53
|
+
.argument('[input...]', 'Process a FlowMind request directly')
|
|
54
|
+
.option('-s, --skill <skill>', 'Use a specific skill')
|
|
55
|
+
.action(async (inputParts, options) => {
|
|
56
|
+
if (!inputParts.length) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const flowmind = await createFlowMind();
|
|
62
|
+
const input = inputParts.join(' ');
|
|
63
|
+
const result = await flowmind.process(input, {
|
|
64
|
+
skill: options.skill
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
printJson({
|
|
68
|
+
ok: result.type !== 'error',
|
|
69
|
+
command: 'ask',
|
|
70
|
+
input,
|
|
71
|
+
result
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
if (result.type === 'error') {
|
|
75
|
+
process.exitCode = 1;
|
|
76
|
+
}
|
|
77
|
+
} catch (error) {
|
|
78
|
+
fail(error.message, { command: 'ask' });
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
program
|
|
83
|
+
.command('ask')
|
|
84
|
+
.description('Process a request and emit JSON')
|
|
85
|
+
.argument('<input...>', 'Input to process')
|
|
86
|
+
.option('-s, --skill <skill>', 'Use a specific skill')
|
|
87
|
+
.action(async (inputParts, options) => {
|
|
88
|
+
try {
|
|
89
|
+
const flowmind = await createFlowMind();
|
|
90
|
+
const input = inputParts.join(' ');
|
|
91
|
+
const result = await flowmind.process(input, {
|
|
92
|
+
skill: options.skill
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
printJson({
|
|
96
|
+
ok: result.type !== 'error',
|
|
97
|
+
command: 'ask',
|
|
98
|
+
input,
|
|
99
|
+
result
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
if (result.type === 'error') {
|
|
103
|
+
process.exitCode = 1;
|
|
104
|
+
}
|
|
105
|
+
} catch (error) {
|
|
106
|
+
fail(error.message, { command: 'ask' });
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
program
|
|
111
|
+
.command('skills')
|
|
112
|
+
.description('List skills as JSON')
|
|
113
|
+
.action(async () => {
|
|
114
|
+
try {
|
|
115
|
+
const flowmind = await createFlowMind();
|
|
116
|
+
printJson({
|
|
117
|
+
ok: true,
|
|
118
|
+
command: 'skills',
|
|
119
|
+
skills: flowmind.skills.list()
|
|
120
|
+
});
|
|
121
|
+
} catch (error) {
|
|
122
|
+
fail(error.message, { command: 'skills' });
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
program
|
|
127
|
+
.command('skill')
|
|
128
|
+
.description('Get one skill as JSON')
|
|
129
|
+
.argument('<name>', 'Skill name')
|
|
130
|
+
.action(async (name) => {
|
|
131
|
+
try {
|
|
132
|
+
const flowmind = await createFlowMind();
|
|
133
|
+
const skill = flowmind.skills.get(name);
|
|
134
|
+
|
|
135
|
+
if (!skill) {
|
|
136
|
+
fail(`Skill not found: ${name}`, { command: 'skill', skill: name });
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
printJson({
|
|
141
|
+
ok: true,
|
|
142
|
+
command: 'skill',
|
|
143
|
+
skill: {
|
|
144
|
+
name: skill.name,
|
|
145
|
+
description: skill.definition?.description,
|
|
146
|
+
category: skill.definition?.category || skill.definition?.metadata?.category,
|
|
147
|
+
version: skill.definition?.version || skill.definition?.metadata?.version,
|
|
148
|
+
author: skill.definition?.author || skill.definition?.metadata?.author,
|
|
149
|
+
triggers: skill.definition?.triggers || [],
|
|
150
|
+
componentDependencies: skill.definition?.componentDependencies || []
|
|
151
|
+
}
|
|
152
|
+
});
|
|
153
|
+
} catch (error) {
|
|
154
|
+
fail(error.message, { command: 'skill', skill: name });
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
program
|
|
159
|
+
.command('doctor')
|
|
160
|
+
.description('Run health checks as JSON')
|
|
161
|
+
.action(async () => {
|
|
162
|
+
try {
|
|
163
|
+
const flowmind = await createFlowMind();
|
|
164
|
+
const result = await flowmind.doctor();
|
|
165
|
+
printJson({
|
|
166
|
+
ok: result.summary?.errors === 0,
|
|
167
|
+
command: 'doctor',
|
|
168
|
+
result
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
if (result.summary?.errors > 0) {
|
|
172
|
+
process.exitCode = 1;
|
|
173
|
+
}
|
|
174
|
+
} catch (error) {
|
|
175
|
+
fail(error.message, { command: 'doctor' });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
program
|
|
180
|
+
.command('ai-status')
|
|
181
|
+
.description('Show AI status as JSON')
|
|
182
|
+
.action(async () => {
|
|
183
|
+
try {
|
|
184
|
+
const flowmind = await createFlowMind();
|
|
185
|
+
printJson({
|
|
186
|
+
ok: true,
|
|
187
|
+
command: 'ai-status',
|
|
188
|
+
result: flowmind.getAIStatus()
|
|
189
|
+
});
|
|
190
|
+
} catch (error) {
|
|
191
|
+
fail(error.message, { command: 'ai-status' });
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
program.parseAsync(process.argv).catch((error) => {
|
|
196
|
+
fail(error.message);
|
|
197
|
+
});
|
package/bin/flowmind.js
CHANGED
|
@@ -24,8 +24,10 @@ function restoreTerminal() {
|
|
|
24
24
|
process.stdin.setRawMode(false);
|
|
25
25
|
}
|
|
26
26
|
} catch (e) { /* ignore */ }
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
if (process.stdout.isTTY) {
|
|
28
|
+
// Show cursor, reset colors, clear scroll region
|
|
29
|
+
process.stdout.write('\x1b[?25h\x1b[0m\x1b[r');
|
|
30
|
+
}
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
// Global error handlers to prevent silent CLI crashes
|
|
@@ -503,6 +505,7 @@ program
|
|
|
503
505
|
.alias('p')
|
|
504
506
|
.description('Process a request')
|
|
505
507
|
.argument('[input]', 'Input to process')
|
|
508
|
+
.option('-j, --json', 'Output as JSON (for tool integration)')
|
|
506
509
|
.option('-s, --skill <skill>', 'Use specific skill')
|
|
507
510
|
.option('-v, --verbose', 'Verbose output')
|
|
508
511
|
.action(async (input, options) => {
|
|
@@ -514,18 +517,36 @@ program
|
|
|
514
517
|
await runInteractiveMode(fm);
|
|
515
518
|
} else {
|
|
516
519
|
// Single command mode
|
|
517
|
-
const spinner = ora('Processing...').start();
|
|
520
|
+
const spinner = options.json ? null : ora('Processing...').start();
|
|
518
521
|
|
|
519
522
|
const result = await fm.process(input, {
|
|
520
523
|
skill: options.skill,
|
|
521
524
|
verbose: options.verbose
|
|
522
525
|
});
|
|
523
526
|
|
|
524
|
-
spinner
|
|
525
|
-
|
|
527
|
+
if (spinner) {
|
|
528
|
+
spinner.stop();
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
if (options.json) {
|
|
532
|
+
console.log(JSON.stringify(result, null, 2));
|
|
533
|
+
if (result.type === 'error') {
|
|
534
|
+
process.exitCode = 1;
|
|
535
|
+
}
|
|
536
|
+
} else {
|
|
537
|
+
displayResult(result);
|
|
538
|
+
}
|
|
526
539
|
}
|
|
527
540
|
} catch (error) {
|
|
528
|
-
|
|
541
|
+
if (options.json) {
|
|
542
|
+
console.log(JSON.stringify({
|
|
543
|
+
type: 'error',
|
|
544
|
+
message: error.message
|
|
545
|
+
}, null, 2));
|
|
546
|
+
process.exitCode = 1;
|
|
547
|
+
} else {
|
|
548
|
+
console.error(chalk.red('Error:'), error.message);
|
|
549
|
+
}
|
|
529
550
|
} finally {
|
|
530
551
|
restoreTerminal();
|
|
531
552
|
}
|
|
@@ -233,6 +233,30 @@ class ComponentRegistry {
|
|
|
233
233
|
return result;
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
+
/**
|
|
237
|
+
* Get a flat list of all registered component providers.
|
|
238
|
+
* @returns {object[]}
|
|
239
|
+
*/
|
|
240
|
+
getAll() {
|
|
241
|
+
const result = [];
|
|
242
|
+
|
|
243
|
+
for (const type of Object.values(ComponentType)) {
|
|
244
|
+
const typeAdapters = this.adapters.get(type);
|
|
245
|
+
if (!typeAdapters) continue;
|
|
246
|
+
|
|
247
|
+
for (const [name, adapter] of typeAdapters) {
|
|
248
|
+
result.push({
|
|
249
|
+
name,
|
|
250
|
+
type,
|
|
251
|
+
active: this.activeProviders.get(type) === name,
|
|
252
|
+
...adapter.getStatus()
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return result;
|
|
258
|
+
}
|
|
259
|
+
|
|
236
260
|
/**
|
|
237
261
|
* Get the active provider name for a component type.
|
|
238
262
|
* @param {string} componentType
|
package/core/config-manager.js
CHANGED
|
@@ -312,7 +312,7 @@ class ConfigManager {
|
|
|
312
312
|
* Get home directory
|
|
313
313
|
*/
|
|
314
314
|
getHomeDir() {
|
|
315
|
-
return process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
315
|
+
return process.env.FLOWMIND_HOME || process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
316
316
|
}
|
|
317
317
|
|
|
318
318
|
/**
|
package/core/honor-engine.js
CHANGED
|
@@ -26,20 +26,26 @@ const DRAGON_LEVELS = [
|
|
|
26
26
|
class HonorEngine {
|
|
27
27
|
constructor(config) {
|
|
28
28
|
this.config = config;
|
|
29
|
-
this.honorPath =
|
|
30
|
-
config.get('storagePath') || path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind'),
|
|
31
|
-
'honor.json'
|
|
32
|
-
);
|
|
29
|
+
this.honorPath = null;
|
|
33
30
|
this.skillsPath = config.get('skills.path', path.join(__dirname, '..', 'skills'));
|
|
34
31
|
this.data = null;
|
|
35
32
|
this.initialized = false;
|
|
36
33
|
}
|
|
37
34
|
|
|
35
|
+
resolveHonorPath() {
|
|
36
|
+
const storagePath = this.config.get('storagePath')
|
|
37
|
+
|| process.env.FLOWMIND_HOME
|
|
38
|
+
|| path.join(process.env.HOME || process.env.USERPROFILE, '.flowmind');
|
|
39
|
+
return path.join(storagePath, 'honor.json');
|
|
40
|
+
}
|
|
41
|
+
|
|
38
42
|
/**
|
|
39
43
|
* Initialize honor engine
|
|
40
44
|
*/
|
|
41
45
|
async init() {
|
|
42
46
|
try {
|
|
47
|
+
this.honorPath = this.resolveHonorPath();
|
|
48
|
+
|
|
43
49
|
if (await fs.pathExists(this.honorPath)) {
|
|
44
50
|
this.data = await fs.readJson(this.honorPath);
|
|
45
51
|
} else {
|
package/core/index.js
CHANGED
|
@@ -98,41 +98,33 @@ class FlowMind {
|
|
|
98
98
|
// 1. AI Intent Understanding (if available)
|
|
99
99
|
const intent = await this.ai.understandIntent(input, enhancedContext);
|
|
100
100
|
|
|
101
|
-
// 2.
|
|
102
|
-
|
|
103
|
-
const aiLearningResult = await this.ai.analyzeLearningFeedback(input, enhancedContext);
|
|
104
|
-
const learningResult = aiLearningResult?.isLearning
|
|
105
|
-
? aiLearningResult
|
|
106
|
-
: await this.learning.detectLearning(input, enhancedContext);
|
|
107
|
-
if (learningResult) {
|
|
108
|
-
return this.formatLearningResponse(learningResult);
|
|
109
|
-
}
|
|
101
|
+
// 2. Select skill (AI-assisted if available)
|
|
102
|
+
const skill = await this.selectSkill(input, context);
|
|
110
103
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
if (sceneMatch && sceneMatch.confidence >= 0.7) {
|
|
114
|
-
return this.executeSceneWorkflow(sceneMatch, input, context);
|
|
104
|
+
if (!skill) {
|
|
105
|
+
return this.formatError('No matching skill found', input);
|
|
115
106
|
}
|
|
116
107
|
|
|
117
|
-
//
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
108
|
+
// 3. Capture explicit learning feedback after skill resolution so bindings are accurate.
|
|
109
|
+
if (this.shouldCaptureLearningInput(input, skill, enhancedContext)) {
|
|
110
|
+
const learningContext = {
|
|
111
|
+
...enhancedContext,
|
|
112
|
+
flowmind: this,
|
|
113
|
+
currentSkill: this.resolveLearningTargetSkill(skill, enhancedContext)
|
|
114
|
+
};
|
|
115
|
+
const aiLearningResult = await this.ai.analyzeLearningFeedback(input, learningContext);
|
|
116
|
+
const learningResult = aiLearningResult?.isLearning
|
|
117
|
+
? aiLearningResult
|
|
118
|
+
: await this.learning.detectLearning(input, learningContext);
|
|
119
|
+
if (learningResult) {
|
|
120
|
+
return this.formatLearningResponse(learningResult);
|
|
126
121
|
}
|
|
127
122
|
}
|
|
128
123
|
|
|
129
|
-
//
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
if (!skill) {
|
|
135
|
-
return this.formatError('No matching skill found', input);
|
|
124
|
+
// 4. Check scene mappings (with AI intent if available)
|
|
125
|
+
const sceneMatch = await this.matcher.match(input, intent);
|
|
126
|
+
if (sceneMatch && sceneMatch.confidence >= 0.7 && !context.skill) {
|
|
127
|
+
return this.executeSceneWorkflow(sceneMatch, input, enhancedContext);
|
|
136
128
|
}
|
|
137
129
|
|
|
138
130
|
// 5. Extract parameters using AI (if available)
|
|
@@ -142,7 +134,9 @@ class FlowMind {
|
|
|
142
134
|
const executeContext = {
|
|
143
135
|
...enhancedContext,
|
|
144
136
|
...extractedParams,
|
|
145
|
-
intent: intent
|
|
137
|
+
intent: intent,
|
|
138
|
+
flowmind: this,
|
|
139
|
+
currentSkill: skill.name
|
|
146
140
|
};
|
|
147
141
|
const result = await this.executeWithLearning(skill, input, executeContext);
|
|
148
142
|
|
|
@@ -200,6 +194,8 @@ class FlowMind {
|
|
|
200
194
|
// Apply learning rules to context
|
|
201
195
|
const enhancedContext = {
|
|
202
196
|
...context,
|
|
197
|
+
flowmind: context.flowmind || this,
|
|
198
|
+
currentSkill: context.currentSkill || skill.name,
|
|
203
199
|
learnings: learnings,
|
|
204
200
|
preferences: await this.learning.getPreferences(skill.name)
|
|
205
201
|
};
|
|
@@ -237,7 +233,9 @@ class FlowMind {
|
|
|
237
233
|
const stepContext = {
|
|
238
234
|
...context,
|
|
239
235
|
params: { ...step.params, ...params },
|
|
240
|
-
previousResults: results
|
|
236
|
+
previousResults: results,
|
|
237
|
+
flowmind: this,
|
|
238
|
+
currentSkill: step.skill
|
|
241
239
|
};
|
|
242
240
|
|
|
243
241
|
const result = await this.executeWithLearning(skill, input, stepContext);
|
|
@@ -270,6 +268,61 @@ class FlowMind {
|
|
|
270
268
|
};
|
|
271
269
|
}
|
|
272
270
|
|
|
271
|
+
async selectSkill(input, context = {}) {
|
|
272
|
+
if (context.skill) {
|
|
273
|
+
const explicitSkill = this.skills.get(context.skill);
|
|
274
|
+
if (!explicitSkill) {
|
|
275
|
+
return null;
|
|
276
|
+
}
|
|
277
|
+
return explicitSkill;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
const candidates = await this.skills.getCandidates(input, context);
|
|
281
|
+
|
|
282
|
+
if (candidates.length > 0) {
|
|
283
|
+
const aiSelection = await this.ai.selectSkill(input, candidates);
|
|
284
|
+
if (aiSelection && aiSelection.selectedSkill) {
|
|
285
|
+
const selected = this.skills.get(aiSelection.selectedSkill);
|
|
286
|
+
if (selected) {
|
|
287
|
+
return selected;
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
return this.skills.select(input, context);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
shouldCaptureLearningInput(input, skill, context = {}) {
|
|
296
|
+
if (skill?.name === 'learning-feedback') {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const normalized = input.trim();
|
|
301
|
+
const explicitFeedbackPatterns = [
|
|
302
|
+
/^(不对|错了|应该是|正确的是|改成|改为|重新处理|重来|下次|以后|记住|别再|不要|别这样)/i,
|
|
303
|
+
/^(wrong|should be|change to|next time|remember|don't|do not)/i
|
|
304
|
+
];
|
|
305
|
+
|
|
306
|
+
return explicitFeedbackPatterns.some((pattern) => pattern.test(normalized))
|
|
307
|
+
&& context.conversationHistory?.length > 0;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
resolveLearningTargetSkill(skill, context = {}) {
|
|
311
|
+
if (context.currentSkill) {
|
|
312
|
+
return context.currentSkill;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
if (skill && skill.name !== 'learning-feedback') {
|
|
316
|
+
return skill.name;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const previousSkill = [...this.conversationHistory]
|
|
320
|
+
.reverse()
|
|
321
|
+
.find((entry) => entry.skill && entry.skill !== 'learning-feedback');
|
|
322
|
+
|
|
323
|
+
return previousSkill?.skill || 'global';
|
|
324
|
+
}
|
|
325
|
+
|
|
273
326
|
/**
|
|
274
327
|
* Format result
|
|
275
328
|
*/
|
package/core/learning-engine.js
CHANGED
|
@@ -32,7 +32,7 @@ class LearningEngine {
|
|
|
32
32
|
constructor(config, honorEngine = null) {
|
|
33
33
|
this.config = config;
|
|
34
34
|
this.honorEngine = honorEngine;
|
|
35
|
-
this.learningPath =
|
|
35
|
+
this.learningPath = null;
|
|
36
36
|
this.writeQueue = new WriteQueue();
|
|
37
37
|
this.records = {};
|
|
38
38
|
this.skillBindings = {};
|
|
@@ -44,6 +44,11 @@ class LearningEngine {
|
|
|
44
44
|
* Initialize learning engine
|
|
45
45
|
*/
|
|
46
46
|
async init() {
|
|
47
|
+
this.learningPath = this.config.get(
|
|
48
|
+
'learning.storagePath',
|
|
49
|
+
path.join(process.env.FLOWMIND_HOME || process.env.HOME || process.env.USERPROFILE || '', '.flowmind', 'learning')
|
|
50
|
+
);
|
|
51
|
+
|
|
47
52
|
// Ensure directories exist
|
|
48
53
|
await fs.ensureDir(this.expandPath(this.learningPath));
|
|
49
54
|
await fs.ensureDir(path.join(this.expandPath(this.learningPath), 'records'));
|
|
@@ -287,7 +292,7 @@ class LearningEngine {
|
|
|
287
292
|
* Get learning rules for a skill
|
|
288
293
|
*/
|
|
289
294
|
async getSkillLearnings(skillName) {
|
|
290
|
-
const bindings = this.skillBindings[skillName] || { records: [], rules: [] };
|
|
295
|
+
const bindings = this.skillBindings.bindings?.[skillName] || { records: [], rules: [] };
|
|
291
296
|
return bindings.rules || [];
|
|
292
297
|
}
|
|
293
298
|
|
|
@@ -372,15 +377,17 @@ class LearningEngine {
|
|
|
372
377
|
* Update skill bindings
|
|
373
378
|
*/
|
|
374
379
|
async updateSkillBindings(record) {
|
|
375
|
-
|
|
376
|
-
|
|
380
|
+
this.skillBindings.bindings = this.skillBindings.bindings || {};
|
|
381
|
+
|
|
382
|
+
if (!this.skillBindings.bindings[record.skill]) {
|
|
383
|
+
this.skillBindings.bindings[record.skill] = {
|
|
377
384
|
learningCount: 0,
|
|
378
385
|
records: [],
|
|
379
386
|
rules: []
|
|
380
387
|
};
|
|
381
388
|
}
|
|
382
389
|
|
|
383
|
-
const binding = this.skillBindings[record.skill];
|
|
390
|
+
const binding = this.skillBindings.bindings[record.skill];
|
|
384
391
|
binding.learningCount++;
|
|
385
392
|
binding.lastLearning = record.timestamp;
|
|
386
393
|
binding.records.push({
|
|
@@ -402,6 +409,7 @@ class LearningEngine {
|
|
|
402
409
|
|
|
403
410
|
if (await fs.pathExists(bindingsPath)) {
|
|
404
411
|
this.skillBindings = await fs.readJson(bindingsPath);
|
|
412
|
+
this.skillBindings.bindings = this.skillBindings.bindings || {};
|
|
405
413
|
} else {
|
|
406
414
|
this.skillBindings = { version: '1.0', bindings: {} };
|
|
407
415
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "flowmind",
|
|
3
|
-
"version": "1.4.
|
|
3
|
+
"version": "1.4.5",
|
|
4
4
|
"description": "The AI Agent That Learns How You Work - Stop repeating yourself, FlowMind learns your workflows and applies them automatically.",
|
|
5
5
|
"main": "core/index.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"flowmind": "./bin/flowmind.js",
|
|
8
|
+
"flowmind-codex": "./bin/flowmind-codex.js",
|
|
8
9
|
"flowmind-mcp": "./mcp/server.js"
|
|
9
10
|
},
|
|
10
11
|
"scripts": {
|
|
@@ -50,7 +50,7 @@ module.exports = {
|
|
|
50
50
|
type: 'learning',
|
|
51
51
|
skill: 'learning-feedback',
|
|
52
52
|
learningType: 'preference',
|
|
53
|
-
message: `Recorded preference: ${preference.
|
|
53
|
+
message: `Recorded preference: ${preference.preferenceType}`,
|
|
54
54
|
data: record,
|
|
55
55
|
input,
|
|
56
56
|
timestamp: new Date().toISOString()
|
|
@@ -65,7 +65,7 @@ module.exports = {
|
|
|
65
65
|
type: 'learning',
|
|
66
66
|
skill: 'learning-feedback',
|
|
67
67
|
learningType: 'scene_mapping',
|
|
68
|
-
message:
|
|
68
|
+
message: 'Recorded scene mapping for future matching',
|
|
69
69
|
data: record,
|
|
70
70
|
input,
|
|
71
71
|
timestamp: new Date().toISOString()
|
|
@@ -24,34 +24,23 @@ module.exports = {
|
|
|
24
24
|
};
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
message: `Trace query for: ${params.traceId}`,
|
|
33
|
-
data: {
|
|
34
|
-
action: 'trace',
|
|
35
|
-
traceId: params.traceId,
|
|
36
|
-
query: `* and "${params.traceId}"`,
|
|
37
|
-
timeRange: params.timeRange || 'last 1 hour',
|
|
38
|
-
endpoint: params.endpoint || 'cn-shenzhen.log.aliyuncs.com'
|
|
39
|
-
},
|
|
40
|
-
input,
|
|
41
|
-
timestamp: new Date().toISOString()
|
|
42
|
-
};
|
|
43
|
-
}
|
|
27
|
+
const queryInput = buildLogQueryInput(params);
|
|
28
|
+
const queryParams = typeof logService.buildQueryParams === 'function'
|
|
29
|
+
? logService.buildQueryParams(queryInput)
|
|
30
|
+
: queryInput;
|
|
31
|
+
const execution = await logService.queryLogs(queryParams);
|
|
44
32
|
|
|
45
33
|
return {
|
|
46
34
|
type: 'result',
|
|
47
35
|
skill: 'log-audit',
|
|
48
|
-
message:
|
|
36
|
+
message: params.traceId
|
|
37
|
+
? `Executed trace query for: ${params.traceId}`
|
|
38
|
+
: `Executed log query: ${params.service || 'all services'}, ${params.level || 'all levels'}, ${params.timeRange || 'last 1 hour'}`,
|
|
49
39
|
data: {
|
|
50
|
-
action: 'query',
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
limit: params.limit || 100
|
|
40
|
+
action: params.traceId ? 'trace' : 'query',
|
|
41
|
+
provider: logService.providerName,
|
|
42
|
+
queryParams,
|
|
43
|
+
execution
|
|
55
44
|
},
|
|
56
45
|
input,
|
|
57
46
|
timestamp: new Date().toISOString()
|
|
@@ -86,3 +75,15 @@ function buildSLSQuery(params) {
|
|
|
86
75
|
if (params.keyword) parts.push(`"${params.keyword}"`);
|
|
87
76
|
return parts.length > 0 ? parts.join(' and ') : '*';
|
|
88
77
|
}
|
|
78
|
+
|
|
79
|
+
function buildLogQueryInput(params) {
|
|
80
|
+
return {
|
|
81
|
+
project: params.project,
|
|
82
|
+
logstore: params.logstore,
|
|
83
|
+
env: params.env,
|
|
84
|
+
query: params.traceId ? `* and "${params.traceId}"` : buildSLSQuery(params),
|
|
85
|
+
from: params.from,
|
|
86
|
+
to: params.to,
|
|
87
|
+
line: params.limit || 100
|
|
88
|
+
};
|
|
89
|
+
}
|
|
@@ -14,12 +14,21 @@ module.exports = {
|
|
|
14
14
|
const params = parseResourceParams(input);
|
|
15
15
|
|
|
16
16
|
if (params.action === 'list') {
|
|
17
|
-
const components = registry ? registry.getAll() : [];
|
|
17
|
+
const components = registry?.getAll ? registry.getAll() : [];
|
|
18
18
|
return {
|
|
19
19
|
type: 'result',
|
|
20
20
|
skill: 'resource-bind',
|
|
21
21
|
message: `Found ${components.length} configured component(s)`,
|
|
22
|
-
data: {
|
|
22
|
+
data: {
|
|
23
|
+
components: components.map(c => ({
|
|
24
|
+
name: c.name,
|
|
25
|
+
type: c.type,
|
|
26
|
+
provider: c.provider,
|
|
27
|
+
active: c.active,
|
|
28
|
+
initialized: c.initialized,
|
|
29
|
+
mcpServer: c.mcpServer
|
|
30
|
+
}))
|
|
31
|
+
},
|
|
23
32
|
input,
|
|
24
33
|
timestamp: new Date().toISOString()
|
|
25
34
|
};
|
package/tui/app.jsx
CHANGED
|
@@ -69,8 +69,8 @@ function ChatPanel({ onSubmit, isProcessing, onExit, focused }) {
|
|
|
69
69
|
const displayHistory = history.slice(-20);
|
|
70
70
|
|
|
71
71
|
return (
|
|
72
|
-
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: 'green', paddingX: 1 },
|
|
73
|
-
React.createElement(Text, { bold: true, color: 'green' }, 'Command Input'),
|
|
72
|
+
React.createElement(Box, { flexDirection: 'column', borderStyle: 'single', borderColor: focused ? 'green' : 'gray', paddingX: 1 },
|
|
73
|
+
React.createElement(Text, { bold: true, color: focused ? 'green' : 'gray' }, focused ? 'Command Input [Focused]' : 'Command Input'),
|
|
74
74
|
React.createElement(Box, { flexDirection: 'column', marginTop: 1, minHeight: 6 },
|
|
75
75
|
displayHistory.length === 0 && React.createElement(Text, { color: 'gray' }, 'Type a command to get started. Type "exit" to quit.'),
|
|
76
76
|
displayHistory.map((msg, i) =>
|
|
@@ -94,8 +94,15 @@ function ChatPanel({ onSubmit, isProcessing, onExit, focused }) {
|
|
|
94
94
|
' Processing...'
|
|
95
95
|
)
|
|
96
96
|
: React.createElement(Box, null,
|
|
97
|
-
React.createElement(Text, { color: 'green', bold: true }, '> '),
|
|
98
|
-
React.createElement(TextInput, {
|
|
97
|
+
React.createElement(Text, { color: focused ? 'green' : 'gray', bold: true }, '> '),
|
|
98
|
+
React.createElement(TextInput, {
|
|
99
|
+
value: input,
|
|
100
|
+
onChange: setInput,
|
|
101
|
+
onSubmit: handleSubmit,
|
|
102
|
+
focus: focused,
|
|
103
|
+
showCursor: focused,
|
|
104
|
+
placeholder: focused ? 'Ask FlowMind to do something...' : 'Press Tab to focus input'
|
|
105
|
+
})
|
|
99
106
|
)
|
|
100
107
|
)
|
|
101
108
|
)
|
|
@@ -36,7 +36,8 @@ function Sidebar({ flowmind, width, onSkillSelect, focused }) {
|
|
|
36
36
|
const progressBar = '\u2588'.repeat(filled) + '\u2591'.repeat(barWidth - filled);
|
|
37
37
|
|
|
38
38
|
return (
|
|
39
|
-
React.createElement(Box, { flexDirection: 'column', width: width, borderStyle: 'single', borderColor: 'cyan', paddingX: 1 },
|
|
39
|
+
React.createElement(Box, { flexDirection: 'column', width: width, borderStyle: 'single', borderColor: focused ? 'cyan' : 'gray', paddingX: 1 },
|
|
40
|
+
React.createElement(Text, { color: focused ? 'cyan' : 'gray' }, focused ? 'Sidebar [Focused]' : 'Sidebar'),
|
|
40
41
|
React.createElement(DragonTotem, { honorData: honorData, compact: true }),
|
|
41
42
|
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
42
43
|
React.createElement(Text, { bold: true, color: 'cyan' }, 'Progress'),
|
|
@@ -52,12 +53,16 @@ function Sidebar({ flowmind, width, onSkillSelect, focused }) {
|
|
|
52
53
|
const category = skill.category || 'general';
|
|
53
54
|
const prefix = isSelected ? '\u25B6 ' : ' ';
|
|
54
55
|
return React.createElement(Text, { key: skill.name },
|
|
55
|
-
React.createElement(Text, { color: isSelected ? 'green' : 'white' }, prefix + skill.name),
|
|
56
|
+
React.createElement(Text, { color: isSelected ? (focused ? 'green' : 'yellow') : 'white' }, prefix + skill.name),
|
|
56
57
|
React.createElement(Text, { color: 'gray' }, ' [' + category + ']')
|
|
57
58
|
);
|
|
58
59
|
})
|
|
59
60
|
)
|
|
60
61
|
),
|
|
62
|
+
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
63
|
+
React.createElement(Text, { color: 'gray' }, 'Tab: switch focus'),
|
|
64
|
+
React.createElement(Text, { color: 'gray' }, 'Enter: inspect skill')
|
|
65
|
+
),
|
|
61
66
|
React.createElement(Box, { flexDirection: 'column', marginTop: 1 },
|
|
62
67
|
React.createElement(Text, { bold: true, color: 'cyan' }, 'Stats'),
|
|
63
68
|
React.createElement(Text, { color: 'gray' }, 'Skills used: ' + (honorData.stats?.skillUseCount || 0)),
|
|
@@ -3,7 +3,7 @@ const { Box, Text } = require('ink');
|
|
|
3
3
|
|
|
4
4
|
const LEVEL_NAMES = ['Egg', 'Hatchling', 'Juvenile', 'Adult', 'Elder', 'Ascended'];
|
|
5
5
|
|
|
6
|
-
function StatusBar({ flowmind }) {
|
|
6
|
+
function StatusBar({ flowmind, focusPanel }) {
|
|
7
7
|
const [aiStatus, setAiStatus] = React.useState(null);
|
|
8
8
|
const [componentStatus, setComponentStatus] = React.useState(null);
|
|
9
9
|
const [honorData, setHonorData] = React.useState(null);
|
|
@@ -41,6 +41,11 @@ function StatusBar({ flowmind }) {
|
|
|
41
41
|
React.createElement(Text, { color: 'gray' }, 'Honor: '),
|
|
42
42
|
React.createElement(Text, { color: 'yellow' }, LEVEL_NAMES[level]),
|
|
43
43
|
React.createElement(Text, { color: 'gray' }, ' (' + points + ' pts)')
|
|
44
|
+
),
|
|
45
|
+
React.createElement(Text, null,
|
|
46
|
+
React.createElement(Text, { color: 'gray' }, 'Focus: '),
|
|
47
|
+
React.createElement(Text, { color: focusPanel === 'chat' ? 'green' : 'cyan' }, focusPanel || 'chat'),
|
|
48
|
+
React.createElement(Text, { color: 'gray' }, ' | Tab switch')
|
|
44
49
|
)
|
|
45
50
|
)
|
|
46
51
|
);
|