@geminilight/mindos 0.5.57 → 0.5.59
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 +1 -1
- package/README_zh.md +1 -1
- package/app/app/api/ask/route.ts +4 -1
- package/app/app/api/extract-pdf/route.ts +4 -2
- package/app/app/api/file/route.ts +54 -2
- package/app/app/api/health/route.ts +3 -1
- package/app/app/api/mcp/install-skill/route.ts +4 -2
- package/app/app/api/mcp/restart/route.ts +1 -1
- package/app/app/api/restart/route.ts +1 -1
- package/app/app/api/skills/route.ts +1 -1
- package/app/app/api/sync/route.ts +2 -2
- package/app/app/api/update/route.ts +1 -1
- package/app/app/api/update-check/route.ts +11 -3
- package/app/app/globals.css +4 -0
- package/app/app/layout.tsx +6 -0
- package/app/app/login/page.tsx +12 -1
- package/app/app/register-sw.tsx +4 -0
- package/app/app/view/[...path]/page.tsx +3 -3
- package/app/components/DirView.tsx +96 -9
- package/app/components/FileTree.tsx +245 -27
- package/app/components/Sidebar.tsx +1 -0
- package/app/components/SidebarLayout.tsx +1 -1
- package/app/components/ask/AskContent.tsx +56 -14
- package/app/instrumentation.ts +2 -1
- package/app/lib/actions.ts +53 -25
- package/app/lib/core/create-space.ts +36 -0
- package/app/lib/core/fs-ops.ts +78 -1
- package/app/lib/core/index.ts +8 -0
- package/app/lib/core/list-spaces.ts +58 -0
- package/app/lib/core/types.ts +7 -0
- package/app/lib/fs.ts +78 -5
- package/app/lib/i18n-en.ts +10 -0
- package/app/lib/i18n-zh.ts +10 -0
- package/app/lib/project-root.ts +13 -0
- package/app/lib/template.ts +13 -6
- package/app/next-env.d.ts +1 -1
- package/bin/lib/mcp-spawn.js +38 -1
- package/mcp/README.md +5 -2
- package/mcp/src/index.ts +80 -0
- package/package.json +1 -1
- package/scripts/setup.js +1 -1
- package/skills/mindos/SKILL.md +4 -1
- package/skills/mindos/references/README.md +11 -0
- package/skills/mindos/references/post-task-hooks.md +53 -0
- package/skills/mindos/references/preference-capture.md +41 -0
- package/skills/mindos/references/sop-template.md +74 -0
- package/skills/mindos-zh/SKILL.md +4 -1
- package/skills/mindos-zh/references/README.md +11 -0
- package/skills/mindos-zh/references/post-task-hooks.md +53 -0
- package/skills/mindos-zh/references/preference-capture.md +41 -0
- package/skills/mindos-zh/references/sop-template.md +74 -0
- package/templates/empty/CONFIG.json +7 -5
- package/templates/empty/INSTRUCTION.md +5 -5
- package/templates/empty/README.md +1 -2
- package/templates/en/CONFIG.json +7 -5
- package/templates/en/INSTRUCTION.md +5 -5
- package/templates/en/README.md +1 -2
- package/templates/en//360/237/223/232 Resources/README.md" +4 -4
- package/templates/en//360/237/223/232 Resources//360/237/247/276 Books.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 Learning Resources.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 People to Follow.csv" +1 -0
- package/templates/en//360/237/223/232 Resources//360/237/247/276 Tools.csv" +1 -0
- package/templates/en//360/237/223/235 Notes/Ideas//360/237/247/252_example_product_idea.md" +9 -12
- package/templates/en//360/237/224/204 Workflows/Configurations//360/237/247/252_example_config_update_sop.md" +2 -3
- package/templates/en//360/237/224/204 Workflows/INSTRUCTION.md" +13 -5
- package/templates/en//360/237/232/200 Projects/Products//360/237/247/252_example_product_project_brief.md" +12 -14
- package/templates/template-generation-skill.md +4 -5
- package/templates/zh/CONFIG.json +7 -5
- package/templates/zh/INSTRUCTION.md +5 -5
- package/templates/zh/README.md +1 -2
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217/README.md" +4 -4
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//342/232/231/357/270/{217 Preferences.md" → 217 /345/201/217/345/245/275.md" } +1 -1
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/216/{257 Focus.md" → 257 /350/201/232/347/204/246.md" } +1 -1
- package/templates/zh//360/237/221/244 /347/224/273/345/203/217//360/237/221/{244 Identity.md" → 244 /350/272/253/344/273/275.md" } +1 -1
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220/README.md" +4 -4
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 /344/271/246/345/215/225.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 /345/200/274/345/276/227/345/205/263/346/263/250/347/232/204/344/272/272.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 /345/255/246/344/271/240/350/265/204/346/272/220.csv" +1 -0
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 /345/267/245/345/205/267/346/270/205/345/215/225.csv" +1 -0
- package/templates/zh//360/237/223/235 /347/254/224/350/256/260//346/203/263/346/263/225//360/237/247/252_example_/344/272/247/345/223/201/346/203/263/346/263/225.md" +8 -11
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213/README.md" +1 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/210/233/344/270/232/README.md" +3 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//345/210/233/344/270/232//360/237/247/252_example_/346/257/217/345/221/250/345/210/233/345/247/213/344/272/272/350/277/220/350/220/245/350/212/202/345/245/217.md" +22 -0
- package/templates/zh//360/237/224/204 /346/265/201/347/250/213//351/205/215/347/275/256//360/237/247/252_example_/351/205/215/347/275/256/346/233/264/346/226/260/346/265/201/347/250/213.md" +1 -1
- package/templates/zh//360/237/232/200 /351/241/271/347/233/256//344/272/247/345/223/201//360/237/247/252_example_/344/272/247/345/223/201/351/241/271/347/233/256/347/256/200/346/212/245.md" +12 -14
- package/templates/empty/CONFIG.md +0 -73
- package/templates/en/CONFIG.md +0 -73
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Influencers.csv" +0 -1
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Products.csv" +0 -1
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Scholars.csv" +0 -1
- package/templates/en//360/237/223/232 Resources//360/237/247/276 AI Tools.csv" +0 -1
- package/templates/zh/CONFIG.md +0 -66
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI Inferencers.csv" +0 -1
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /344/272/247/345/223/201.csv" +0 -1
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/255/246/350/200/205/346/270/205/345/215/225.csv" +0 -1
- package/templates/zh//360/237/223/232 /350/265/204/346/272/220//360/237/247/276 AI /345/267/245/345/205/267/346/270/205/345/215/225.csv" +0 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { MindOSError, ErrorCodes } from '@/lib/errors';
|
|
2
|
+
import { createFile } from './fs-ops';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Create a Mind Space on disk: `{fullPath}/README.md` plus scaffold from {@link createFile} / scaffoldIfNewSpace.
|
|
6
|
+
* Caller must invalidate app file-tree cache (e.g. `invalidateCache()` in `lib/fs.ts`).
|
|
7
|
+
*/
|
|
8
|
+
export function createSpaceFilesystem(
|
|
9
|
+
mindRoot: string,
|
|
10
|
+
name: string,
|
|
11
|
+
description: string,
|
|
12
|
+
parentPath = '',
|
|
13
|
+
): { path: string } {
|
|
14
|
+
const trimmed = name.trim();
|
|
15
|
+
if (!trimmed) {
|
|
16
|
+
throw new MindOSError(ErrorCodes.INVALID_REQUEST, 'Space name is required', { name });
|
|
17
|
+
}
|
|
18
|
+
if (trimmed.includes('/') || trimmed.includes('\\')) {
|
|
19
|
+
throw new MindOSError(ErrorCodes.INVALID_PATH, 'Space name must not contain path separators', { name: trimmed });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const cleanParent = parentPath.replace(/\/+$/, '').trim();
|
|
23
|
+
if (cleanParent.includes('..') || cleanParent.startsWith('/') || cleanParent.includes('\\')) {
|
|
24
|
+
throw new MindOSError(ErrorCodes.INVALID_PATH, 'Invalid parent path', { parentPath });
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const prefix = cleanParent ? `${cleanParent}/` : '';
|
|
28
|
+
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`;
|
|
33
|
+
|
|
34
|
+
createFile(mindRoot, `${fullPath}/README.md`, readmeContent);
|
|
35
|
+
return { path: fullPath };
|
|
36
|
+
}
|
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 { scaffoldIfNewSpace } from './space-scaffold';
|
|
5
|
+
import { scaffoldIfNewSpace, 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.
|
|
@@ -55,6 +55,48 @@ export function deleteFile(mindRoot: string, filePath: string): void {
|
|
|
55
55
|
fs.unlinkSync(resolved);
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Recursively deletes a directory and all its contents.
|
|
60
|
+
* Throws if the path does not exist, is outside mindRoot, or is not a directory.
|
|
61
|
+
*/
|
|
62
|
+
export function deleteDirectory(mindRoot: string, dirPath: string): void {
|
|
63
|
+
const resolved = resolveSafe(mindRoot, dirPath);
|
|
64
|
+
if (!fs.existsSync(resolved)) {
|
|
65
|
+
throw new MindOSError(ErrorCodes.FILE_NOT_FOUND, `Directory not found: ${dirPath}`, { dirPath });
|
|
66
|
+
}
|
|
67
|
+
if (!fs.statSync(resolved).isDirectory()) {
|
|
68
|
+
throw new MindOSError(ErrorCodes.INVALID_PATH, `Not a directory: ${dirPath}`, { dirPath });
|
|
69
|
+
}
|
|
70
|
+
fs.rmSync(resolved, { recursive: true, force: true });
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Converts an existing directory into a Space by creating INSTRUCTION.md
|
|
75
|
+
* (and README.md if missing). Idempotent — skips files that already exist.
|
|
76
|
+
*/
|
|
77
|
+
export function convertToSpace(mindRoot: string, dirPath: string): void {
|
|
78
|
+
const resolved = resolveSafe(mindRoot, dirPath);
|
|
79
|
+
if (!fs.existsSync(resolved)) {
|
|
80
|
+
throw new MindOSError(ErrorCodes.FILE_NOT_FOUND, `Directory not found: ${dirPath}`, { dirPath });
|
|
81
|
+
}
|
|
82
|
+
if (!fs.statSync(resolved).isDirectory()) {
|
|
83
|
+
throw new MindOSError(ErrorCodes.INVALID_PATH, `Not a directory: ${dirPath}`, { dirPath });
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const dirName = path.basename(resolved);
|
|
87
|
+
const name = cleanDirName(dirName);
|
|
88
|
+
|
|
89
|
+
const instructionPath = path.join(resolved, 'INSTRUCTION.md');
|
|
90
|
+
if (!fs.existsSync(instructionPath)) {
|
|
91
|
+
fs.writeFileSync(instructionPath, INSTRUCTION_TEMPLATE(name), 'utf-8');
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const readmePath = path.join(resolved, 'README.md');
|
|
95
|
+
if (!fs.existsSync(readmePath)) {
|
|
96
|
+
fs.writeFileSync(readmePath, README_TEMPLATE(name), 'utf-8');
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
58
100
|
/**
|
|
59
101
|
* Renames a file within its current directory.
|
|
60
102
|
* newName must be a plain filename (no path separators).
|
|
@@ -79,6 +121,41 @@ export function renameFile(mindRoot: string, oldPath: string, newName: string):
|
|
|
79
121
|
return path.relative(root, newResolved);
|
|
80
122
|
}
|
|
81
123
|
|
|
124
|
+
/**
|
|
125
|
+
* Renames a directory (Mind Space) under mindRoot. `newName` must be a single segment (no `/` or `\\`).
|
|
126
|
+
* Does not rewrite links inside markdown files.
|
|
127
|
+
*/
|
|
128
|
+
export function renameSpaceDirectory(mindRoot: string, spacePath: string, newName: string): string {
|
|
129
|
+
const trimmedName = newName.trim();
|
|
130
|
+
if (!trimmedName || trimmedName.includes('/') || trimmedName.includes('\\')) {
|
|
131
|
+
throw new MindOSError(ErrorCodes.INVALID_PATH, 'Invalid space name: must not contain path separators', { newName });
|
|
132
|
+
}
|
|
133
|
+
const normalized = spacePath.replace(/\/+$/g, '').trim();
|
|
134
|
+
if (!normalized) {
|
|
135
|
+
throw new MindOSError(ErrorCodes.INVALID_PATH, 'Space path is required', { spacePath });
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const root = path.resolve(mindRoot);
|
|
139
|
+
const oldResolved = resolveSafe(mindRoot, normalized);
|
|
140
|
+
if (!fs.existsSync(oldResolved)) {
|
|
141
|
+
throw new MindOSError(ErrorCodes.FILE_NOT_FOUND, `Space not found: ${normalized}`, { spacePath: normalized });
|
|
142
|
+
}
|
|
143
|
+
if (!fs.statSync(oldResolved).isDirectory()) {
|
|
144
|
+
throw new MindOSError(ErrorCodes.INVALID_PATH, `Not a directory: ${normalized}`, { spacePath: normalized });
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const parentDir = path.dirname(oldResolved);
|
|
148
|
+
const newResolved = path.join(parentDir, trimmedName);
|
|
149
|
+
assertWithinRoot(newResolved, root);
|
|
150
|
+
|
|
151
|
+
if (fs.existsSync(newResolved)) {
|
|
152
|
+
throw new MindOSError(ErrorCodes.FILE_ALREADY_EXISTS, 'A space with that name already exists', { newName: trimmedName });
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
fs.renameSync(oldResolved, newResolved);
|
|
156
|
+
return path.relative(root, newResolved);
|
|
157
|
+
}
|
|
158
|
+
|
|
82
159
|
/**
|
|
83
160
|
* Moves a file from one path to another within mindRoot.
|
|
84
161
|
* Returns the new path and a list of files that referenced the old path.
|
package/app/lib/core/index.ts
CHANGED
|
@@ -22,7 +22,10 @@ export {
|
|
|
22
22
|
writeFile,
|
|
23
23
|
createFile,
|
|
24
24
|
deleteFile,
|
|
25
|
+
deleteDirectory,
|
|
26
|
+
convertToSpace,
|
|
25
27
|
renameFile,
|
|
28
|
+
renameSpaceDirectory,
|
|
26
29
|
moveFile,
|
|
27
30
|
getRecentlyModified,
|
|
28
31
|
} from './fs-ops';
|
|
@@ -56,3 +59,8 @@ export { findBacklinks } from './backlinks';
|
|
|
56
59
|
|
|
57
60
|
// Git
|
|
58
61
|
export { isGitRepo, gitLog, gitShowFile } from './git';
|
|
62
|
+
|
|
63
|
+
// Mind Space
|
|
64
|
+
export { createSpaceFilesystem } from './create-space';
|
|
65
|
+
export { summarizeTopLevelSpaces } from './list-spaces';
|
|
66
|
+
export type { MindSpaceSummary } from './list-spaces';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import type { FileNode } from './types';
|
|
3
|
+
import { readFile } from './fs-ops';
|
|
4
|
+
|
|
5
|
+
/** One top-level Mind Space (aligned with home page Spaces grid). */
|
|
6
|
+
export interface MindSpaceSummary {
|
|
7
|
+
/** Directory entry name as on disk (may include leading emoji). */
|
|
8
|
+
name: string;
|
|
9
|
+
/** Relative path to the space directory, forward slashes only. */
|
|
10
|
+
path: string;
|
|
11
|
+
/** Count of .md/.csv files under this space (recursive). */
|
|
12
|
+
fileCount: number;
|
|
13
|
+
/** First non-empty body line from README.md after the title, or empty. */
|
|
14
|
+
description: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function countFiles(node: FileNode): number {
|
|
18
|
+
if (node.type === 'file') return 1;
|
|
19
|
+
return (node.children ?? []).reduce((sum, c) => sum + countFiles(c), 0);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function toPosixRel(p: string): string {
|
|
23
|
+
return p.split(path.sep).join('/');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function extractDescription(mindRoot: string, dirRelPosix: string): string {
|
|
27
|
+
const readmeRel = `${dirRelPosix}/README.md`;
|
|
28
|
+
try {
|
|
29
|
+
const content = readFile(mindRoot, readmeRel);
|
|
30
|
+
const lines = content.split('\n');
|
|
31
|
+
for (const line of lines) {
|
|
32
|
+
const trimmed = line.trim();
|
|
33
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
34
|
+
return trimmed;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
/* no README */
|
|
38
|
+
}
|
|
39
|
+
return '';
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Build summaries from the **app file tree** (same nodes as home Spaces).
|
|
44
|
+
* Caller passes `getFileTree()` so ignore rules match the UI cache.
|
|
45
|
+
*/
|
|
46
|
+
export function summarizeTopLevelSpaces(mindRoot: string, tree: FileNode[]): MindSpaceSummary[] {
|
|
47
|
+
return tree
|
|
48
|
+
.filter((n) => n.type === 'directory' && !n.name.startsWith('.'))
|
|
49
|
+
.map((n) => {
|
|
50
|
+
const posix = toPosixRel(n.path);
|
|
51
|
+
return {
|
|
52
|
+
name: n.name,
|
|
53
|
+
path: posix,
|
|
54
|
+
fileCount: countFiles(n),
|
|
55
|
+
description: extractDescription(mindRoot, posix),
|
|
56
|
+
};
|
|
57
|
+
});
|
|
58
|
+
}
|
package/app/lib/core/types.ts
CHANGED
|
@@ -1,3 +1,8 @@
|
|
|
1
|
+
export interface SpacePreview {
|
|
2
|
+
instructionLines: string[];
|
|
3
|
+
readmeLines: string[];
|
|
4
|
+
}
|
|
5
|
+
|
|
1
6
|
export interface FileNode {
|
|
2
7
|
name: string;
|
|
3
8
|
path: string;
|
|
@@ -5,6 +10,8 @@ export interface FileNode {
|
|
|
5
10
|
children?: FileNode[];
|
|
6
11
|
extension?: string;
|
|
7
12
|
mtime?: number;
|
|
13
|
+
isSpace?: boolean;
|
|
14
|
+
spacePreview?: SpacePreview;
|
|
8
15
|
}
|
|
9
16
|
|
|
10
17
|
export interface SearchResult {
|
package/app/lib/fs.ts
CHANGED
|
@@ -7,7 +7,10 @@ import {
|
|
|
7
7
|
writeFile as coreWriteFile,
|
|
8
8
|
createFile as coreCreateFile,
|
|
9
9
|
deleteFile as coreDeleteFile,
|
|
10
|
+
deleteDirectory as coreDeleteDirectory,
|
|
11
|
+
convertToSpace as coreConvertToSpace,
|
|
10
12
|
renameFile as coreRenameFile,
|
|
13
|
+
renameSpaceDirectory as coreRenameSpaceDirectory,
|
|
11
14
|
moveFile as coreMoveFile,
|
|
12
15
|
readLines as coreReadLines,
|
|
13
16
|
insertLines as coreInsertLines,
|
|
@@ -21,8 +24,10 @@ import {
|
|
|
21
24
|
gitLog as coreGitLog,
|
|
22
25
|
gitShowFile as coreGitShowFile,
|
|
23
26
|
invalidateSearchIndex,
|
|
27
|
+
summarizeTopLevelSpaces,
|
|
24
28
|
} from './core';
|
|
25
|
-
import {
|
|
29
|
+
import type { MindSpaceSummary } from './core';
|
|
30
|
+
import { FileNode, SpacePreview } from './core/types';
|
|
26
31
|
import { SearchMatch } from './types';
|
|
27
32
|
import { effectiveSopRoot } from './settings';
|
|
28
33
|
|
|
@@ -77,8 +82,31 @@ function ensureCache(): FileTreeCache {
|
|
|
77
82
|
|
|
78
83
|
// ─── Internal builders ────────────────────────────────────────────────────────
|
|
79
84
|
|
|
80
|
-
|
|
81
|
-
|
|
85
|
+
const SPACE_PREVIEW_MAX_LINES = 3;
|
|
86
|
+
|
|
87
|
+
function extractBodyLines(filePath: string, maxLines: number): string[] {
|
|
88
|
+
try {
|
|
89
|
+
const content = fs.readFileSync(filePath, 'utf-8');
|
|
90
|
+
const bodyLines: string[] = [];
|
|
91
|
+
for (const line of content.split('\n')) {
|
|
92
|
+
const trimmed = line.trim();
|
|
93
|
+
if (!trimmed || trimmed.startsWith('#')) continue;
|
|
94
|
+
bodyLines.push(trimmed);
|
|
95
|
+
if (bodyLines.length >= maxLines) break;
|
|
96
|
+
}
|
|
97
|
+
return bodyLines;
|
|
98
|
+
} catch { return []; }
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function buildSpacePreview(dirAbsPath: string) {
|
|
102
|
+
return {
|
|
103
|
+
instructionLines: extractBodyLines(path.join(dirAbsPath, 'INSTRUCTION.md'), SPACE_PREVIEW_MAX_LINES),
|
|
104
|
+
readmeLines: extractBodyLines(path.join(dirAbsPath, 'README.md'), SPACE_PREVIEW_MAX_LINES),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function buildFileTree(dirPath: string, rootOverride?: string): FileNode[] {
|
|
109
|
+
const root = rootOverride ?? getMindRoot();
|
|
82
110
|
let entries: fs.Dirent[];
|
|
83
111
|
try {
|
|
84
112
|
entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
@@ -94,9 +122,15 @@ function buildFileTree(dirPath: string): FileNode[] {
|
|
|
94
122
|
|
|
95
123
|
if (entry.isDirectory()) {
|
|
96
124
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
97
|
-
const children = buildFileTree(fullPath);
|
|
125
|
+
const children = buildFileTree(fullPath, root);
|
|
98
126
|
if (children.length > 0) {
|
|
99
|
-
|
|
127
|
+
const hasInstruction = children.some(c => c.type === 'file' && c.name === 'INSTRUCTION.md');
|
|
128
|
+
const node: FileNode = { name: entry.name, path: relativePath, type: 'directory', children };
|
|
129
|
+
if (hasInstruction) {
|
|
130
|
+
node.isSpace = true;
|
|
131
|
+
node.spacePreview = buildSpacePreview(fullPath);
|
|
132
|
+
}
|
|
133
|
+
nodes.push(node);
|
|
100
134
|
}
|
|
101
135
|
} else if (entry.isFile()) {
|
|
102
136
|
const ext = path.extname(entry.name).toLowerCase();
|
|
@@ -114,6 +148,11 @@ function buildFileTree(dirPath: string): FileNode[] {
|
|
|
114
148
|
return nodes;
|
|
115
149
|
}
|
|
116
150
|
|
|
151
|
+
/** Exposed for testing only — builds a file tree from an arbitrary root path. */
|
|
152
|
+
export function buildFileTreeForTest(rootPath: string): FileNode[] {
|
|
153
|
+
return buildFileTree(rootPath, rootPath);
|
|
154
|
+
}
|
|
155
|
+
|
|
117
156
|
function buildAllFiles(dirPath: string): string[] {
|
|
118
157
|
const root = getMindRoot();
|
|
119
158
|
let entries: fs.Dirent[];
|
|
@@ -146,6 +185,20 @@ export function getFileTree(): FileNode[] {
|
|
|
146
185
|
return ensureCache().tree;
|
|
147
186
|
}
|
|
148
187
|
|
|
188
|
+
/** Top-level Mind Spaces (same cached tree as home Spaces grid). */
|
|
189
|
+
export function listMindSpaces(): MindSpaceSummary[] {
|
|
190
|
+
return summarizeTopLevelSpaces(getMindRoot(), ensureCache().tree);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Returns space preview (INSTRUCTION + README excerpts) for a directory, or null if not a space. */
|
|
194
|
+
export function getSpacePreview(dirPath: string): SpacePreview | null {
|
|
195
|
+
const root = getMindRoot();
|
|
196
|
+
const abs = path.join(root, dirPath);
|
|
197
|
+
const instructionPath = path.join(abs, 'INSTRUCTION.md');
|
|
198
|
+
if (!fs.existsSync(instructionPath)) return null;
|
|
199
|
+
return buildSpacePreview(abs);
|
|
200
|
+
}
|
|
201
|
+
|
|
149
202
|
/** Returns cached list of all file paths (relative to MIND_ROOT). */
|
|
150
203
|
export function collectAllFiles(): string[] {
|
|
151
204
|
return ensureCache().allFiles;
|
|
@@ -251,6 +304,25 @@ export function renameFile(oldPath: string, newName: string): string {
|
|
|
251
304
|
return result;
|
|
252
305
|
}
|
|
253
306
|
|
|
307
|
+
/** Renames a Space directory under MIND_ROOT. newName must be a single path segment. */
|
|
308
|
+
export function renameSpace(spacePath: string, newName: string): string {
|
|
309
|
+
const result = coreRenameSpaceDirectory(getMindRoot(), spacePath, newName);
|
|
310
|
+
invalidateCache();
|
|
311
|
+
return result;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Recursively deletes a directory under MIND_ROOT. */
|
|
315
|
+
export function deleteDirectory(dirPath: string): void {
|
|
316
|
+
coreDeleteDirectory(getMindRoot(), dirPath);
|
|
317
|
+
invalidateCache();
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Converts a regular folder into a Space by adding INSTRUCTION.md + README.md. */
|
|
321
|
+
export function convertToSpace(dirPath: string): void {
|
|
322
|
+
coreConvertToSpace(getMindRoot(), dirPath);
|
|
323
|
+
invalidateCache();
|
|
324
|
+
}
|
|
325
|
+
|
|
254
326
|
// ─── Public API: Line-level operations (delegated to @mindos/core) ───────────
|
|
255
327
|
|
|
256
328
|
export function readLines(filePath: string): string[] {
|
|
@@ -471,6 +543,7 @@ export function gitShowFile(filePath: string, commit: string): string {
|
|
|
471
543
|
|
|
472
544
|
import type { BacklinkEntry } from './core/types';
|
|
473
545
|
export type { BacklinkEntry } from './core/types';
|
|
546
|
+
export type { MindSpaceSummary } from './core';
|
|
474
547
|
|
|
475
548
|
export function findBacklinks(targetPath: string): BacklinkEntry[] {
|
|
476
549
|
return coreFindBacklinks(getMindRoot(), targetPath);
|
package/app/lib/i18n-en.ts
CHANGED
|
@@ -278,6 +278,16 @@ export const en = {
|
|
|
278
278
|
enterFileName: 'Enter a file name',
|
|
279
279
|
failed: 'Failed',
|
|
280
280
|
confirmDelete: (name: string) => `Delete "${name}"? This cannot be undone.`,
|
|
281
|
+
rules: 'Rules',
|
|
282
|
+
about: 'About',
|
|
283
|
+
viewAll: 'View all',
|
|
284
|
+
editRules: 'Edit Rules',
|
|
285
|
+
renameSpace: 'Rename Space',
|
|
286
|
+
deleteSpace: 'Delete Space',
|
|
287
|
+
confirmDeleteSpace: (name: string) => `Delete space "${name}" and all its files? This cannot be undone.`,
|
|
288
|
+
convertToSpace: 'Convert to Space',
|
|
289
|
+
deleteFolder: 'Delete Folder',
|
|
290
|
+
confirmDeleteFolder: (name: string) => `Delete folder "${name}" and all its contents? This cannot be undone.`,
|
|
281
291
|
},
|
|
282
292
|
dirView: {
|
|
283
293
|
gridView: 'Grid view',
|
package/app/lib/i18n-zh.ts
CHANGED
|
@@ -302,6 +302,16 @@ export const zh = {
|
|
|
302
302
|
enterFileName: '请输入文件名',
|
|
303
303
|
failed: '操作失败',
|
|
304
304
|
confirmDelete: (name: string) => `确定删除「${name}」?此操作不可撤销。`,
|
|
305
|
+
rules: '规则',
|
|
306
|
+
about: '说明',
|
|
307
|
+
viewAll: '查看全部',
|
|
308
|
+
editRules: '编辑规则',
|
|
309
|
+
renameSpace: '重命名空间',
|
|
310
|
+
deleteSpace: '删除空间',
|
|
311
|
+
confirmDeleteSpace: (name: string) => `删除空间「${name}」及其所有文件?此操作不可撤销。`,
|
|
312
|
+
convertToSpace: '转为空间',
|
|
313
|
+
deleteFolder: '删除文件夹',
|
|
314
|
+
confirmDeleteFolder: (name: string) => `删除文件夹「${name}」及其所有内容?此操作不可撤销。`,
|
|
305
315
|
},
|
|
306
316
|
dirView: {
|
|
307
317
|
gridView: '网格视图',
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Resolve the MindOS project root directory.
|
|
5
|
+
*
|
|
6
|
+
* In standalone mode (`node .next/standalone/server.js`), `process.cwd()` points to
|
|
7
|
+
* `.next/standalone/` — NOT the app or project root. `MINDOS_PROJECT_ROOT` is injected
|
|
8
|
+
* by Desktop ProcessManager; for CLI launches it defaults to `cwd/..` which is correct
|
|
9
|
+
* when cwd is `app/`.
|
|
10
|
+
*/
|
|
11
|
+
export function getProjectRoot(): string {
|
|
12
|
+
return process.env.MINDOS_PROJECT_ROOT || path.resolve(process.cwd(), '..');
|
|
13
|
+
}
|
package/app/lib/template.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
+
import { getProjectRoot } from './project-root';
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Recursively copy `src` to `dest`, skipping files that already exist in dest.
|
|
@@ -29,12 +30,18 @@ export function applyTemplate(template: string, destDir: string): void {
|
|
|
29
30
|
throw new Error(`Invalid template: ${template}`);
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
// templates/
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
// templates/ lives at the repo/project root (sibling of app/).
|
|
34
|
+
// In standalone mode process.cwd() is .next/standalone/ — unreliable for relative paths.
|
|
35
|
+
// MINDOS_PROJECT_ROOT is set by Desktop ProcessManager and CLI startup.
|
|
36
|
+
const projectRoot = getProjectRoot();
|
|
37
|
+
const candidates = [
|
|
38
|
+
path.join(projectRoot, 'templates', template),
|
|
39
|
+
path.resolve(process.cwd(), '..', 'templates', template),
|
|
40
|
+
path.resolve(process.cwd(), 'templates', template),
|
|
41
|
+
];
|
|
42
|
+
const templateDir = candidates.find((d) => fs.existsSync(d));
|
|
43
|
+
if (!templateDir) {
|
|
44
|
+
throw new Error(`Template "${template}" not found at ${candidates.join(', ')}`);
|
|
38
45
|
}
|
|
39
46
|
|
|
40
47
|
if (!fs.existsSync(destDir)) {
|
package/app/next-env.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="next" />
|
|
2
2
|
/// <reference types="next/image-types/global" />
|
|
3
|
-
import "./.next/types/routes.d.ts";
|
|
3
|
+
import "./.next/dev/types/routes.d.ts";
|
|
4
4
|
|
|
5
5
|
// NOTE: This file should not be edited
|
|
6
6
|
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
package/bin/lib/mcp-spawn.js
CHANGED
|
@@ -1,10 +1,46 @@
|
|
|
1
1
|
import { execSync, spawn } from 'node:child_process';
|
|
2
|
-
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
|
|
3
3
|
import { resolve } from 'node:path';
|
|
4
|
+
import { arch, platform } from 'node:os';
|
|
4
5
|
import { ROOT, CONFIG_PATH } from './constants.js';
|
|
5
6
|
import { bold, red, yellow } from './colors.js';
|
|
6
7
|
import { npmInstall } from './utils.js';
|
|
7
8
|
|
|
9
|
+
/**
|
|
10
|
+
* If mcp/node_modules was installed on a different platform (e.g. Linux CI → macOS user),
|
|
11
|
+
* native packages like esbuild will crash. Detect via stamp or @esbuild heuristics, then reinstall.
|
|
12
|
+
*/
|
|
13
|
+
function ensureMcpNativeDeps() {
|
|
14
|
+
const mcpDir = resolve(ROOT, 'mcp');
|
|
15
|
+
const nm = resolve(mcpDir, 'node_modules');
|
|
16
|
+
if (!existsSync(nm)) return;
|
|
17
|
+
|
|
18
|
+
const host = `${platform()}-${arch()}`;
|
|
19
|
+
const stamp = resolve(mcpDir, '.mindos-npm-ci-platform');
|
|
20
|
+
let needsReinstall = false;
|
|
21
|
+
|
|
22
|
+
if (existsSync(stamp)) {
|
|
23
|
+
try {
|
|
24
|
+
needsReinstall = readFileSync(stamp, 'utf-8').trim() !== host;
|
|
25
|
+
} catch { /* fall through */ }
|
|
26
|
+
} else {
|
|
27
|
+
const esbuildDir = resolve(nm, '@esbuild');
|
|
28
|
+
if (existsSync(esbuildDir)) {
|
|
29
|
+
try {
|
|
30
|
+
const names = readdirSync(esbuildDir);
|
|
31
|
+
if (names.length > 0 && !names.includes(host)) needsReinstall = true;
|
|
32
|
+
} catch { /* ignore */ }
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!needsReinstall) return;
|
|
37
|
+
|
|
38
|
+
console.log(yellow('MCP dependencies were built for another platform — reinstalling...'));
|
|
39
|
+
rmSync(nm, { recursive: true, force: true });
|
|
40
|
+
npmInstall(mcpDir, '--no-workspaces');
|
|
41
|
+
try { writeFileSync(stamp, host, 'utf-8'); } catch { /* non-fatal */ }
|
|
42
|
+
}
|
|
43
|
+
|
|
8
44
|
export function spawnMcp(verbose = false) {
|
|
9
45
|
const mcpPort = process.env.MINDOS_MCP_PORT || '8781';
|
|
10
46
|
const webPort = process.env.MINDOS_WEB_PORT || '3456';
|
|
@@ -14,6 +50,7 @@ export function spawnMcp(verbose = false) {
|
|
|
14
50
|
console.log(yellow('Installing MCP dependencies (first run)...\n'));
|
|
15
51
|
npmInstall(resolve(ROOT, 'mcp'), '--no-workspaces');
|
|
16
52
|
}
|
|
53
|
+
ensureMcpNativeDeps();
|
|
17
54
|
|
|
18
55
|
// Read AUTH_TOKEN directly from config to avoid stale system env overriding
|
|
19
56
|
// the user's configured token. Config is the source of truth for auth.
|
package/mcp/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# MindOS MCP Server
|
|
2
2
|
|
|
3
|
-
Pure HTTP client wrapper that maps
|
|
3
|
+
Pure HTTP client wrapper that maps MCP tools to the App REST API via `fetch`. Zero business logic — all operations are delegated to the App.
|
|
4
4
|
|
|
5
5
|
## Architecture
|
|
6
6
|
|
|
@@ -41,14 +41,17 @@ MCP_TRANSPORT=stdio mindos mcp # stdio mode
|
|
|
41
41
|
| `MCP_PORT` | `8781` | HTTP listen port (configurable via `mindos onboard`) |
|
|
42
42
|
| `MCP_ENDPOINT` | `/mcp` | HTTP endpoint path |
|
|
43
43
|
|
|
44
|
-
## MCP Tools
|
|
44
|
+
## MCP Tools
|
|
45
45
|
|
|
46
46
|
| Tool | App API | Description |
|
|
47
47
|
|------|---------|-------------|
|
|
48
48
|
| `mindos_list_files` | `GET /api/files` | List all files in the knowledge base |
|
|
49
|
+
| `mindos_list_spaces` | `GET /api/file?op=list_spaces` | List top-level Mind Spaces (name, path, counts, README blurb) |
|
|
49
50
|
| `mindos_read_file` | `GET /api/file?path=...` | Read file content (with offset/limit pagination) |
|
|
50
51
|
| `mindos_write_file` | `POST /api/file` op=save_file | Overwrite file content |
|
|
51
52
|
| `mindos_create_file` | `POST /api/file` op=create_file | Create a new .md or .csv file |
|
|
53
|
+
| `mindos_create_space` | `POST /api/file` op=create_space | Create a Mind Space (README + INSTRUCTION scaffold) |
|
|
54
|
+
| `mindos_rename_space` | `POST /api/file` op=rename_space | Rename a Space directory (folder only) |
|
|
52
55
|
| `mindos_delete_file` | `POST /api/file` op=delete_file | Delete a file |
|
|
53
56
|
| `mindos_rename_file` | `POST /api/file` op=rename_file | Rename a file (same directory) |
|
|
54
57
|
| `mindos_move_file` | `POST /api/file` op=move_file | Move a file to a new path |
|
package/mcp/src/index.ts
CHANGED
|
@@ -114,6 +114,36 @@ server.registerTool("mindos_list_files", {
|
|
|
114
114
|
} catch (e) { logOp("mindos_list_files", { response_format }, "error", String(e)); return error(String(e)); }
|
|
115
115
|
});
|
|
116
116
|
|
|
117
|
+
// ── mindos_list_spaces ──────────────────────────────────────────────────────
|
|
118
|
+
|
|
119
|
+
server.registerTool("mindos_list_spaces", {
|
|
120
|
+
title: "List Mind Spaces",
|
|
121
|
+
description:
|
|
122
|
+
"List top-level Mind Spaces (same as home Spaces grid): name, path, file count, and README blurb. Only spaces that appear in the file tree (at least one .md/.csv under the folder) are included.",
|
|
123
|
+
inputSchema: z.object({
|
|
124
|
+
response_format: z.enum(["markdown", "json"]).default("json"),
|
|
125
|
+
}),
|
|
126
|
+
annotations: { readOnlyHint: true },
|
|
127
|
+
}, async ({ response_format }) => {
|
|
128
|
+
try {
|
|
129
|
+
const json = await get("/api/file", { op: "list_spaces" });
|
|
130
|
+
const spaces = json.spaces as Array<{ name: string; path: string; fileCount: number; description: string }>;
|
|
131
|
+
if (response_format === "json") {
|
|
132
|
+
logOp("mindos_list_spaces", { response_format }, "ok", `${spaces?.length ?? 0} spaces`);
|
|
133
|
+
return ok(JSON.stringify({ spaces: spaces ?? [] }, null, 2));
|
|
134
|
+
}
|
|
135
|
+
const lines = (spaces ?? []).map(
|
|
136
|
+
(s) => `- **${s.name}** (\`${s.path}/\`) — ${s.fileCount} file(s)${s.description ? ` — ${s.description}` : ""}`,
|
|
137
|
+
);
|
|
138
|
+
const text = lines.length ? lines.join("\n") : "(no top-level spaces in tree)";
|
|
139
|
+
logOp("mindos_list_spaces", { response_format }, "ok", `${spaces?.length ?? 0} spaces`);
|
|
140
|
+
return ok(text);
|
|
141
|
+
} catch (e) {
|
|
142
|
+
logOp("mindos_list_spaces", { response_format }, "error", String(e));
|
|
143
|
+
return error(String(e));
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
|
|
117
147
|
// ── mindos_read_file ────────────────────────────────────────────────────────
|
|
118
148
|
|
|
119
149
|
server.registerTool("mindos_read_file", {
|
|
@@ -173,6 +203,56 @@ server.registerTool("mindos_create_file", {
|
|
|
173
203
|
} catch (e) { logOp("mindos_create_file", { path }, "error", String(e)); return error(String(e)); }
|
|
174
204
|
});
|
|
175
205
|
|
|
206
|
+
// ── mindos_create_space ─────────────────────────────────────────────────────
|
|
207
|
+
|
|
208
|
+
server.registerTool("mindos_create_space", {
|
|
209
|
+
title: "Create Mind Space",
|
|
210
|
+
description:
|
|
211
|
+
"Create a new Mind Space (top-level or under parent_path): directory + README.md + INSTRUCTION.md scaffold. Use this instead of create_file when adding a new cognitive zone to the knowledge base.",
|
|
212
|
+
inputSchema: z.object({
|
|
213
|
+
name: z.string().min(1).describe("Space directory name (no path separators)"),
|
|
214
|
+
description: z.string().default("").describe("Short purpose text stored in README.md"),
|
|
215
|
+
parent_path: z.string().default("").describe("Optional parent directory under MIND_ROOT (empty = top-level Space)"),
|
|
216
|
+
}),
|
|
217
|
+
}, async ({ name, description, parent_path }) => {
|
|
218
|
+
try {
|
|
219
|
+
const json = await post("/api/file", {
|
|
220
|
+
op: "create_space",
|
|
221
|
+
path: "_",
|
|
222
|
+
name,
|
|
223
|
+
description,
|
|
224
|
+
parent_path,
|
|
225
|
+
});
|
|
226
|
+
const p = json.path as string;
|
|
227
|
+
logOp("mindos_create_space", { name, parent_path }, "ok", p);
|
|
228
|
+
return ok(`Created Mind Space at "${p}"`);
|
|
229
|
+
} catch (e) {
|
|
230
|
+
logOp("mindos_create_space", { name, parent_path }, "error", String(e));
|
|
231
|
+
return error(String(e));
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
// ── mindos_rename_space ─────────────────────────────────────────────────────
|
|
236
|
+
|
|
237
|
+
server.registerTool("mindos_rename_space", {
|
|
238
|
+
title: "Rename Mind Space",
|
|
239
|
+
description:
|
|
240
|
+
"Rename a Space directory (relative path to the folder, e.g. Notes or Work/Notes). Only the final folder name changes; new_name must be a single segment. Does not rewrite links inside files.",
|
|
241
|
+
inputSchema: z.object({
|
|
242
|
+
path: z.string().min(1).describe("Relative path to the space directory to rename"),
|
|
243
|
+
new_name: z.string().min(1).describe("New folder name only (no slashes)"),
|
|
244
|
+
}),
|
|
245
|
+
}, async ({ path: spacePath, new_name }) => {
|
|
246
|
+
try {
|
|
247
|
+
const json = await post("/api/file", { op: "rename_space", path: spacePath, new_name });
|
|
248
|
+
logOp("mindos_rename_space", { path: spacePath, new_name }, "ok", String(json.newPath));
|
|
249
|
+
return ok(`Renamed space "${spacePath}" → "${json.newPath}"`);
|
|
250
|
+
} catch (e) {
|
|
251
|
+
logOp("mindos_rename_space", { path: spacePath, new_name }, "error", String(e));
|
|
252
|
+
return error(String(e));
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
|
|
176
256
|
// ── mindos_delete_file ──────────────────────────────────────────────────────
|
|
177
257
|
|
|
178
258
|
server.registerTool("mindos_delete_file", {
|
package/package.json
CHANGED
package/scripts/setup.js
CHANGED
|
@@ -598,7 +598,7 @@ function expandHomePath(p) {
|
|
|
598
598
|
return p.startsWith('~/') ? resolve(homedir(), p.slice(2)) : p;
|
|
599
599
|
}
|
|
600
600
|
|
|
601
|
-
/** Parse JSONC
|
|
601
|
+
/** Parse JSONC: JSON plus line // comments and slash-star block comments (e.g. VS Code configs). */
|
|
602
602
|
function parseJsonc(text) {
|
|
603
603
|
let stripped = text.replace(/\\"|"(?:\\"|[^"])*"|(\/\/.*$)/gm, (m, g) => g ? '' : m);
|
|
604
604
|
stripped = stripped.replace(/\/\*[\s\S]*?\*\//g, '');
|