@geminilight/mindos 0.6.22 → 0.6.23
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +58 -46
- package/README_zh.md +58 -46
- package/app/app/api/file/import/route.ts +0 -2
- package/app/app/api/setup/route.ts +2 -0
- package/app/components/Breadcrumb.tsx +1 -1
- package/app/components/FileTree.tsx +14 -1
- package/app/components/HomeContent.tsx +1 -1
- package/app/components/RightAskPanel.tsx +17 -10
- package/app/components/SidebarLayout.tsx +4 -2
- package/app/components/ask/AskContent.tsx +5 -5
- package/app/components/ask/FileChip.tsx +1 -1
- package/app/components/ask/MessageList.tsx +1 -1
- package/app/hooks/useAskPanel.ts +7 -3
- package/app/hooks/useFileImport.ts +1 -1
- package/app/lib/agent/tools.ts +1 -1
- package/app/lib/core/fs-ops.ts +3 -2
- package/app/lib/fs.ts +28 -11
- package/app/lib/i18n-en.ts +2 -0
- package/app/lib/i18n-zh.ts +2 -0
- package/app/lib/settings.ts +1 -1
- package/bin/cli.js +38 -20
- package/bin/commands/ask.js +101 -0
- package/bin/commands/file.js +286 -0
- package/bin/commands/space.js +167 -0
- package/bin/commands/status.js +69 -0
- package/bin/lib/command.js +156 -0
- package/mcp/dist/index.cjs +1 -1
- package/mcp/src/index.ts +1 -1
- package/package.json +1 -1
- package/skills/mindos/SKILL.md +2 -2
- package/skills/mindos-zh/SKILL.md +2 -2
package/README.md
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<a href="https://tianfuwang.tech/MindOS"><img src="https://img.shields.io/badge/Website-MindOS-0ea5e9.svg?style=for-the-badge" alt="Website"></a>
|
|
17
17
|
<a href="https://www.npmjs.com/package/@geminilight/mindos"><img src="https://img.shields.io/npm/v/@geminilight/mindos.svg?style=for-the-badge&color=f59e0b" alt="npm version"></a>
|
|
18
|
-
<a href="https://www.npmjs.com/package/@geminilight/mindos"><img src="https://img.shields.io/npm/dw/@geminilight/mindos.svg?style=for-the-badge&color=10b981" alt="npm downloads"></a>
|
|
19
18
|
<a href="#wechat"><img src="https://img.shields.io/badge/WeChat-Group-07C160.svg?style=for-the-badge&logo=wechat&logoColor=white" alt="WeChat"></a>
|
|
20
19
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-6366f1.svg?style=for-the-badge" alt="MIT License"></a>
|
|
21
20
|
</p>
|
|
@@ -32,6 +31,25 @@ MindOS is where you think, and where your AI agents act — a local-first knowle
|
|
|
32
31
|
</picture>
|
|
33
32
|
</p>
|
|
34
33
|
|
|
34
|
+
<table>
|
|
35
|
+
<tr>
|
|
36
|
+
<td width="50%"><img src="assets/images/mindos-home.png" alt="MindOS Home" /></td>
|
|
37
|
+
<td width="50%"><img src="assets/images/mindos-chat.png" alt="MindOS AI Chat" /></td>
|
|
38
|
+
</tr>
|
|
39
|
+
<tr>
|
|
40
|
+
<td align="center"><em>Home — Knowledge base overview</em></td>
|
|
41
|
+
<td align="center"><em>AI Chat — Converse with your knowledge in context</em></td>
|
|
42
|
+
</tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td width="50%"><img src="assets/images/mindos-dashboard.png" alt="MindOS Agents Dashboard" /></td>
|
|
45
|
+
<td width="50%"><img src="assets/images/mindos-echo.png" alt="MindOS Echo" /></td>
|
|
46
|
+
</tr>
|
|
47
|
+
<tr>
|
|
48
|
+
<td align="center"><em>Agents — Manage all connected AI agents</em></td>
|
|
49
|
+
<td align="center"><em>Echo — Reflect and distill cognitive growth</em></td>
|
|
50
|
+
</tr>
|
|
51
|
+
</table>
|
|
52
|
+
|
|
35
53
|
> [!IMPORTANT]
|
|
36
54
|
> **⭐ One-click install:** Send this to your Agent (Claude Code, Cursor, etc.) to set up everything automatically:
|
|
37
55
|
> ```
|
|
@@ -67,37 +85,6 @@ You express preferences but the next chat starts from zero, leaving your thinkin
|
|
|
67
85
|
|
|
68
86
|
> **Foundation:** Local-first by default — all data stays in local plain text for privacy, ownership, and speed.
|
|
69
87
|
|
|
70
|
-
## ✨ Features
|
|
71
|
-
|
|
72
|
-
**For Humans**
|
|
73
|
-
|
|
74
|
-
- **GUI Workbench**: browse, edit, search notes with unified search + AI entry (`⌘K` / `⌘/`), designed for human-AI co-creation.
|
|
75
|
-
- **Built-in Agent Assistant**: converse with the knowledge base in context; edits seamlessly capture human-curated knowledge.
|
|
76
|
-
- **Plugin Extensions**: multiple built-in renderer plugins — TODO Board, CSV Views, Wiki Graph, Timeline, Agent Inspector, and more.
|
|
77
|
-
|
|
78
|
-
**For Agents**
|
|
79
|
-
|
|
80
|
-
- **MCP Server + Skills**: stdio + HTTP dual transport, full-lineup Agent compatible (OpenClaw, Claude Code, Cursor, etc.). Zero-config access.
|
|
81
|
-
- **Structured Templates**: pre-set directory structures for Profiles, Workflows, Configurations, etc., to jumpstart personal context.
|
|
82
|
-
- **Agent-Ready Docs**: everyday notes naturally double as high-quality executable Agent commands — no format conversion needed, write and dispatch.
|
|
83
|
-
|
|
84
|
-
**Infrastructure**
|
|
85
|
-
|
|
86
|
-
- **Security**: Bearer Token auth, path sandboxing, INSTRUCTION.md write-protection, atomic writes.
|
|
87
|
-
- **Knowledge Graph**: dynamically parses and visualizes inter-file references and dependencies.
|
|
88
|
-
- **Backlinks View**: displays all files that reference the current file, helping you understand how a note fits into the knowledge network.
|
|
89
|
-
- **Git Time Machine**: Git auto-sync (commit/push/pull), records every edit by both humans and Agents. One-click rollback, cross-device sync.
|
|
90
|
-
- **Desktop App**: native macOS/Windows/Linux app with system tray, auto-start, and local process management.
|
|
91
|
-
|
|
92
|
-
<details>
|
|
93
|
-
<summary><strong>Coming Soon</strong></summary>
|
|
94
|
-
|
|
95
|
-
- [ ] ACP (Agent Communication Protocol): connect external Agents (e.g., Claude Code, Cursor) and turn the knowledge base into a multi-Agent collaboration hub
|
|
96
|
-
- [ ] Deep RAG integration: retrieval-augmented generation grounded in your knowledge base for more accurate, context-aware AI responses
|
|
97
|
-
- [ ] Agent Inspector: render Agent operation logs as a filterable timeline to audit every tool call in detail
|
|
98
|
-
|
|
99
|
-
</details>
|
|
100
|
-
|
|
101
88
|
---
|
|
102
89
|
|
|
103
90
|
## 🚀 Getting Started
|
|
@@ -150,9 +137,6 @@ mindos open
|
|
|
150
137
|
2. Upload your resume or any personal/project material.
|
|
151
138
|
3. Send this prompt: `Help me sync this information into my MindOS knowledge base.`
|
|
152
139
|
|
|
153
|
-
<p align="center">
|
|
154
|
-
<img src="assets/images/gui-sync-cv.png" alt="Sync CV Example" width="800" />
|
|
155
|
-
</p>
|
|
156
140
|
|
|
157
141
|
### 4. Make Any Agent Ready (MCP + Skills)
|
|
158
142
|
|
|
@@ -172,6 +156,37 @@ npx skills add https://github.com/GeminiLight/MindOS --skill mindos-zh -g -y #
|
|
|
172
156
|
|
|
173
157
|
> For remote access, manual JSON config, and common pitfalls, see **[docs/en/supported-agents.md](docs/en/supported-agents.md)**.
|
|
174
158
|
|
|
159
|
+
## ✨ Features
|
|
160
|
+
|
|
161
|
+
**For Humans**
|
|
162
|
+
|
|
163
|
+
- **GUI Workbench**: browse, edit, search notes with unified search + AI entry (`⌘K` / `⌘/`), designed for human-AI co-creation.
|
|
164
|
+
- **Built-in Agent Assistant**: converse with the knowledge base in context; edits seamlessly capture human-curated knowledge.
|
|
165
|
+
- **Plugin Extensions**: multiple built-in renderer plugins — TODO Board, CSV Views, Wiki Graph, Timeline, Agent Inspector, and more.
|
|
166
|
+
|
|
167
|
+
**For Agents**
|
|
168
|
+
|
|
169
|
+
- **MCP Server + Skills**: stdio + HTTP dual transport, full-lineup Agent compatible (OpenClaw, Claude Code, Cursor, etc.). Zero-config access.
|
|
170
|
+
- **Structured Templates**: pre-set directory structures for Profiles, Workflows, Configurations, etc., to jumpstart personal context.
|
|
171
|
+
- **Agent-Ready Docs**: everyday notes naturally double as high-quality executable Agent commands — no format conversion needed, write and dispatch.
|
|
172
|
+
|
|
173
|
+
**Infrastructure**
|
|
174
|
+
|
|
175
|
+
- **Security**: Bearer Token auth, path sandboxing, INSTRUCTION.md write-protection, atomic writes.
|
|
176
|
+
- **Knowledge Graph**: dynamically parses and visualizes inter-file references and dependencies.
|
|
177
|
+
- **Backlinks View**: displays all files that reference the current file, helping you understand how a note fits into the knowledge network.
|
|
178
|
+
- **Git Time Machine**: Git auto-sync (commit/push/pull), records every edit by both humans and Agents. One-click rollback, cross-device sync.
|
|
179
|
+
- **Desktop App**: native macOS/Windows/Linux app with system tray, auto-start, and local process management.
|
|
180
|
+
|
|
181
|
+
<details>
|
|
182
|
+
<summary><strong>Coming Soon</strong></summary>
|
|
183
|
+
|
|
184
|
+
- [ ] ACP (Agent Communication Protocol): connect external Agents (e.g., Claude Code, Cursor) and turn the knowledge base into a multi-Agent collaboration hub
|
|
185
|
+
- [ ] Deep RAG integration: retrieval-augmented generation grounded in your knowledge base for more accurate, context-aware AI responses
|
|
186
|
+
- [ ] Agent Inspector: render Agent operation logs as a filterable timeline to audit every tool call in detail
|
|
187
|
+
|
|
188
|
+
</details>
|
|
189
|
+
|
|
175
190
|
## ⚙️ How It Works
|
|
176
191
|
|
|
177
192
|
```mermaid
|
|
@@ -203,22 +218,21 @@ graph LR
|
|
|
203
218
|
|
|
204
219
|
| Agent | MCP | Skills |
|
|
205
220
|
|:------|:---:|:------:|
|
|
206
|
-
| MindOS Agent | ✅ | ✅ |
|
|
207
221
|
| OpenClaw | ✅ | ✅ |
|
|
208
|
-
| Claude
|
|
209
|
-
| CodeBuddy | ✅ | ✅ |
|
|
222
|
+
| Claude Code | ✅ | ✅ |
|
|
210
223
|
| Cursor | ✅ | ✅ |
|
|
211
|
-
|
|
|
212
|
-
| Cline | ✅ | ✅ |
|
|
213
|
-
| Trae | ✅ | ✅ |
|
|
224
|
+
| Codex | ✅ | ✅ |
|
|
214
225
|
| Gemini CLI | ✅ | ✅ |
|
|
215
226
|
| GitHub Copilot | ✅ | ✅ |
|
|
216
|
-
|
|
|
227
|
+
| Trae | ✅ | ✅ |
|
|
228
|
+
| CodeBuddy | ✅ | ✅ |
|
|
229
|
+
| Qoder | ✅ | ✅ |
|
|
230
|
+
| Cline | ✅ | ✅ |
|
|
231
|
+
| Windsurf | ✅ | ✅ |
|
|
217
232
|
|
|
218
233
|
---
|
|
219
234
|
|
|
220
|
-
|
|
221
|
-
<summary><strong>📁 Project Structure</strong></summary>
|
|
235
|
+
## 📁 Project Structure
|
|
222
236
|
|
|
223
237
|
```bash
|
|
224
238
|
MindOS/
|
|
@@ -236,8 +250,6 @@ MindOS/
|
|
|
236
250
|
└── mind/ # Your private knowledge base (default: ~/MindOS/mind, customizable on onboard)
|
|
237
251
|
```
|
|
238
252
|
|
|
239
|
-
</details>
|
|
240
|
-
|
|
241
253
|
## ⌨️ CLI Commands
|
|
242
254
|
|
|
243
255
|
> Full command reference: **[docs/en/cli-commands.md](docs/en/cli-commands.md)**
|
package/README_zh.md
CHANGED
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
<p align="center">
|
|
16
16
|
<a href="https://tianfuwang.tech/MindOS"><img src="https://img.shields.io/badge/Website-MindOS-0ea5e9.svg?style=for-the-badge" alt="Website"></a>
|
|
17
17
|
<a href="https://www.npmjs.com/package/@geminilight/mindos"><img src="https://img.shields.io/npm/v/@geminilight/mindos.svg?style=for-the-badge&color=f59e0b" alt="npm version"></a>
|
|
18
|
-
<a href="https://www.npmjs.com/package/@geminilight/mindos"><img src="https://img.shields.io/npm/dw/@geminilight/mindos.svg?style=for-the-badge&color=10b981" alt="npm downloads"></a>
|
|
19
18
|
<a href="#wechat"><img src="https://img.shields.io/badge/WeChat-群聊-07C160.svg?style=for-the-badge&logo=wechat&logoColor=white" alt="WeChat"></a>
|
|
20
19
|
<a href="LICENSE"><img src="https://img.shields.io/badge/License-MIT-6366f1.svg?style=for-the-badge" alt="MIT License"></a>
|
|
21
20
|
</p>
|
|
@@ -32,6 +31,25 @@ MindOS 是你思考的地方,也是 AI Agent 行动的起点——一个你和
|
|
|
32
31
|
</picture>
|
|
33
32
|
</p>
|
|
34
33
|
|
|
34
|
+
<table>
|
|
35
|
+
<tr>
|
|
36
|
+
<td width="50%"><img src="assets/images/mindos-home.png" alt="MindOS 首页" /></td>
|
|
37
|
+
<td width="50%"><img src="assets/images/mindos-chat.png" alt="MindOS AI 对话" /></td>
|
|
38
|
+
</tr>
|
|
39
|
+
<tr>
|
|
40
|
+
<td align="center"><em>首页 — 知识库概览</em></td>
|
|
41
|
+
<td align="center"><em>AI 对话 — 在上下文中与知识库对话</em></td>
|
|
42
|
+
</tr>
|
|
43
|
+
<tr>
|
|
44
|
+
<td width="50%"><img src="assets/images/mindos-dashboard.png" alt="MindOS Agent 工作台" /></td>
|
|
45
|
+
<td width="50%"><img src="assets/images/mindos-echo.png" alt="MindOS Echo" /></td>
|
|
46
|
+
</tr>
|
|
47
|
+
<tr>
|
|
48
|
+
<td align="center"><em>Agent 工作台 — 管理所有已连接的 AI Agent</em></td>
|
|
49
|
+
<td align="center"><em>Echo — 复盘与认知沉淀</em></td>
|
|
50
|
+
</tr>
|
|
51
|
+
</table>
|
|
52
|
+
|
|
35
53
|
> [!IMPORTANT]
|
|
36
54
|
> **⭐ 一键安装:** 把这句话发给你的 Agent(Claude Code、Cursor 等),自动完成全部安装:
|
|
37
55
|
> ```
|
|
@@ -67,37 +85,6 @@ Agent 记忆锁在黑箱中,推理无法审查,错误极难纠正且幻觉
|
|
|
67
85
|
|
|
68
86
|
> **底层原则:** 默认本地优先,全部数据以本地纯文本保存,兼顾隐私、主权与性能。
|
|
69
87
|
|
|
70
|
-
## ✨ 功能特性
|
|
71
|
-
|
|
72
|
-
**人类侧**
|
|
73
|
-
|
|
74
|
-
- **GUI 工作台**:浏览、编辑、搜索笔记,统一搜索 + AI 入口(`⌘K` / `⌘/`),专为人机共创设计。
|
|
75
|
-
- **内置 Agent 助手**:在上下文中与知识库对话,编辑无缝沉淀为可管理知识。
|
|
76
|
-
- **插件扩展**:多种内置渲染器插件——TODO Board、CSV Views、Wiki Graph、Timeline、Agent Inspector 等。
|
|
77
|
-
|
|
78
|
-
**Agent 侧**
|
|
79
|
-
|
|
80
|
-
- **MCP Server + Skills**:stdio + HTTP 双传输,全阵容 Agent 兼容(OpenClaw, Claude Code, Cursor 等),零配置接入。
|
|
81
|
-
- **结构化模板**:预置 Profile、Workflows、Configurations 等目录骨架,快速冷启动个人 Context。
|
|
82
|
-
- **笔记即指令**:日常笔记天然就是 Agent 可直接执行的高质量指令——无需额外格式转换,写下即可调度。
|
|
83
|
-
|
|
84
|
-
**基础设施**
|
|
85
|
-
|
|
86
|
-
- **安全防线**:Bearer Token 认证、路径沙箱、INSTRUCTION.md 写保护、原子写入。
|
|
87
|
-
- **知识图谱**:动态解析并可视化文件间的引用与依赖关系。
|
|
88
|
-
- **反向链接视图**:展示所有引用当前文件的反向链接,理解笔记在知识网络中的位置。
|
|
89
|
-
- **Git 时光机**:Git 自动同步(commit/push/pull),记录人类与 Agent 的每次编辑历史,一键回滚,跨设备同步。
|
|
90
|
-
- **桌面客户端**:原生 macOS/Windows/Linux 应用,系统托盘、开机自启、本地进程管理。
|
|
91
|
-
|
|
92
|
-
<details>
|
|
93
|
-
<summary><strong>即将到来</strong></summary>
|
|
94
|
-
|
|
95
|
-
- [ ] ACP(Agent Communication Protocol):连接外部 Agent(如 Claude Code、Cursor),让知识库成为多 Agent 协作的中枢
|
|
96
|
-
- [ ] RAG 深度集成:基于知识库内容的检索增强生成,让 AI 回答更精准、更有上下文
|
|
97
|
-
- [ ] Agent 审计面板(Agent Inspector):将 Agent 操作日志渲染为可筛选的时间线,审查每次工具调用的详情
|
|
98
|
-
|
|
99
|
-
</details>
|
|
100
|
-
|
|
101
88
|
---
|
|
102
89
|
|
|
103
90
|
## 🚀 快速开始
|
|
@@ -150,9 +137,6 @@ mindos open
|
|
|
150
137
|
2. 上传你的简历或任意个人/项目资料。
|
|
151
138
|
3. 发送指令:`帮我把这些信息同步到我的 MindOS 知识库。`
|
|
152
139
|
|
|
153
|
-
<p align="center">
|
|
154
|
-
<img src="assets/images/gui-sync-cv.png" alt="同步简历示例" width="800" />
|
|
155
|
-
</p>
|
|
156
140
|
|
|
157
141
|
### 4. 让任意 Agent 可用(MCP + Skills)
|
|
158
142
|
|
|
@@ -172,6 +156,37 @@ npx skills add https://github.com/GeminiLight/MindOS --skill mindos-zh -g -y #
|
|
|
172
156
|
|
|
173
157
|
> 远程配置、手动 JSON 片段、常见误区等详见 **[docs/zh/supported-agents.md](docs/zh/supported-agents.md)**。
|
|
174
158
|
|
|
159
|
+
## ✨ 功能特性
|
|
160
|
+
|
|
161
|
+
**人类侧**
|
|
162
|
+
|
|
163
|
+
- **GUI 工作台**:浏览、编辑、搜索笔记,统一搜索 + AI 入口(`⌘K` / `⌘/`),专为人机共创设计。
|
|
164
|
+
- **内置 Agent 助手**:在上下文中与知识库对话,编辑无缝沉淀为可管理知识。
|
|
165
|
+
- **插件扩展**:多种内置渲染器插件——TODO Board、CSV Views、Wiki Graph、Timeline、Agent Inspector 等。
|
|
166
|
+
|
|
167
|
+
**Agent 侧**
|
|
168
|
+
|
|
169
|
+
- **MCP Server + Skills**:stdio + HTTP 双传输,全阵容 Agent 兼容(OpenClaw, Claude Code, Cursor 等),零配置接入。
|
|
170
|
+
- **结构化模板**:预置 Profile、Workflows、Configurations 等目录骨架,快速冷启动个人 Context。
|
|
171
|
+
- **笔记即指令**:日常笔记天然就是 Agent 可直接执行的高质量指令——无需额外格式转换,写下即可调度。
|
|
172
|
+
|
|
173
|
+
**基础设施**
|
|
174
|
+
|
|
175
|
+
- **安全防线**:Bearer Token 认证、路径沙箱、INSTRUCTION.md 写保护、原子写入。
|
|
176
|
+
- **知识图谱**:动态解析并可视化文件间的引用与依赖关系。
|
|
177
|
+
- **反向链接视图**:展示所有引用当前文件的反向链接,理解笔记在知识网络中的位置。
|
|
178
|
+
- **Git 时光机**:Git 自动同步(commit/push/pull),记录人类与 Agent 的每次编辑历史,一键回滚,跨设备同步。
|
|
179
|
+
- **桌面客户端**:原生 macOS/Windows/Linux 应用,系统托盘、开机自启、本地进程管理。
|
|
180
|
+
|
|
181
|
+
<details>
|
|
182
|
+
<summary><strong>即将到来</strong></summary>
|
|
183
|
+
|
|
184
|
+
- [ ] ACP(Agent Communication Protocol):连接外部 Agent(如 Claude Code、Cursor),让知识库成为多 Agent 协作的中枢
|
|
185
|
+
- [ ] RAG 深度集成:基于知识库内容的检索增强生成,让 AI 回答更精准、更有上下文
|
|
186
|
+
- [ ] Agent 审计面板(Agent Inspector):将 Agent 操作日志渲染为可筛选的时间线,审查每次工具调用的详情
|
|
187
|
+
|
|
188
|
+
</details>
|
|
189
|
+
|
|
175
190
|
## ⚙️ 运作机制
|
|
176
191
|
|
|
177
192
|
```mermaid
|
|
@@ -203,22 +218,21 @@ graph LR
|
|
|
203
218
|
|
|
204
219
|
| Agent | MCP | Skills |
|
|
205
220
|
|:------|:---:|:------:|
|
|
206
|
-
| MindOS Agent | ✅ | ✅ |
|
|
207
221
|
| OpenClaw | ✅ | ✅ |
|
|
208
|
-
| Claude
|
|
209
|
-
| CodeBuddy | ✅ | ✅ |
|
|
222
|
+
| Claude Code | ✅ | ✅ |
|
|
210
223
|
| Cursor | ✅ | ✅ |
|
|
211
|
-
|
|
|
212
|
-
| Cline | ✅ | ✅ |
|
|
213
|
-
| Trae | ✅ | ✅ |
|
|
224
|
+
| Codex | ✅ | ✅ |
|
|
214
225
|
| Gemini CLI | ✅ | ✅ |
|
|
215
226
|
| GitHub Copilot | ✅ | ✅ |
|
|
216
|
-
|
|
|
227
|
+
| Trae | ✅ | ✅ |
|
|
228
|
+
| CodeBuddy | ✅ | ✅ |
|
|
229
|
+
| Qoder | ✅ | ✅ |
|
|
230
|
+
| Cline | ✅ | ✅ |
|
|
231
|
+
| Windsurf | ✅ | ✅ |
|
|
217
232
|
|
|
218
233
|
---
|
|
219
234
|
|
|
220
|
-
|
|
221
|
-
<summary><strong>📁 项目架构</strong></summary>
|
|
235
|
+
## 📁 项目架构
|
|
222
236
|
|
|
223
237
|
```bash
|
|
224
238
|
MindOS/
|
|
@@ -236,8 +250,6 @@ MindOS/
|
|
|
236
250
|
└── my-mind/ # 你的私有知识库(默认路径,onboard 时可自定义)
|
|
237
251
|
```
|
|
238
252
|
|
|
239
|
-
</details>
|
|
240
|
-
|
|
241
253
|
## ⌨️ CLI 命令
|
|
242
254
|
|
|
243
255
|
> 完整命令参考:**[docs/zh/cli-commands.md](docs/zh/cli-commands.md)**
|
|
@@ -7,7 +7,6 @@ import { NextRequest, NextResponse } from 'next/server';
|
|
|
7
7
|
import { revalidatePath } from 'next/cache';
|
|
8
8
|
import { sanitizeFileName, convertToMarkdown } from '@/lib/core/file-convert';
|
|
9
9
|
import { resolveSafe } from '@/lib/core/security';
|
|
10
|
-
import { scaffoldIfNewSpace } from '@/lib/core/space-scaffold';
|
|
11
10
|
import { organizeAfterImport } from '@/lib/core/organize';
|
|
12
11
|
import { invalidateSearchIndex } from '@/lib/core/search';
|
|
13
12
|
import { effectiveSopRoot } from '@/lib/settings';
|
|
@@ -157,7 +156,6 @@ export async function POST(req: NextRequest) {
|
|
|
157
156
|
|
|
158
157
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
159
158
|
fs.writeFileSync(resolved, convertResult.content, 'utf-8');
|
|
160
|
-
scaffoldIfNewSpace(mindRoot, finalRel);
|
|
161
159
|
|
|
162
160
|
created.push({ original: entry.name, path: finalRel });
|
|
163
161
|
createdPaths.push(finalRel);
|
|
@@ -172,6 +172,8 @@ export async function PATCH(req: NextRequest) {
|
|
|
172
172
|
if (typeof patch.askedAI === 'boolean') updated.askedAI = patch.askedAI;
|
|
173
173
|
if (typeof patch.nextStepIndex === 'number' && patch.nextStepIndex >= 0) updated.nextStepIndex = patch.nextStepIndex;
|
|
174
174
|
if (typeof patch.active === 'boolean') updated.active = patch.active;
|
|
175
|
+
if (typeof patch.walkthroughStep === 'number' && patch.walkthroughStep >= 0) updated.walkthroughStep = patch.walkthroughStep;
|
|
176
|
+
if (typeof patch.walkthroughDismissed === 'boolean') updated.walkthroughDismissed = patch.walkthroughDismissed;
|
|
175
177
|
|
|
176
178
|
writeSettings({ ...current, guideState: updated });
|
|
177
179
|
return NextResponse.json({ ok: true, guideState: updated });
|
|
@@ -29,7 +29,7 @@ export default function Breadcrumb({ filePath }: { filePath: string }) {
|
|
|
29
29
|
<span suppressHydrationWarning>{part}</span>
|
|
30
30
|
</span>
|
|
31
31
|
) : (
|
|
32
|
-
<Link href={href} className="hover:
|
|
32
|
+
<Link href={href} className="px-2 py-0.5 rounded-md hover:bg-muted/50 transition-colors truncate max-w-[200px]">
|
|
33
33
|
<span suppressHydrationWarning>{part}</span>
|
|
34
34
|
</Link>
|
|
35
35
|
)}
|
|
@@ -6,7 +6,7 @@ import { FileNode } from '@/lib/types';
|
|
|
6
6
|
import { encodePath } from '@/lib/utils';
|
|
7
7
|
import {
|
|
8
8
|
ChevronDown, FileText, Table, Folder, FolderOpen, Plus, Loader2,
|
|
9
|
-
Trash2, Pencil, Layers, ScrollText, FolderInput,
|
|
9
|
+
Trash2, Pencil, Layers, ScrollText, FolderInput, Copy,
|
|
10
10
|
} from 'lucide-react';
|
|
11
11
|
import { createFileAction, deleteFileAction, renameFileAction, renameSpaceAction, deleteSpaceAction, convertToSpaceAction, deleteFolderAction } from '@/lib/actions';
|
|
12
12
|
import { useLocale } from '@/lib/LocaleContext';
|
|
@@ -16,6 +16,10 @@ function notifyFilesChanged() {
|
|
|
16
16
|
window.dispatchEvent(new Event('mindos:files-changed'));
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
+
async function copyPathToClipboard(path: string) {
|
|
20
|
+
try { await navigator.clipboard.writeText(path); } catch { /* noop */ }
|
|
21
|
+
}
|
|
22
|
+
|
|
19
23
|
const SYSTEM_FILES = new Set(['INSTRUCTION.md', 'README.md']);
|
|
20
24
|
|
|
21
25
|
const HIDDEN_FILES_KEY = 'show-hidden-files';
|
|
@@ -141,6 +145,9 @@ function SpaceContextMenu({ x, y, node, onClose, onRename, onImport, onDelete }:
|
|
|
141
145
|
<FolderInput size={14} className="shrink-0" /> {t.fileTree.importFile}
|
|
142
146
|
</button>
|
|
143
147
|
)}
|
|
148
|
+
<button className={MENU_ITEM} onClick={() => { copyPathToClipboard(node.path); onClose(); }}>
|
|
149
|
+
<Copy size={14} className="shrink-0" /> {t.fileTree.copyPath}
|
|
150
|
+
</button>
|
|
144
151
|
<button className={MENU_ITEM} onClick={() => { onRename(); onClose(); }}>
|
|
145
152
|
<Pencil size={14} className="shrink-0" /> {t.fileTree.renameSpace}
|
|
146
153
|
</button>
|
|
@@ -173,6 +180,9 @@ function FolderContextMenu({ x, y, node, onClose, onRename, onDelete }: {
|
|
|
173
180
|
}}>
|
|
174
181
|
<Layers size={14} className="shrink-0 text-[var(--amber)]" /> {t.fileTree.convertToSpace}
|
|
175
182
|
</button>
|
|
183
|
+
<button className={MENU_ITEM} onClick={() => { copyPathToClipboard(node.path); onClose(); }}>
|
|
184
|
+
<Copy size={14} className="shrink-0" /> {t.fileTree.copyPath}
|
|
185
|
+
</button>
|
|
176
186
|
<button className={MENU_ITEM} onClick={() => { onRename(); onClose(); }}>
|
|
177
187
|
<Pencil size={14} className="shrink-0" /> {t.fileTree.rename}
|
|
178
188
|
</button>
|
|
@@ -643,6 +653,9 @@ function FileNodeItem({ node, depth, currentPath, onNavigate }: {
|
|
|
643
653
|
<span className="truncate leading-5" suppressHydrationWarning>{node.name}</span>
|
|
644
654
|
</button>
|
|
645
655
|
<div className="absolute right-1 top-1/2 -translate-y-1/2 hidden group-hover/file:flex items-center gap-0.5">
|
|
656
|
+
<button onClick={() => copyPathToClipboard(node.path)} className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors" title={t.fileTree.copyPath}>
|
|
657
|
+
<Copy size={12} />
|
|
658
|
+
</button>
|
|
646
659
|
<button onClick={startRename} className="p-0.5 rounded text-muted-foreground hover:text-foreground hover:bg-muted transition-colors" title={t.fileTree.rename}>
|
|
647
660
|
<Pencil size={12} />
|
|
648
661
|
</button>
|
|
@@ -159,7 +159,7 @@ function FeatureChip({ id, icon, name, entryPath, active, inactiveTitle }: {
|
|
|
159
159
|
);
|
|
160
160
|
|
|
161
161
|
if (active && entryPath) {
|
|
162
|
-
return <Link key={id} href={`/view/${encodePath(entryPath)}`}
|
|
162
|
+
return <Link key={id} href={`/view/${encodePath(entryPath)}`} className={cls}>{inner}</Link>;
|
|
163
163
|
}
|
|
164
164
|
return <span key={id} className={cls} title={inactiveTitle}>{inner}</span>;
|
|
165
165
|
}
|
|
@@ -21,11 +21,14 @@ interface RightAskPanelProps {
|
|
|
21
21
|
onWidthCommit: (w: number) => void;
|
|
22
22
|
askMode?: 'panel' | 'popup';
|
|
23
23
|
onModeSwitch?: () => void;
|
|
24
|
+
maximized?: boolean;
|
|
25
|
+
onMaximize?: () => void;
|
|
24
26
|
}
|
|
25
27
|
|
|
26
28
|
export default function RightAskPanel({
|
|
27
29
|
open, onClose, currentFile, initialMessage, onFirstMessage,
|
|
28
30
|
width, onWidthChange, onWidthCommit, askMode, onModeSwitch,
|
|
31
|
+
maximized = false, onMaximize,
|
|
29
32
|
}: RightAskPanelProps) {
|
|
30
33
|
const handleMouseDown = useResizeDrag({
|
|
31
34
|
width,
|
|
@@ -42,10 +45,11 @@ export default function RightAskPanel({
|
|
|
42
45
|
className={`
|
|
43
46
|
hidden md:flex fixed top-0 right-0 h-screen z-40
|
|
44
47
|
flex-col bg-card border-l border-border
|
|
45
|
-
transition-
|
|
48
|
+
transition-all duration-200 ease-out
|
|
46
49
|
${open ? 'translate-x-0' : 'translate-x-full pointer-events-none'}
|
|
50
|
+
${maximized ? 'border-l-0' : ''}
|
|
47
51
|
`}
|
|
48
|
-
style={{ width: `${width}px` }}
|
|
52
|
+
style={{ width: maximized ? '100vw' : `${width}px` }}
|
|
49
53
|
role="complementary"
|
|
50
54
|
aria-label="MindOS Agent panel"
|
|
51
55
|
>
|
|
@@ -61,7 +65,6 @@ export default function RightAskPanel({
|
|
|
61
65
|
</button>
|
|
62
66
|
</div>
|
|
63
67
|
}>
|
|
64
|
-
{/* Flex column + min-h-0 so MessageList flex-1 gets a bounded height (fragment children are direct flex items) */}
|
|
65
68
|
<div className="flex min-h-0 w-full flex-1 flex-col overflow-hidden">
|
|
66
69
|
<AskContent
|
|
67
70
|
visible={open}
|
|
@@ -72,17 +75,21 @@ export default function RightAskPanel({
|
|
|
72
75
|
onClose={onClose}
|
|
73
76
|
askMode={askMode}
|
|
74
77
|
onModeSwitch={onModeSwitch}
|
|
78
|
+
maximized={maximized}
|
|
79
|
+
onMaximize={onMaximize}
|
|
75
80
|
/>
|
|
76
81
|
</div>
|
|
77
82
|
</ErrorBoundary>
|
|
78
83
|
|
|
79
|
-
{/* Drag resize handle — LEFT edge */}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
84
|
+
{/* Drag resize handle — LEFT edge (hidden when maximized) */}
|
|
85
|
+
{!maximized && (
|
|
86
|
+
<div
|
|
87
|
+
className="absolute top-0 -left-[3px] w-[6px] h-full cursor-col-resize z-40 group hidden md:block"
|
|
88
|
+
onMouseDown={handleMouseDown}
|
|
89
|
+
>
|
|
90
|
+
<div className="absolute left-[2px] top-0 w-[2px] h-full opacity-0 group-hover:opacity-100 bg-[var(--amber)]/60 transition-opacity" />
|
|
91
|
+
</div>
|
|
92
|
+
)}
|
|
86
93
|
</aside>
|
|
87
94
|
);
|
|
88
95
|
}
|
|
@@ -413,6 +413,8 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
413
413
|
onWidthCommit={ap.handleAskWidthCommit}
|
|
414
414
|
askMode={ap.askMode}
|
|
415
415
|
onModeSwitch={ap.handleAskModeSwitch}
|
|
416
|
+
maximized={ap.askMaximized}
|
|
417
|
+
onMaximize={ap.toggleAskMaximized}
|
|
416
418
|
/>
|
|
417
419
|
|
|
418
420
|
<RightAgentDetailPanel
|
|
@@ -493,7 +495,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
493
495
|
|
|
494
496
|
<main
|
|
495
497
|
id="main-content"
|
|
496
|
-
className=
|
|
498
|
+
className={`min-h-screen transition-all duration-200 pt-[52px] md:pt-0 ${ap.askMaximized ? 'hidden' : ''}`}
|
|
497
499
|
onDragEnter={(e) => {
|
|
498
500
|
if (!e.dataTransfer.types.includes('Files')) return;
|
|
499
501
|
e.preventDefault();
|
|
@@ -560,7 +562,7 @@ export default function SidebarLayout({ fileTree, children }: SidebarLayoutProps
|
|
|
560
562
|
<style>{`
|
|
561
563
|
@media (min-width: 768px) {
|
|
562
564
|
:root {
|
|
563
|
-
--right-panel-width: ${ap.askPanelOpen ? ap.askPanelWidth : 0}px;
|
|
565
|
+
--right-panel-width: ${ap.askMaximized ? '100vw' : `${ap.askPanelOpen ? ap.askPanelWidth : 0}px`};
|
|
564
566
|
--right-agent-detail-width: ${agentDockOpen ? agentDetailWidth : 0}px;
|
|
565
567
|
}
|
|
566
568
|
#main-content {
|
|
@@ -601,24 +601,24 @@ export default function AskContent({ visible, currentFile, initialMessage, onFir
|
|
|
601
601
|
</span>
|
|
602
602
|
</div>
|
|
603
603
|
<div className="flex items-center gap-1">
|
|
604
|
-
<button type="button" onClick={() => setShowHistory(v => !v)} aria-pressed={showHistory} className={`p-1 rounded transition-colors ${showHistory ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted'}`} title="Session history">
|
|
604
|
+
<button type="button" onClick={() => setShowHistory(v => !v)} aria-pressed={showHistory} className={`p-1.5 rounded transition-colors ${showHistory ? 'bg-muted text-foreground' : 'text-muted-foreground hover:text-foreground hover:bg-muted'}`} title="Session history">
|
|
605
605
|
<History size={iconSize} />
|
|
606
606
|
</button>
|
|
607
|
-
<button type="button" onClick={handleResetSession} disabled={isLoading} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors disabled:opacity-40" title="New session">
|
|
607
|
+
<button type="button" onClick={handleResetSession} disabled={isLoading} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors disabled:opacity-40" title="New session">
|
|
608
608
|
<RotateCcw size={iconSize} />
|
|
609
609
|
</button>
|
|
610
610
|
{isPanel && onMaximize && (
|
|
611
|
-
<button type="button" onClick={onMaximize} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={maximized ? 'Restore panel' : 'Maximize panel'}>
|
|
611
|
+
<button type="button" onClick={onMaximize} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={maximized ? 'Restore panel' : 'Maximize panel'}>
|
|
612
612
|
{maximized ? <Minimize2 size={iconSize} /> : <Maximize2 size={iconSize} />}
|
|
613
613
|
</button>
|
|
614
614
|
)}
|
|
615
615
|
{onModeSwitch && (
|
|
616
|
-
<button type="button" onClick={onModeSwitch} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? 'Dock to side panel' : 'Open as popup'}>
|
|
616
|
+
<button type="button" onClick={onModeSwitch} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" title={askMode === 'popup' ? 'Dock to side panel' : 'Open as popup'}>
|
|
617
617
|
{askMode === 'popup' ? <PanelRight size={iconSize} /> : <AppWindow size={iconSize} />}
|
|
618
618
|
</button>
|
|
619
619
|
)}
|
|
620
620
|
{onClose && (
|
|
621
|
-
<button type="button" onClick={onClose} className="p-1 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" aria-label="Close">
|
|
621
|
+
<button type="button" onClick={onClose} className="p-1.5 rounded hover:bg-muted text-muted-foreground hover:text-foreground transition-colors" aria-label="Close">
|
|
622
622
|
<X size={isPanel ? iconSize : 15} />
|
|
623
623
|
</button>
|
|
624
624
|
)}
|
|
@@ -22,7 +22,7 @@ export default function FileChip({ path, onRemove, variant = 'kb' }: FileChipPro
|
|
|
22
22
|
type="button"
|
|
23
23
|
onClick={onRemove}
|
|
24
24
|
aria-label={`Remove ${name}`}
|
|
25
|
-
className="
|
|
25
|
+
className="p-1 -mr-1 rounded hover:bg-muted hover:text-foreground transition-colors shrink-0"
|
|
26
26
|
>
|
|
27
27
|
<X size={10} />
|
|
28
28
|
</button>
|
|
@@ -137,7 +137,7 @@ export default function MessageList({
|
|
|
137
137
|
}, [messages]);
|
|
138
138
|
|
|
139
139
|
return (
|
|
140
|
-
<div className="flex-1 overflow-y-auto px-4 py-4 space-y-4 min-h-0">
|
|
140
|
+
<div className="flex-1 overflow-y-auto overflow-x-hidden px-4 py-4 space-y-4 min-h-0">
|
|
141
141
|
{messages.length === 0 && (
|
|
142
142
|
<div className="mt-6 space-y-3">
|
|
143
143
|
<p className="text-center text-sm text-muted-foreground/60">{emptyPrompt}</p>
|
package/app/hooks/useAskPanel.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { useAskModal } from './useAskModal';
|
|
|
7
7
|
export interface AskPanelState {
|
|
8
8
|
askPanelOpen: boolean;
|
|
9
9
|
askPanelWidth: number;
|
|
10
|
+
askMaximized: boolean;
|
|
10
11
|
askMode: 'panel' | 'popup';
|
|
11
12
|
desktopAskPopupOpen: boolean;
|
|
12
13
|
askInitialMessage: string;
|
|
@@ -17,6 +18,7 @@ export interface AskPanelState {
|
|
|
17
18
|
handleAskWidthChange: (w: number) => void;
|
|
18
19
|
handleAskWidthCommit: (w: number) => void;
|
|
19
20
|
handleAskModeSwitch: () => void;
|
|
21
|
+
toggleAskMaximized: () => void;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
/**
|
|
@@ -29,6 +31,7 @@ export function useAskPanel(): AskPanelState {
|
|
|
29
31
|
const [askMode, setAskMode] = useState<'panel' | 'popup'>('panel');
|
|
30
32
|
const [desktopAskPopupOpen, setDesktopAskPopupOpen] = useState(false);
|
|
31
33
|
const [askInitialMessage, setAskInitialMessage] = useState('');
|
|
34
|
+
const [askMaximized, setAskMaximized] = useState(false);
|
|
32
35
|
const [askOpenSource, setAskOpenSource] = useState<'user' | 'guide' | 'guide-next'>('user');
|
|
33
36
|
|
|
34
37
|
const askModal = useAskModal();
|
|
@@ -82,7 +85,8 @@ export function useAskPanel(): AskPanelState {
|
|
|
82
85
|
}
|
|
83
86
|
}, [askMode]);
|
|
84
87
|
|
|
85
|
-
const closeAskPanel = useCallback(() => setAskPanelOpen(false), []);
|
|
88
|
+
const closeAskPanel = useCallback(() => { setAskPanelOpen(false); setAskMaximized(false); }, []);
|
|
89
|
+
const toggleAskMaximized = useCallback(() => setAskMaximized(v => !v), []);
|
|
86
90
|
const closeDesktopAskPopup = useCallback(() => setDesktopAskPopupOpen(false), []);
|
|
87
91
|
|
|
88
92
|
const handleAskWidthChange = useCallback((w: number) => setAskPanelWidth(w), []);
|
|
@@ -109,9 +113,9 @@ export function useAskPanel(): AskPanelState {
|
|
|
109
113
|
}, []);
|
|
110
114
|
|
|
111
115
|
return {
|
|
112
|
-
askPanelOpen, askPanelWidth, askMode, desktopAskPopupOpen,
|
|
116
|
+
askPanelOpen, askPanelWidth, askMaximized, askMode, desktopAskPopupOpen,
|
|
113
117
|
askInitialMessage, askOpenSource,
|
|
114
118
|
toggleAskPanel, closeAskPanel, closeDesktopAskPopup,
|
|
115
|
-
handleAskWidthChange, handleAskWidthCommit, handleAskModeSwitch,
|
|
119
|
+
handleAskWidthChange, handleAskWidthCommit, handleAskModeSwitch, toggleAskMaximized,
|
|
116
120
|
};
|
|
117
121
|
}
|
|
@@ -106,7 +106,7 @@ export function useFileImport() {
|
|
|
106
106
|
const merged = [...prev];
|
|
107
107
|
for (const f of newFiles) {
|
|
108
108
|
const isDup = merged.some(m =>
|
|
109
|
-
m.name === f.name && m.size === f.size
|
|
109
|
+
m.name === f.name && m.size === f.size && m.file.lastModified === f.file.lastModified
|
|
110
110
|
);
|
|
111
111
|
if (!isDup && merged.length < MAX_FILES) merged.push(f);
|
|
112
112
|
}
|
package/app/lib/agent/tools.ts
CHANGED
|
@@ -532,7 +532,7 @@ export const knowledgeBaseTools: AgentTool<any>[] = [
|
|
|
532
532
|
{
|
|
533
533
|
name: 'create_file',
|
|
534
534
|
label: 'Create File',
|
|
535
|
-
description: 'Create a new file. Only .md and .csv files are allowed. Parent directories are created automatically.',
|
|
535
|
+
description: 'Create a new file. Only .md and .csv files are allowed. Parent directories are created automatically. Does NOT create Space scaffolding (INSTRUCTION.md/README.md). Use create_space to create a Space.',
|
|
536
536
|
parameters: CreateFileParams,
|
|
537
537
|
execute: safeExecute(async (_id, params: Static<typeof CreateFileParams>) => {
|
|
538
538
|
createFile(params.path, params.content ?? '');
|
package/app/lib/core/fs-ops.ts
CHANGED
|
@@ -2,7 +2,7 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { resolveSafe, assertWithinRoot } from './security';
|
|
4
4
|
import { MindOSError, ErrorCodes } from '@/lib/errors';
|
|
5
|
-
import {
|
|
5
|
+
import { cleanDirName, INSTRUCTION_TEMPLATE, README_TEMPLATE } from './space-scaffold';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Reads the content of a file given a relative path from mindRoot.
|
|
@@ -33,6 +33,8 @@ export function writeFile(mindRoot: string, filePath: string, content: string):
|
|
|
33
33
|
/**
|
|
34
34
|
* Creates a new file. Throws if the file already exists.
|
|
35
35
|
* Creates parent directories as needed.
|
|
36
|
+
* NOTE: Does NOT auto-scaffold Space files (INSTRUCTION.md/README.md).
|
|
37
|
+
* Use createSpaceFilesystem() or convertToSpace() for explicit Space creation.
|
|
36
38
|
*/
|
|
37
39
|
export function createFile(mindRoot: string, filePath: string, initialContent = ''): void {
|
|
38
40
|
const resolved = resolveSafe(mindRoot, filePath);
|
|
@@ -41,7 +43,6 @@ export function createFile(mindRoot: string, filePath: string, initialContent =
|
|
|
41
43
|
}
|
|
42
44
|
fs.mkdirSync(path.dirname(resolved), { recursive: true });
|
|
43
45
|
fs.writeFileSync(resolved, initialContent, 'utf-8');
|
|
44
|
-
scaffoldIfNewSpace(mindRoot, filePath);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
/**
|