@geminilight/mindos 0.3.0 → 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.
Files changed (76) hide show
  1. package/app/app/api/mcp/agents/route.ts +72 -0
  2. package/app/app/api/mcp/install/route.ts +95 -0
  3. package/app/app/api/mcp/status/route.ts +47 -0
  4. package/app/app/api/skills/route.ts +208 -0
  5. package/app/app/api/sync/route.ts +54 -3
  6. package/app/app/api/update-check/route.ts +52 -0
  7. package/app/app/globals.css +12 -0
  8. package/app/app/layout.tsx +4 -2
  9. package/app/app/login/page.tsx +20 -13
  10. package/app/app/page.tsx +17 -2
  11. package/app/app/view/[...path]/ViewPageClient.tsx +47 -21
  12. package/app/app/view/[...path]/loading.tsx +1 -1
  13. package/app/app/view/[...path]/not-found.tsx +101 -0
  14. package/app/components/AskFab.tsx +1 -1
  15. package/app/components/AskModal.tsx +1 -1
  16. package/app/components/Backlinks.tsx +1 -1
  17. package/app/components/Breadcrumb.tsx +13 -3
  18. package/app/components/CsvView.tsx +5 -6
  19. package/app/components/DirView.tsx +42 -21
  20. package/app/components/FindInPage.tsx +211 -0
  21. package/app/components/HomeContent.tsx +97 -44
  22. package/app/components/JsonView.tsx +1 -2
  23. package/app/components/MarkdownEditor.tsx +1 -2
  24. package/app/components/OnboardingView.tsx +6 -7
  25. package/app/components/SettingsModal.tsx +5 -2
  26. package/app/components/SetupWizard.tsx +4 -4
  27. package/app/components/Sidebar.tsx +1 -1
  28. package/app/components/UpdateBanner.tsx +101 -0
  29. package/app/components/renderers/{AgentInspectorRenderer.tsx → agent-inspector/AgentInspectorRenderer.tsx} +13 -11
  30. package/app/components/renderers/agent-inspector/manifest.ts +14 -0
  31. package/app/components/renderers/{BacklinksRenderer.tsx → backlinks/BacklinksRenderer.tsx} +6 -6
  32. package/app/components/renderers/backlinks/manifest.ts +14 -0
  33. package/app/components/renderers/config/manifest.ts +14 -0
  34. package/app/components/renderers/csv/BoardView.tsx +12 -12
  35. package/app/components/renderers/csv/ConfigPanel.tsx +7 -8
  36. package/app/components/renderers/{CsvRenderer.tsx → csv/CsvRenderer.tsx} +8 -9
  37. package/app/components/renderers/csv/GalleryView.tsx +3 -3
  38. package/app/components/renderers/csv/TableView.tsx +4 -5
  39. package/app/components/renderers/csv/manifest.ts +14 -0
  40. package/app/components/renderers/{DiffRenderer.tsx → diff/DiffRenderer.tsx} +10 -9
  41. package/app/components/renderers/diff/manifest.ts +14 -0
  42. package/app/components/renderers/{GraphRenderer.tsx → graph/GraphRenderer.tsx} +4 -5
  43. package/app/components/renderers/graph/manifest.ts +14 -0
  44. package/app/components/renderers/{SummaryRenderer.tsx → summary/SummaryRenderer.tsx} +6 -6
  45. package/app/components/renderers/summary/manifest.ts +14 -0
  46. package/app/components/renderers/{TimelineRenderer.tsx → timeline/TimelineRenderer.tsx} +6 -6
  47. package/app/components/renderers/timeline/manifest.ts +14 -0
  48. package/app/components/renderers/{TodoRenderer.tsx → todo/TodoRenderer.tsx} +2 -2
  49. package/app/components/renderers/todo/manifest.ts +14 -0
  50. package/app/components/renderers/{WorkflowRenderer.tsx → workflow/WorkflowRenderer.tsx} +13 -13
  51. package/app/components/renderers/workflow/manifest.ts +14 -0
  52. package/app/components/settings/McpTab.tsx +549 -0
  53. package/app/components/settings/SyncTab.tsx +139 -50
  54. package/app/components/settings/types.ts +1 -1
  55. package/app/data/pages/home.png +0 -0
  56. package/app/lib/i18n.ts +178 -10
  57. package/app/lib/renderers/index.ts +20 -89
  58. package/app/lib/renderers/registry.ts +4 -1
  59. package/app/lib/settings.ts +3 -0
  60. package/app/package.json +1 -0
  61. package/app/types/semver.d.ts +8 -0
  62. package/bin/cli.js +137 -24
  63. package/bin/lib/build.js +53 -18
  64. package/bin/lib/colors.js +3 -1
  65. package/bin/lib/config.js +4 -0
  66. package/bin/lib/constants.js +2 -0
  67. package/bin/lib/debug.js +10 -0
  68. package/bin/lib/startup.js +21 -20
  69. package/bin/lib/stop.js +41 -3
  70. package/bin/lib/sync.js +65 -53
  71. package/bin/lib/update-check.js +94 -0
  72. package/bin/lib/utils.js +2 -2
  73. package/package.json +1 -1
  74. package/scripts/gen-renderer-index.js +57 -0
  75. package/scripts/setup.js +24 -0
  76. /package/app/components/renderers/{ConfigRenderer.tsx → config/ConfigRenderer.tsx} +0 -0
package/bin/lib/sync.js CHANGED
@@ -139,52 +139,66 @@ let activePullInterval = null;
139
139
  /**
140
140
  * Interactive sync init — configure remote git repo
141
141
  */
142
- export async function initSync(mindRoot) {
142
+ export async function initSync(mindRoot, opts = {}) {
143
143
  if (!mindRoot) { console.error(red('No mindRoot configured.')); process.exit(1); }
144
144
 
145
- const readline = await import('node:readline');
146
- const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
147
- const ask = (q) => new Promise(r => rl.question(q, r));
145
+ const nonInteractive = opts.nonInteractive || false;
146
+ let remoteUrl = opts.remote || '';
147
+ let token = opts.token || '';
148
+ let branch = opts.branch || 'main';
148
149
 
149
- // 1. Ensure git repo
150
- if (!isGitRepo(mindRoot)) {
151
- console.log(dim('Initializing git repository...'));
152
- execSync('git init', { cwd: mindRoot, stdio: 'inherit' });
153
- execSync('git checkout -b main', { cwd: mindRoot, stdio: 'pipe' }).toString();
154
- }
150
+ if (nonInteractive) {
151
+ // Non-interactive mode: all params from opts
152
+ if (!remoteUrl) {
153
+ throw new Error('Remote URL is required in non-interactive mode');
154
+ }
155
+ } else {
156
+ // Interactive mode: prompt user
157
+ const readline = await import('node:readline');
158
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
159
+ const ask = (q) => new Promise(r => rl.question(q, r));
160
+
161
+ // 2. Remote URL
162
+ const currentRemote = getRemoteUrl(mindRoot);
163
+ const defaultUrl = currentRemote || '';
164
+ const urlPrompt = currentRemote
165
+ ? `${bold('Remote URL')} ${dim(`[${currentRemote}]`)}: `
166
+ : `${bold('Remote URL')} ${dim('(HTTPS or SSH)')}: `;
167
+ remoteUrl = (await ask(urlPrompt)).trim() || defaultUrl;
168
+
169
+ if (!remoteUrl) {
170
+ console.error(red('Remote URL is required.'));
171
+ rl.close();
172
+ process.exit(1);
173
+ }
155
174
 
156
- // 2. Remote URL
157
- const currentRemote = getRemoteUrl(mindRoot);
158
- const defaultUrl = currentRemote || '';
159
- const urlPrompt = currentRemote
160
- ? `${bold('Remote URL')} ${dim(`[${currentRemote}]`)}: `
161
- : `${bold('Remote URL')} ${dim('(HTTPS or SSH)')}: `;
162
- let remoteUrl = (await ask(urlPrompt)).trim() || defaultUrl;
175
+ // 3. Token for HTTPS
176
+ if (remoteUrl.startsWith('https://')) {
177
+ token = (await ask(`${bold('Access Token')} ${dim('(GitHub PAT / GitLab PAT, leave empty if SSH)')}: `)).trim();
178
+ }
163
179
 
164
- if (!remoteUrl) {
165
- console.error(red('Remote URL is required.'));
166
180
  rl.close();
167
- process.exit(1);
168
181
  }
169
182
 
170
- // 3. Token for HTTPS
171
- let token = '';
172
- if (remoteUrl.startsWith('https://')) {
173
- token = (await ask(`${bold('Access Token')} ${dim('(GitHub PAT / GitLab PAT, leave empty if SSH)')}: `)).trim();
174
- if (token) {
175
- // Inject token into URL for credential storage
176
- const urlObj = new URL(remoteUrl);
177
- urlObj.username = 'oauth2';
178
- urlObj.password = token;
179
- const authUrl = urlObj.toString();
180
- // Configure credential helper
181
- try { execSync(`git config credential.helper store`, { cwd: mindRoot, stdio: 'pipe' }); } catch {}
182
- // Store the credential
183
- try {
184
- const credInput = `protocol=${urlObj.protocol.replace(':', '')}\nhost=${urlObj.host}\nusername=oauth2\npassword=${token}\n\n`;
185
- execSync('git credential approve', { cwd: mindRoot, input: credInput, stdio: 'pipe' });
186
- } catch {}
187
- }
183
+ // 1. Ensure git repo
184
+ if (!isGitRepo(mindRoot)) {
185
+ if (!nonInteractive) console.log(dim('Initializing git repository...'));
186
+ execSync('git init', { cwd: mindRoot, stdio: 'pipe' });
187
+ try { execSync('git checkout -b main', { cwd: mindRoot, stdio: 'pipe' }); } catch {}
188
+ }
189
+
190
+ // Handle token for HTTPS
191
+ if (token && remoteUrl.startsWith('https://')) {
192
+ const urlObj = new URL(remoteUrl);
193
+ urlObj.username = 'oauth2';
194
+ urlObj.password = token;
195
+ // Configure credential helper
196
+ try { execSync(`git config credential.helper store`, { cwd: mindRoot, stdio: 'pipe' }); } catch {}
197
+ // Store the credential
198
+ try {
199
+ const credInput = `protocol=${urlObj.protocol.replace(':', '')}\nhost=${urlObj.host}\nusername=oauth2\npassword=${token}\n\n`;
200
+ execSync('git credential approve', { cwd: mindRoot, input: credInput, stdio: 'pipe' });
201
+ } catch {}
188
202
  }
189
203
 
190
204
  // 4. Set remote
@@ -195,50 +209,48 @@ export async function initSync(mindRoot) {
195
209
  }
196
210
 
197
211
  // 5. Test connection
198
- console.log(dim('Testing connection...'));
212
+ if (!nonInteractive) console.log(dim('Testing connection...'));
199
213
  try {
200
- execSync('git ls-remote --exit-code origin', { cwd: mindRoot, stdio: 'pipe' });
201
- console.log(green('✔ Connection successful'));
214
+ execSync('git ls-remote --exit-code origin', { cwd: mindRoot, stdio: 'pipe', timeout: 15000 });
215
+ if (!nonInteractive) console.log(green('✔ Connection successful'));
202
216
  } catch {
217
+ const errMsg = 'Remote not reachable — check URL and credentials';
218
+ if (nonInteractive) throw new Error(errMsg);
203
219
  console.error(red('✘ Could not connect to remote. Check your URL and credentials.'));
204
- rl.close();
205
220
  process.exit(1);
206
221
  }
207
222
 
208
- rl.close();
209
-
210
223
  // 6. Save sync config
211
224
  const syncConfig = {
212
225
  enabled: true,
213
226
  provider: 'git',
214
227
  remote: 'origin',
215
- branch: getBranch(mindRoot),
228
+ branch: branch || getBranch(mindRoot),
216
229
  autoCommitInterval: 30,
217
230
  autoPullInterval: 300,
218
231
  };
219
232
  saveSyncConfig(syncConfig);
220
- console.log(green('✔ Sync configured'));
233
+ if (!nonInteractive) console.log(green('✔ Sync configured'));
221
234
 
222
235
  // 7. First sync: pull if remote has content, push otherwise
223
236
  try {
224
237
  const refs = gitExec('git ls-remote --heads origin', mindRoot);
225
238
  if (refs) {
226
- console.log(dim('Pulling from remote...'));
239
+ if (!nonInteractive) console.log(dim('Pulling from remote...'));
227
240
  try {
228
- execSync(`git pull origin ${syncConfig.branch} --allow-unrelated-histories`, { cwd: mindRoot, stdio: 'inherit' });
241
+ execSync(`git pull origin ${syncConfig.branch} --allow-unrelated-histories`, { cwd: mindRoot, stdio: nonInteractive ? 'pipe' : 'inherit' });
229
242
  } catch {
230
- // Might fail if empty or conflicts that's fine for initial setup
231
- console.log(yellow('Pull completed with warnings. Check for conflicts.'));
243
+ if (!nonInteractive) console.log(yellow('Pull completed with warnings. Check for conflicts.'));
232
244
  }
233
245
  } else {
234
- console.log(dim('Pushing to remote...'));
246
+ if (!nonInteractive) console.log(dim('Pushing to remote...'));
235
247
  autoCommitAndPush(mindRoot);
236
248
  }
237
249
  } catch {
238
- console.log(dim('Performing initial push...'));
250
+ if (!nonInteractive) console.log(dim('Performing initial push...'));
239
251
  autoCommitAndPush(mindRoot);
240
252
  }
241
- console.log(green('✔ Initial sync complete\n'));
253
+ if (!nonInteractive) console.log(green('✔ Initial sync complete\n'));
242
254
  }
243
255
 
244
256
  /**
@@ -0,0 +1,94 @@
1
+ import { existsSync, readFileSync, writeFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import { ROOT, UPDATE_CHECK_PATH } from './constants.js';
4
+ import { bold, dim, cyan, yellow } from './colors.js';
5
+
6
+ const TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
7
+
8
+ const REGISTRIES = [
9
+ 'https://registry.npmmirror.com/@geminilight/mindos/latest',
10
+ 'https://registry.npmjs.org/@geminilight/mindos/latest',
11
+ ];
12
+
13
+ /** Simple semver "a > b" comparison (major.minor.patch only). */
14
+ function semverGt(a, b) {
15
+ const pa = a.split('.').map(Number);
16
+ const pb = b.split('.').map(Number);
17
+ for (let i = 0; i < 3; i++) {
18
+ if ((pa[i] || 0) > (pb[i] || 0)) return true;
19
+ if ((pa[i] || 0) < (pb[i] || 0)) return false;
20
+ }
21
+ return false;
22
+ }
23
+
24
+ function getCurrentVersion() {
25
+ try {
26
+ return JSON.parse(readFileSync(resolve(ROOT, 'package.json'), 'utf-8')).version;
27
+ } catch {
28
+ return '0.0.0';
29
+ }
30
+ }
31
+
32
+ function readCache() {
33
+ try {
34
+ return JSON.parse(readFileSync(UPDATE_CHECK_PATH, 'utf-8'));
35
+ } catch {
36
+ return null;
37
+ }
38
+ }
39
+
40
+ function writeCache(latestVersion) {
41
+ try {
42
+ writeFileSync(UPDATE_CHECK_PATH, JSON.stringify({
43
+ lastCheck: new Date().toISOString(),
44
+ latestVersion,
45
+ }), 'utf-8');
46
+ } catch { /* best-effort */ }
47
+ }
48
+
49
+ async function fetchLatest() {
50
+ for (const url of REGISTRIES) {
51
+ try {
52
+ const res = await fetch(url, { signal: AbortSignal.timeout(3000) });
53
+ if (res.ok) {
54
+ const data = await res.json();
55
+ return data.version;
56
+ }
57
+ } catch {
58
+ continue;
59
+ }
60
+ }
61
+ return null;
62
+ }
63
+
64
+ /**
65
+ * Check for updates. Returns the latest version string if an update is
66
+ * available, or null if up-to-date / check fails.
67
+ */
68
+ export async function checkForUpdate() {
69
+ if (process.env.MINDOS_NO_UPDATE_CHECK === '1') return null;
70
+
71
+ const current = getCurrentVersion();
72
+ const cache = readCache();
73
+
74
+ // Cache hit — still fresh
75
+ if (cache?.lastCheck) {
76
+ const age = Date.now() - new Date(cache.lastCheck).getTime();
77
+ if (age < TTL_MS) {
78
+ return (cache.latestVersion && semverGt(cache.latestVersion, current))
79
+ ? cache.latestVersion
80
+ : null;
81
+ }
82
+ }
83
+
84
+ // Cache miss or expired — fetch
85
+ const latest = await fetchLatest();
86
+ if (latest) writeCache(latest);
87
+ return (latest && semverGt(latest, current)) ? latest : null;
88
+ }
89
+
90
+ /** Print update hint line if an update is available. */
91
+ export function printUpdateHint(latestVersion) {
92
+ const current = getCurrentVersion();
93
+ console.log(`\n ${yellow('⬆')} ${bold(`MindOS v${latestVersion}`)} available ${dim(`(current: v${current})`)}. Run ${cyan('mindos update')} to upgrade.`);
94
+ }
package/bin/lib/utils.js CHANGED
@@ -6,8 +6,8 @@ import { ROOT } from './constants.js';
6
6
  export function run(command, cwd = ROOT) {
7
7
  try {
8
8
  execSync(command, { cwd, stdio: 'inherit', env: process.env });
9
- } catch {
10
- process.exit(1);
9
+ } catch (err) {
10
+ process.exit(err.status || 1);
11
11
  }
12
12
  }
13
13
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@geminilight/mindos",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "MindOS — Human-Agent Collaborative Mind System. Local-first knowledge base that syncs your mind to all AI Agents via MCP.",
5
5
  "keywords": [
6
6
  "mindos",
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ // Scans app/components/renderers/*/manifest.ts and generates
3
+ // app/lib/renderers/index.ts with auto-discovered imports.
4
+ //
5
+ // Run: node scripts/gen-renderer-index.js
6
+ // Hooked into: npm run build (via prebuild)
7
+
8
+ import fs from 'fs';
9
+ import path from 'path';
10
+ import { fileURLToPath } from 'url';
11
+
12
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
13
+ const renderersDir = path.resolve(__dirname, '../app/components/renderers');
14
+ const outputFile = path.resolve(__dirname, '../app/lib/renderers/index.ts');
15
+
16
+ // Scan for manifest.ts in immediate subdirectories
17
+ const dirs = fs.readdirSync(renderersDir, { withFileTypes: true })
18
+ .filter(d => d.isDirectory())
19
+ .filter(d => fs.existsSync(path.join(renderersDir, d.name, 'manifest.ts')))
20
+ .map(d => d.name)
21
+ .sort();
22
+
23
+ if (dirs.length === 0) {
24
+ console.error('No manifest.ts files found in', renderersDir);
25
+ process.exit(1);
26
+ }
27
+
28
+ // kebab-case → camelCase
29
+ function toCamel(s) {
30
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
31
+ }
32
+
33
+ const imports = dirs.map(dir => {
34
+ const varName = toCamel(dir);
35
+ return `import { manifest as ${varName} } from '@/components/renderers/${dir}/manifest';`;
36
+ }).join('\n');
37
+
38
+ const varNames = dirs.map(toCamel);
39
+
40
+ const code = `/**
41
+ * AUTO-GENERATED by scripts/gen-renderer-index.js — do not edit manually.
42
+ * To regenerate: node scripts/gen-renderer-index.js
43
+ */
44
+ import { registerRenderer } from './registry';
45
+ ${imports}
46
+
47
+ const manifests = [
48
+ ${varNames.join(', ')},
49
+ ];
50
+
51
+ for (const m of manifests) {
52
+ registerRenderer(m);
53
+ }
54
+ `;
55
+
56
+ fs.writeFileSync(outputFile, code, 'utf-8');
57
+ console.log(`Generated ${path.relative(process.cwd(), outputFile)} with ${dirs.length} renderers: ${dirs.join(', ')}`);
package/scripts/setup.js CHANGED
@@ -114,6 +114,8 @@ const T = {
114
114
  cfgKept: { en: '✔ Keeping existing config', zh: '✔ 保留现有配置' },
115
115
  cfgKeptNote: { en: ' Settings from this session were not saved', zh: ' 本次填写的设置未保存' },
116
116
  cfgSaved: { en: '✔ Config saved', zh: '✔ 配置已保存' },
117
+ cfgConfirm: { en: 'Save this configuration?', zh: '保存此配置?' },
118
+ cfgAborted: { en: '✘ Setup cancelled. Run `mindos onboard` to try again.', zh: '✘ 设置已取消。运行 `mindos onboard` 重新开始。' },
117
119
  yesNo: { en: '[y/N]', zh: '[y/N]' },
118
120
  yesNoDefault: { en: '[Y/n]', zh: '[Y/n]' },
119
121
  startNow: { en: 'Start MindOS now?', zh: '现在启动 MindOS?' },
@@ -764,6 +766,28 @@ async function main() {
764
766
  },
765
767
  };
766
768
 
769
+ // ── Configuration Summary & Confirmation ──────────────────────────────────
770
+ const maskPw = (s) => s ? '•'.repeat(Math.min(s.length, 8)) : '';
771
+ const maskTk = (s) => s && s.length > 8 ? s.slice(0, 8) + '····' : (s ? s.slice(0, 4) + '····' : '');
772
+ const sep = '━'.repeat(40);
773
+ write(`\n${sep}\n`);
774
+ write(`${c.bold(uiLang === 'zh' ? '配置摘要' : 'Configuration Summary')}\n`);
775
+ write(`${sep}\n`);
776
+ write(` ${c.dim('Knowledge base:')} ${mindDir}\n`);
777
+ write(` ${c.dim('Web port:')} ${webPort}\n`);
778
+ write(` ${c.dim('MCP port:')} ${mcpPort}\n`);
779
+ write(` ${c.dim('Auth token:')} ${maskTk(authToken)}\n`);
780
+ if (webPassword) write(` ${c.dim('Web password:')} ${maskPw(webPassword)}\n`);
781
+ write(` ${c.dim('AI provider:')} ${config.ai.provider}\n`);
782
+ write(` ${c.dim('Start mode:')} ${startMode}\n`);
783
+ write(`${sep}\n`);
784
+
785
+ const confirmSave = await askYesNoDefault('cfgConfirm');
786
+ if (!confirmSave) {
787
+ console.log(c.red(t('cfgAborted')));
788
+ process.exit(0);
789
+ }
790
+
767
791
  mkdirSync(MINDOS_DIR, { recursive: true });
768
792
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
769
793
  console.log(`\n${c.green(t('cfgSaved'))}: ${c.dim(CONFIG_PATH)}`);