@geminilight/mindos 0.2.1 → 0.4.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/init/route.ts +7 -41
- package/app/app/api/mcp/agents/route.ts +72 -0
- package/app/app/api/mcp/install/route.ts +95 -0
- package/app/app/api/mcp/status/route.ts +47 -0
- package/app/app/api/settings/route.ts +3 -0
- package/app/app/api/setup/generate-token/route.ts +23 -0
- package/app/app/api/setup/route.ts +81 -0
- package/app/app/api/skills/route.ts +208 -0
- package/app/app/api/sync/route.ts +54 -3
- package/app/app/api/update-check/route.ts +52 -0
- package/app/app/globals.css +12 -0
- package/app/app/layout.tsx +4 -2
- package/app/app/login/page.tsx +20 -13
- package/app/app/page.tsx +22 -2
- package/app/app/setup/page.tsx +9 -0
- package/app/app/view/[...path]/ViewPageClient.tsx +47 -21
- package/app/app/view/[...path]/loading.tsx +1 -1
- package/app/app/view/[...path]/not-found.tsx +101 -0
- package/app/components/AskFab.tsx +1 -1
- package/app/components/AskModal.tsx +1 -1
- package/app/components/Backlinks.tsx +1 -1
- package/app/components/Breadcrumb.tsx +13 -3
- package/app/components/CsvView.tsx +5 -6
- package/app/components/DirView.tsx +42 -21
- package/app/components/FindInPage.tsx +211 -0
- package/app/components/HomeContent.tsx +97 -44
- package/app/components/JsonView.tsx +1 -2
- package/app/components/MarkdownEditor.tsx +1 -2
- package/app/components/OnboardingView.tsx +6 -7
- package/app/components/SettingsModal.tsx +5 -2
- package/app/components/SetupWizard.tsx +479 -0
- package/app/components/Sidebar.tsx +1 -1
- package/app/components/UpdateBanner.tsx +101 -0
- package/app/components/renderers/{AgentInspectorRenderer.tsx → agent-inspector/AgentInspectorRenderer.tsx} +13 -11
- package/app/components/renderers/agent-inspector/manifest.ts +14 -0
- package/app/components/renderers/{BacklinksRenderer.tsx → backlinks/BacklinksRenderer.tsx} +6 -6
- package/app/components/renderers/backlinks/manifest.ts +14 -0
- package/app/components/renderers/config/manifest.ts +14 -0
- package/app/components/renderers/csv/BoardView.tsx +12 -12
- package/app/components/renderers/csv/ConfigPanel.tsx +7 -8
- package/app/components/renderers/{CsvRenderer.tsx → csv/CsvRenderer.tsx} +8 -9
- package/app/components/renderers/csv/GalleryView.tsx +3 -3
- package/app/components/renderers/csv/TableView.tsx +4 -5
- package/app/components/renderers/csv/manifest.ts +14 -0
- package/app/components/renderers/{DiffRenderer.tsx → diff/DiffRenderer.tsx} +10 -9
- package/app/components/renderers/diff/manifest.ts +14 -0
- package/app/components/renderers/{GraphRenderer.tsx → graph/GraphRenderer.tsx} +4 -5
- package/app/components/renderers/graph/manifest.ts +14 -0
- package/app/components/renderers/{SummaryRenderer.tsx → summary/SummaryRenderer.tsx} +6 -6
- package/app/components/renderers/summary/manifest.ts +14 -0
- package/app/components/renderers/{TimelineRenderer.tsx → timeline/TimelineRenderer.tsx} +6 -6
- package/app/components/renderers/timeline/manifest.ts +14 -0
- package/app/components/renderers/{TodoRenderer.tsx → todo/TodoRenderer.tsx} +2 -2
- package/app/components/renderers/todo/manifest.ts +14 -0
- package/app/components/renderers/{WorkflowRenderer.tsx → workflow/WorkflowRenderer.tsx} +13 -13
- package/app/components/renderers/workflow/manifest.ts +14 -0
- package/app/components/settings/McpTab.tsx +549 -0
- package/app/components/settings/SyncTab.tsx +139 -50
- package/app/components/settings/types.ts +1 -1
- package/app/data/pages/home.png +0 -0
- package/app/lib/i18n.ts +270 -10
- package/app/lib/renderers/index.ts +20 -89
- package/app/lib/renderers/registry.ts +4 -1
- package/app/lib/settings.ts +15 -1
- package/app/lib/template.ts +45 -0
- package/app/package.json +1 -0
- package/app/types/semver.d.ts +8 -0
- package/bin/cli.js +137 -24
- package/bin/lib/build.js +53 -18
- package/bin/lib/colors.js +3 -1
- package/bin/lib/config.js +4 -0
- package/bin/lib/constants.js +2 -0
- package/bin/lib/debug.js +10 -0
- package/bin/lib/startup.js +21 -20
- package/bin/lib/stop.js +41 -3
- package/bin/lib/sync.js +65 -53
- package/bin/lib/update-check.js +94 -0
- package/bin/lib/utils.js +2 -2
- package/package.json +1 -1
- package/scripts/gen-renderer-index.js +57 -0
- package/scripts/setup.js +117 -1
- /package/app/components/renderers/{ConfigRenderer.tsx → config/ConfigRenderer.tsx} +0 -0
|
@@ -1,92 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AUTO-GENERATED by scripts/gen-renderer-index.js — do not edit manually.
|
|
3
|
+
* To regenerate: node scripts/gen-renderer-index.js
|
|
4
|
+
*/
|
|
1
5
|
import { registerRenderer } from './registry';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
6
|
+
import { manifest as agentInspector } from '@/components/renderers/agent-inspector/manifest';
|
|
7
|
+
import { manifest as backlinks } from '@/components/renderers/backlinks/manifest';
|
|
8
|
+
import { manifest as config } from '@/components/renderers/config/manifest';
|
|
9
|
+
import { manifest as csv } from '@/components/renderers/csv/manifest';
|
|
10
|
+
import { manifest as diff } from '@/components/renderers/diff/manifest';
|
|
11
|
+
import { manifest as graph } from '@/components/renderers/graph/manifest';
|
|
12
|
+
import { manifest as summary } from '@/components/renderers/summary/manifest';
|
|
13
|
+
import { manifest as timeline } from '@/components/renderers/timeline/manifest';
|
|
14
|
+
import { manifest as todo } from '@/components/renderers/todo/manifest';
|
|
15
|
+
import { manifest as workflow } from '@/components/renderers/workflow/manifest';
|
|
9
16
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
description: 'Renders TODO.md/TODO.csv as an interactive kanban board grouped by section. Check items off directly — changes are written back to the source file.',
|
|
14
|
-
author: 'MindOS',
|
|
15
|
-
icon: '✅',
|
|
16
|
-
tags: ['productivity', 'tasks', 'markdown'],
|
|
17
|
-
builtin: true,
|
|
18
|
-
match: ({ filePath }) => /\bTODO\b.*\.(md|csv)$/i.test(filePath),
|
|
19
|
-
component: TodoRenderer,
|
|
20
|
-
});
|
|
17
|
+
const manifests = [
|
|
18
|
+
agentInspector, backlinks, config, csv, diff, graph, summary, timeline, todo, workflow,
|
|
19
|
+
];
|
|
21
20
|
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
description: 'Renders any CSV file as Table, Gallery, or Board. Each view is independently configurable — choose which columns map to title, description, tag, and group.',
|
|
26
|
-
author: 'MindOS',
|
|
27
|
-
icon: '📊',
|
|
28
|
-
tags: ['csv', 'table', 'gallery', 'board', 'data'],
|
|
29
|
-
builtin: true,
|
|
30
|
-
match: ({ extension, filePath }) => extension === 'csv' && !/\bTODO\b/i.test(filePath),
|
|
31
|
-
component: CsvRenderer,
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
registerRenderer({
|
|
35
|
-
id: 'config-panel',
|
|
36
|
-
name: 'Config Panel',
|
|
37
|
-
description: 'Renders CONFIG.json as an editable control panel based on uiSchema/keySpecs. Changes are written back to the JSON file directly.',
|
|
38
|
-
author: 'MindOS',
|
|
39
|
-
icon: '🧩',
|
|
40
|
-
tags: ['config', 'json', 'settings', 'schema'],
|
|
41
|
-
builtin: true,
|
|
42
|
-
match: ({ filePath, extension }) => extension === 'json' && /(^|\/)CONFIG\.json$/i.test(filePath),
|
|
43
|
-
component: ConfigRenderer,
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
registerRenderer({
|
|
47
|
-
id: 'graph',
|
|
48
|
-
name: 'Wiki Graph',
|
|
49
|
-
description: 'Force-directed graph of wikilink references across all markdown files. Supports Global and Local (2-hop) scope filters.',
|
|
50
|
-
author: 'MindOS',
|
|
51
|
-
icon: '🕸️',
|
|
52
|
-
tags: ['graph', 'wiki', 'links', 'visualization'],
|
|
53
|
-
builtin: true,
|
|
54
|
-
match: ({ extension }) => extension === 'md',
|
|
55
|
-
component: GraphRenderer,
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
registerRenderer({
|
|
59
|
-
id: 'timeline',
|
|
60
|
-
name: 'Timeline',
|
|
61
|
-
description: 'Renders changelog and journal files as a vertical timeline. Any markdown with ## date headings (e.g. ## 2025-01-15) becomes a card in the feed.',
|
|
62
|
-
author: 'MindOS',
|
|
63
|
-
icon: '📅',
|
|
64
|
-
tags: ['timeline', 'changelog', 'journal', 'history'],
|
|
65
|
-
builtin: true,
|
|
66
|
-
match: ({ filePath }) => /\b(CHANGELOG|changelog|TIMELINE|timeline|journal|Journal|diary|Diary)\b.*\.md$/i.test(filePath),
|
|
67
|
-
component: TimelineRenderer,
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
registerRenderer({
|
|
71
|
-
id: 'summary',
|
|
72
|
-
name: 'AI Briefing',
|
|
73
|
-
description: 'Streams an AI-generated daily briefing summarizing your most recently modified files — key changes, recurring themes, and suggested next actions.',
|
|
74
|
-
author: 'MindOS',
|
|
75
|
-
icon: '✨',
|
|
76
|
-
tags: ['ai', 'summary', 'briefing', 'daily'],
|
|
77
|
-
builtin: true,
|
|
78
|
-
match: ({ filePath }) => /\b(SUMMARY|summary|Summary|BRIEFING|briefing|Briefing|DAILY|daily|Daily)\b.*\.md$/i.test(filePath),
|
|
79
|
-
component: SummaryRenderer,
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
registerRenderer({
|
|
83
|
-
id: 'agent-inspector',
|
|
84
|
-
name: 'Agent Inspector',
|
|
85
|
-
description: 'Visualizes agent tool-call logs as a filterable timeline. Auto-activates on .agent-log.json (JSON Lines format).',
|
|
86
|
-
author: 'MindOS',
|
|
87
|
-
icon: '🔍',
|
|
88
|
-
tags: ['agent', 'inspector', 'log', 'mcp', 'tools'],
|
|
89
|
-
builtin: true,
|
|
90
|
-
match: ({ filePath }) => /\.agent-log\.json$/i.test(filePath),
|
|
91
|
-
component: AgentInspectorRenderer,
|
|
92
|
-
});
|
|
21
|
+
for (const m of manifests) {
|
|
22
|
+
registerRenderer(m);
|
|
23
|
+
}
|
|
@@ -15,8 +15,11 @@ export interface RendererDefinition {
|
|
|
15
15
|
icon: string; // emoji or short string
|
|
16
16
|
tags: string[];
|
|
17
17
|
builtin: boolean; // true = ships with MindOS; false = user-installed (future)
|
|
18
|
+
entryPath?: string; // canonical entry file shown on home page (e.g. 'TODO.md')
|
|
18
19
|
match: (ctx: Pick<RendererContext, 'filePath' | 'extension'>) => boolean;
|
|
19
|
-
component
|
|
20
|
+
// Provide either `component` (eager) or `load` (lazy). Prefer `load` for code-splitting.
|
|
21
|
+
component?: ComponentType<RendererContext>;
|
|
22
|
+
load?: () => Promise<{ default: ComponentType<RendererContext> }>;
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
const registry: RendererDefinition[] = [];
|
package/app/lib/settings.ts
CHANGED
|
@@ -21,12 +21,13 @@ export interface AiConfig {
|
|
|
21
21
|
export interface ServerSettings {
|
|
22
22
|
ai: AiConfig;
|
|
23
23
|
mindRoot: string; // empty = use env var / default
|
|
24
|
-
// Fields managed by CLI only (not edited via GUI, preserved on write)
|
|
25
24
|
port?: number;
|
|
26
25
|
mcpPort?: number;
|
|
27
26
|
authToken?: string;
|
|
28
27
|
webPassword?: string;
|
|
29
28
|
startMode?: 'dev' | 'start' | 'daemon';
|
|
29
|
+
setupPending?: boolean; // true → / redirects to /setup
|
|
30
|
+
disabledSkills?: string[];
|
|
30
31
|
}
|
|
31
32
|
|
|
32
33
|
const DEFAULTS: ServerSettings = {
|
|
@@ -108,6 +109,10 @@ export function readSettings(): ServerSettings {
|
|
|
108
109
|
webPassword: typeof parsed.webPassword === 'string' ? parsed.webPassword : undefined,
|
|
109
110
|
authToken: typeof parsed.authToken === 'string' ? parsed.authToken : undefined,
|
|
110
111
|
mcpPort: typeof parsed.mcpPort === 'number' ? parsed.mcpPort : undefined,
|
|
112
|
+
port: typeof parsed.port === 'number' ? parsed.port : undefined,
|
|
113
|
+
startMode: typeof parsed.startMode === 'string' ? parsed.startMode as ServerSettings['startMode'] : undefined,
|
|
114
|
+
setupPending: parsed.setupPending === true ? true : undefined,
|
|
115
|
+
disabledSkills: Array.isArray(parsed.disabledSkills) ? parsed.disabledSkills as string[] : undefined,
|
|
111
116
|
};
|
|
112
117
|
} catch {
|
|
113
118
|
return { ...DEFAULTS, ai: { ...DEFAULTS.ai, providers: { ...DEFAULTS.ai.providers } } };
|
|
@@ -123,6 +128,15 @@ export function writeSettings(settings: ServerSettings): void {
|
|
|
123
128
|
const merged: Record<string, unknown> = { ...existing, ai: settings.ai, mindRoot: settings.mindRoot };
|
|
124
129
|
if (settings.webPassword !== undefined) merged.webPassword = settings.webPassword;
|
|
125
130
|
if (settings.authToken !== undefined) merged.authToken = settings.authToken;
|
|
131
|
+
if (settings.port !== undefined) merged.port = settings.port;
|
|
132
|
+
if (settings.mcpPort !== undefined) merged.mcpPort = settings.mcpPort;
|
|
133
|
+
if (settings.startMode !== undefined) merged.startMode = settings.startMode;
|
|
134
|
+
if (settings.disabledSkills !== undefined) merged.disabledSkills = settings.disabledSkills;
|
|
135
|
+
// setupPending: false/undefined → remove the field (cleanup); true → set it
|
|
136
|
+
if ('setupPending' in settings) {
|
|
137
|
+
if (settings.setupPending) merged.setupPending = true;
|
|
138
|
+
else delete merged.setupPending;
|
|
139
|
+
}
|
|
126
140
|
fs.writeFileSync(SETTINGS_PATH, JSON.stringify(merged, null, 2) + '\n', 'utf-8');
|
|
127
141
|
}
|
|
128
142
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Recursively copy `src` to `dest`, skipping files that already exist in dest.
|
|
6
|
+
*/
|
|
7
|
+
export function copyRecursive(src: string, dest: string) {
|
|
8
|
+
const stat = fs.statSync(src);
|
|
9
|
+
if (stat.isDirectory()) {
|
|
10
|
+
if (!fs.existsSync(dest)) fs.mkdirSync(dest, { recursive: true });
|
|
11
|
+
for (const entry of fs.readdirSync(src)) {
|
|
12
|
+
copyRecursive(path.join(src, entry), path.join(dest, entry));
|
|
13
|
+
}
|
|
14
|
+
} else {
|
|
15
|
+
// Skip if file already exists
|
|
16
|
+
if (fs.existsSync(dest)) return;
|
|
17
|
+
const dir = path.dirname(dest);
|
|
18
|
+
if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
|
|
19
|
+
fs.copyFileSync(src, dest);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Apply a built-in template (en / zh / empty) to the given directory.
|
|
25
|
+
* Returns true on success, throws on error.
|
|
26
|
+
*/
|
|
27
|
+
export function applyTemplate(template: string, destDir: string): void {
|
|
28
|
+
if (!['en', 'zh', 'empty'].includes(template)) {
|
|
29
|
+
throw new Error(`Invalid template: ${template}`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// templates/ is at the repo root (sibling of app/)
|
|
33
|
+
const repoRoot = path.resolve(process.cwd(), '..');
|
|
34
|
+
const templateDir = path.join(repoRoot, 'templates', template);
|
|
35
|
+
|
|
36
|
+
if (!fs.existsSync(templateDir)) {
|
|
37
|
+
throw new Error(`Template "${template}" not found at ${templateDir}`);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (!fs.existsSync(destDir)) {
|
|
41
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
copyRecursive(templateDir, destDir);
|
|
45
|
+
}
|
package/app/package.json
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
declare module 'semver' {
|
|
2
|
+
export function gt(v1: string, v2: string): boolean;
|
|
3
|
+
export function lt(v1: string, v2: string): boolean;
|
|
4
|
+
export function gte(v1: string, v2: string): boolean;
|
|
5
|
+
export function lte(v1: string, v2: string): boolean;
|
|
6
|
+
export function eq(v1: string, v2: string): boolean;
|
|
7
|
+
export function valid(v: string | null): string | null;
|
|
8
|
+
}
|
package/bin/cli.js
CHANGED
|
@@ -33,6 +33,7 @@
|
|
|
33
33
|
* mindos logs — tail service logs (~/.mindos/mindos.log)
|
|
34
34
|
* mindos config show — print current config (API keys masked)
|
|
35
35
|
* mindos config set <key> <val> — update a single config field
|
|
36
|
+
* mindos config unset <key> — remove a config field
|
|
36
37
|
* mindos config validate — validate config file
|
|
37
38
|
*/
|
|
38
39
|
|
|
@@ -58,6 +59,15 @@ import { initSync, startSyncDaemon, stopSyncDaemon, getSyncStatus, manualSync, l
|
|
|
58
59
|
// ── Commands ──────────────────────────────────────────────────────────────────
|
|
59
60
|
|
|
60
61
|
const cmd = process.argv[2];
|
|
62
|
+
|
|
63
|
+
// ── --version / -v ──────────────────────────────────────────────────────────
|
|
64
|
+
// --help / -h is handled at entry section (resolvedCmd = null → help block)
|
|
65
|
+
if (cmd === '--version' || cmd === '-v') {
|
|
66
|
+
const version = JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version;
|
|
67
|
+
console.log(`mindos/${version} node/${process.version} ${process.platform}-${process.arch}`);
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
61
71
|
const isDaemon = process.argv.includes('--daemon') || (!cmd && isDaemonMode());
|
|
62
72
|
const isVerbose = process.argv.includes('--verbose');
|
|
63
73
|
const extra = process.argv.slice(3).filter(a => a !== '--daemon' && a !== '--verbose').join(' ');
|
|
@@ -68,8 +78,8 @@ const commands = {
|
|
|
68
78
|
const daemonFlag = process.argv.includes('--install-daemon') ? ' --install-daemon' : '';
|
|
69
79
|
run(`node ${resolve(ROOT, 'scripts/setup.js')}${daemonFlag}`);
|
|
70
80
|
},
|
|
71
|
-
init: () =>
|
|
72
|
-
setup: () =>
|
|
81
|
+
init: async () => commands.onboard(),
|
|
82
|
+
setup: async () => commands.onboard(),
|
|
73
83
|
|
|
74
84
|
// ── open ───────────────────────────────────────────────────────────────────
|
|
75
85
|
open: () => {
|
|
@@ -123,8 +133,8 @@ const commands = {
|
|
|
123
133
|
console.log(`${sep}`);
|
|
124
134
|
console.log(`${bold('Claude Code')}`);
|
|
125
135
|
console.log(`${sep}`);
|
|
126
|
-
console.log(dim('
|
|
127
|
-
console.log(dim('\
|
|
136
|
+
console.log(dim('Quick install:') + ` mindos mcp install claude-code -g -y`);
|
|
137
|
+
console.log(dim('\nManual config (~/.claude.json):'));
|
|
128
138
|
console.log(JSON.stringify({
|
|
129
139
|
mcpServers: {
|
|
130
140
|
mindos: {
|
|
@@ -138,8 +148,8 @@ const commands = {
|
|
|
138
148
|
console.log(`\n${sep}`);
|
|
139
149
|
console.log(`${bold('CodeBuddy (Claude Code Internal)')}`);
|
|
140
150
|
console.log(`${sep}`);
|
|
141
|
-
console.log(dim('
|
|
142
|
-
console.log(dim('\
|
|
151
|
+
console.log(dim('Quick install:') + ` mindos mcp install codebuddy -g -y`);
|
|
152
|
+
console.log(dim('\nManual config (~/.claude-internal/.claude.json):'));
|
|
143
153
|
console.log(JSON.stringify({
|
|
144
154
|
mcpServers: {
|
|
145
155
|
mindos: {
|
|
@@ -153,8 +163,8 @@ const commands = {
|
|
|
153
163
|
console.log(`\n${sep}`);
|
|
154
164
|
console.log(`${bold('Cursor')}`);
|
|
155
165
|
console.log(`${sep}`);
|
|
156
|
-
console.log(dim('
|
|
157
|
-
console.log(dim('\
|
|
166
|
+
console.log(dim('Quick install:') + ` mindos mcp install cursor -g -y`);
|
|
167
|
+
console.log(dim('\nManual config (~/.cursor/mcp.json):'));
|
|
158
168
|
console.log(JSON.stringify({
|
|
159
169
|
mcpServers: {
|
|
160
170
|
mindos: {
|
|
@@ -168,7 +178,7 @@ const commands = {
|
|
|
168
178
|
if (localIP) {
|
|
169
179
|
const remoteUrl = `http://${localIP}:${mcpPort}/mcp`;
|
|
170
180
|
console.log(`\n${sep}`);
|
|
171
|
-
console.log(`${bold('Remote (
|
|
181
|
+
console.log(`${bold('Remote (other devices)')}`);
|
|
172
182
|
console.log(`${sep}`);
|
|
173
183
|
console.log(`URL: ${cyan(remoteUrl)}`);
|
|
174
184
|
console.log(JSON.stringify({
|
|
@@ -200,12 +210,21 @@ const commands = {
|
|
|
200
210
|
if (devMindRoot) {
|
|
201
211
|
startSyncDaemon(devMindRoot).catch(() => {});
|
|
202
212
|
}
|
|
203
|
-
printStartupInfo(webPort, mcpPort);
|
|
213
|
+
await printStartupInfo(webPort, mcpPort);
|
|
204
214
|
run(`npx next dev -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
|
|
205
215
|
},
|
|
206
216
|
|
|
207
217
|
// ── start ──────────────────────────────────────────────────────────────────
|
|
208
218
|
start: async () => {
|
|
219
|
+
// Check for incomplete setup
|
|
220
|
+
if (existsSync(CONFIG_PATH)) {
|
|
221
|
+
try {
|
|
222
|
+
const cfg = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8'));
|
|
223
|
+
if (cfg.setupPending === true) {
|
|
224
|
+
console.log(`\n ${yellow('⚠ Setup was not completed.')} Run ${cyan('mindos onboard')} to finish, or ${cyan('mindos config set setupPending false')} to dismiss.\n`);
|
|
225
|
+
}
|
|
226
|
+
} catch {}
|
|
227
|
+
}
|
|
209
228
|
if (isDaemon) {
|
|
210
229
|
const platform = getPlatform();
|
|
211
230
|
if (!platform) {
|
|
@@ -225,13 +244,13 @@ const commands = {
|
|
|
225
244
|
console.error(dim(' Check logs with: mindos logs\n'));
|
|
226
245
|
process.exit(1);
|
|
227
246
|
}
|
|
228
|
-
printStartupInfo(webPort, mcpPort);
|
|
247
|
+
await printStartupInfo(webPort, mcpPort);
|
|
229
248
|
// System notification
|
|
230
249
|
try {
|
|
231
250
|
if (process.platform === 'darwin') {
|
|
232
|
-
execSync(`osascript -e 'display notification "http://localhost:${webPort}" with title "MindOS
|
|
251
|
+
execSync(`osascript -e 'display notification "http://localhost:${webPort}" with title "MindOS Ready"'`, { stdio: 'ignore' });
|
|
233
252
|
} else if (process.platform === 'linux') {
|
|
234
|
-
execSync(`notify-send "MindOS
|
|
253
|
+
execSync(`notify-send "MindOS Ready" "http://localhost:${webPort}"`, { stdio: 'ignore' });
|
|
235
254
|
}
|
|
236
255
|
} catch { /* notification is best-effort */ }
|
|
237
256
|
console.log(`${green('✔ MindOS is running as a background service')}`);
|
|
@@ -250,6 +269,7 @@ const commands = {
|
|
|
250
269
|
if (needsBuild()) {
|
|
251
270
|
console.log(yellow('Building MindOS (first run or new version detected)...\n'));
|
|
252
271
|
cleanNextDir();
|
|
272
|
+
run('node scripts/gen-renderer-index.js', ROOT);
|
|
253
273
|
run('npx next build', resolve(ROOT, 'app'));
|
|
254
274
|
writeBuildStamp();
|
|
255
275
|
}
|
|
@@ -261,7 +281,7 @@ const commands = {
|
|
|
261
281
|
if (mindRoot) {
|
|
262
282
|
startSyncDaemon(mindRoot).catch(() => {});
|
|
263
283
|
}
|
|
264
|
-
printStartupInfo(webPort, mcpPort);
|
|
284
|
+
await printStartupInfo(webPort, mcpPort);
|
|
265
285
|
run(`npx next start -p ${webPort} ${extra}`, resolve(ROOT, 'app'));
|
|
266
286
|
},
|
|
267
287
|
|
|
@@ -269,6 +289,7 @@ const commands = {
|
|
|
269
289
|
build: () => {
|
|
270
290
|
ensureAppDeps();
|
|
271
291
|
cleanNextDir();
|
|
292
|
+
run('node scripts/gen-renderer-index.js', ROOT);
|
|
272
293
|
run(`npx next build ${extra}`, resolve(ROOT, 'app'));
|
|
273
294
|
writeBuildStamp();
|
|
274
295
|
},
|
|
@@ -466,6 +487,23 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
466
487
|
}
|
|
467
488
|
}
|
|
468
489
|
|
|
490
|
+
// 9. Update check
|
|
491
|
+
try {
|
|
492
|
+
const { checkForUpdate } = await import('./lib/update-check.js');
|
|
493
|
+
const latestVersion = await Promise.race([
|
|
494
|
+
checkForUpdate(),
|
|
495
|
+
new Promise(r => setTimeout(() => r(null), 4000)),
|
|
496
|
+
]);
|
|
497
|
+
if (latestVersion) {
|
|
498
|
+
const currentVersion = (() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })();
|
|
499
|
+
warn(`Update available: v${currentVersion} → ${bold(`v${latestVersion}`)} ${dim('run `mindos update`')}`);
|
|
500
|
+
} else {
|
|
501
|
+
ok('MindOS is up to date');
|
|
502
|
+
}
|
|
503
|
+
} catch {
|
|
504
|
+
warn('Could not check for updates');
|
|
505
|
+
}
|
|
506
|
+
|
|
469
507
|
console.log(hasError
|
|
470
508
|
? `\n${red('Some checks failed.')} Run ${cyan('mindos onboard')} to reconfigure.\n`
|
|
471
509
|
: `\n${green('All checks passed.')}\n`);
|
|
@@ -575,7 +613,7 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
575
613
|
display.authToken = maskKey(display.authToken);
|
|
576
614
|
if (display.webPassword)
|
|
577
615
|
display.webPassword = maskKey(display.webPassword);
|
|
578
|
-
console.log(`\n${bold('📋 MindOS Config')} ${dim(CONFIG_PATH)}\n`);
|
|
616
|
+
console.log(`\n${bold('📋 MindOS Config')} ${dim(`v${(() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })()}`)} ${dim(CONFIG_PATH)}\n`);
|
|
579
617
|
console.log(JSON.stringify(display, null, 2));
|
|
580
618
|
console.log();
|
|
581
619
|
return;
|
|
@@ -640,13 +678,50 @@ ${dim('Shortcut: mindos start --daemon → install + start in one step')}
|
|
|
640
678
|
if (typeof obj[parts[i]] !== 'object' || !obj[parts[i]]) obj[parts[i]] = {};
|
|
641
679
|
obj = obj[parts[i]];
|
|
642
680
|
}
|
|
643
|
-
|
|
681
|
+
// Coerce string values to appropriate types
|
|
682
|
+
function coerceValue(v) {
|
|
683
|
+
if (v === 'true') return true;
|
|
684
|
+
if (v === 'false') return false;
|
|
685
|
+
if (v === 'null') return null;
|
|
686
|
+
if (v === '""' || v === "''") return '';
|
|
687
|
+
if (v.trim() !== '' && !isNaN(Number(v))) return Number(v);
|
|
688
|
+
return v;
|
|
689
|
+
}
|
|
690
|
+
const coerced = coerceValue(val);
|
|
644
691
|
obj[parts[parts.length - 1]] = coerced;
|
|
645
692
|
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
646
693
|
console.log(`${green('✔')} Set ${cyan(key)} = ${bold(String(coerced))}`);
|
|
647
694
|
return;
|
|
648
695
|
}
|
|
649
696
|
|
|
697
|
+
if (sub === 'unset') {
|
|
698
|
+
const key = process.argv[4];
|
|
699
|
+
if (!key) {
|
|
700
|
+
console.error(red('Usage: mindos config unset <key>'));
|
|
701
|
+
process.exit(1);
|
|
702
|
+
}
|
|
703
|
+
if (!existsSync(CONFIG_PATH)) {
|
|
704
|
+
console.error(red('No config found. Run `mindos onboard` first.'));
|
|
705
|
+
process.exit(1);
|
|
706
|
+
}
|
|
707
|
+
let config;
|
|
708
|
+
try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch {
|
|
709
|
+
console.error(red('Failed to parse config file.'));
|
|
710
|
+
process.exit(1);
|
|
711
|
+
}
|
|
712
|
+
const parts = key.split('.');
|
|
713
|
+
let obj = config;
|
|
714
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
715
|
+
if (!obj[parts[i]]) { console.log(dim(`Key "${key}" not found`)); return; }
|
|
716
|
+
obj = obj[parts[i]];
|
|
717
|
+
}
|
|
718
|
+
if (!(parts[parts.length - 1] in obj)) { console.log(dim(`Key "${key}" not found`)); return; }
|
|
719
|
+
delete obj[parts[parts.length - 1]];
|
|
720
|
+
writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
|
|
721
|
+
console.log(`${green('✔')} Removed ${cyan(key)}`);
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
|
|
650
725
|
// no subcommand or unknown → show help
|
|
651
726
|
const row = (c, d) => ` ${cyan(c.padEnd(32))}${dim(d)}`;
|
|
652
727
|
console.log(`
|
|
@@ -656,10 +731,13 @@ ${bold('Subcommands:')}
|
|
|
656
731
|
${row('mindos config show', 'Print current config (API keys masked)')}
|
|
657
732
|
${row('mindos config validate', 'Validate config file')}
|
|
658
733
|
${row('mindos config set <key> <v>', 'Update a single field (dot-notation supported)')}
|
|
734
|
+
${row('mindos config unset <key>', 'Remove a config field')}
|
|
659
735
|
|
|
660
736
|
${bold('Examples:')}
|
|
661
737
|
${dim('mindos config set port 3002')}
|
|
662
738
|
${dim('mindos config set ai.provider openai')}
|
|
739
|
+
${dim('mindos config set setupPending false')}
|
|
740
|
+
${dim('mindos config unset webPassword')}
|
|
663
741
|
`);
|
|
664
742
|
},
|
|
665
743
|
|
|
@@ -670,7 +748,22 @@ ${bold('Examples:')}
|
|
|
670
748
|
const mindRoot = process.env.MIND_ROOT;
|
|
671
749
|
|
|
672
750
|
if (sub === 'init') {
|
|
673
|
-
|
|
751
|
+
// Parse --non-interactive --remote <url> --branch <branch> --token <token>
|
|
752
|
+
const args = process.argv.slice(4);
|
|
753
|
+
const flagIdx = (flag) => args.indexOf(flag);
|
|
754
|
+
const flagVal = (flag) => { const i = flagIdx(flag); return i >= 0 && i + 1 < args.length ? args[i + 1] : ''; };
|
|
755
|
+
const nonInteractive = args.includes('--non-interactive');
|
|
756
|
+
|
|
757
|
+
if (nonInteractive) {
|
|
758
|
+
await initSync(mindRoot, {
|
|
759
|
+
nonInteractive: true,
|
|
760
|
+
remote: flagVal('--remote'),
|
|
761
|
+
token: flagVal('--token'),
|
|
762
|
+
branch: flagVal('--branch') || 'main',
|
|
763
|
+
});
|
|
764
|
+
} else {
|
|
765
|
+
await initSync(mindRoot);
|
|
766
|
+
}
|
|
674
767
|
return;
|
|
675
768
|
}
|
|
676
769
|
|
|
@@ -695,6 +788,16 @@ ${bold('Examples:')}
|
|
|
695
788
|
return;
|
|
696
789
|
}
|
|
697
790
|
|
|
791
|
+
// Unknown subcommand check
|
|
792
|
+
if (sub) {
|
|
793
|
+
const validSubs = ['init', 'now', 'conflicts', 'on', 'off'];
|
|
794
|
+
if (!validSubs.includes(sub)) {
|
|
795
|
+
console.error(red(`Unknown sync subcommand: ${sub}`));
|
|
796
|
+
console.error(dim(`Available: ${validSubs.join(' | ')}`));
|
|
797
|
+
process.exit(1);
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
|
|
698
801
|
// default: sync status
|
|
699
802
|
const status = getSyncStatus(mindRoot);
|
|
700
803
|
if (!status.enabled) {
|
|
@@ -727,15 +830,16 @@ ${bold('Examples:')}
|
|
|
727
830
|
|
|
728
831
|
// ── Entry ─────────────────────────────────────────────────────────────────────
|
|
729
832
|
|
|
730
|
-
const resolvedCmd = cmd || (existsSync(CONFIG_PATH) ? getStartMode() : null);
|
|
833
|
+
const resolvedCmd = (cmd === '--help' || cmd === '-h') ? null : (cmd || (existsSync(CONFIG_PATH) ? getStartMode() : null));
|
|
731
834
|
|
|
732
835
|
if (!resolvedCmd || !commands[resolvedCmd]) {
|
|
836
|
+
const pkgVersion = (() => { try { return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version; } catch { return '?'; } })();
|
|
733
837
|
const row = (c, d) => ` ${cyan(c.padEnd(36))}${dim(d)}`;
|
|
734
838
|
console.log(`
|
|
735
|
-
${bold('🧠 MindOS CLI')}
|
|
839
|
+
${bold('🧠 MindOS CLI')} ${dim(`v${pkgVersion}`)}
|
|
736
840
|
|
|
737
|
-
${bold('
|
|
738
|
-
${row('mindos onboard', 'Interactive setup (
|
|
841
|
+
${bold('Core:')}
|
|
842
|
+
${row('mindos onboard', 'Interactive setup (aliases: init, setup)')}
|
|
739
843
|
${row('mindos onboard --install-daemon', 'Setup + install & start as background OS service')}
|
|
740
844
|
${row('mindos start', 'Start app + MCP server (production, auto-rebuilds if needed)')}
|
|
741
845
|
${row('mindos start --daemon', 'Install + start as background OS service (survives terminal close)')}
|
|
@@ -745,19 +849,28 @@ ${row('mindos dev --turbopack', 'Start with Turbopack (faster HMR)')}
|
|
|
745
849
|
${row('mindos stop', 'Stop running MindOS processes')}
|
|
746
850
|
${row('mindos restart', 'Stop then start again')}
|
|
747
851
|
${row('mindos build', 'Build the app for production')}
|
|
852
|
+
${row('mindos open', 'Open Web UI in the default browser')}
|
|
853
|
+
|
|
854
|
+
${bold('MCP:')}
|
|
748
855
|
${row('mindos mcp', 'Start MCP server only')}
|
|
749
856
|
${row('mindos mcp install [agent]', 'Install MindOS MCP config into Agent (claude-code/cursor/windsurf/…) [-g]')}
|
|
750
|
-
${row('mindos open', 'Open Web UI in the default browser')}
|
|
751
857
|
${row('mindos token', 'Show current auth token and MCP config snippet')}
|
|
858
|
+
|
|
859
|
+
${bold('Sync:')}
|
|
752
860
|
${row('mindos sync', 'Show sync status (init/now/conflicts/on/off)')}
|
|
861
|
+
|
|
862
|
+
${bold('Gateway (Background Service):')}
|
|
753
863
|
${row('mindos gateway <subcommand>', 'Manage background service (install/uninstall/start/stop/status/logs)')}
|
|
864
|
+
|
|
865
|
+
${bold('Config & Diagnostics:')}
|
|
866
|
+
${row('mindos config <subcommand>', 'View/update config (show/validate/set/unset)')}
|
|
754
867
|
${row('mindos doctor', 'Health check (config, ports, build, daemon)')}
|
|
755
868
|
${row('mindos update', 'Update MindOS to the latest version')}
|
|
756
869
|
${row('mindos logs', 'Tail service logs (~/.mindos/mindos.log)')}
|
|
757
|
-
${row('mindos config <subcommand>', 'View/update config (show/validate/set)')}
|
|
758
870
|
${row('mindos', 'Start using mode saved in ~/.mindos/config.json')}
|
|
759
871
|
`);
|
|
760
|
-
|
|
872
|
+
const isHelp = (cmd === '--help' || cmd === '-h');
|
|
873
|
+
process.exit((cmd && !isHelp) ? 1 : 0);
|
|
761
874
|
}
|
|
762
875
|
|
|
763
876
|
commands[resolvedCmd]();
|
package/bin/lib/build.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { execSync } from 'node:child_process';
|
|
2
2
|
import { existsSync, readFileSync, writeFileSync, rmSync } from 'node:fs';
|
|
3
|
+
import { createHash } from 'node:crypto';
|
|
3
4
|
import { resolve } from 'node:path';
|
|
4
|
-
import { ROOT, BUILD_STAMP } from './constants.js';
|
|
5
|
+
import { ROOT, BUILD_STAMP, DEPS_STAMP } from './constants.js';
|
|
5
6
|
import { red, dim, yellow } from './colors.js';
|
|
6
7
|
import { run } from './utils.js';
|
|
7
8
|
|
|
@@ -36,24 +37,58 @@ export function cleanNextDir() {
|
|
|
36
37
|
}
|
|
37
38
|
}
|
|
38
39
|
|
|
40
|
+
function depsHash() {
|
|
41
|
+
const lockPath = resolve(ROOT, 'app', 'package-lock.json');
|
|
42
|
+
try {
|
|
43
|
+
const content = readFileSync(lockPath);
|
|
44
|
+
return createHash('sha256').update(content).digest('hex').slice(0, 16);
|
|
45
|
+
} catch {
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function depsChanged() {
|
|
51
|
+
const currentHash = depsHash();
|
|
52
|
+
if (!currentHash) return true;
|
|
53
|
+
try {
|
|
54
|
+
const savedHash = readFileSync(DEPS_STAMP, 'utf-8').trim();
|
|
55
|
+
return savedHash !== currentHash;
|
|
56
|
+
} catch {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function writeDepsStamp() {
|
|
62
|
+
const hash = depsHash();
|
|
63
|
+
if (hash) {
|
|
64
|
+
try { writeFileSync(DEPS_STAMP, hash, 'utf-8'); } catch {}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
39
68
|
export function ensureAppDeps() {
|
|
40
69
|
const appNext = resolve(ROOT, 'app', 'node_modules', 'next', 'package.json');
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
run('npm install --prefer-offline --no-workspaces', resolve(ROOT, 'app'));
|
|
70
|
+
const needsInstall = !existsSync(appNext) || depsChanged();
|
|
71
|
+
if (!needsInstall) return;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
execSync('npm --version', { stdio: 'pipe' });
|
|
75
|
+
} catch {
|
|
76
|
+
console.error(red('\n\u2718 npm not found in PATH.\n'));
|
|
77
|
+
console.error(' MindOS needs npm to install its app dependencies on first run.');
|
|
78
|
+
console.error(' This usually means Node.js is installed via a version manager (nvm, fnm, volta, etc.)');
|
|
79
|
+
console.error(' that only loads in interactive shells, but not in /bin/sh.\n');
|
|
80
|
+
console.error(' Fix: add your Node.js bin directory to a profile that /bin/sh reads (~/.profile).');
|
|
81
|
+
console.error(' Example:');
|
|
82
|
+
console.error(dim(' echo \'export PATH="$HOME/.nvm/versions/node/$(node --version)/bin:$PATH"\' >> ~/.profile'));
|
|
83
|
+
console.error(dim(' source ~/.profile\n'));
|
|
84
|
+
console.error(' Then run `mindos start` again.\n');
|
|
85
|
+
process.exit(1);
|
|
58
86
|
}
|
|
87
|
+
|
|
88
|
+
const label = existsSync(appNext)
|
|
89
|
+
? 'Updating app dependencies (package-lock.json changed)...\n'
|
|
90
|
+
: 'Installing app dependencies (first run)...\n';
|
|
91
|
+
console.log(yellow(label));
|
|
92
|
+
run('npm install --prefer-offline --no-workspaces', resolve(ROOT, 'app'));
|
|
93
|
+
writeDepsStamp();
|
|
59
94
|
}
|
package/bin/lib/colors.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
const noColor = 'NO_COLOR' in process.env;
|
|
2
|
+
const forceColor = process.env.FORCE_COLOR !== undefined && process.env.FORCE_COLOR !== '0';
|
|
3
|
+
export const isTTY = noColor ? false : (forceColor || process.stdout.isTTY);
|
|
2
4
|
export const bold = (s) => isTTY ? `\x1b[1m${s}\x1b[0m` : s;
|
|
3
5
|
export const dim = (s) => isTTY ? `\x1b[2m${s}\x1b[0m` : s;
|
|
4
6
|
export const cyan = (s) => isTTY ? `\x1b[36m${s}\x1b[0m` : s;
|