@geminilight/mindos 0.5.29 → 0.5.32

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.
@@ -92,26 +92,33 @@ git push origin main
92
92
  git push origin "$VERSION"
93
93
  echo ""
94
94
 
95
- # 5. Wait for CI (if gh is available)
95
+ # 5. Wait for CI
96
+ # Flow: tag push → sync-to-mindos (syncs code + tag to public repo) → public repo publish-npm
96
97
  if command -v gh &>/dev/null; then
97
- echo "⏳ Waiting for CI publish workflow..."
98
+ echo "⏳ Waiting for sync publish pipeline..."
99
+ echo " mindos-dev tag push → sync-to-mindos → GeminiLight/MindOS tag → npm publish"
98
100
  TIMEOUT=120
99
101
  ELAPSED=0
100
102
  RUN_ID=""
101
103
 
102
- # Wait for the workflow run to appear
104
+ # Watch the sync workflow on mindos-dev
103
105
  while [ -z "$RUN_ID" ] && [ "$ELAPSED" -lt 30 ]; do
104
106
  sleep 3
105
107
  ELAPSED=$((ELAPSED + 3))
106
- RUN_ID=$(gh run list --workflow=publish-npm.yml --limit=1 --json databaseId,headBranch --jq ".[0].databaseId" 2>/dev/null || true)
108
+ RUN_ID=$(gh run list --workflow=sync-to-mindos.yml --limit=1 --json databaseId,headBranch --jq ".[0].databaseId" 2>/dev/null || true)
107
109
  done
108
110
 
109
111
  if [ -n "$RUN_ID" ]; then
110
- gh run watch "$RUN_ID" --exit-status && echo "✅ Published $VERSION to npm" || echo "❌ CI failed — check: gh run view $RUN_ID --log"
112
+ gh run watch "$RUN_ID" --exit-status && echo "✅ Synced $VERSION to GeminiLight/MindOS" || echo "❌ Sync failed — check: gh run view $RUN_ID --log"
113
+ echo " npm publish will be triggered on GeminiLight/MindOS."
114
+ echo " Check: https://github.com/GeminiLight/MindOS/actions"
111
115
  else
112
- echo "⚠️ Could not find CI run. Check manually: https://github.com/GeminiLight/mindos-dev/actions"
116
+ echo "⚠️ Could not find CI run. Check manually:"
117
+ echo " Sync: https://github.com/GeminiLight/mindos-dev/actions"
118
+ echo " Publish: https://github.com/GeminiLight/MindOS/actions"
113
119
  fi
114
120
  else
115
- echo "💡 Install 'gh' CLI to auto-watch CI status."
116
- echo " Check publish status: https://github.com/GeminiLight/mindos-dev/actions"
121
+ echo "💡 Release pipeline: mindos-dev sync GeminiLight/MindOS → npm publish"
122
+ echo " Check sync: https://github.com/GeminiLight/mindos-dev/actions"
123
+ echo " Check publish: https://github.com/GeminiLight/MindOS/actions"
117
124
  fi
package/scripts/setup.js CHANGED
@@ -186,14 +186,18 @@ function write(s) { process.stdout.write(s); }
186
186
  // ── State ─────────────────────────────────────────────────────────────────────
187
187
 
188
188
  function detectSystemLang() {
189
- // Check env vars first (Linux / macOS / WSL)
190
- const vars = [
191
- process.env.LANG,
189
+ // Check env vars by precedence (LC_ALL > LC_MESSAGES > LANG > LANGUAGE)
190
+ // For LANGUAGE, only use the first entry before ':' (it's a priority list)
191
+ const candidates = [
192
192
  process.env.LC_ALL,
193
193
  process.env.LC_MESSAGES,
194
- process.env.LANGUAGE,
195
- ].filter(Boolean).join(' ').toLowerCase();
196
- if (vars.includes('zh')) return 'zh';
194
+ process.env.LANG,
195
+ ...(process.env.LANGUAGE ? [process.env.LANGUAGE.split(':')[0]] : []),
196
+ ];
197
+ const first = candidates.find(Boolean);
198
+ if (first) {
199
+ return first.toLowerCase().startsWith('zh') ? 'zh' : 'en';
200
+ }
197
201
 
198
202
  // Fallback: Intl API (works on Windows where LANG is often unset)
199
203
  try {
@@ -1,274 +0,0 @@
1
- 'use client';
2
-
3
- import { useState, useEffect, useMemo, useCallback } from 'react';
4
- import { Plug, Copy, Check, ChevronDown, Monitor, Globe, Code } from 'lucide-react';
5
- import { copyToClipboard } from '@/lib/clipboard';
6
- import type { McpStatus, AgentInfo, McpServerStatusProps } from './types';
7
-
8
- /* ── Helpers ───────────────────────────────────────────────────── */
9
-
10
- function CopyButton({ text, label, copiedLabel }: { text: string; label: string; copiedLabel?: string }) {
11
- const [copied, setCopied] = useState(false);
12
- const handleCopy = useCallback(async () => {
13
- const ok = await copyToClipboard(text);
14
- if (ok) {
15
- setCopied(true);
16
- setTimeout(() => setCopied(false), 2000);
17
- }
18
- }, [text]);
19
- return (
20
- <button
21
- type="button"
22
- onClick={handleCopy}
23
- className="inline-flex items-center gap-1 px-2.5 py-1 text-xs rounded-md border border-border text-muted-foreground hover:text-foreground hover:bg-muted transition-colors cursor-pointer shrink-0 relative z-10"
24
- >
25
- {copied ? <Check size={11} /> : <Copy size={11} />}
26
- {copied ? (copiedLabel ?? 'Copied!') : label}
27
- </button>
28
- );
29
- }
30
-
31
- /* ── Config Snippet Generator ─────────────────────────────────── */
32
-
33
- interface ConfigSnippet {
34
- /** Snippet with full token — for clipboard copy */
35
- snippet: string;
36
- /** Snippet with masked token — for display in UI */
37
- displaySnippet: string;
38
- path: string;
39
- }
40
-
41
- function generateStdioSnippet(agent: AgentInfo): ConfigSnippet {
42
- const stdioEntry: Record<string, unknown> = { type: 'stdio', command: 'mindos', args: ['mcp'] };
43
-
44
- if (agent.format === 'toml') {
45
- const lines = [
46
- `[${agent.configKey}.mindos]`,
47
- `command = "mindos"`,
48
- `args = ["mcp"]`,
49
- '',
50
- `[${agent.configKey}.mindos.env]`,
51
- `MCP_TRANSPORT = "stdio"`,
52
- ];
53
- const s = lines.join('\n');
54
- return { snippet: s, displaySnippet: s, path: agent.globalPath };
55
- }
56
-
57
- if (agent.globalNestedKey) {
58
- const s = JSON.stringify({ [agent.configKey]: { mindos: stdioEntry } }, null, 2);
59
- return { snippet: s, displaySnippet: s, path: agent.projectPath ?? agent.globalPath };
60
- }
61
-
62
- const s = JSON.stringify({ [agent.configKey]: { mindos: stdioEntry } }, null, 2);
63
- return { snippet: s, displaySnippet: s, path: agent.globalPath };
64
- }
65
-
66
- function generateHttpSnippet(
67
- agent: AgentInfo,
68
- endpoint: string,
69
- token?: string,
70
- maskedToken?: string,
71
- ): ConfigSnippet {
72
- // Full token for copy
73
- const httpEntry: Record<string, unknown> = { url: endpoint };
74
- if (token) httpEntry.headers = { Authorization: `Bearer ${token}` };
75
-
76
- // Masked token for display
77
- const displayEntry: Record<string, unknown> = { url: endpoint };
78
- if (maskedToken) displayEntry.headers = { Authorization: `Bearer ${maskedToken}` };
79
-
80
- const buildSnippet = (entry: Record<string, unknown>) => {
81
- if (agent.format === 'toml') {
82
- const lines = [
83
- `[${agent.configKey}.mindos]`,
84
- `type = "http"`,
85
- `url = "${endpoint}"`,
86
- ];
87
- const authVal = (entry.headers as Record<string, string>)?.Authorization;
88
- if (authVal) {
89
- lines.push('');
90
- lines.push(`[${agent.configKey}.mindos.headers]`);
91
- lines.push(`Authorization = "${authVal}"`);
92
- }
93
- return lines.join('\n');
94
- }
95
-
96
- if (agent.globalNestedKey) {
97
- return JSON.stringify({ [agent.configKey]: { mindos: entry } }, null, 2);
98
- }
99
-
100
- return JSON.stringify({ [agent.configKey]: { mindos: entry } }, null, 2);
101
- };
102
-
103
- return {
104
- snippet: buildSnippet(httpEntry),
105
- displaySnippet: buildSnippet(token ? displayEntry : httpEntry),
106
- path: agent.format === 'toml' ? agent.globalPath : (agent.globalNestedKey ? (agent.projectPath ?? agent.globalPath) : agent.globalPath),
107
- };
108
- }
109
-
110
- /* ── MCP Server Status ─────────────────────────────────────────── */
111
-
112
- export default function ServerStatus({ status, agents, t }: McpServerStatusProps) {
113
- const m = t.settings?.mcp;
114
- const [selectedAgent, setSelectedAgent] = useState<string>('');
115
- const [mode, setMode] = useState<'stdio' | 'http'>('stdio');
116
- const [showSnippet, setShowSnippet] = useState(false);
117
-
118
- useEffect(() => {
119
- if (agents.length > 0 && !selectedAgent) {
120
- const first = agents.find(a => a.installed) ?? agents.find(a => a.present) ?? agents[0];
121
- if (first) setSelectedAgent(first.key);
122
- }
123
- }, [agents, selectedAgent]);
124
-
125
- useEffect(() => {
126
- if (status?.endpoint && !status.endpoint.includes('127.0.0.1') && !status.endpoint.includes('localhost')) {
127
- setMode('http');
128
- }
129
- }, [status?.endpoint]);
130
-
131
- if (!status) return null;
132
-
133
- const currentAgent = agents.find(a => a.key === selectedAgent);
134
-
135
- const snippetResult = useMemo(() => {
136
- if (!currentAgent) return null;
137
- if (mode === 'stdio') return generateStdioSnippet(currentAgent);
138
- return generateHttpSnippet(currentAgent, status.endpoint, status.authToken, status.maskedToken);
139
- }, [currentAgent, status, mode]);
140
-
141
- const isRemote = status.endpoint && !status.endpoint.includes('127.0.0.1') && !status.endpoint.includes('localhost');
142
-
143
- return (
144
- <div>
145
- <h3 className="text-sm font-medium text-foreground mb-3">{m?.serverTitle ?? 'MCP Server'}</h3>
146
-
147
- <div className="rounded-xl border p-4 space-y-3" style={{ borderColor: 'var(--border)', background: 'var(--card)' }}>
148
- {/* Status line */}
149
- <div className="flex items-center gap-2.5 text-xs">
150
- <Plug size={14} className="text-muted-foreground shrink-0" />
151
- <span className={`inline-block w-1.5 h-1.5 rounded-full shrink-0 ${status.running ? 'bg-success' : 'bg-muted-foreground'}`} />
152
- <span className="text-foreground font-medium">
153
- {status.running ? (m?.running ?? 'Running') : (m?.stopped ?? 'Stopped')}
154
- </span>
155
- {status.running && (
156
- <>
157
- <span className="text-muted-foreground">·</span>
158
- <span className="font-mono text-muted-foreground">{status.transport.toUpperCase()}</span>
159
- <span className="text-muted-foreground">·</span>
160
- <span className="text-muted-foreground">{m?.toolsRegistered ? m.toolsRegistered(status.toolCount) : `${status.toolCount} tools`}</span>
161
- <span className="text-muted-foreground">·</span>
162
- <span className={status.authConfigured ? 'text-success' : 'text-muted-foreground'}>
163
- {status.authConfigured ? (m?.authSet ?? 'Token set') : (m?.authNotSet ?? 'No token')}
164
- </span>
165
- </>
166
- )}
167
- </div>
168
-
169
- {/* Endpoint + copy */}
170
- <div className="flex items-center gap-2 text-xs">
171
- <span className="text-muted-foreground shrink-0">{m?.endpoint ?? 'Endpoint'}</span>
172
- <span className="font-mono text-foreground truncate">{status.endpoint}</span>
173
- <CopyButton text={status.endpoint} label={m?.copyEndpoint ?? 'Copy'} copiedLabel={m?.copied} />
174
- </div>
175
-
176
- {/* Quick Setup */}
177
- {agents.length > 0 && (
178
- <div className="pt-2 border-t border-border space-y-2.5">
179
- {/* Agent selector + transport mode toggle */}
180
- <div className="flex items-center gap-2 text-xs flex-wrap">
181
- <span className="text-muted-foreground shrink-0">{m?.configureFor ?? 'Configure for'}</span>
182
- <select
183
- value={selectedAgent}
184
- onChange={e => setSelectedAgent(e.target.value)}
185
- className="text-xs px-2 py-1 rounded-md border border-border bg-background text-foreground outline-none focus-visible:ring-1 focus-visible:ring-ring"
186
- >
187
- {agents.map(a => (
188
- <option key={a.key} value={a.key}>
189
- {a.name}{a.installed ? ' ✓' : a.present ? ' ·' : ''}
190
- </option>
191
- ))}
192
- </select>
193
-
194
- <div className="flex items-center rounded-md border border-border overflow-hidden ml-auto">
195
- <button
196
- type="button"
197
- onClick={() => setMode('stdio')}
198
- className={`flex items-center gap-1 px-2 py-1 text-xs transition-colors ${
199
- mode === 'stdio'
200
- ? 'bg-muted text-foreground font-medium'
201
- : 'text-muted-foreground hover:text-foreground'
202
- }`}
203
- title={m?.transportLocalHint ?? 'Local — same machine as MindOS server'}
204
- >
205
- <Monitor size={11} />
206
- {m?.transportLocal ?? 'Local'}
207
- </button>
208
- <button
209
- type="button"
210
- onClick={() => setMode('http')}
211
- className={`flex items-center gap-1 px-2 py-1 text-xs transition-colors ${
212
- mode === 'http'
213
- ? 'bg-muted text-foreground font-medium'
214
- : 'text-muted-foreground hover:text-foreground'
215
- }`}
216
- title={m?.transportRemoteHint ?? 'Remote — connect from another device via HTTP'}
217
- >
218
- <Globe size={11} />
219
- {m?.transportRemote ?? 'Remote'}
220
- </button>
221
- </div>
222
- </div>
223
-
224
- {/* Hint for remote mode */}
225
- {mode === 'http' && (
226
- <div className="text-[11px] text-muted-foreground leading-relaxed space-y-1">
227
- <p>
228
- {isRemote
229
- ? (m?.remoteDetectedHint ?? 'Using your current remote IP.')
230
- : (m?.remoteManualHint ?? 'Replace 127.0.0.1 with your server\'s public or LAN IP.')}
231
- </p>
232
- <p>
233
- {(m?.remoteSteps ?? 'To connect from another device: ① Open port {port} in firewall/security group ② Use the config below in your Agent ③ For public networks, consider SSH tunnel for encryption.')
234
- .replace('{port}', String(status.port))}
235
- </p>
236
- {!status.authConfigured && (
237
- <p className="text-amber-500">{m?.noAuthWarning ?? '⚠ No auth token — set one in Settings → General before enabling remote access.'}</p>
238
- )}
239
- </div>
240
- )}
241
-
242
- {/* Copy config + show JSON toggle */}
243
- {snippetResult && (
244
- <div className="space-y-2">
245
- <div className="flex items-center gap-2 text-xs flex-wrap">
246
- {/* Copy button uses full token (snippetResult.snippet) */}
247
- <CopyButton text={snippetResult.snippet} label={m?.copyConfig ?? 'Copy Config'} copiedLabel={m?.copied} />
248
- <span className="text-muted-foreground">→</span>
249
- <span className="font-mono text-muted-foreground text-[11px] truncate">{snippetResult.path}</span>
250
- <button
251
- type="button"
252
- onClick={() => setShowSnippet(!showSnippet)}
253
- className="inline-flex items-center gap-1 px-1.5 py-0.5 text-[11px] text-muted-foreground hover:text-foreground transition-colors ml-auto"
254
- >
255
- <Code size={10} />
256
- {showSnippet ? (m?.hideJson ?? 'Hide JSON') : (m?.showJson ?? 'Show JSON')}
257
- <ChevronDown size={10} className={`transition-transform ${showSnippet ? 'rotate-180' : ''}`} />
258
- </button>
259
- </div>
260
-
261
- {/* Display snippet uses masked token */}
262
- {showSnippet && (
263
- <pre className="text-xs font-mono bg-muted/50 border border-border rounded-lg p-3 overflow-x-auto whitespace-pre select-all">
264
- {snippetResult.displaySnippet}
265
- </pre>
266
- )}
267
- </div>
268
- )}
269
- </div>
270
- )}
271
- </div>
272
- </div>
273
- );
274
- }