@geminilight/mindos 0.5.3 → 0.5.5

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/scripts/setup.js CHANGED
@@ -133,6 +133,12 @@ const T = {
133
133
  mcpInstallDone: { en: (n) => `✔ ${n} agent(s) configured`, zh: (n) => `✔ 已配置 ${n} 个 Agent` },
134
134
  mcpSkipped: { en: ' → Skipped. Run `mindos mcp install` anytime to configure agents.', zh: ' → 已跳过。随时运行 `mindos mcp install` 配置 Agent。' },
135
135
 
136
+ // skill install step
137
+ skillInstalling: { en: (name) => `⏳ Installing Skill "${name}"...`, zh: (name) => `⏳ 正在安装 Skill "${name}"...` },
138
+ skillInstallOk: { en: (name) => ` ${c.green('✔')} Skill "${name}" installed`, zh: (name) => ` ${c.green('✔')} Skill "${name}" 已安装` },
139
+ skillInstallFail: { en: (name, msg) => ` ${c.red('✘')} Skill "${name}" failed: ${msg}`, zh: (name, msg) => ` ${c.red('✘')} Skill "${name}" 安装失败:${msg}` },
140
+ skillSkipped: { en: ' → No agents selected, skill install skipped.', zh: ' → 未选择 Agent,跳过 Skill 安装。' },
141
+
136
142
  // restart prompts (re-onboard with config changes)
137
143
  restartRequired: { en: 'Config changed. Service restart required.', zh: '配置已变更,需要重启服务。' },
138
144
  restartNow: { en: 'Restart now?', zh: '立即重启?' },
@@ -441,8 +447,13 @@ async function isSelfPort(port) {
441
447
  res.on('end', () => {
442
448
  try {
443
449
  const data = JSON.parse(body);
444
- resolve(data.service === 'mindos');
445
- } catch { resolve(false); }
450
+ // 200 with service=mindos definitely us.
451
+ // 401 Unauthorized → also us (webPassword is set).
452
+ resolve(data.service === 'mindos' || res.statusCode === 401);
453
+ } catch {
454
+ // Non-JSON but got a response on /api/health → likely us
455
+ resolve(res.statusCode === 401 || res.statusCode === 200);
456
+ }
446
457
  });
447
458
  });
448
459
  req.on('error', () => resolve(false));
@@ -671,7 +682,7 @@ async function runMcpInstallStep(mcpPort, authToken) {
671
682
 
672
683
  if (selected.length === 0) {
673
684
  write(c.dim(t('mcpSkipped') + '\n'));
674
- return;
685
+ return [];
675
686
  }
676
687
 
677
688
  write('\n' + c.dim(tf('mcpInstalling', selected.length) + '\n'));
@@ -705,6 +716,64 @@ async function runMcpInstallStep(mcpPort, authToken) {
705
716
  }
706
717
 
707
718
  console.log(`\n${c.green(tf('mcpInstallDone', okCount))}`);
719
+ return selected;
720
+ }
721
+
722
+ /* ── Skill auto-install ────────────────────────────────────────────────────── */
723
+
724
+ const UNIVERSAL_AGENTS = new Set([
725
+ 'amp', 'cline', 'codex', 'cursor', 'gemini-cli',
726
+ 'github-copilot', 'kimi-cli', 'opencode', 'warp',
727
+ ]);
728
+ const SKILL_UNSUPPORTED = new Set(['claude-desktop']);
729
+ const AGENT_NAME_MAP = {
730
+ 'claude-code': 'claude-code',
731
+ 'windsurf': 'windsurf',
732
+ 'trae': 'trae',
733
+ 'openclaw': 'openclaw',
734
+ 'codebuddy': 'codebuddy',
735
+ };
736
+
737
+ /**
738
+ * Install the appropriate MindOS Skill to selected agents via `npx skills add`.
739
+ * @param {string} template - 'en' | 'zh' | 'empty' | 'custom'
740
+ * @param {string[]} selectedAgents - MCP agent keys from the multi-select step
741
+ */
742
+ function runSkillInstallStep(template, selectedAgents) {
743
+ if (!selectedAgents || selectedAgents.length === 0) {
744
+ write(c.dim(t('skillSkipped') + '\n'));
745
+ return;
746
+ }
747
+
748
+ const skillName = template === 'zh' ? 'mindos-zh' : 'mindos';
749
+ const source = resolve(ROOT, 'skills');
750
+
751
+ // Filter to non-universal, skill-capable agents
752
+ const additionalAgents = selectedAgents
753
+ .filter(key => !UNIVERSAL_AGENTS.has(key) && !SKILL_UNSUPPORTED.has(key))
754
+ .map(key => AGENT_NAME_MAP[key] || key);
755
+
756
+ let cmd;
757
+ if (additionalAgents.length > 0) {
758
+ cmd = `npx skills add "${source}" -s ${skillName} -a ${additionalAgents.join(',')} -g -y`;
759
+ } else {
760
+ cmd = `npx skills add "${source}" -s ${skillName} -a universal -g -y`;
761
+ }
762
+
763
+ write(tf('skillInstalling', skillName) + '\n');
764
+
765
+ try {
766
+ execSync(cmd, {
767
+ encoding: 'utf-8',
768
+ timeout: 30_000,
769
+ env: { ...process.env, NODE_ENV: 'production' },
770
+ stdio: 'pipe',
771
+ });
772
+ write(tf('skillInstallOk', skillName) + '\n');
773
+ } catch (err) {
774
+ const msg = err.stderr || err.message || 'Unknown error';
775
+ write(tf('skillInstallFail', skillName, msg.split('\n')[0]) + '\n');
776
+ }
708
777
  }
709
778
 
710
779
  // ── GUI Setup ─────────────────────────────────────────────────────────────────
@@ -739,16 +808,34 @@ async function startGuiSetup() {
739
808
  // Read or create config, set setupPending
740
809
  let config = {};
741
810
  try { config = JSON.parse(readFileSync(CONFIG_PATH, 'utf-8')); } catch { /* ignore */ }
811
+
812
+ const isFirstTime = !config.mindRoot;
742
813
  config.setupPending = true;
743
814
  writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
744
815
 
745
- // Find a free port
746
- const port = await findFreePort(3000);
747
- if (config.port === undefined) {
748
- config.port = port;
749
- writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2) + '\n');
816
+ // Determine which port to use for the setup wizard
817
+ let usePort;
818
+ if (isFirstTime) {
819
+ // First-time onboard: use a temporary port (scan from 9100) so the user's
820
+ // chosen port in Step 3 can differ without a mid-setup restart.
821
+ // 9100 is chosen to avoid conflicts with common services (5000=AirPlay, 3000/8080=dev).
822
+ usePort = await findFreePort(9100);
823
+ } else {
824
+ // Re-onboard: service is already running on config.port — reuse it.
825
+ const existingPort = config.port || 3000;
826
+ if (await isSelfPort(existingPort)) {
827
+ // Service already running — just open the setup page, no need to spawn.
828
+ const url = `http://localhost:${existingPort}/setup`;
829
+ console.log(`\n${c.green(tf('guiReady', url))}\n`);
830
+ const opened = openBrowser(url);
831
+ if (!opened) console.log(c.dim(tf('guiOpenFailed', url)));
832
+ process.exit(0);
833
+ }
834
+ // Service not running — start on existing port
835
+ usePort = await isPortInUse(existingPort)
836
+ ? await findFreePort(9100) // existing port occupied by another process
837
+ : existingPort;
750
838
  }
751
- const usePort = config.port || port;
752
839
 
753
840
  write(c.yellow(t('guiStarting') + '\n'));
754
841
 
@@ -841,6 +928,15 @@ async function main() {
841
928
 
842
929
  const { readdirSync } = await import('node:fs');
843
930
  let mindDir;
931
+ let selectedTemplate = 'en'; // hoisted — set by template selection or inferred from existing config
932
+ // Infer template from existing config's disabledSkills or UI language
933
+ if (resumeCfg.disabledSkills?.includes('mindos')) {
934
+ selectedTemplate = 'zh';
935
+ } else if (resumeCfg.disabledSkills?.includes('mindos-zh')) {
936
+ selectedTemplate = 'en';
937
+ } else {
938
+ selectedTemplate = uiLang; // fallback to UI language for first-time existing KB
939
+ }
844
940
 
845
941
  // Default KB path: existing mindRoot if set, otherwise ~/MindOS (same as GUI default)
846
942
  const HOME = homedir();
@@ -875,9 +971,9 @@ async function main() {
875
971
  } else {
876
972
  // ── Template selection (part of Step 1) ─────────────────────────────
877
973
  write('\n');
878
- const tpl = await select('tplPrompt', 'tplOptions', 'tplValues');
974
+ selectedTemplate = await select('tplPrompt', 'tplOptions', 'tplValues');
879
975
  mkdirSync(mindDir, { recursive: true });
880
- await applyTemplate(tpl, mindDir);
976
+ await applyTemplate(selectedTemplate, mindDir);
881
977
  break;
882
978
  }
883
979
  }
@@ -1000,12 +1096,13 @@ async function main() {
1000
1096
  }
1001
1097
 
1002
1098
  const config = {
1003
- mindRoot: mindDir,
1004
- port: webPort,
1005
- mcpPort: mcpPort,
1006
- authToken: authToken,
1007
- webPassword: webPassword || '',
1008
- startMode: startMode,
1099
+ mindRoot: mindDir,
1100
+ port: webPort,
1101
+ mcpPort: mcpPort,
1102
+ authToken: authToken,
1103
+ webPassword: webPassword || '',
1104
+ startMode: startMode,
1105
+ disabledSkills: selectedTemplate === 'zh' ? ['mindos'] : ['mindos-zh'],
1009
1106
  ai: {
1010
1107
  provider: isSkip ? existingAiProvider : (isAnthropic ? 'anthropic' : 'openai'),
1011
1108
  providers: existingProviders,
@@ -1052,7 +1149,13 @@ async function main() {
1052
1149
  stepHeader(7);
1053
1150
  write(c.dim(tf('mcpStepHint') + '\n\n'));
1054
1151
 
1055
- await runMcpInstallStep(mcpPort, authToken);
1152
+ const selectedAgents = await runMcpInstallStep(mcpPort, authToken);
1153
+
1154
+ // ── Skill auto-install ────────────────────────────────────────────────────
1155
+ if (selectedAgents && selectedAgents.length > 0) {
1156
+ write('\n');
1157
+ runSkillInstallStep(selectedTemplate, selectedAgents);
1158
+ }
1056
1159
 
1057
1160
  // ── Sync setup (optional) ──────────────────────────────────────────────────
1058
1161
  const wantSync = await askYesNo('syncSetup');