@geminilight/mindos 0.5.70 โ 0.6.0
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/app/app/api/ask/route.ts +122 -92
- package/app/app/api/mcp/agents/route.ts +53 -2
- package/app/app/api/mcp/status/route.ts +1 -1
- package/app/app/api/skills/route.ts +10 -114
- package/app/components/ActivityBar.tsx +3 -4
- package/app/components/CreateSpaceModal.tsx +31 -6
- package/app/components/FileTree.tsx +33 -2
- package/app/components/GuideCard.tsx +197 -131
- package/app/components/HomeContent.tsx +85 -18
- package/app/components/SidebarLayout.tsx +13 -0
- package/app/components/SpaceInitToast.tsx +173 -0
- package/app/components/agents/AgentDetailContent.tsx +32 -17
- package/app/components/agents/AgentsContentPage.tsx +2 -1
- package/app/components/agents/AgentsOverviewSection.tsx +1 -14
- package/app/components/agents/agents-content-model.ts +16 -8
- package/app/components/ask/AskContent.tsx +137 -50
- package/app/components/ask/MentionPopover.tsx +16 -8
- package/app/components/ask/SlashCommandPopover.tsx +62 -0
- package/app/components/settings/KnowledgeTab.tsx +61 -0
- package/app/components/walkthrough/steps.ts +11 -6
- package/app/hooks/useMention.ts +14 -6
- package/app/hooks/useSlashCommand.ts +114 -0
- package/app/lib/actions.ts +79 -2
- package/app/lib/agent/index.ts +1 -1
- package/app/lib/agent/prompt.ts +2 -0
- package/app/lib/agent/tools.ts +106 -0
- package/app/lib/core/create-space.ts +11 -4
- package/app/lib/core/index.ts +1 -1
- package/app/lib/i18n-en.ts +51 -46
- package/app/lib/i18n-zh.ts +50 -45
- package/app/lib/mcp-agents.ts +8 -0
- package/app/lib/pdf-extract.ts +33 -0
- package/app/lib/pi-integration/extensions.ts +45 -0
- package/app/lib/pi-integration/mcporter.ts +219 -0
- package/app/lib/pi-integration/session-store.ts +62 -0
- package/app/lib/pi-integration/skills.ts +116 -0
- package/app/lib/settings.ts +1 -1
- package/app/next-env.d.ts +1 -1
- package/app/next.config.ts +1 -1
- package/app/package.json +2 -0
- package/mcp/src/index.ts +29 -0
- package/package.json +1 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
4
|
+
import type { SkillInfo } from '@/components/settings/types';
|
|
5
|
+
|
|
6
|
+
export interface SlashItem {
|
|
7
|
+
type: 'skill';
|
|
8
|
+
name: string;
|
|
9
|
+
description: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function safeFetchSkills(): Promise<SkillInfo[]> {
|
|
13
|
+
return fetch('/api/skills')
|
|
14
|
+
.then((r) => (r.ok ? r.json() : { skills: [] }))
|
|
15
|
+
.then((data) => (Array.isArray(data?.skills) ? data.skills : []))
|
|
16
|
+
.catch(() => [] as SkillInfo[]);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function useSlashCommand() {
|
|
20
|
+
const [allSkills, setAllSkills] = useState<SkillInfo[]>([]);
|
|
21
|
+
const [slashQuery, setSlashQuery] = useState<string | null>(null);
|
|
22
|
+
const [slashResults, setSlashResults] = useState<SlashItem[]>([]);
|
|
23
|
+
const [slashIndex, setSlashIndex] = useState(0);
|
|
24
|
+
const loaded = useRef(false);
|
|
25
|
+
|
|
26
|
+
const loadSkills = useCallback(async () => {
|
|
27
|
+
const skills = await safeFetchSkills();
|
|
28
|
+
setAllSkills(skills.filter((s) => s.enabled));
|
|
29
|
+
loaded.current = true;
|
|
30
|
+
}, []);
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
loadSkills();
|
|
34
|
+
const handler = () => loadSkills();
|
|
35
|
+
window.addEventListener('mindos:skills-changed', handler);
|
|
36
|
+
return () => window.removeEventListener('mindos:skills-changed', handler);
|
|
37
|
+
}, [loadSkills]);
|
|
38
|
+
|
|
39
|
+
const updateSlashFromInput = useCallback(
|
|
40
|
+
(val: string, cursorPos: number) => {
|
|
41
|
+
const before = val.slice(0, cursorPos);
|
|
42
|
+
const slashIdx = before.lastIndexOf('/');
|
|
43
|
+
|
|
44
|
+
if (slashIdx === -1) {
|
|
45
|
+
setSlashQuery(null);
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// `/` must be at line start or preceded by whitespace
|
|
50
|
+
if (slashIdx > 0 && before[slashIdx - 1] !== ' ' && before[slashIdx - 1] !== '\n') {
|
|
51
|
+
setSlashQuery(null);
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// No space in the typed query โ slash commands are single tokens
|
|
56
|
+
const query = before.slice(slashIdx + 1);
|
|
57
|
+
if (query.includes(' ')) {
|
|
58
|
+
setSlashQuery(null);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (!loaded.current) {
|
|
63
|
+
loadSkills();
|
|
64
|
+
setSlashQuery(null);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const q = query.toLowerCase();
|
|
69
|
+
const items: SlashItem[] = allSkills
|
|
70
|
+
.filter((s) => s.name.toLowerCase().includes(q) || s.description.toLowerCase().includes(q))
|
|
71
|
+
.slice(0, 20)
|
|
72
|
+
.map((s) => ({ type: 'skill', name: s.name, description: s.description }));
|
|
73
|
+
|
|
74
|
+
if (items.length === 0) {
|
|
75
|
+
setSlashQuery(null);
|
|
76
|
+
setSlashResults([]);
|
|
77
|
+
setSlashIndex(0);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
setSlashQuery(query);
|
|
82
|
+
setSlashResults(items);
|
|
83
|
+
setSlashIndex(0);
|
|
84
|
+
},
|
|
85
|
+
[allSkills, loadSkills],
|
|
86
|
+
);
|
|
87
|
+
|
|
88
|
+
const navigateSlash = useCallback(
|
|
89
|
+
(direction: 'up' | 'down') => {
|
|
90
|
+
if (slashResults.length === 0) return;
|
|
91
|
+
if (direction === 'down') {
|
|
92
|
+
setSlashIndex((i) => Math.min(i + 1, slashResults.length - 1));
|
|
93
|
+
} else {
|
|
94
|
+
setSlashIndex((i) => Math.max(i - 1, 0));
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
[slashResults.length],
|
|
98
|
+
);
|
|
99
|
+
|
|
100
|
+
const resetSlash = useCallback(() => {
|
|
101
|
+
setSlashQuery(null);
|
|
102
|
+
setSlashResults([]);
|
|
103
|
+
setSlashIndex(0);
|
|
104
|
+
}, []);
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
slashQuery,
|
|
108
|
+
slashResults,
|
|
109
|
+
slashIndex,
|
|
110
|
+
updateSlashFromInput,
|
|
111
|
+
navigateSlash,
|
|
112
|
+
resetSlash,
|
|
113
|
+
};
|
|
114
|
+
}
|
package/app/lib/actions.ts
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
'use server';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { createFile, deleteFile, deleteDirectory, convertToSpace, renameFile, renameSpace, getMindRoot, invalidateCache, collectAllFiles } from '@/lib/fs';
|
|
6
|
+
import { createSpaceFilesystem, generateReadmeTemplate } from '@/lib/core/create-space';
|
|
7
|
+
import { INSTRUCTION_TEMPLATE, cleanDirName } from '@/lib/core/space-scaffold';
|
|
5
8
|
import { revalidatePath } from 'next/cache';
|
|
6
9
|
|
|
7
10
|
export async function createFileAction(dirPath: string, fileName: string): Promise<{ success: boolean; filePath?: string; error?: string }> {
|
|
@@ -112,3 +115,77 @@ export async function createSpaceAction(
|
|
|
112
115
|
return { success: false, error: msg };
|
|
113
116
|
}
|
|
114
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Revert AI-generated space content back to scaffold templates.
|
|
121
|
+
* Called when user discards AI initialization from SpaceInitToast.
|
|
122
|
+
*/
|
|
123
|
+
export async function revertSpaceInitAction(
|
|
124
|
+
spacePath: string,
|
|
125
|
+
name: string,
|
|
126
|
+
description: string,
|
|
127
|
+
): Promise<{ success: boolean; error?: string }> {
|
|
128
|
+
try {
|
|
129
|
+
const mindRoot = getMindRoot();
|
|
130
|
+
const absDir = path.resolve(mindRoot, spacePath);
|
|
131
|
+
if (!absDir.startsWith(mindRoot)) {
|
|
132
|
+
return { success: false, error: 'Invalid path' };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const readmePath = path.join(absDir, 'README.md');
|
|
136
|
+
const instructionPath = path.join(absDir, 'INSTRUCTION.md');
|
|
137
|
+
|
|
138
|
+
const readmeContent = generateReadmeTemplate(spacePath, name, description);
|
|
139
|
+
fs.writeFileSync(readmePath, readmeContent, 'utf-8');
|
|
140
|
+
|
|
141
|
+
const dirName = cleanDirName(name);
|
|
142
|
+
fs.writeFileSync(instructionPath, INSTRUCTION_TEMPLATE(dirName), 'utf-8');
|
|
143
|
+
|
|
144
|
+
invalidateCache();
|
|
145
|
+
revalidatePath('/', 'layout');
|
|
146
|
+
return { success: true };
|
|
147
|
+
} catch (err) {
|
|
148
|
+
return { success: false, error: err instanceof Error ? err.message : 'Failed to revert' };
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const EXAMPLE_PREFIX = '๐งช_example_';
|
|
153
|
+
|
|
154
|
+
export async function scanExampleFilesAction(): Promise<{ files: string[] }> {
|
|
155
|
+
const all = collectAllFiles();
|
|
156
|
+
const examples = all.filter(f => path.basename(f).startsWith(EXAMPLE_PREFIX));
|
|
157
|
+
return { files: examples };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export async function cleanupExamplesAction(): Promise<{ success: boolean; deleted: number; error?: string }> {
|
|
161
|
+
try {
|
|
162
|
+
const { files } = await scanExampleFilesAction();
|
|
163
|
+
if (files.length === 0) return { success: true, deleted: 0 };
|
|
164
|
+
|
|
165
|
+
const root = getMindRoot();
|
|
166
|
+
for (const relPath of files) {
|
|
167
|
+
const absPath = path.resolve(root, relPath);
|
|
168
|
+
if (absPath.startsWith(root) && fs.existsSync(absPath)) {
|
|
169
|
+
fs.unlinkSync(absPath);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Clean up empty directories left behind
|
|
174
|
+
const dirs = new Set(files.map(f => path.dirname(path.resolve(root, f))));
|
|
175
|
+
const sortedDirs = [...dirs].sort((a, b) => b.length - a.length);
|
|
176
|
+
for (const dir of sortedDirs) {
|
|
177
|
+
try {
|
|
178
|
+
if (dir.startsWith(root) && dir !== root) {
|
|
179
|
+
const entries = fs.readdirSync(dir);
|
|
180
|
+
if (entries.length === 0) fs.rmdirSync(dir);
|
|
181
|
+
}
|
|
182
|
+
} catch { /* directory not empty or already removed */ }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
invalidateCache();
|
|
186
|
+
revalidatePath('/', 'layout');
|
|
187
|
+
return { success: true, deleted: files.length };
|
|
188
|
+
} catch (err) {
|
|
189
|
+
return { success: false, deleted: 0, error: err instanceof Error ? err.message : 'Failed to cleanup' };
|
|
190
|
+
}
|
|
191
|
+
}
|
package/app/lib/agent/index.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export { getModelConfig } from './model';
|
|
2
|
-
export { knowledgeBaseTools, WRITE_TOOLS, truncate } from './tools';
|
|
2
|
+
export { getRequestScopedTools, knowledgeBaseTools, WRITE_TOOLS, truncate } from './tools';
|
|
3
3
|
export { AGENT_SYSTEM_PROMPT } from './prompt';
|
|
4
4
|
export {
|
|
5
5
|
estimateTokens, estimateStringTokens, getContextLimit, needsCompact,
|
package/app/lib/agent/prompt.ts
CHANGED
|
@@ -27,6 +27,8 @@ Persona: Methodical, strictly objective, execution-oriented. Zero fluff. Never u
|
|
|
27
27
|
|
|
28
28
|
- **Auto-loaded**: Configs, instructions, and SKILL.md are already in your context. Do not search for them unless explicitly asked.
|
|
29
29
|
- **Uploaded Files**: Local files attached by the user appear in the "โ ๏ธ USER-UPLOADED FILES" section below. Use this content directly. Do NOT use tools to read/search them.
|
|
30
|
+
- **Skills**: Use the list_skills and load_skill tools to discover available skills on demand.
|
|
31
|
+
- **MCP**: The MindOS MCP server is built-in. Use list_mcp_tools and call_mcp_tool to inspect and invoke additional MCP tools configured in ~/.mindos/mcp.json.
|
|
30
32
|
|
|
31
33
|
## Output
|
|
32
34
|
|
package/app/lib/agent/tools.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from 'path';
|
|
1
2
|
import { Type, type Static } from '@sinclair/typebox';
|
|
2
3
|
import type { AgentTool, AgentToolResult } from '@mariozechner/pi-agent-core';
|
|
3
4
|
import {
|
|
@@ -6,6 +7,8 @@ import {
|
|
|
6
7
|
deleteFile, renameFile, moveFile, findBacklinks, gitLog, gitShowFile, appendCsvRow,
|
|
7
8
|
getMindRoot,
|
|
8
9
|
} from '@/lib/fs';
|
|
10
|
+
import { readSkillContentByName, scanSkillDirs } from '@/lib/pi-integration/skills';
|
|
11
|
+
import { callMcporterTool, createMcporterAgentTools, listMcporterServers, listMcporterTools } from '@/lib/pi-integration/mcporter';
|
|
9
12
|
|
|
10
13
|
// Max chars per file to avoid token overflow (~100k chars โ ~25k tokens)
|
|
11
14
|
const MAX_FILE_CHARS = 20_000;
|
|
@@ -146,6 +149,22 @@ const CsvAppendParams = Type.Object({
|
|
|
146
149
|
row: Type.Array(Type.String(), { description: 'Array of cell values for the new row' }),
|
|
147
150
|
});
|
|
148
151
|
|
|
152
|
+
const ListSkillsParams = Type.Object({});
|
|
153
|
+
|
|
154
|
+
const LoadSkillParams = Type.Object({
|
|
155
|
+
name: Type.String({ description: 'Skill name, e.g. "mindos" or "context7"' }),
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
const ListMcpToolsParams = Type.Object({
|
|
159
|
+
server: Type.Optional(Type.String({ description: 'Optional MCP server name. Omit to list discovered servers only.' })),
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const CallMcpToolParams = Type.Object({
|
|
163
|
+
server: Type.String({ description: 'MCP server name discovered via list_mcp_tools' }),
|
|
164
|
+
tool: Type.String({ description: 'Exact MCP tool name to invoke' }),
|
|
165
|
+
arguments_json: Type.Optional(Type.String({ description: 'Optional JSON object string of tool arguments. Example: {"query":"react hooks"}' })),
|
|
166
|
+
});
|
|
167
|
+
|
|
149
168
|
// โโโ Tool Definitions (AgentTool interface) โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
|
150
169
|
|
|
151
170
|
// Write-operation tool names โ used by beforeToolCall for write-protection
|
|
@@ -154,6 +173,28 @@ export const WRITE_TOOLS = new Set([
|
|
|
154
173
|
'update_section', 'edit_lines', 'delete_file', 'rename_file', 'move_file', 'append_csv',
|
|
155
174
|
]);
|
|
156
175
|
|
|
176
|
+
export async function getRequestScopedTools(): Promise<AgentTool<any>[]> {
|
|
177
|
+
try {
|
|
178
|
+
const result = await listMcporterServers();
|
|
179
|
+
const okServers = (result.servers ?? []).filter((server) => server.status === 'ok');
|
|
180
|
+
if (okServers.length === 0) return knowledgeBaseTools;
|
|
181
|
+
|
|
182
|
+
const detailedServers = await Promise.all(okServers.map(async (server) => {
|
|
183
|
+
try {
|
|
184
|
+
return await listMcporterTools(server.name);
|
|
185
|
+
} catch {
|
|
186
|
+
return server;
|
|
187
|
+
}
|
|
188
|
+
}));
|
|
189
|
+
|
|
190
|
+
const dynamicMcpTools = createMcporterAgentTools(detailedServers);
|
|
191
|
+
if (dynamicMcpTools.length === 0) return knowledgeBaseTools;
|
|
192
|
+
return [...knowledgeBaseTools, ...dynamicMcpTools];
|
|
193
|
+
} catch {
|
|
194
|
+
return knowledgeBaseTools;
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
157
198
|
export const knowledgeBaseTools: AgentTool<any>[] = [
|
|
158
199
|
{
|
|
159
200
|
name: 'list_files',
|
|
@@ -251,6 +292,71 @@ export const knowledgeBaseTools: AgentTool<any>[] = [
|
|
|
251
292
|
}),
|
|
252
293
|
},
|
|
253
294
|
|
|
295
|
+
{
|
|
296
|
+
name: 'list_skills',
|
|
297
|
+
label: 'List Skills',
|
|
298
|
+
description: 'List available MindOS skills discovered from app/data/skills, skills, {mindRoot}/.skills, and ~/.mindos/skills. Use this before load_skill when you need a skill by name.',
|
|
299
|
+
parameters: ListSkillsParams,
|
|
300
|
+
execute: safeExecute(async () => {
|
|
301
|
+
const projectRoot = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
|
|
302
|
+
const skills = scanSkillDirs({ projectRoot, mindRoot: getMindRoot() });
|
|
303
|
+
if (skills.length === 0) return textResult('No skills found.');
|
|
304
|
+
return textResult(skills.map((skill) => `- **${skill.name}** [${skill.origin}]${skill.enabled ? '' : ' (disabled)'} โ ${skill.description || 'No description'}\n Path: ${skill.path}`).join('\n'));
|
|
305
|
+
}),
|
|
306
|
+
},
|
|
307
|
+
|
|
308
|
+
{
|
|
309
|
+
name: 'load_skill',
|
|
310
|
+
label: 'Load Skill',
|
|
311
|
+
description: 'Load the full content of a specific skill by name. Use list_skills first if you do not know the exact skill name.',
|
|
312
|
+
parameters: LoadSkillParams,
|
|
313
|
+
execute: safeExecute(async (_id, params: Static<typeof LoadSkillParams>) => {
|
|
314
|
+
const projectRoot = process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
|
|
315
|
+
const content = readSkillContentByName(params.name, { projectRoot, mindRoot: getMindRoot() });
|
|
316
|
+
if (!content) return textResult(`Skill not found: ${params.name}`);
|
|
317
|
+
return textResult(truncate(content));
|
|
318
|
+
}),
|
|
319
|
+
},
|
|
320
|
+
|
|
321
|
+
{
|
|
322
|
+
name: 'list_mcp_tools',
|
|
323
|
+
label: 'List MCP Tools',
|
|
324
|
+
description: 'List MCP servers configured in ~/.mindos/mcp.json. Without `server`, lists discovered servers and their health. With `server`, lists that server\'s tools and JSON schemas.',
|
|
325
|
+
parameters: ListMcpToolsParams,
|
|
326
|
+
execute: safeExecute(async (_id, params: Static<typeof ListMcpToolsParams>) => {
|
|
327
|
+
if (!params.server) {
|
|
328
|
+
const result = await listMcporterServers();
|
|
329
|
+
if (!result.servers || result.servers.length === 0) return textResult('No external MCP servers configured. The MindOS built-in MCP server is always available.');
|
|
330
|
+
return textResult(result.servers.map((server) => `- **${server.name}** โ status: ${server.status}${server.transport ? ` | transport: ${server.transport}` : ''}${server.error ? ` | error: ${server.error}` : ''}`).join('\n'));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const server = await listMcporterTools(params.server);
|
|
334
|
+
if (!server.tools || server.tools.length === 0) {
|
|
335
|
+
return textResult(`No tools found for MCP server: ${params.server}`);
|
|
336
|
+
}
|
|
337
|
+
return textResult(server.tools.map((tool) => `## ${tool.name}\n${tool.description || 'No description'}\n\nSchema:\n${JSON.stringify(tool.inputSchema ?? {}, null, 2)}`).join('\n\n'));
|
|
338
|
+
}),
|
|
339
|
+
},
|
|
340
|
+
|
|
341
|
+
{
|
|
342
|
+
name: 'call_mcp_tool',
|
|
343
|
+
label: 'Call MCP Tool',
|
|
344
|
+
description: 'Call a specific MCP tool by server and tool name. Pass `arguments_json` as a JSON object string. Use list_mcp_tools first to discover names and schemas.',
|
|
345
|
+
parameters: CallMcpToolParams,
|
|
346
|
+
execute: safeExecute(async (_id, params: Static<typeof CallMcpToolParams>) => {
|
|
347
|
+
let parsedArgs: Record<string, unknown> = {};
|
|
348
|
+
if (params.arguments_json?.trim()) {
|
|
349
|
+
try {
|
|
350
|
+
parsedArgs = JSON.parse(params.arguments_json) as Record<string, unknown>;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
return textResult(`Invalid arguments_json. Expected a JSON object string. Error: ${formatToolError(error)}`);
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
const output = await callMcporterTool(params.server, params.tool, parsedArgs);
|
|
356
|
+
return textResult(output || '(empty MCP response)');
|
|
357
|
+
}),
|
|
358
|
+
},
|
|
359
|
+
|
|
254
360
|
{
|
|
255
361
|
name: 'web_search',
|
|
256
362
|
label: 'Web Search',
|
|
@@ -1,6 +1,16 @@
|
|
|
1
1
|
import { MindOSError, ErrorCodes } from '@/lib/errors';
|
|
2
2
|
import { createFile } from './fs-ops';
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Generate the template README.md content for a new space.
|
|
6
|
+
* Extracted so both createSpaceFilesystem and revert can produce identical content.
|
|
7
|
+
*/
|
|
8
|
+
export function generateReadmeTemplate(fullPath: string, name: string, description: string): string {
|
|
9
|
+
const cleanName = name.replace(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}\s]+/u, '') || name;
|
|
10
|
+
const desc = description.trim() || '(Describe the purpose and usage of this space.)';
|
|
11
|
+
return `# ${cleanName}\n\n${desc}\n\n## ๐ Structure\n\n\`\`\`bash\n${fullPath}/\nโโโ INSTRUCTION.md\nโโโ README.md\nโโโ (your files here)\n\`\`\`\n\n## ๐ก Usage\n\n(Add usage guidelines for this space.)\n`;
|
|
12
|
+
}
|
|
13
|
+
|
|
4
14
|
/**
|
|
5
15
|
* Create a Mind Space on disk: `{fullPath}/README.md` plus scaffold from {@link createFile} / scaffoldIfNewSpace.
|
|
6
16
|
* Caller must invalidate app file-tree cache (e.g. `invalidateCache()` in `lib/fs.ts`).
|
|
@@ -26,10 +36,7 @@ export function createSpaceFilesystem(
|
|
|
26
36
|
|
|
27
37
|
const prefix = cleanParent ? `${cleanParent}/` : '';
|
|
28
38
|
const fullPath = `${prefix}${trimmed}`;
|
|
29
|
-
|
|
30
|
-
const cleanName = trimmed.replace(/^[\p{Emoji_Presentation}\p{Extended_Pictographic}\s]+/u, '') || trimmed;
|
|
31
|
-
const desc = description.trim() || '(Describe the purpose and usage of this space.)';
|
|
32
|
-
const readmeContent = `# ${cleanName}\n\n${desc}\n\n## ๐ Structure\n\n\`\`\`bash\n${fullPath}/\nโโโ INSTRUCTION.md\nโโโ README.md\nโโโ (your files here)\n\`\`\`\n\n## ๐ก Usage\n\n(Add usage guidelines for this space.)\n`;
|
|
39
|
+
const readmeContent = generateReadmeTemplate(fullPath, trimmed, description);
|
|
33
40
|
|
|
34
41
|
createFile(mindRoot, `${fullPath}/README.md`, readmeContent);
|
|
35
42
|
return { path: fullPath };
|
package/app/lib/core/index.ts
CHANGED
|
@@ -61,7 +61,7 @@ export { findBacklinks } from './backlinks';
|
|
|
61
61
|
export { isGitRepo, gitLog, gitShowFile } from './git';
|
|
62
62
|
|
|
63
63
|
// Mind Space
|
|
64
|
-
export { createSpaceFilesystem } from './create-space';
|
|
64
|
+
export { createSpaceFilesystem, generateReadmeTemplate } from './create-space';
|
|
65
65
|
export { summarizeTopLevelSpaces } from './list-spaces';
|
|
66
66
|
export type { MindSpaceSummary } from './list-spaces';
|
|
67
67
|
|
package/app/lib/i18n-en.ts
CHANGED
|
@@ -28,6 +28,11 @@ export const en = {
|
|
|
28
28
|
aiInit: 'AI initialize content',
|
|
29
29
|
aiInitHint: 'AI will generate README and INSTRUCTION for this space',
|
|
30
30
|
aiInitNoKey: 'Configure an API key in Settings โ AI to enable',
|
|
31
|
+
aiInitGenerating: (name: string) => `Generating content for ${name}`,
|
|
32
|
+
aiInitReady: (name: string) => `${name} ready`,
|
|
33
|
+
aiInitReview: 'Review',
|
|
34
|
+
aiInitDiscard: 'Discard',
|
|
35
|
+
aiInitReverted: (name: string) => `${name} reverted to template`,
|
|
31
36
|
createSpace: 'Create',
|
|
32
37
|
cancelCreate: 'Cancel',
|
|
33
38
|
continueEditing: 'Continue editing',
|
|
@@ -53,6 +58,9 @@ export const en = {
|
|
|
53
58
|
hoursAgo: (n: number) => `${n}h ago`,
|
|
54
59
|
daysAgo: (n: number) => `${n}d ago`,
|
|
55
60
|
},
|
|
61
|
+
cleanupExamples: (n: number) => `${n} example file${n > 1 ? 's' : ''} from the template can be removed`,
|
|
62
|
+
cleanupExamplesButton: 'Clean up',
|
|
63
|
+
cleanupExamplesDone: 'Example files removed',
|
|
56
64
|
},
|
|
57
65
|
sidebar: {
|
|
58
66
|
files: 'Spaces',
|
|
@@ -97,7 +105,7 @@ export const en = {
|
|
|
97
105
|
},
|
|
98
106
|
ask: {
|
|
99
107
|
title: 'MindOS Agent',
|
|
100
|
-
placeholder: 'Ask a question...
|
|
108
|
+
placeholder: 'Ask a question... @ files, / skills',
|
|
101
109
|
emptyPrompt: 'Ask anything about your knowledge base',
|
|
102
110
|
send: 'send',
|
|
103
111
|
newlineHint: 'new line',
|
|
@@ -105,7 +113,9 @@ export const en = {
|
|
|
105
113
|
panelComposerFooter: 'Resize height',
|
|
106
114
|
panelComposerResetHint: 'Double-click to reset height',
|
|
107
115
|
panelComposerKeyboard: 'Arrow keys adjust height; Home/End min/max',
|
|
108
|
-
attachFile: '
|
|
116
|
+
attachFile: 'Context',
|
|
117
|
+
uploadedFiles: 'Uploaded',
|
|
118
|
+
skillsHint: 'skills',
|
|
109
119
|
attachCurrent: 'attach current file',
|
|
110
120
|
stopTitle: 'Stop',
|
|
111
121
|
connecting: 'Thinking with your mind...',
|
|
@@ -351,6 +361,9 @@ export const en = {
|
|
|
351
361
|
enabledUnit: (n: number) => `${n} enabled`,
|
|
352
362
|
agentCount: (n: number) => `${n} agent${n !== 1 ? 's' : ''}`,
|
|
353
363
|
runtimeActive: 'Active',
|
|
364
|
+
riskMcpStopped: 'MCP server is not running.',
|
|
365
|
+
riskDetected: (n: number) => `${n} detected agent(s) need configuration.`,
|
|
366
|
+
riskSkillsDisabled: 'All skills are disabled.',
|
|
354
367
|
},
|
|
355
368
|
mcp: {
|
|
356
369
|
title: 'MCP Connections',
|
|
@@ -384,6 +397,7 @@ export const en = {
|
|
|
384
397
|
riskMcpStopped: 'MCP server is not running.',
|
|
385
398
|
riskDetected: (n: number) => `${n} detected agent(s) need configuration.`,
|
|
386
399
|
riskNotFound: (n: number) => `${n} agent(s) not found on this machine.`,
|
|
400
|
+
riskSkillsDisabled: 'All skills are disabled.',
|
|
387
401
|
bulkReconnectFiltered: 'Reconnect all',
|
|
388
402
|
bulkRunning: 'Running reconnect...',
|
|
389
403
|
bulkSummary: (ok: number, failed: number) => `Reconnected ${ok}, failed ${failed}.`,
|
|
@@ -792,6 +806,15 @@ export const en = {
|
|
|
792
806
|
authTokenResetConfirm: 'Regenerate token? All existing MCP clients will need to update their config.',
|
|
793
807
|
authTokenMcpPort: 'MCP port',
|
|
794
808
|
restartWalkthrough: 'Restart walkthrough',
|
|
809
|
+
showHiddenFiles: 'Show hidden files',
|
|
810
|
+
showHiddenFilesHint: 'Display dot-prefixed folders (.agents, .claude, .mindos, etc.) in the file tree.',
|
|
811
|
+
cleanupExamples: 'Clean up example files',
|
|
812
|
+
cleanupExamplesHint: 'Remove all template example files (๐งช_example_*) from your knowledge base.',
|
|
813
|
+
cleanupExamplesButton: 'Clean up',
|
|
814
|
+
cleanupExamplesNone: 'No example files found',
|
|
815
|
+
cleanupExamplesConfirm: (n: number) => `Delete ${n} example file${n > 1 ? 's' : ''}? This cannot be undone.`,
|
|
816
|
+
cleanupExamplesDone: (n: number) => `Removed ${n} example file${n > 1 ? 's' : ''}`,
|
|
817
|
+
cleanupExamplesScanning: 'Scanning...',
|
|
795
818
|
},
|
|
796
819
|
sync: {
|
|
797
820
|
emptyTitle: 'Cross-device Sync',
|
|
@@ -1143,50 +1166,36 @@ export const en = {
|
|
|
1143
1166
|
welcomeLinkMCP: 'MCP Settings',
|
|
1144
1167
|
},
|
|
1145
1168
|
guide: {
|
|
1146
|
-
title: '
|
|
1169
|
+
title: 'Quick Start',
|
|
1147
1170
|
showGuide: 'Show getting started guide',
|
|
1148
1171
|
close: 'Close',
|
|
1149
1172
|
skip: 'Skip',
|
|
1150
|
-
|
|
1151
|
-
title: '
|
|
1152
|
-
cta: '
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
profile: 'Who you are, preferences, goals',
|
|
1156
|
-
notes: 'Daily capture: ideas, meetings, todos',
|
|
1157
|
-
connections: 'Your network of people',
|
|
1158
|
-
workflows: 'Reusable process SOPs',
|
|
1159
|
-
resources: 'Structured data: product lists, tool lists',
|
|
1160
|
-
projects: 'Project plans and progress',
|
|
1161
|
-
},
|
|
1162
|
-
instructionHint: 'Click INSTRUCTION.md to see how AI agents behave.',
|
|
1163
|
-
emptyDesc: 'Your knowledge base has 3 core files:',
|
|
1164
|
-
emptyFiles: {
|
|
1165
|
-
instruction: 'INSTRUCTION.md โ Rules that all AI agents follow',
|
|
1166
|
-
readme: 'README.md โ Directory index and navigation',
|
|
1167
|
-
config: 'CONFIG.json โ Machine-readable preferences',
|
|
1168
|
-
},
|
|
1169
|
-
emptyHint: 'Create your own folder structure anytime.',
|
|
1170
|
-
progress: (n: number) => `Browsed ${n}/1 file`,
|
|
1171
|
-
done: 'Done',
|
|
1173
|
+
import: {
|
|
1174
|
+
title: 'Import your files',
|
|
1175
|
+
cta: 'Import',
|
|
1176
|
+
desc: 'Upload your resume, project docs, or notes โ anything you want AI agents to know about you.',
|
|
1177
|
+
button: 'Import Files',
|
|
1172
1178
|
},
|
|
1173
1179
|
ai: {
|
|
1174
|
-
title: '
|
|
1175
|
-
cta: '
|
|
1176
|
-
|
|
1180
|
+
title: 'See AI read your content',
|
|
1181
|
+
cta: 'Try it',
|
|
1182
|
+
desc: 'Your files are in the knowledge base. Ask MindOS Agent what it learned:',
|
|
1183
|
+
prompt: 'Introduce me based on my knowledge base โ who am I and what am I working on?',
|
|
1177
1184
|
promptEmpty: 'Help me design a knowledge base folder structure that fits my needs',
|
|
1178
1185
|
},
|
|
1179
|
-
|
|
1180
|
-
title: '
|
|
1181
|
-
|
|
1182
|
-
|
|
1186
|
+
agent: {
|
|
1187
|
+
title: 'Try in another Agent',
|
|
1188
|
+
cta: 'Copy prompt',
|
|
1189
|
+
desc: 'Open Cursor, Claude Code, or any MCP-connected Agent and paste this:',
|
|
1190
|
+
copyPrompt: 'Read my MindOS knowledge base and summarize my background, then suggest what I should focus on next.',
|
|
1191
|
+
copy: 'Copy',
|
|
1192
|
+
copied: 'Copied!',
|
|
1183
1193
|
},
|
|
1184
1194
|
done: {
|
|
1185
1195
|
title: "You're all set!",
|
|
1186
1196
|
titleFinal: "You've mastered MindOS essentials!",
|
|
1187
1197
|
steps: [
|
|
1188
|
-
{ hint: 'Next: try saving an article โ', prompt: 'Help me save the key points from this article into MindOS.' },
|
|
1189
|
-
{ hint: 'Next: try using your KB in another Agent โ', prompt: 'Help me start coding based on the plan in MindOS.' },
|
|
1198
|
+
{ hint: 'Next: try saving an article into your KB โ', prompt: 'Help me save the key points from this article into MindOS.' },
|
|
1190
1199
|
{ hint: 'Next: try turning experience into a reusable SOP โ', prompt: 'Help me distill this conversation into a reusable workflow in MindOS.' },
|
|
1191
1200
|
],
|
|
1192
1201
|
},
|
|
@@ -1267,24 +1276,20 @@ prompt: "Here's my resume, read it and organize my info into MindOS.",
|
|
|
1267
1276
|
exploreCta: 'Explore what you can do โ',
|
|
1268
1277
|
steps: [
|
|
1269
1278
|
{
|
|
1270
|
-
title: '
|
|
1271
|
-
body: '
|
|
1272
|
-
},
|
|
1273
|
-
{
|
|
1274
|
-
title: 'Your Knowledge Base',
|
|
1275
|
-
body: 'Browse and organize your notes, profiles, and projects in the file panel.',
|
|
1279
|
+
title: 'Your Project Memory',
|
|
1280
|
+
body: 'Organize projects, SOPs, and preferences in Spaces. Everything is local-first and under your control.',
|
|
1276
1281
|
},
|
|
1277
1282
|
{
|
|
1278
|
-
title: '
|
|
1279
|
-
body: '
|
|
1283
|
+
title: 'AI That Already Knows You',
|
|
1284
|
+
body: 'MindOS Agent reads your entire knowledge base automatically. Ask about your projects โ no need to re-explain anything.',
|
|
1280
1285
|
},
|
|
1281
1286
|
{
|
|
1282
|
-
title: '
|
|
1283
|
-
body: '
|
|
1287
|
+
title: 'Connect Any Agent',
|
|
1288
|
+
body: 'Link Cursor, Claude Code, or Windsurf via MCP โ they all share the same project memory.',
|
|
1284
1289
|
},
|
|
1285
1290
|
{
|
|
1286
|
-
title: '
|
|
1287
|
-
body: '
|
|
1291
|
+
title: 'Echo โ Growth Compounds',
|
|
1292
|
+
body: 'About you, daily reflections, growth tracking โ MindOS helps you accumulate cognitive compound interest over time.',
|
|
1288
1293
|
},
|
|
1289
1294
|
],
|
|
1290
1295
|
},
|