pikiloom 0.4.21 → 0.4.22

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 (41) hide show
  1. package/dashboard/dist/assets/{AgentTab-Crg9GEUv.js → AgentTab-G0uiUjBk.js} +1 -1
  2. package/dashboard/dist/assets/ConnectionModal-BQvV5ok2.js +1 -0
  3. package/dashboard/dist/assets/{DirBrowser-Do4XzF2X.js → DirBrowser-BGacUCRb.js} +1 -1
  4. package/dashboard/dist/assets/ExtensionsTab-Bd2rjOVu.js +1 -0
  5. package/dashboard/dist/assets/{IMAccessTab-BX90Aiuq.js → IMAccessTab-BD-ZwAOb.js} +1 -1
  6. package/dashboard/dist/assets/{Modal-Dd5twuoE.js → Modal-BwmINu44.js} +1 -1
  7. package/dashboard/dist/assets/{Modals--Xl6dsUf.js → Modals-ncL7g7fW.js} +1 -1
  8. package/dashboard/dist/assets/{Select-Bp6SwWv6.js → Select-D7Iz4BLf.js} +1 -1
  9. package/dashboard/dist/assets/{SessionPanel-CHaOUor-.js → SessionPanel-zxiA8Pfd.js} +1 -1
  10. package/dashboard/dist/assets/{SystemTab-MVpMgFFD.js → SystemTab-DZjEZmVB.js} +1 -1
  11. package/dashboard/dist/assets/{index-7pyJSn4B.js → index-CTHVuMnd.js} +17 -17
  12. package/dashboard/dist/assets/index-CyCspgtc.css +1 -0
  13. package/dashboard/dist/assets/index-DEvh1R9o.js +3 -0
  14. package/dashboard/dist/assets/{shared-CxR9M3rn.js → shared-D5VHAaFg.js} +1 -1
  15. package/dashboard/dist/index.html +2 -2
  16. package/dist/agent/index.js +1 -1
  17. package/dist/agent/skill-installer.js +95 -0
  18. package/dist/catalog/skill-repos.js +12 -0
  19. package/dist/cli/main.js +31 -1
  20. package/dist/core/secrets/ref.js +8 -0
  21. package/dist/core/secrets/store.js +31 -11
  22. package/dist/dashboard/routes/extensions.js +137 -24
  23. package/dist/dashboard/server.js +27 -64
  24. package/dist/pikichannel/adapter-pikiloom.js +220 -0
  25. package/dist/pikichannel/code.js +35 -0
  26. package/dist/pikichannel/codec.js +50 -0
  27. package/dist/pikichannel/host.js +252 -0
  28. package/dist/pikichannel/protocol.js +105 -0
  29. package/dist/pikichannel/rendezvous-broker.js +114 -0
  30. package/dist/pikichannel/rendezvous-host.js +138 -0
  31. package/dist/pikichannel/server.js +284 -0
  32. package/dist/pikichannel/transport.js +49 -0
  33. package/dist/pikichannel/transports/webrtc-host.js +61 -0
  34. package/dist/pikichannel/transports/webrtc-shared.js +132 -0
  35. package/dist/pikichannel/transports/websocket-host.js +78 -0
  36. package/dist/pikichannel/web/demo.html +246 -0
  37. package/dist/pikichannel/web/sdk.js +361 -0
  38. package/package.json +4 -2
  39. package/dashboard/dist/assets/ExtensionsTab-j77Yqoz1.js +0 -1
  40. package/dashboard/dist/assets/index-BiPI4uFE.js +0 -3
  41. package/dashboard/dist/assets/index-dzfjF9Js.css +0 -1
@@ -0,0 +1,361 @@
1
+ /**
2
+ * pikichannel-sdk.js — the reference browser client SDK (vanilla ESM, zero deps).
3
+ *
4
+ * This is the embeddable client: drop it into any web app (or wrap it in a
5
+ * WebView for mobile) and you get a live, bidirectional agent session over
6
+ * either transport. It mirrors the L2 protocol in src/pikichannel/protocol.ts —
7
+ * keep the message `type` literals in lockstep with that file.
8
+ *
9
+ * Layering mirrors the host:
10
+ * - Transport (L1): WsTransport | RtcTransport — both expose connect/send and
11
+ * onopen/onmessage/onclose. The client is blind to which one it holds.
12
+ * - Client (L2): PikichannelClient — speaks the protocol, keeps a reactive
13
+ * `sessions` store, exposes prompt/stop/steer/interact, and emits events.
14
+ *
15
+ * Public surface:
16
+ * const c = new Pikichannel.Client({ transport: 'websocket' | 'webrtc' })
17
+ * c.on('status'|'welcome'|'session'|'sessions'|'accepted'|'error', cb)
18
+ * await c.connect(); c.subscribeAll();
19
+ * c.prompt({ prompt, sessionKey?, agent?, model?, effort? })
20
+ * c.stop(key) / c.steer(taskId) / c.recall(taskId) / c.interact(promptId, action, value)
21
+ * c.sessions // Map<sessionKey, UniversalSnapshot>
22
+ * c.stats() // { transport, state, rtt, framesIn, framesOut, bytesIn, bytesOut }
23
+ */
24
+
25
+ export const PROTOCOL_VERSION = 1;
26
+
27
+ const ICE_SERVERS = [{ urls: 'stun:stun.l.google.com:19302' }];
28
+
29
+ // Resolve the ws(s):// base for a target host. `host` may be a bare authority
30
+ // ("192.168.1.5:3940"), a full ws(s)/http(s) URL, or empty (same origin). This
31
+ // is what makes a client repointable: pass `host` to talk to a different node.
32
+ function wsBase(host) {
33
+ const loc = window.location;
34
+ const proto = loc.protocol === 'https:' ? 'wss:' : 'ws:';
35
+ if (!host) return proto + '//' + loc.host;
36
+ if (/^wss?:\/\//.test(host)) return host.replace(/\/$/, '');
37
+ if (/^https:\/\//.test(host)) return 'wss://' + host.slice('https://'.length).replace(/\/$/, '');
38
+ if (/^http:\/\//.test(host)) return 'ws://' + host.slice('http://'.length).replace(/\/$/, '');
39
+ return proto + '//' + host.replace(/\/$/, '');
40
+ }
41
+
42
+ // ---------------------------------------------------------------------------
43
+ // L1 transports (browser side)
44
+ // ---------------------------------------------------------------------------
45
+
46
+ class WsTransport {
47
+ constructor(base) {
48
+ this.kind = 'websocket';
49
+ this.base = base;
50
+ this.ws = null;
51
+ this.onopen = null; this.onmessage = null; this.onclose = null; this.onerror = null;
52
+ }
53
+ connect() {
54
+ const ws = new WebSocket(this.base + '/pikichannel/ws');
55
+ this.ws = ws;
56
+ ws.onopen = () => this.onopen && this.onopen();
57
+ ws.onmessage = (e) => this.onmessage && this.onmessage(e.data);
58
+ ws.onclose = () => this.onclose && this.onclose();
59
+ ws.onerror = () => this.onerror && this.onerror(new Error('websocket error'));
60
+ }
61
+ send(frame) { if (this.ws && this.ws.readyState === WebSocket.OPEN) this.ws.send(frame); }
62
+ close() { if (this.ws) { try { this.ws.close(); } catch (e) {} } }
63
+ isOpen() { return !!this.ws && this.ws.readyState === WebSocket.OPEN; }
64
+ }
65
+
66
+ // The browser is always the WebRTC offerer (creates the datachannel + offer).
67
+ // Two signaling paths share the same PC setup:
68
+ // - direct: SDP/ICE over `${base}/pikichannel/signal` (reachable host).
69
+ // - rendezvous: dial a NodeID through a broker both peers reach (NAT traversal).
70
+ class RtcTransport {
71
+ constructor(cfg) {
72
+ this.kind = 'webrtc';
73
+ this.base = cfg.base;
74
+ this.rendezvous = cfg.rendezvous || null; // broker ws(s):// URL
75
+ this.nodeId = cfg.nodeId || null; // host NodeID to dial
76
+ this.pc = null; this.dc = null; this.signal = null;
77
+ this.onopen = null; this.onmessage = null; this.onclose = null; this.onerror = null;
78
+ }
79
+ async connect() {
80
+ const pc = new RTCPeerConnection({ iceServers: ICE_SERVERS });
81
+ this.pc = pc;
82
+ const dc = pc.createDataChannel('piki', { ordered: true });
83
+ this.dc = dc;
84
+
85
+ let remoteSet = false;
86
+ const pendingRemote = [];
87
+ const onAnswer = async (sdp) => { await pc.setRemoteDescription({ type: 'answer', sdp }); remoteSet = true; for (const c of pendingRemote.splice(0)) { try { await pc.addIceCandidate(c); } catch (e) {} } };
88
+ const onCandidate = async (cand) => { if (remoteSet) { try { await pc.addIceCandidate(cand); } catch (e) {} } else pendingRemote.push(cand); };
89
+
90
+ dc.onopen = () => { this.onopen && this.onopen(); try { this.signal && this.signal.close(); } catch (e) {} };
91
+ dc.onmessage = (e) => this.onmessage && this.onmessage(e.data);
92
+ dc.onclose = () => this.onclose && this.onclose();
93
+ pc.onconnectionstatechange = () => {
94
+ const s = pc.connectionState;
95
+ if (s === 'failed' || s === 'closed' || s === 'disconnected') this.onclose && this.onclose();
96
+ };
97
+
98
+ const useRendezvous = !!(this.rendezvous && this.nodeId);
99
+ const signal = new WebSocket(useRendezvous ? this.rendezvous : this.base + '/pikichannel/signal');
100
+ this.signal = signal;
101
+
102
+ if (useRendezvous) {
103
+ // NAT path: dial the NodeID through the broker; signaling is relayed.
104
+ let sessionId = null;
105
+ pc.onicecandidate = (e) => { if (e.candidate && sessionId && signal.readyState === WebSocket.OPEN) signal.send(JSON.stringify({ t: 'signal', sessionId, data: { kind: 'candidate', candidate: e.candidate.toJSON() } })); };
106
+ signal.onopen = () => signal.send(JSON.stringify({ t: 'dial', nodeId: this.nodeId }));
107
+ signal.onmessage = async (m) => {
108
+ let msg; try { msg = JSON.parse(m.data); } catch (e) { return; }
109
+ if (msg.t === 'dialed') {
110
+ sessionId = msg.sessionId;
111
+ const offer = await pc.createOffer(); await pc.setLocalDescription(offer);
112
+ signal.send(JSON.stringify({ t: 'signal', sessionId, data: { kind: 'offer', type: offer.type, sdp: offer.sdp } }));
113
+ } else if (msg.t === 'signal' && msg.data) {
114
+ const d = msg.data;
115
+ if (d.kind === 'answer') await onAnswer(d.sdp);
116
+ else if (d.kind === 'candidate' && d.candidate) await onCandidate(d.candidate);
117
+ else if (d.kind === 'error') this.onerror && this.onerror(new Error(d.message || 'signaling error'));
118
+ } else if (msg.t === 'error') { this.onerror && this.onerror(new Error(msg.message || 'rendezvous error')); }
119
+ else if (msg.t === 'close') { this.onclose && this.onclose(); }
120
+ };
121
+ signal.onerror = () => this.onerror && this.onerror(new Error('rendezvous socket error'));
122
+ } else {
123
+ // Direct path: SDP/ICE straight to the reachable host.
124
+ pc.onicecandidate = (e) => { if (e.candidate && signal.readyState === WebSocket.OPEN) signal.send(JSON.stringify({ kind: 'candidate', candidate: e.candidate.toJSON() })); };
125
+ signal.onmessage = async (m) => {
126
+ let msg; try { msg = JSON.parse(m.data); } catch (e) { return; }
127
+ if (msg.kind === 'answer') await onAnswer(msg.sdp);
128
+ else if (msg.kind === 'candidate' && msg.candidate) await onCandidate(msg.candidate);
129
+ else if (msg.kind === 'error') this.onerror && this.onerror(new Error(msg.message || 'signaling error'));
130
+ };
131
+ signal.onerror = () => this.onerror && this.onerror(new Error('signaling socket error'));
132
+ signal.onopen = async () => {
133
+ const offer = await pc.createOffer(); await pc.setLocalDescription(offer);
134
+ signal.send(JSON.stringify({ kind: 'offer', type: offer.type, sdp: offer.sdp }));
135
+ };
136
+ }
137
+ }
138
+ send(frame) { if (this.dc && this.dc.readyState === 'open') this.dc.send(frame); }
139
+ close() {
140
+ try { this.dc && this.dc.close(); } catch (e) {}
141
+ try { this.pc && this.pc.close(); } catch (e) {}
142
+ try { this.signal && this.signal.close(); } catch (e) {}
143
+ }
144
+ isOpen() { return !!this.dc && this.dc.readyState === 'open'; }
145
+ }
146
+
147
+ function makeTransport(kind, cfg) {
148
+ if (kind === 'webrtc') return new RtcTransport(cfg);
149
+ return new WsTransport(cfg.base);
150
+ }
151
+
152
+ // Mirror of applySnapshotPatch() in src/pikichannel/protocol.ts — keep in lockstep.
153
+ function applyPatch(prev, patch) {
154
+ if (patch.full) return patch.full;
155
+ const next = prev ? Object.assign({}, prev) : { phase: 'idle', updatedAt: 0 };
156
+ if (patch.appendText) next.text = (next.text || '') + patch.appendText;
157
+ if (patch.appendReasoning) next.reasoning = (next.reasoning || '') + patch.appendReasoning;
158
+ if (patch.set) Object.assign(next, patch.set);
159
+ return next;
160
+ }
161
+
162
+ // ---------------------------------------------------------------------------
163
+ // L2 client
164
+ // ---------------------------------------------------------------------------
165
+
166
+ export class PikichannelClient {
167
+ constructor(opts) {
168
+ opts = opts || {};
169
+ this.rendezvous = opts.rendezvous || null; // broker URL for NAT traversal
170
+ this.nodeId = opts.nodeId || null; // host NodeID to dial via the broker
171
+ // A rendezvous dial implies WebRTC (the broker only brokers P2P signaling).
172
+ this.transportKind = (this.rendezvous && this.nodeId) ? 'webrtc' : (opts.transport === 'webrtc' ? 'webrtc' : 'websocket');
173
+ this.token = opts.token || null;
174
+ this.endpoint = opts.host || null; // target node authority/URL (null = same origin)
175
+ this._base = wsBase(this.endpoint); // resolved ws(s):// base
176
+ this.transport = null;
177
+ this.state = 'idle'; // idle | connecting | open | closed
178
+ this.host = null; // HostInfo from welcome
179
+ this.sessions = new Map(); // sessionKey -> UniversalSnapshot (reconstructed from patches)
180
+ this.sessionMetas = []; // SessionMeta[]
181
+ this._seqs = new Map(); // sessionKey -> last applied seq (gap detection)
182
+ this._listeners = new Map();
183
+ this._pending = new Map(); // request id -> {resolve,reject} (control-plane tunnel)
184
+ this._reqSeq = 0;
185
+ this._pingTimer = null;
186
+ this._rtt = null;
187
+ this._stats = { framesIn: 0, framesOut: 0, bytesIn: 0, bytesOut: 0, connectedAt: null };
188
+ this._pendingResolve = null;
189
+ }
190
+
191
+ // -- event emitter --
192
+ on(type, cb) {
193
+ let set = this._listeners.get(type);
194
+ if (!set) { set = new Set(); this._listeners.set(type, set); }
195
+ set.add(cb);
196
+ return () => this.off(type, cb);
197
+ }
198
+ off(type, cb) { const set = this._listeners.get(type); if (set) set.delete(cb); }
199
+ _emit(type, payload) { const set = this._listeners.get(type); if (set) for (const cb of set) { try { cb(payload); } catch (e) {} } }
200
+
201
+ _setState(state) { this.state = state; this._emit('status', this.stats()); }
202
+
203
+ // -- lifecycle --
204
+ connect() {
205
+ return new Promise((resolve, reject) => {
206
+ this._pendingResolve = resolve;
207
+ this._setState('connecting');
208
+ const t = makeTransport(this.transportKind, { base: this._base, rendezvous: this.rendezvous, nodeId: this.nodeId });
209
+ this.transport = t;
210
+ t.onopen = () => {
211
+ this._stats.connectedAt = Date.now();
212
+ const hello = { type: 'hello', v: PROTOCOL_VERSION, client: { name: 'pikichannel-web-sdk', platform: navigator.userAgent } };
213
+ if (this.token) hello.token = this.token;
214
+ this._send(hello);
215
+ this._setState('open');
216
+ this._startPing();
217
+ };
218
+ t.onmessage = (data) => this._onFrame(data);
219
+ t.onclose = () => { this._stopPing(); this._failPending('connection closed'); this._setState('closed'); this._emit('close', null); };
220
+ t.onerror = (err) => { this._emit('error', { message: err && err.message ? err.message : 'transport error' }); if (this._pendingResolve) { reject(err); this._pendingResolve = null; } };
221
+ // resolve connect() on welcome
222
+ const off = this.on('welcome', () => { off(); if (this._pendingResolve) { this._pendingResolve(this.host); this._pendingResolve = null; } });
223
+ Promise.resolve(t.connect()).catch((e) => { this._emit('error', { message: e && e.message ? e.message : 'connect failed' }); reject(e); });
224
+ });
225
+ }
226
+
227
+ disconnect() { this._stopPing(); if (this.transport) this.transport.close(); this._setState('closed'); }
228
+
229
+ // -- outbound commands --
230
+ subscribeAll() { this._send({ type: 'subscribe', sessionKey: '*' }); }
231
+ subscribe(sessionKey) { this._send({ type: 'subscribe', sessionKey: sessionKey }); }
232
+ unsubscribe(sessionKey) { this._send({ type: 'unsubscribe', sessionKey: sessionKey }); }
233
+ getSnapshot(sessionKey) { this._send({ type: 'getSnapshot', sessionKey: sessionKey }); }
234
+ listSessions() { this._send({ type: 'listSessions' }); }
235
+ prompt(opts) {
236
+ const ref = 'r' + Math.random().toString(36).slice(2, 9);
237
+ this._send(Object.assign({ type: 'prompt', clientRef: ref }, opts));
238
+ return ref;
239
+ }
240
+ stop(sessionKey) { this._send({ type: 'stop', sessionKey: sessionKey }); }
241
+ steer(taskId) { this._send({ type: 'steer', taskId: taskId }); }
242
+ recall(taskId) { this._send({ type: 'recall', taskId: taskId }); }
243
+ interact(promptId, action, value, requestFreeform) {
244
+ this._send({ type: 'interact', promptId: promptId, action: action, value: value, requestFreeform: requestFreeform });
245
+ }
246
+
247
+ /**
248
+ * Control-plane HTTP over the channel — full management of the connected host
249
+ * WITHOUT it exposing REST publicly (no CORS, rides the authenticated channel).
250
+ * Only `/api/*` is allowed by the host. Resolves with a fetch-like response:
251
+ * { status, ok, headers, text(), json(), base64(), bytes() }.
252
+ */
253
+ request(method, path, opts) {
254
+ opts = opts || {};
255
+ let body = opts.body;
256
+ if (body != null && typeof body !== 'string') body = JSON.stringify(body);
257
+ const headers = opts.headers || (body != null ? { 'content-type': 'application/json' } : undefined);
258
+ const id = 'q' + (++this._reqSeq);
259
+ return new Promise((resolve, reject) => {
260
+ this._pending.set(id, { resolve, reject });
261
+ this._send({ type: 'request', id: id, method: method || 'GET', path: path, headers: headers, body: body, encoding: 'utf8' });
262
+ setTimeout(() => { if (this._pending.has(id)) { this._pending.delete(id); reject(new Error('request timeout')); } }, opts.timeout || 20000);
263
+ });
264
+ }
265
+
266
+ _failPending(reason) {
267
+ for (const [, p] of this._pending) { try { p.reject(new Error(reason)); } catch (e) {} }
268
+ this._pending.clear();
269
+ }
270
+
271
+ // -- inbound --
272
+ _onFrame(data) {
273
+ this._stats.framesIn++;
274
+ this._stats.bytesIn += (data && data.length) ? data.length : 0;
275
+ let msg; try { msg = JSON.parse(data); } catch (e) { return; }
276
+ switch (msg.type) {
277
+ case 'welcome':
278
+ this.host = msg.host;
279
+ this.sessionMetas = msg.sessions || [];
280
+ this._emit('welcome', msg.host);
281
+ this._emit('sessions', this.sessionMetas);
282
+ break;
283
+ case 'session': {
284
+ const key = msg.sessionKey;
285
+ const prev = this.sessions.get(key) || null;
286
+ const lastSeq = this._seqs.get(key);
287
+ const contiguous = lastSeq === undefined || msg.seq === lastSeq + 1 || !!msg.patch.full;
288
+ if (!msg.patch.full && (prev === null || !contiguous)) {
289
+ this.getSnapshot(key); // missing baseline or seq gap → ask for a full resync
290
+ break; // don't apply a delta we can't anchor
291
+ }
292
+ const next = applyPatch(prev, msg.patch);
293
+ this.sessions.set(key, next);
294
+ this._seqs.set(key, msg.seq);
295
+ this._emit('session', Object.assign({ sessionKey: key }, next));
296
+ break;
297
+ }
298
+ case 'sessions':
299
+ this.sessionMetas = msg.sessions || [];
300
+ this._emit('sessions', this.sessionMetas);
301
+ break;
302
+ case 'accepted':
303
+ this._emit('accepted', msg);
304
+ break;
305
+ case 'response': {
306
+ const p = this._pending.get(msg.id);
307
+ if (!p) break;
308
+ this._pending.delete(msg.id);
309
+ const enc = msg.encoding || 'utf8';
310
+ const raw = msg.body || '';
311
+ if (msg.error && (msg.status === undefined || msg.status === 0)) { p.reject(new Error(msg.error)); break; }
312
+ p.resolve({
313
+ status: msg.status, ok: msg.status >= 200 && msg.status < 300, headers: msg.headers || {}, error: msg.error || null,
314
+ text: () => raw,
315
+ json: () => JSON.parse(raw),
316
+ base64: () => raw,
317
+ bytes: () => (enc === 'base64' ? Uint8Array.from(atob(raw), (c) => c.charCodeAt(0)) : new TextEncoder().encode(raw)),
318
+ });
319
+ break;
320
+ }
321
+ case 'error':
322
+ this._emit('error', { message: msg.message, code: msg.code, clientRef: msg.clientRef });
323
+ break;
324
+ case 'pong':
325
+ if (typeof msg.t === 'number') { this._rtt = Math.max(0, Math.round(performance.now() - msg.t)); this._emit('status', this.stats()); }
326
+ break;
327
+ }
328
+ }
329
+
330
+ _send(msg) {
331
+ if (!this.transport || !this.transport.isOpen()) return;
332
+ const frame = JSON.stringify(msg);
333
+ this._stats.framesOut++;
334
+ this._stats.bytesOut += frame.length;
335
+ this.transport.send(frame);
336
+ }
337
+
338
+ _startPing() {
339
+ this._stopPing();
340
+ this._pingTimer = setInterval(() => { this._send({ type: 'ping', t: performance.now() }); }, 3000);
341
+ }
342
+ _stopPing() { if (this._pingTimer) { clearInterval(this._pingTimer); this._pingTimer = null; } }
343
+
344
+ stats() {
345
+ return {
346
+ transport: this.transportKind,
347
+ state: this.state,
348
+ rtt: this._rtt,
349
+ framesIn: this._stats.framesIn,
350
+ framesOut: this._stats.framesOut,
351
+ bytesIn: this._stats.bytesIn,
352
+ bytesOut: this._stats.bytesOut,
353
+ sessions: this.sessions.size,
354
+ host: this.host,
355
+ };
356
+ }
357
+ }
358
+
359
+ if (typeof window !== 'undefined') {
360
+ window.Pikichannel = { Client: PikichannelClient, PROTOCOL_VERSION: PROTOCOL_VERSION };
361
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pikiloom",
3
- "version": "0.4.21",
3
+ "version": "0.4.22",
4
4
  "description": "Put the world's smartest AI agents in your pocket. Command local Claude & Gemini via IM. | 让最好用的 IM 变成你电脑上的顶级 Agent 控制台",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,8 @@
32
32
  "command": "set -ae && . ./.env && set +a && tsx src/cli/run.ts",
33
33
  "build:dashboard": "vite build --config dashboard/vite.config.ts",
34
34
  "clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\"",
35
- "build": "npm run clean && npm run build:dashboard && tsc",
35
+ "copy:pikichannel-web": "node -e \"require('fs').cpSync('src/pikichannel/web','dist/pikichannel/web',{recursive:true})\"",
36
+ "build": "npm run clean && npm run build:dashboard && tsc && npm run copy:pikichannel-web",
36
37
  "postbuild": "node -e \"require('fs').chmodSync('dist/cli/main.js',0o755)\"",
37
38
  "prepack": "npm run build",
38
39
  "test": "vitest run",
@@ -74,6 +75,7 @@
74
75
  "qrcode": "^1.5.4",
75
76
  "remark-breaks": "^4.0.0",
76
77
  "undici": "^7.22.0",
78
+ "werift": "^0.23.0",
77
79
  "which": "^6.0.1",
78
80
  "ws": "^8.18.0"
79
81
  },
@@ -1 +0,0 @@
1
- import{r as o,j as e}from"./react-vendor-C7Sl8SE7.js";import{u as F,z as fe,F as ge,c as U,a as A,G as J,S as P,k as z,I as B,B as ve}from"./index-BiPI4uFE.js";import{M as q,a as K}from"./Modal-Dd5twuoE.js";import"./router-DHISdpPk.js";function a(s,t,n){return s==="zh-CN"?t:n}function re(s,t){return t.type==="mcp-oauth"?a(s,"OAuth","OAuth"):t.type==="credentials"?a(s,"API Key","API Key"):a(s,"无需配置","No auth")}const be={github:{hex:"#24292f",letter:"GH"},atlassian:{hex:"#0052cc",letter:"A"},notion:{hex:"#111827",letter:"N"},linear:{hex:"#5e6ad2",letter:"L"},sentry:{hex:"#362d59",letter:"S"},cloudflare:{hex:"#f6821f",letter:"CF"},gamma:{hex:"#9f2eff",letter:"G"},huggingface:{hex:"#ff9d00",letter:"HF"},slack:{hex:"#4a154b",letter:"S"},lark:{hex:"#00d6b9",letter:"L"},feishu:{hex:"#00d6b9",letter:"F"},stripe:{hex:"#635bff",letter:"S"},perplexity:{hex:"#20b8cd",letter:"P"},brave:{hex:"#fb542b",letter:"B"},filesystem:{hex:"#64748b",letter:"FS"},fetch:{hex:"#0ea5e9",letter:"F"},memory:{hex:"#a855f7",letter:"M"},time:{hex:"#10b981",letter:"T"},sqlite:{hex:"#0369a1",letter:"SQ"},postgres:{hex:"#336791",letter:"PG"}},je={hex:"#6b7280"};function ye(s,t){const n=(s||"").toLowerCase(),l=be[n]||je,i=l.letter||(t||"").replace(/[^a-zA-Z0-9]/g,"").slice(0,2).toUpperCase()||"?";return{hex:l.hex,letter:i}}function Z(s,t){const n=s.replace("#",""),l=n.length===3?n.split("").map(u=>u+u).join(""):n.padEnd(6,"0").slice(0,6),i=parseInt(l.slice(0,2),16),c=parseInt(l.slice(2,4),16),x=parseInt(l.slice(4,6),16);return`rgba(${i}, ${c}, ${x}, ${t})`}function Q(s,t,n=[]){const[l,i]=o.useState(()=>{try{const p=localStorage.getItem(s);return p?JSON.parse(p):null}catch{return null}}),[c,x]=o.useState(!1),u=o.useRef(!0);o.useEffect(()=>()=>{u.current=!1},[]);const m=o.useCallback(async()=>{x(!0);try{const p=await t();if(!u.current)return;i(p);try{localStorage.setItem(s,JSON.stringify(p))}catch{}}finally{u.current&&x(!1)}},[s,...n]);return o.useEffect(()=>{m()},[m]),{data:l,loading:c,refresh:m}}const D=()=>e.jsxs("svg",{width:"11",height:"11",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("path",{d:"M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"}),e.jsx("polyline",{points:"15 3 21 3 21 9"}),e.jsx("line",{x1:"10",y1:"14",x2:"21",y2:"3"})]}),W=({size:s=12})=>e.jsxs("svg",{width:s,height:s,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("path",{d:"M22 11.08V12a10 10 0 1 1-5.93-9.14"}),e.jsx("polyline",{points:"22 4 12 14.01 9 11.01"})]}),Ne=({size:s=12})=>e.jsxs("svg",{width:s,height:s,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("path",{d:"M18.36 6.64a9 9 0 1 1-12.73 0"}),e.jsx("line",{x1:"12",y1:"2",x2:"12",y2:"12"})]}),le=({size:s=12})=>e.jsxs("svg",{width:s,height:s,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("rect",{x:"3",y:"11",width:"18",height:"11",rx:"2",ry:"2"}),e.jsx("path",{d:"M7 11V7a5 5 0 0 1 10 0v4"})]}),ke=({size:s=12})=>e.jsxs("svg",{width:s,height:s,viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("circle",{cx:"12",cy:"12",r:"10"}),e.jsx("line",{x1:"12",y1:"8",x2:"12",y2:"12"}),e.jsx("line",{x1:"12",y1:"16",x2:"12.01",y2:"16"})]}),ie=new Set(["claude","codex","gemini","telegram","feishu","weixin","playwright","vscode","cursor","windsurf","finder"]),Ce={github:"logos:github-icon",atlassian:"logos:atlassian",notion:"logos:notion-icon",linear:"logos:linear-icon",sentry:"logos:sentry-icon",cloudflare:"logos:cloudflare-icon","cloudflare-docs":"logos:cloudflare-icon","cloudflare-bindings":"logos:cloudflare-icon","cloudflare-observability":"logos:cloudflare-icon",slack:"logos:slack-icon",lark:"icon-park:lark",feishu:"icon-park:lark",stripe:"logos:stripe",perplexity:"logos:perplexity-icon",brave:"logos:brave","brave-search":"logos:brave",huggingface:"logos:hugging-face-icon",postgres:"logos:postgresql",postgresql:"logos:postgresql",sqlite:"logos:sqlite",vercel:"logos:vercel-icon",netlify:"logos:netlify-icon",supabase:"logos:supabase-icon",heroku:"logos:heroku-icon",docker:"logos:docker-icon",pnpm:"logos:pnpm",aws:"logos:aws","google-cloud":"logos:google-cloud",googlecloud:"logos:google-cloud",amazonwebservices:"logos:aws"},we=new Set(["stripe"]);function Se(s,t){if(t)return t;if(!s||ie.has(s))return;const n=Ce[s];if(n)return`https://api.iconify.design/${n}.svg`}function T({iconSlug:s,iconUrl:t,name:n,size:l=32,className:i}){const{hex:c,letter:x}=ye(s,n),[u,m]=o.useState(!1),p=Se(s,t),b=s&&ie.has(s),d=b||!!p&&!u,g=!!s&&we.has(s),f=Math.round(l*(g?.92:.76));return d?e.jsxs("div",{className:U("relative flex shrink-0 items-center justify-center overflow-hidden rounded-xl bg-white",i),style:{width:l,height:l,boxShadow:`0 0 0 1px ${Z(c,.18)}, 0 4px 12px ${Z(c,.14)}`},children:[e.jsx("div",{"aria-hidden":!0,className:"pointer-events-none absolute inset-0",style:{background:`linear-gradient(135deg, ${Z(c,.06)} 0%, transparent 70%)`}}),b?e.jsx(ve,{brand:s,size:f}):e.jsx("img",{src:p,alt:"",width:f,height:f,loading:"lazy",decoding:"async",onError:()=>m(!0),className:"relative"})]}):e.jsxs("div",{className:U("relative flex shrink-0 items-center justify-center overflow-hidden rounded-xl font-semibold text-white",i),style:{width:l,height:l,background:`linear-gradient(135deg, ${Z(c,1)} 0%, ${Z(c,.82)} 100%)`,boxShadow:`0 1px 0 rgba(255,255,255,0.08) inset, 0 6px 14px ${Z(c,.28)}`,fontSize:Math.max(10,Math.round(l*.36)),letterSpacing:x.length>1?"-0.02em":0},children:[e.jsx("div",{"aria-hidden":!0,className:"pointer-events-none absolute inset-0",style:{background:"radial-gradient(circle at 30% 20%, rgba(255,255,255,0.22), transparent 55%)"}}),e.jsx("span",{className:"relative",children:x})]})}function Ie({state:s,locale:t}){return s==="ready"?e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold text-[var(--th-ok)]",style:{background:"color-mix(in oklab, var(--th-ok) 12%, transparent)"},children:[e.jsx(W,{size:10}),a(t,"已连接","Connected")]}):s==="disabled"?e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full border border-edge bg-inset/60 px-2 py-0.5 text-[10px] font-medium text-fg-5",children:[e.jsx(Ne,{size:10}),a(t,"已停用","Paused")]}):s==="needs_auth"?e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold text-[var(--th-warn)]",style:{background:"color-mix(in oklab, var(--th-warn) 12%, transparent)"},children:[e.jsx(le,{size:10}),a(t,"待授权","Needs auth")]}):s==="unhealthy"?e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold text-[var(--th-err)]",style:{background:"color-mix(in oklab, var(--th-err) 12%, transparent)"},children:[e.jsx(ke,{size:10}),a(t,"异常","Unhealthy")]}):null}function ze({open:s,onClose:t,locale:n,item:l,initial:i,onSubmit:c}){const[x,u]=o.useState({}),[m,p]=o.useState(!1);if(o.useEffect(()=>{if(s&&l&&l.auth.type==="credentials"){const d={};for(const g of l.auth.fields)d[g.key]=i?.[g.key]||"";u(d)}},[s,l,i]),!l||l.auth.type!=="credentials")return null;const b=l.auth.fields.some(d=>d.required&&!(x[d.key]||"").trim()),N=async()=>{p(!0);try{await c(x)}finally{p(!1)}};return e.jsxs(q,{open:s,onClose:t,children:[e.jsx(K,{title:a(n,`配置 ${l.name}`,`Configure ${l.name}`),description:n==="zh-CN"?l.descriptionZh:l.description,onClose:t}),e.jsxs("div",{className:"space-y-3",children:[l.auth.fields.map(d=>e.jsxs("div",{children:[e.jsxs("label",{className:"mb-1 flex items-center justify-between",children:[e.jsxs("span",{className:"text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:[n==="zh-CN"?d.labelZh:d.label,d.required&&e.jsx("span",{className:"ml-1 text-err",children:"*"})]}),d.helpUrl&&e.jsxs("a",{href:d.helpUrl,target:"_blank",rel:"noreferrer",className:"flex items-center gap-1 text-[11px] text-primary hover:text-primary/80",children:[a(n,"获取","Get one")," ",e.jsx(D,{})]})]}),e.jsx(B,{value:x[d.key]||"",onChange:g=>u({...x,[d.key]:g.target.value}),type:d.secret?"password":"text",placeholder:d.placeholder,className:"font-mono text-[12px]"})]},d.key)),e.jsxs("div",{className:"flex justify-end gap-2 border-t border-edge pt-3",children:[e.jsx(z,{variant:"ghost",onClick:t,children:a(n,"取消","Cancel")}),e.jsx(z,{variant:"primary",disabled:m||b,onClick:N,children:m?e.jsx(P,{}):a(n,"保存并启用","Save & Enable")})]})]})]})}function Le({open:s,onClose:t,locale:n,scope:l,workdir:i,onAdded:c}){const x=F(j=>j.toast),[u,m]=o.useState(""),[p,b]=o.useState("stdio"),[N,d]=o.useState("npx"),[g,f]=o.useState(""),[I,$]=o.useState(""),[E,S]=o.useState([]),[L,R]=o.useState(!1);o.useEffect(()=>{s&&(m(""),b("stdio"),d("npx"),f(""),$(""),S([]))},[s]);const M=async()=>{if(u.trim()){R(!0);try{const j={};for(const{k,v:h}of E)k.trim()&&(j[k.trim()]=h);const y=p==="http"?{type:"http",url:I.trim(),enabled:!0,...Object.keys(j).length?{headers:j}:{}}:{type:"stdio",command:N.trim(),args:g.trim()?g.trim().split(/\s+/):[],enabled:!0,...Object.keys(j).length?{env:j}:{}};await A.addCustomMcp(u.trim(),y,l,i),x(a(n,`${u} 已添加`,`${u} added`),!0),c(),t()}catch(j){x(j?.message||"Failed",!1)}finally{R(!1)}}};return e.jsxs(q,{open:s,onClose:t,wide:!0,children:[e.jsx(K,{title:a(n,"添加自定义 MCP 服务","Add Custom MCP Server"),description:a(n,"不在推荐列表中的自定义服务。","For servers not in the recommended catalog."),onClose:t}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"grid gap-3 sm:grid-cols-2",children:[e.jsxs("div",{children:[e.jsx("label",{className:"mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:a(n,"名称","Name")}),e.jsx(B,{value:u,onChange:j=>m(j.target.value),placeholder:"my-server"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:a(n,"传输","Transport")}),e.jsx("div",{className:"flex gap-1.5",children:["stdio","http"].map(j=>e.jsx("button",{onClick:()=>b(j),className:U("flex-1 rounded-md border px-3 py-1.5 text-[12px] font-medium transition-colors",p===j?"border-primary/40 bg-primary/10 text-primary":"border-edge bg-inset/50 text-fg-4 hover:bg-inset"),children:j},j))})]})]}),p==="stdio"?e.jsxs(e.Fragment,{children:[e.jsxs("div",{children:[e.jsx("label",{className:"mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:a(n,"命令","Command")}),e.jsx(B,{value:N,onChange:j=>d(j.target.value),className:"font-mono",placeholder:"npx"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:a(n,"参数","Arguments")}),e.jsx(B,{value:g,onChange:j=>f(j.target.value),className:"font-mono",placeholder:"-y @example/server"})]})]}):e.jsxs("div",{children:[e.jsx("label",{className:"mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:"URL"}),e.jsx(B,{value:I,onChange:j=>$(j.target.value),className:"font-mono",placeholder:"https://example.com/mcp"})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"mb-1.5 flex items-center justify-between",children:[e.jsx("span",{className:"text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:p==="http"?a(n,"Headers","Headers"):a(n,"环境变量","Env")}),e.jsxs("button",{className:"text-[11px] font-medium text-primary hover:text-primary/80",onClick:()=>S([...E,{k:"",v:""}]),children:["+ ",a(n,"添加","Add")]})]}),E.length>0&&e.jsx("div",{className:"space-y-1 rounded-md border border-edge bg-inset/40 p-2",children:E.map((j,y)=>e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx(B,{className:"w-2/5 !h-7 !text-[12px] font-mono",value:j.k,onChange:k=>{const h=[...E];h[y]={...h[y],k:k.target.value},S(h)},placeholder:"KEY"}),e.jsx(B,{className:"flex-1 !h-7 !text-[12px] font-mono",value:j.v,onChange:k=>{const h=[...E];h[y]={...h[y],v:k.target.value},S(h)},type:/token|secret|key|bearer/i.test(j.k)?"password":"text",placeholder:"value"}),e.jsx("button",{className:"shrink-0 rounded p-1 text-fg-5 hover:text-err",onClick:()=>S(E.filter((k,h)=>h!==y)),children:e.jsxs("svg",{width:"9",height:"9",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",children:[e.jsx("line",{x1:"18",y1:"6",x2:"6",y2:"18"}),e.jsx("line",{x1:"6",y1:"6",x2:"18",y2:"18"})]})})]},y))})]}),e.jsxs("div",{className:"flex justify-end gap-2 border-t border-edge pt-3",children:[e.jsx(z,{variant:"ghost",onClick:t,children:a(n,"取消","Cancel")}),e.jsx(z,{variant:"primary",disabled:!u.trim()||L,onClick:M,children:L?e.jsx(P,{}):a(n,"添加","Add")})]})]})]})}function Me({open:s,onClose:t,locale:n,scope:l,workdir:i,onInstalled:c}){const x=F(f=>f.toast),[u,m]=o.useState(""),[p,b]=o.useState(""),[N,d]=o.useState(!1);o.useEffect(()=>{s&&(m(""),b(""))},[s]);const g=async()=>{if(u.trim()){d(!0);try{const f=await A.installSkill(u.trim(),l==="global",p.trim()||void 0,i);f.ok?(x(a(n,"技能安装成功","Skill installed"),!0),c(),t()):x(f.error||"Failed",!1)}catch(f){x(f?.message||"Failed",!1)}finally{d(!1)}}};return e.jsxs(q,{open:s,onClose:t,children:[e.jsx(K,{title:a(n,"安装自定义技能","Install Custom Skill"),description:a(n,"通过 npx skills add 从 GitHub 仓库安装。","Installs via npx skills add from a GitHub repo."),onClose:t}),e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{children:[e.jsx("label",{className:"mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:a(n,"GitHub 来源","GitHub Source")}),e.jsx(B,{value:u,onChange:f=>m(f.target.value),placeholder:"owner/repo",className:"font-mono"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"mb-1.5 block text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:a(n,"指定技能(可选)","Specific skill (optional)")}),e.jsx(B,{value:p,onChange:f=>b(f.target.value),placeholder:a(n,"留空安装全部","Leave empty for all")})]}),e.jsxs("div",{className:"flex justify-end gap-2 border-t border-edge pt-3",children:[e.jsx(z,{variant:"ghost",onClick:t,children:a(n,"取消","Cancel")}),e.jsx(z,{variant:"primary",disabled:!u.trim()||N,onClick:g,children:N?e.jsx(P,{}):a(n,"安装","Install")})]})]})]})}function Ae(s,t){return new Promise(n=>{const l=window.open(s,"pikiloom_mcp_oauth","width=640,height=780,noopener=no");if(!l){n(!1);return}let i=!1;const c=m=>{i||(i=!0,window.removeEventListener("message",x),clearInterval(u),n(m))},x=m=>{const p=m.data;if(!(!p||p.type!=="mcp-oauth")&&!(t&&p.state!==t)){c(!!p.ok);try{l.close()}catch{}}};window.addEventListener("message",x);const u=setInterval(()=>{l.closed&&c(!1)},500)})}function ne({item:s,locale:t,busy:n,index:l,onPrimary:i,onRemove:c,onReauth:x,onReconfigure:u}){const m=(()=>{switch(s.state){case"ready":return a(t,"停用","Pause");case"unhealthy":return a(t,"停用","Pause");case"disabled":return a(t,"启用","Enable");case"needs_auth":return s.auth.type==="mcp-oauth"?a(t,"授权","Authorize"):a(t,"配置","Configure");default:return a(t,"启用","Enable")}})(),p={animationDelay:`${Math.min(l,8)*40}ms`};return e.jsxs("div",{className:U("group relative overflow-hidden rounded-lg border border-[var(--edge-subtle)] bg-[var(--surface-2)] p-4","transition-[background,border-color] duration-200","hover:border-[var(--edge-default)] hover:bg-[var(--surface-3)]","animate-in-up"),style:p,children:[e.jsxs("div",{className:"relative flex items-start gap-3",children:[e.jsx(T,{iconSlug:s.iconSlug,iconUrl:s.iconUrl,name:s.name,size:36}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("div",{className:"truncate text-[14px] font-semibold text-fg",children:s.name}),s.homepage&&e.jsx("a",{href:s.homepage,target:"_blank",rel:"noreferrer",className:"text-fg-5 hover:text-fg-3 transition-colors",children:e.jsx(D,{})})]}),e.jsx("div",{className:"mt-0.5 line-clamp-2 text-[12px] leading-snug text-fg-4",children:t==="zh-CN"?s.descriptionZh:s.description})]}),e.jsx(Ie,{state:s.state,locale:t})]}),e.jsxs("div",{className:"relative mt-3 flex items-center justify-between border-t border-[var(--edge-subtle)] pt-3",children:[e.jsxs("span",{className:"inline-flex items-center gap-1 text-[11px] text-fg-5",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-fg-6"}),re(t,s.auth)]}),e.jsxs("div",{className:"flex items-center gap-1",children:[s.installed&&s.state!=="needs_auth"&&x&&e.jsx(z,{variant:"ghost",size:"sm",onClick:x,disabled:n,children:a(t,"重新授权","Re-auth")}),s.installed&&s.state!=="needs_auth"&&u&&e.jsx(z,{variant:"ghost",size:"sm",onClick:u,disabled:n,children:a(t,"编辑","Edit")}),e.jsx(z,{variant:s.state==="disabled"||s.state==="needs_auth"?"primary":"ghost",size:"sm",onClick:i,disabled:n,children:n?e.jsx(P,{}):m}),s.installed&&c&&e.jsx(z,{variant:"ghost",size:"sm",onClick:c,disabled:n,className:"hover:!text-err",children:a(t,"移除","Remove")})]})]})]})}function ae({item:s,locale:t,busy:n,index:l,onPrimary:i}){const c=s.auth.type==="none"?a(t,"一键启用","One-click enable"):a(t,"授权并启用","Authorize & enable");return e.jsxs("div",{className:U("group relative flex flex-col gap-3 rounded-lg border border-[var(--edge-subtle)] bg-[var(--surface-2)] p-4","transition-[background,border-color] duration-200","hover:border-[var(--edge-default)] hover:bg-[var(--surface-3)]","animate-in-up"),style:{animationDelay:`${Math.min(l,12)*30}ms`},children:[e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(T,{iconSlug:s.iconSlug,iconUrl:s.iconUrl,name:s.name,size:32}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-1.5",children:[e.jsx("div",{className:"truncate text-[13.5px] font-semibold text-fg",children:s.name}),s.homepage&&e.jsx("a",{href:s.homepage,target:"_blank",rel:"noreferrer",className:"text-fg-5 hover:text-fg-3 transition-colors",children:e.jsx(D,{})})]}),e.jsx("div",{className:"mt-0.5 line-clamp-2 text-[12px] leading-snug text-fg-4",children:t==="zh-CN"?s.descriptionZh:s.description})]})]}),e.jsxs("div",{className:"mt-auto flex items-center justify-between",children:[e.jsx("span",{className:"inline-flex items-center gap-1 text-[11px] text-fg-5",children:re(t,s.auth)}),e.jsx(z,{variant:"outline",size:"sm",onClick:i,disabled:n,className:"group-hover:border-edge-h",children:n?e.jsx(P,{}):c})]})]})}function $e(s,t){const n=s.installedNames.length;if(typeof s.totalCount=="number"){const l=s.partial?`${s.totalCount}+`:String(s.totalCount);return t==="zh-CN"?`${n} / ${l} 已安装`:`${n} / ${l} installed`}return n>0?t==="zh-CN"?`${n} 已安装`:`${n} installed`:a(t,"未安装","Not installed")}function Ee({item:s,locale:t,animationDelay:n,onClick:l}){return e.jsxs("button",{type:"button",onClick:l,className:"animate-in-up group relative flex min-h-[112px] w-full flex-col overflow-hidden rounded-lg border border-[var(--edge-subtle)] bg-[var(--surface-2)] p-4 text-left transition-[background,border-color] duration-200 hover:border-[var(--edge-default)] hover:bg-[var(--surface-3)] focus-visible:outline-none focus-visible:shadow-[0_0_0_3px_var(--brand-glow-a)]",style:{animationDelay:n},children:[e.jsxs("div",{className:"relative flex items-start gap-3",children:[e.jsx(T,{iconUrl:s.iconUrl,name:s.name,size:36}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-1.5 min-w-0",children:[e.jsx("span",{className:"truncate text-[13px] font-semibold text-fg",children:s.name}),s.homepage&&e.jsx("a",{href:s.homepage,target:"_blank",rel:"noreferrer",onClick:i=>i.stopPropagation(),className:"text-fg-5 transition-colors hover:text-primary",children:e.jsx(D,{})})]}),e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold text-[var(--th-ok)] shrink-0",style:{background:"color-mix(in oklab, var(--th-ok) 12%, transparent)"},children:[e.jsx(W,{size:10}),$e(s,t)]})]}),e.jsx("div",{className:"mt-0.5 truncate text-[11.5px] text-fg-5",children:s.source}),e.jsx("div",{className:"mt-1 line-clamp-2 text-[11.5px] text-fg-4",children:t==="zh-CN"?s.descriptionZh:s.description})]})]}),e.jsxs("div",{className:"relative mt-auto pt-3 flex items-center justify-between text-[10.5px] text-fg-5",children:[e.jsxs("span",{className:"flex items-center gap-2",children:[s.stars!==void 0&&e.jsxs("span",{className:"inline-flex items-center gap-0.5 font-medium text-fg-4",children:[e.jsx(X,{size:10}),ee(s.stars)]}),s.pushedAt&&e.jsx("span",{children:oe(s.pushedAt,t)})]}),e.jsx("span",{className:"text-fg-5 group-hover:text-primary transition-colors",children:a(t,"管理 →","Manage →")})]})]})}function Re({item:s,locale:t,animationDelay:n,onClick:l}){return e.jsxs("button",{type:"button",onClick:l,className:"animate-in-up group relative flex min-h-[112px] w-full flex-col overflow-hidden rounded-lg border border-[var(--edge-subtle)] bg-[var(--surface-2)] p-4 text-left transition-[background,border-color] duration-200 hover:border-[var(--edge-default)] hover:bg-[var(--surface-3)] focus-visible:outline-none focus-visible:shadow-[0_0_0_3px_var(--brand-glow-a)]",style:{animationDelay:n},children:[e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(T,{iconUrl:s.iconUrl,name:s.name,size:32}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"truncate text-[13px] font-semibold text-fg",children:s.name}),s.homepage&&e.jsx("a",{href:s.homepage,target:"_blank",rel:"noreferrer",onClick:i=>i.stopPropagation(),className:"text-fg-5 transition-colors hover:text-primary",children:e.jsx(D,{})})]}),e.jsx("div",{className:"mt-0.5 line-clamp-2 text-[11.5px] text-fg-4",children:t==="zh-CN"?s.descriptionZh:s.description})]})]}),e.jsxs("div",{className:"mt-auto pt-3 flex items-center justify-between",children:[e.jsxs("span",{className:"flex items-center gap-2 text-[11px] text-fg-5",children:[s.stars!==void 0&&e.jsxs("span",{className:"inline-flex items-center gap-0.5 font-medium text-fg-4",children:[e.jsx(X,{size:10}),ee(s.stars)]}),typeof s.totalCount=="number"&&e.jsxs("span",{children:["· ",s.partial?`${s.totalCount}+`:s.totalCount," skills"]})]}),e.jsx("span",{className:"inline-flex items-center gap-1 rounded-md border border-[var(--edge-subtle)] bg-transparent px-2.5 py-1 text-[11px] font-semibold text-fg-3 transition-colors group-hover:border-[var(--edge-default)] group-hover:text-fg",children:a(t,"查看 →","Browse →")})]})]})}function _e({skill:s,locale:t,animationDelay:n,busy:l,onRemove:i}){const[c,x]=o.useState(!1);return e.jsxs("div",{className:"animate-in-up group relative flex min-h-[112px] w-full flex-col overflow-hidden rounded-lg border border-[var(--edge-subtle)] bg-[var(--surface-2)] p-4 text-left transition-[background,border-color] duration-200 hover:border-[var(--edge-default)] hover:bg-[var(--surface-3)]",style:{animationDelay:n},onMouseLeave:()=>x(!1),children:[e.jsxs("div",{className:"relative flex items-start gap-3",children:[e.jsx(T,{name:s.name,size:36}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("span",{className:"truncate text-[13px] font-semibold text-fg",children:s.label||s.name}),e.jsx("span",{className:"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold text-fg-4 shrink-0",style:{background:"color-mix(in oklab, var(--fg-5) 14%, transparent)"},children:a(t,"本地","Local")})]}),e.jsxs("div",{className:"mt-0.5 truncate font-mono text-[11.5px] text-fg-5",children:["/",s.name]}),s.description&&e.jsx("div",{className:"mt-1 line-clamp-2 text-[11.5px] text-fg-4",children:s.description})]})]}),e.jsxs("div",{className:"relative mt-auto pt-3 flex items-center justify-between text-[10.5px] text-fg-5",children:[e.jsx("span",{children:s.scope==="global"?a(t,"全局 · ~/.pikiloom/skills","Global · ~/.pikiloom/skills"):a(t,"项目 · .pikiloom/skills","Project · .pikiloom/skills")}),e.jsx(z,{variant:"ghost",size:"sm",disabled:l,onClick:()=>{c?(x(!1),i()):x(!0)},className:c?"!text-err":"hover:!text-err",children:l?e.jsx(P,{}):c?a(t,"确认移除?","Confirm remove?"):a(t,"移除","Remove")})]})]})}function Pe({item:s,open:t,onClose:n,onChanged:l,locale:i,scope:c,workdir:x,installedSkills:u}){const m=F(v=>v.toast),[p,b]=o.useState(null),[N,d]=o.useState(!1),[g,f]=o.useState(null),[I,$]=o.useState(!1),[E,S]=o.useState(null),[L,R]=o.useState(null),[M,j]=o.useState("");o.useEffect(()=>{if(!t||!s)return;let v=!1;return d(!0),f(null),j(""),(async()=>{try{const r=await A.listRepoSkills(s.source);if(v)return;r.ok?(b(r.skills),$(!!r.partial)):(f(r.error||"failed to list"),b([]))}catch(r){if(v)return;f(r?.message||"failed"),b([])}finally{v||d(!1)}})(),()=>{v=!0}},[t,s?.source]);const y=o.useMemo(()=>{const v=new Set,r=c==="global"?"global":"project";for(const C of u)C.scope===r&&v.add(C.name.toLowerCase());return v},[u,c]),k=o.useMemo(()=>{const v=p||[],r=M.trim().toLowerCase();return r?v.filter(C=>C.name.toLowerCase().includes(r)):v},[p,M]),h=o.useMemo(()=>p?p.filter(v=>y.has(v.name.toLowerCase())).length:0,[p,y]),w=o.useCallback(async v=>{if(s){S(v);try{const r=await A.installSkill(s.source,c==="global",v,x);r.ok?(m(a(i,`${v} 已安装`,`${v} installed`),!0),l()):m(r.error||"Failed",!1)}catch(r){m(r?.message||"Failed",!1)}finally{S(null)}}},[s,c,x,i,m,l]),_=o.useCallback(async v=>{S(v);try{const r=await A.removeExtensionSkill(v,c==="global",x);r.ok?(m(a(i,`${v} 已移除`,`${v} removed`),!0),l()):m(r.error||"Failed",!1)}catch(r){m(r?.message||"Failed",!1)}finally{S(null)}},[c,x,i,m,l]),G=o.useCallback(async()=>{if(s){R("install");try{const v=await A.installSkill(s.source,c==="global",void 0,x);v.ok?(m(a(i,"全部安装完成","All skills installed"),!0),l()):m(v.error||"Failed",!1)}catch(v){m(v?.message||"Failed",!1)}finally{R(null)}}},[s,c,x,i,m,l]),V=o.useCallback(async()=>{if(!(!s||!p)){R("remove");try{const v=p.map(r=>r.name).filter(r=>y.has(r.toLowerCase()));for(const r of v)await A.removeExtensionSkill(r,c==="global",x);m(a(i,"已移除该集合下的全部技能","Removed all skills from this collection"),!0),l()}catch(v){m(v?.message||"Failed",!1)}finally{R(null)}}},[s,p,y,c,x,i,m,l]);return s?e.jsxs(q,{open:t,onClose:n,wide:!0,children:[e.jsx(K,{title:s.name,description:i==="zh-CN"?s.descriptionZh:s.description,onClose:n}),e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(T,{iconUrl:s.iconUrl,name:s.name,size:44}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2 text-[13px] font-semibold text-fg",children:[s.name,s.homepage&&e.jsx("a",{href:s.homepage,target:"_blank",rel:"noreferrer",className:"text-fg-5 hover:text-primary",children:e.jsx(D,{})})]}),e.jsxs("div",{className:"mt-0.5 flex items-center gap-2 text-[11.5px] text-fg-4",children:[e.jsx("span",{className:"truncate text-fg-5",children:s.source}),s.stars!==void 0&&e.jsxs("span",{className:"inline-flex items-center gap-0.5 text-fg-5",children:[e.jsx(X,{size:10}),ee(s.stars)]}),s.pushedAt&&e.jsxs("span",{className:"text-fg-5",children:["· ",oe(s.pushedAt,i)]})]})]})]}),e.jsxs("section",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"text-[12px] font-semibold text-fg-3",children:[a(i,"该集合下的技能","Skills in this collection"),p&&e.jsxs("span",{className:"ml-2 text-[11px] font-normal text-fg-5",children:[h," / ",I?`${p.length}+`:p.length," ",a(i,"已安装","installed")]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(z,{variant:"outline",size:"sm",onClick:G,disabled:L!==null||N,children:L==="install"?e.jsx(P,{}):a(i,"全部安装","Install all")}),h>0&&e.jsx(z,{variant:"ghost",size:"sm",onClick:V,disabled:L!==null,className:"hover:!text-err",children:L==="remove"?e.jsx(P,{}):a(i,"全部移除","Remove all")})]})]}),p&&p.length>6&&e.jsx(B,{type:"text",value:M,onChange:v=>j(v.target.value),placeholder:a(i,"搜索技能…","Search skills…"),className:"w-full"}),N?e.jsx("div",{className:"flex items-center justify-center py-10",children:e.jsx(P,{})}):g?e.jsxs("div",{className:"rounded-lg border border-edge/70 bg-panel/60 p-3 text-[12px] text-fg-4",children:[a(i,"无法从 GitHub 拉取技能列表(可能是网络或速率限制)。你仍可以使用上方的「全部安装」按钮一次性安装该集合。",'Could not list skills from GitHub (network or rate-limit). You can still use "Install all" to grab the whole collection.'),e.jsx("div",{className:"mt-1 truncate font-mono text-[11px] text-fg-5",children:g})]}):k.length===0?e.jsx("div",{className:"rounded-lg border border-edge/70 bg-panel/60 p-3 text-center text-[12px] text-fg-5",children:M?a(i,"没有匹配的技能","No matching skills"):a(i,"该集合暂无可识别的技能","No discoverable skills in this collection")}):e.jsx("div",{className:"max-h-[60vh] overflow-y-auto rounded-lg border border-edge/70 bg-panel/40",children:k.map((v,r)=>{const C=y.has(v.name.toLowerCase()),O=E===v.name;return e.jsxs("div",{className:U("flex items-center justify-between gap-3 px-3 py-2 text-[12px]",r>0&&"border-t border-edge/40"),children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[C&&e.jsx(W,{size:12}),e.jsx("span",{className:U("truncate font-medium",C?"text-[var(--th-ok)]":"text-fg-2"),children:v.name})]}),v.description&&e.jsx("div",{className:"mt-0.5 line-clamp-1 text-[11px] text-fg-5",children:v.description})]}),C?e.jsx(z,{variant:"ghost",size:"sm",onClick:()=>{_(v.name)},disabled:O||L!==null,className:"hover:!text-err",children:O?e.jsx(P,{}):a(i,"移除","Remove")}):e.jsx(z,{variant:"outline",size:"sm",onClick:()=>{w(v.name)},disabled:O||L!==null,children:O?e.jsx(P,{}):a(i,"安装","Install")})]},v.name)})}),I&&e.jsx("div",{className:"text-[11px] text-fg-5",children:a(i,"仓库内技能数量较多,仅显示前 1000 个。可使用上方搜索或直接打开仓库浏览全部。","Showing the first 1000 skills. Use search or open the repo on GitHub for the full list.")})]})]})]}):null}const X=({size:s=12})=>e.jsx("svg",{width:s,height:s,viewBox:"0 0 24 24",fill:"currentColor",stroke:"none",children:e.jsx("polygon",{points:"12 2 15.09 8.26 22 9.27 17 14.14 18.18 21.02 12 17.77 5.82 21.02 7 14.14 2 9.27 8.91 8.26 12 2"})});function ee(s){return s>=1e4?`${(s/1e3).toFixed(1).replace(/\.0$/,"")}k`:s>=1e3?`${(s/1e3).toFixed(1)}k`:String(s)}function oe(s,t){const n=Date.parse(s);if(!Number.isFinite(n))return"";const l=Date.now()-n,i=1440*60*1e3,c=Math.max(1,Math.floor(l/i));if(c<30)return t==="zh-CN"?`${c} 天前`:`${c}d ago`;const x=Math.floor(c/30);if(x<12)return t==="zh-CN"?`${x} 个月前`:`${x}mo ago`;const u=Math.floor(x/12);return t==="zh-CN"?`${u} 年前`:`${u}y ago`}const H={dev:{zh:"开发工具",en:"Development",order:0},productivity:{zh:"生产力",en:"Productivity",order:1},communication:{zh:"协作沟通",en:"Communication",order:2},data:{zh:"数据",en:"Data",order:3},search:{zh:"搜索",en:"Search",order:4},utility:{zh:"工具",en:"Utility",order:5},custom:{zh:"自定义",en:"Custom",order:6}};function Oe(s){const t=new Map;for(const n of s){const l=t.get(n.category)||[];l.push(n),t.set(n.category,l)}return[...t.entries()].sort((n,l)=>(H[n[0]]?.order??99)-(H[l[0]]?.order??99)).map(([n,l])=>({key:n,items:l}))}function ce({scope:s,workdir:t,locale:n,onOpenBrowserSetup:l}){const i=F(r=>r.toast),c=`pikiloom.mcp.catalog.${s}.${t||""}`,{data:x,loading:u,refresh:m}=Q(c,async()=>(await A.getMcpCatalog(t,s)).items||[],[t,s]),[p,b]=o.useState(""),[N,d]=o.useState(null),[g,f]=o.useState(!1),[I,$]=o.useState(null),E=x||[],S=o.useMemo(()=>E.filter(r=>!r.installed||r.scope===s||!r.scope),[E,s]),L=o.useMemo(()=>{if(!p.trim())return S;const r=p.trim().toLowerCase();return S.filter(C=>C.name.toLowerCase().includes(r)||C.description.toLowerCase().includes(r)||C.descriptionZh.includes(r)||C.id.toLowerCase().includes(r))},[S,p]),R=o.useMemo(()=>L.filter(r=>r.isBuiltin),[L]),M=o.useMemo(()=>L.filter(r=>!r.isBuiltin&&(r.state==="ready"||r.state==="unhealthy")),[L]),j=o.useMemo(()=>Oe(L.filter(r=>!r.isBuiltin&&r.state!=="ready"&&r.state!=="unhealthy")),[L]),y=o.useCallback(async(r,C)=>{if(r.isRecommended){$(r.id);try{const O=await A.installMcp(r.id,s,C,t,!0);if(!O.ok)throw new Error(O.error||"install failed");return await m(),O.enabled??!1}catch(O){return i(O?.message||"Failed",!1),!1}finally{$(null)}}},[s,t,m,i]),k=o.useCallback(async r=>{$(r.id);try{if(!r.installed){const te=await A.installMcp(r.id,s,void 0,t,!1);if(!te.ok)throw new Error(te.error||"install failed")}const C=await A.startMcpOAuth(r.id);if(!C.ok||!C.authUrl||!C.state)throw new Error(C.error||"oauth start failed");await Ae(C.authUrl,C.state)?(await A.toggleMcp(r.id,!0,s,t),i(a(n,`${r.name} 授权成功`,`${r.name} authorized`),!0)):i(a(n,"授权未完成","Authorization not completed"),!1),await m()}catch(C){i(C?.message||"OAuth failed",!1)}finally{$(null)}},[s,t,n,i,m]),h=o.useCallback(async(r,C)=>{if(r.installedKey){$(r.id);try{await A.toggleMcp(r.installedKey,C,r.scope==="workspace"?"workspace":"global",t),await m()}catch(O){i(O?.message||"Failed",!1)}finally{$(null)}}},[t,m,i]),w=o.useCallback(async r=>{if(r.installedKey){$(r.id);try{await A.removeMcp(r.installedKey,r.scope==="workspace"?"workspace":"global",r.isRecommended?r.id:void 0,t),await m()}catch(C){i(C?.message||"Failed",!1)}finally{$(null)}}},[t,m,i]),_=o.useCallback(async r=>{if(!N)return;await y(N,r)!==!1&&d(null)},[N,y]),G=o.useCallback(r=>{if(r.state==="ready"||r.state==="unhealthy"){h(r,!1);return}if(r.state==="disabled"){h(r,!0);return}if(r.state==="needs_auth"){if(r.auth.type==="mcp-oauth"){k(r);return}if(r.auth.type==="credentials"){d(r);return}}},[h,k]),V=o.useCallback(r=>{if(r.state==="disabled"){h(r,!0);return}if(r.state==="needs_auth"){if(r.auth.type==="mcp-oauth"){k(r);return}if(r.auth.type==="credentials"){d(r);return}}if(r.auth.type==="mcp-oauth"){k(r);return}if(r.auth.type==="credentials"){d(r);return}y(r)},[k,y,h]),v=u&&!x;return e.jsxs("section",{children:[e.jsxs("div",{className:"mb-3 flex flex-wrap items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2.5",children:[e.jsx(J,{children:"MCP Servers"}),!u&&e.jsxs("span",{className:"text-[11px] text-fg-5",children:[M.length," ",a(n,"在用","in use")," · ",S.length-M.length-R.length," ",a(n,"可添加","available")]}),u&&e.jsx(P,{className:"h-3 w-3"})]}),e.jsx("div",{className:"flex items-center gap-1.5",children:e.jsxs("div",{className:"relative",children:[e.jsxs("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2.5",strokeLinecap:"round",strokeLinejoin:"round",className:"pointer-events-none absolute left-2.5 top-1/2 -translate-y-1/2 text-fg-5",children:[e.jsx("circle",{cx:"11",cy:"11",r:"8"}),e.jsx("line",{x1:"21",y1:"21",x2:"16.65",y2:"16.65"})]}),e.jsx("input",{value:p,onChange:r=>b(r.target.value),placeholder:a(n,"搜索...","Search..."),className:"h-7 w-52 rounded-md border border-edge bg-inset/50 pl-7 pr-2.5 text-[12px] text-fg outline-none placeholder:text-fg-5/50 focus:border-primary/30 focus:bg-inset"})]})})]}),v?e.jsx("div",{className:"flex items-center justify-center py-10",children:e.jsx(P,{})}):e.jsxs("div",{className:"space-y-5",children:[R.length>0&&e.jsxs("div",{children:[e.jsxs("div",{className:"mb-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-[var(--th-accent,#7c3aed)]"}),a(n,"内置(pikiloom 优化)","Built-in (optimized by pikiloom)")]}),e.jsx("div",{className:"grid gap-2 sm:grid-cols-2",children:R.map((r,C)=>r.installed?e.jsx(ne,{item:r,locale:n,busy:I===r.id,index:C,onPrimary:()=>G(r),onReconfigure:r.id==="pikiloom-browser"?l:void 0},r.id):e.jsx(ae,{item:r,locale:n,busy:I===r.id,index:C,onPrimary:()=>V(r)},r.id))})]}),M.length>0&&e.jsxs("div",{children:[e.jsxs("div",{className:"mb-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-[var(--th-ok)]"}),a(n,"在用","In use")]}),e.jsx("div",{className:"grid gap-2 sm:grid-cols-2",children:M.map((r,C)=>e.jsx(ne,{item:r,locale:n,busy:I===r.id,index:C,onPrimary:()=>G(r),onRemove:()=>{w(r)},onReauth:r.auth.type==="mcp-oauth"?()=>{k(r)}:void 0,onReconfigure:r.auth.type==="credentials"?()=>d(r):void 0},r.id))})]}),j.length>0&&e.jsxs("div",{children:[e.jsxs("div",{className:"mb-2 flex items-center gap-1.5 text-[11px] font-semibold uppercase tracking-[0.16em] text-fg-5",children:[e.jsx("span",{className:"h-1.5 w-1.5 rounded-full bg-fg-5"}),M.length===0?a(n,"推荐的服务","Recommended services"):a(n,"更多可选","More options")]}),e.jsx("div",{className:"space-y-4",children:j.map(r=>e.jsx(Be,{groupKey:r.key,locale:n,children:e.jsx("div",{className:"grid gap-2 sm:grid-cols-2 lg:grid-cols-3",children:r.items.map((C,O)=>e.jsx(ae,{item:C,locale:n,busy:I===C.id,index:O,onPrimary:()=>V(C)},C.id))})},r.key))})]}),R.length===0&&M.length===0&&j.length===0&&e.jsx(se,{title:a(n,"没有匹配的服务","No matching services"),subtitle:a(n,"试试别的关键词","Try a different search term")})]}),e.jsx("div",{className:"mt-3 flex justify-end",children:e.jsxs("button",{className:"text-[12px] text-fg-4 hover:text-fg-2 transition-colors",onClick:()=>f(!0),children:["+ ",a(n,"添加自定义 MCP","Add custom MCP")]})}),e.jsx(ze,{open:!!N,onClose:()=>d(null),locale:n,item:N,initial:N?.config?.env||N?.config?.headers,onSubmit:_}),e.jsx(Le,{open:g,onClose:()=>f(!1),locale:n,scope:s,workdir:t,onAdded:m})]})}function Be({groupKey:s,locale:t,children:n}){const l=H[s],i=l?a(t,l.zh,l.en):s;return e.jsxs("div",{children:[e.jsx("div",{className:"mb-1.5 text-[11px] font-medium text-fg-5",children:i}),n]})}function se({title:s,subtitle:t}){return e.jsxs("div",{className:"rounded-xl border border-dashed border-edge py-10 text-center",children:[e.jsx("div",{className:"text-[13px] font-medium text-fg-3",children:s}),t&&e.jsx("div",{className:"mt-1 text-[12px] text-fg-5",children:t})]})}function de({scope:s,workdir:t,locale:n}){const l=`pikiloom.skills.catalog.${s}.${t||""}`,{data:i,loading:c,refresh:x}=Q(l,async()=>{const h=await A.getSkillsCatalog(t,s);return{items:h.items||[],installed:h.installed||[]}},[t,s]),[u,m]=o.useState(!1),[p,b]=o.useState(null),[N,d]=o.useState(null),g=F(h=>h.toast),f=i?.items||[],I=i?.installed||[],$=o.useMemo(()=>{const h=s==="global"?"global":"project";return I.filter(w=>w.scope===h)},[I,s]),E=o.useMemo(()=>f.filter(h=>h.installedNames.length>0),[f]),S=o.useMemo(()=>f.filter(h=>h.installedNames.length===0),[f]),L=o.useMemo(()=>{const h=new Set;for(const w of f)for(const _ of w.installedNames)h.add(_.toLowerCase());return $.filter(w=>!h.has(w.name.toLowerCase()))},[f,$]),R=o.useCallback(async h=>{d(h);try{const w=await A.removeExtensionSkill(h,s==="global",t);w.ok?(g(a(n,`${h} 已移除`,`${h} removed`),!0),x()):g(w.error||"Failed",!1)}catch(w){g(w?.message||"Failed",!1)}finally{d(null)}},[s,t,n,g,x]),M=o.useMemo(()=>{const h=new Map;for(const w of S){const _=w.category;h.has(_)||h.set(_,[]),h.get(_).push(w)}return[...h.entries()].sort(([w],[_])=>(H[w]?.order??99)-(H[_]?.order??99))},[S]),j=p&&f.find(h=>h.id===p)||null,y=c&&!i,k=$.length;return e.jsxs("section",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsxs("div",{className:"flex items-center gap-2.5",children:[e.jsx(J,{children:"Skills"}),!c&&e.jsxs("span",{className:"text-[11px] text-fg-5",children:[k," ",a(n,"已安装","installed")," · ",S.length," ",a(n,"可用","available")]}),c&&e.jsx(P,{className:"h-3 w-3"})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(z,{variant:"ghost",size:"sm",onClick:()=>{x()},children:c?a(n,"刷新中…","Refreshing…"):a(n,"刷新","Refresh")}),e.jsxs(z,{variant:"outline",size:"sm",onClick:()=>m(!0),children:["+ ",a(n,"从 GitHub 安装","Install from GitHub")]})]})]}),y?e.jsx("div",{className:"flex items-center justify-center py-10",children:e.jsx(P,{})}):f.length===0&&L.length===0?e.jsx(se,{title:a(n,"暂无可用的技能包","No skill packs available"),subtitle:a(n,"从 GitHub 导入一个开始使用","Import from GitHub to get started")}):e.jsxs(e.Fragment,{children:[(E.length>0||L.length>0)&&e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center gap-2 text-[11px] font-semibold text-fg-3",children:[e.jsx("span",{className:"inline-block h-1.5 w-1.5 rounded-full bg-[var(--th-ok)]"}),a(n,"已安装","Installed")]}),e.jsxs("div",{className:"grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3",children:[E.map((h,w)=>e.jsx(Ee,{item:h,locale:n,animationDelay:`${Math.min(w,12)*30}ms`,onClick:()=>b(h.id)},h.id)),L.map((h,w)=>e.jsx(_e,{skill:h,locale:n,animationDelay:`${Math.min(E.length+w,12)*30}ms`,busy:N===h.name,onRemove:()=>{R(h.name)}},`local-${h.name}`))]})]}),S.length>0&&e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center gap-2 text-[11px] font-semibold text-fg-3",children:[e.jsx("span",{className:"inline-block h-1.5 w-1.5 rounded-full bg-fg-5/50"}),a(n,"推荐","Available")]}),M.map(([h,w])=>e.jsxs("div",{className:"space-y-2",children:[e.jsx("div",{className:"text-[10.5px] font-medium uppercase tracking-[0.06em] text-fg-5",children:n==="zh-CN"?H[h]?.zh||h:H[h]?.en||h}),e.jsx("div",{className:"grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3",children:w.map((_,G)=>e.jsx(Re,{item:_,locale:n,animationDelay:`${Math.min(G,12)*30}ms`,onClick:()=>b(_.id)},_.id))})]},h))]})]}),e.jsx(Pe,{item:j,open:!!j,onClose:()=>b(null),onChanged:()=>{x()},locale:n,scope:s,workdir:t,installedSkills:I}),e.jsx(Me,{open:u,onClose:()=>m(!1),locale:n,scope:s,workdir:t,onInstalled:x})]})}const Y={dev:{zh:"研发工具",en:"Developer",order:1},cloud:{zh:"云与部署",en:"Cloud",order:2},data:{zh:"数据后端",en:"Data",order:3},commerce:{zh:"商业支付",en:"Commerce",order:4},social:{zh:"社交通讯",en:"Social",order:5},content:{zh:"内容创作",en:"Content",order:6}};function xe({state:s,locale:t}){return s==="ready"?e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold text-[var(--th-ok)]",style:{background:"color-mix(in oklab, var(--th-ok) 12%, transparent)"},children:[e.jsx(W,{size:10}),a(t,"已登录","Signed in")]}):s==="installed_not_auth"?e.jsxs("span",{className:"inline-flex items-center gap-1 rounded-full px-2 py-0.5 text-[10px] font-semibold text-amber-600 dark:text-amber-400",style:{background:"color-mix(in oklab, #f59e0b 14%, transparent)"},children:[e.jsx(le,{size:10}),a(t,"待登录","Sign-in needed")]}):s==="not_installed"?e.jsx("span",{className:"inline-flex items-center gap-1 rounded-full border border-edge bg-inset/60 px-2 py-0.5 text-[10px] font-medium text-fg-5",children:a(t,"未安装","Not installed")}):e.jsx("span",{className:"inline-flex items-center gap-1 rounded-full border border-edge bg-inset/60 px-2 py-0.5 text-[10px] font-medium text-fg-5",children:"..."})}function ue({chunks:s,running:t,emptyHint:n}){const l=o.useRef(null);o.useEffect(()=>{l.current&&(l.current.scrollTop=l.current.scrollHeight)},[s.length]);const i=s.join("");return e.jsx("div",{ref:l,className:"relative h-56 overflow-auto rounded-xl border border-edge/60 bg-[#0b0f16] p-3 font-mono text-[11.5px] leading-[1.55] text-[#cdd6f4] scrollbar-thin",style:{boxShadow:"0 1px 0 rgba(255,255,255,0.03) inset"},children:i?e.jsx("pre",{className:"whitespace-pre-wrap break-words",children:i}):e.jsx("div",{className:"flex h-full items-center justify-center text-[#6c7086]",children:t?e.jsxs("span",{className:"inline-flex items-center gap-2",children:[e.jsx(P,{})," ",n||"Starting…"]}):n||"No output yet"})})}function Te({cli:s,locale:t,onInstalled:n}){const[l,i]=o.useState([]),[c,x]=o.useState(!1),[u,m]=o.useState(null),[p,b]=o.useState(null),[N,d]=o.useState(null),g=o.useRef(null),f=o.useCallback(()=>{try{g.current?.close()}catch{}g.current=null},[]);o.useEffect(()=>f,[f]);const I=o.useCallback(async()=>{i([]),b(null),d(null),x(!0);try{const S=await A.startCliInstall(s.id);if(!S.ok||!S.sessionId)throw new Error(S.error||"start failed");m(S.sessionId);const L=new EventSource(`/api/extensions/cli/auth/stream?sessionId=${encodeURIComponent(S.sessionId)}`);g.current=L,L.onmessage=R=>{try{const M=JSON.parse(R.data);M.type==="output"?i(j=>j.length>400?[...j.slice(-400),M.chunk]:[...j,M.chunk]):M.type==="error"?b(M.message||"error"):M.type==="done"&&(x(!1),d(!!M.ok),f(),M.ok&&n())}catch{}},L.addEventListener("close",()=>{x(!1),f()}),L.onerror=()=>{c&&b(a(t,"连接中断","Stream disconnected"))}}catch(S){x(!1),b(S?.message||"failed to start install")}},[s.id,f,t,n,c]),$=o.useCallback(async()=>{if(u)try{await A.cancelCliAuth(u)}catch{}f(),x(!1)},[u,f]);if(!s.autoInstall)return null;const E=c||l.length>0||N!==null;return e.jsxs("div",{className:"space-y-3 rounded-lg border border-edge/70 bg-panel/60 p-3",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3",children:[e.jsx("div",{className:"text-[12px] text-fg-3",children:a(t,`通过 ${s.autoInstall.label} 直接在本机自动安装,无需复制命令。`,`Run the ${s.autoInstall.label} install locally — no copy-paste needed.`)}),c?e.jsx(z,{variant:"outline",size:"sm",onClick:$,children:a(t,"中止","Abort")}):e.jsx(z,{variant:"primary",size:"sm",onClick:I,disabled:N===!0,children:N===!0?a(t,"已安装","Installed"):N===!1?a(t,"重试安装","Retry install"):a(t,"一键安装","Auto-install")})]}),E&&e.jsx(ue,{chunks:l,running:c,emptyHint:a(t,"安装进度将在此显示","Install output will appear here")}),p&&e.jsx("div",{className:"text-[12px] text-[var(--th-err)]",children:p}),N===!1&&!p&&e.jsx("div",{className:"text-[12px] text-[var(--th-err)]",children:a(t,"安装未成功,请查看上方输出排查。","Install did not complete — check the output above.")})]})}function Fe({cliId:s,locale:t,hint:n,commands:l,onSignedIn:i,onCancel:c}){const[x,u]=o.useState(!1),[m,p]=o.useState(null),[b,N]=o.useState(null),d=o.useCallback(async()=>{u(!0),p(null),N(null);try{const g=await A.refreshCli(s);if(!g.ok){N(g.error||a(t,"检测失败","Detection failed"));return}if(g.status?.state==="ready"){i();return}const f=l.length;p(f>1?a(t,`尚未检测到登录。请依次完成上面 ${f} 步后再重试 —— 任一步漏掉都不会算授权成功。`,`Not signed in yet. Run all ${f} steps above in order — sign-in only counts as successful after every step completes.`):a(t,"尚未检测到登录,请先在终端完成上述命令。","Not signed in yet — finish the command above in your terminal first."))}catch(g){N(g?.message||"failed")}finally{u(!1)}},[s,t,i,l.length]);return e.jsxs("div",{className:"space-y-3",children:[n&&e.jsx("div",{className:"text-[12px] leading-relaxed text-fg-4",children:n}),e.jsx(he,{commands:l,locale:t}),b&&e.jsx("div",{className:"text-[12px] text-[var(--th-err)]",children:b}),m&&!b&&e.jsx("div",{className:"text-[12px] text-fg-4",children:m}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(z,{variant:"primary",size:"sm",onClick:d,disabled:x,children:e.jsxs("span",{className:"inline-flex items-center gap-1.5",children:[x&&e.jsx("svg",{width:"12",height:"12",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",className:"animate-spin",style:{animationDuration:"0.9s"},"aria-hidden":"true",children:e.jsx("path",{d:"M21 12a9 9 0 1 1-6.219-8.56"})}),e.jsx("span",{children:x?a(t,"检测中…","Checking…"):a(t,"重新检测状态","Re-check status")})]})}),e.jsx(z,{variant:"ghost",size:"sm",onClick:c,disabled:x,children:a(t,"关闭","Close")})]})]})}function Ue({cli:s,locale:t,onSignedIn:n,onCancel:l}){const[i,c]=o.useState([]),[x,u]=o.useState(!1),[m,p]=o.useState(null),[b,N]=o.useState(null),[d,g]=o.useState(null),f=o.useRef(null),I=o.useCallback(()=>{try{f.current?.close()}catch{}f.current=null},[]);o.useEffect(()=>I,[I]);const $=o.useCallback(async()=>{c([]),g(null),N(null),u(!0);try{const y=await A.startCliAuth(s.id);if(!y.ok||!y.sessionId)throw new Error(y.error||"start failed");p(y.sessionId);const k=new EventSource(`/api/extensions/cli/auth/stream?sessionId=${encodeURIComponent(y.sessionId)}`);f.current=k,k.onmessage=h=>{try{const w=JSON.parse(h.data);w.type==="output"?c(_=>_.length>400?[..._.slice(-400),w.chunk]:[..._,w.chunk]):w.type==="status"?N(w.status.state==="ready"?a(t,"已检测到登录成功","Sign-in detected"):a(t,"等待授权完成…","Waiting for authorization…")):w.type==="error"?g(w.message||"error"):w.type==="done"&&(u(!1),I(),w.ok&&n())}catch{}},k.addEventListener("close",()=>{u(!1),I()}),k.onerror=()=>{x&&g(a(t,"连接中断","Stream disconnected"))}}catch(y){u(!1),g(y?.message||"failed to start sign-in")}},[s.id,I,t,n,x]),E=o.useCallback(async()=>{if(m)try{await A.cancelCliAuth(m)}catch{}I(),u(!1),l()},[m,I,l]),[S,L]=o.useState({}),[R,M]=o.useState(!1),j=o.useCallback(async()=>{M(!0),g(null);try{const y=await A.applyCliToken(s.id,S);y.ok?n():g(y.error||a(t,"应用凭据失败","Failed to apply credentials"))}catch(y){g(y?.message||"failed")}finally{M(!1)}},[s.id,S,t,n]);if(s.auth.type==="oauth-web"){const y=t==="zh-CN"&&s.auth.loginHintZh||s.auth.loginHint,k=s.auth.manualLoginCommands;return k&&k.length>0?e.jsx(Fe,{cliId:s.id,locale:t,hint:y,commands:k,onSignedIn:n,onCancel:l}):e.jsxs("div",{className:"space-y-3",children:[y&&e.jsx("div",{className:"text-[12px] leading-relaxed text-fg-4",children:y}),e.jsx(ue,{chunks:i,running:x,emptyHint:a(t,"点击「开始登录」后将在此展示命令行输出",'Click "Start sign-in" to stream CLI output here')}),d&&e.jsx("div",{className:"text-[12px] text-[var(--th-err)]",children:d}),b&&!d&&e.jsx("div",{className:"text-[12px] text-[var(--th-ok)]",children:b}),e.jsx("div",{className:"flex items-center gap-2",children:x?e.jsx(z,{variant:"outline",size:"sm",onClick:E,children:a(t,"中止","Abort")}):e.jsxs(e.Fragment,{children:[e.jsx(z,{variant:"primary",size:"sm",onClick:$,children:a(t,"开始登录","Start sign-in")}),e.jsx(z,{variant:"ghost",size:"sm",onClick:l,children:a(t,"取消","Cancel")})]})})]})}if(s.auth.type==="token"){const y=t==="zh-CN"&&s.auth.loginHintZh||s.auth.loginHint;return e.jsxs("div",{className:"space-y-3",children:[y&&e.jsx("div",{className:"text-[12px] leading-relaxed text-fg-4",children:y}),e.jsx("div",{className:"space-y-2",children:(s.auth.tokenFields||[]).map(k=>e.jsxs("label",{className:"block text-[12px]",children:[e.jsxs("div",{className:"mb-1 text-fg-3",children:[t==="zh-CN"?k.labelZh:k.label,k.required&&e.jsx("span",{className:"ml-1 text-[var(--th-err)]",children:"*"})]}),e.jsx(B,{type:k.secret?"password":"text",value:S[k.key]||"",onChange:h=>L(w=>({...w,[k.key]:h.target.value})),placeholder:k.placeholder||"",className:"w-full"}),k.helpUrl&&e.jsxs("a",{className:"mt-1 inline-block text-[11px] text-primary hover:underline",href:k.helpUrl,target:"_blank",rel:"noreferrer",children:[a(t,"如何获取","How to get this")," ↗"]})]},k.key))}),d&&e.jsx("div",{className:"text-[12px] text-[var(--th-err)]",children:d}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(z,{variant:"primary",size:"sm",onClick:j,disabled:R,children:R?a(t,"验证中…","Verifying…"):a(t,"保存并验证","Save & verify")}),e.jsx(z,{variant:"ghost",size:"sm",onClick:l,children:a(t,"取消","Cancel")})]})]})}return null}function he({commands:s,locale:t}){return e.jsx("div",{className:"space-y-2",children:s.map((n,l)=>e.jsxs("div",{className:"overflow-hidden rounded-lg border border-edge/70 bg-panel/60",children:[n.label&&e.jsx("div",{className:"flex items-center justify-between border-b border-edge/50 bg-panel-alt/40 px-3 py-1 text-[11px] font-medium text-fg-4",children:e.jsx("span",{children:n.label})}),e.jsxs("div",{className:"flex items-start gap-2 px-3 py-2 font-mono text-[12px] text-fg-2",children:[e.jsx("span",{className:"mt-[2px] select-none text-fg-5",children:"$"}),e.jsx("code",{className:"min-w-0 flex-1 break-all",children:n.cmd}),e.jsx("button",{type:"button",onClick:()=>{navigator.clipboard?.writeText(n.cmd)},className:"shrink-0 rounded px-2 py-0.5 text-[10.5px] text-fg-5 transition-colors hover:bg-panel-h hover:text-fg-2",title:a(t,"复制到剪贴板","Copy to clipboard"),children:a(t,"复制","Copy")})]})]},l))})}function De({cli:s,open:t,onClose:n,onChanged:l,locale:i}){const[c,x]=o.useState(!1),[u,m]=o.useState(!1),[p,b]=o.useState(null);o.useEffect(()=>{t||(x(!1),b(null))},[t]);const N=o.useMemo(()=>s?s.install[s.platform]||[]:[],[s]);if(!s)return null;const d=s.state!=="not_installed",g=s.state==="ready",f=async()=>{m(!0),b(null);try{const I=await A.logoutCli(s.id);I.ok||b(I.error||a(i,"登出失败","Logout failed")),l()}catch(I){b(I?.message||"failed")}finally{m(!1)}};return e.jsxs(q,{open:t,onClose:n,wide:!0,children:[e.jsx(K,{title:s.name,description:i==="zh-CN"?s.descriptionZh:s.description,onClose:n}),e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx(T,{iconSlug:s.iconSlug,iconUrl:s.iconUrl,name:s.name,size:44}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2 text-[13px] font-semibold text-fg",children:[s.name,s.homepage&&e.jsx("a",{href:s.homepage,target:"_blank",rel:"noreferrer",className:"text-fg-5 hover:text-primary",children:e.jsx(D,{})})]}),e.jsxs("div",{className:"mt-0.5 flex items-center gap-2 text-[11.5px] text-fg-4",children:[xe({state:s.state,locale:i}),s.version&&e.jsxs("span",{className:"font-mono text-fg-5",children:["v",s.version]}),g&&s.authDetail&&e.jsxs("span",{className:"truncate text-fg-5",children:["· ",s.authDetail]})]})]})]}),!d&&e.jsxs("section",{className:"space-y-3",children:[e.jsx("div",{className:"text-[12px] font-semibold text-fg-3",children:a(i,"安装","Install")}),s.autoInstall&&e.jsx(Te,{cli:s,locale:i,onInstalled:l}),e.jsx("div",{className:"text-[11.5px] leading-relaxed text-fg-5",children:s.autoInstall?a(i,"或手动复制命令到终端执行。","Or copy a command below and run it in your terminal."):a(i,"复制下面的命令到终端运行。我们不自动代为安装 — 包管理器往往需要 sudo 或交互式确认。","Copy a command below and run it in your terminal. We don't auto-install — package managers often need sudo or interactive confirmation.")}),N.length>0?e.jsx(he,{commands:N,locale:i}):e.jsx("div",{className:"text-[12px] text-fg-5",children:a(i,"请查看官方文档","Check the official installation docs")}),s.install.docs&&e.jsxs("a",{href:s.install.docs,target:"_blank",rel:"noreferrer",className:"inline-flex items-center gap-1 text-[11.5px] text-primary hover:underline",children:[a(i,"查看安装文档","Installation docs")," ↗"]}),e.jsx("div",{className:"pt-2",children:e.jsx(z,{variant:"outline",size:"sm",onClick:l,children:a(i,"我已安装,重新检测","I've installed, re-check")})})]}),d&&s.auth.type!=="none"&&e.jsxs("section",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("div",{className:"text-[12px] font-semibold text-fg-3",children:a(i,"登录","Sign in")}),g&&!c&&e.jsx(z,{variant:"ghost",size:"sm",onClick:f,disabled:u,children:u?a(i,"登出中…","Signing out…"):a(i,"登出","Sign out")})]}),p&&e.jsx("div",{className:"text-[11.5px] text-[var(--th-err)]",children:p}),g&&!c?e.jsxs("div",{className:"rounded-lg border border-edge/70 bg-panel/60 p-3 text-[12px] text-fg-3",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(W,{size:12}),e.jsx("span",{children:a(i,"你已经登录,命令行工具可直接使用。","Already signed in — the CLI is ready to use.")})]}),e.jsx("div",{className:"mt-2",children:e.jsx(z,{variant:"outline",size:"sm",onClick:()=>x(!0),children:a(i,"重新登录","Re-authenticate")})})]}):e.jsx(Ue,{cli:s,locale:i,onSignedIn:()=>{x(!1),l()},onCancel:()=>x(!1)})]}),d&&s.auth.type==="none"&&e.jsx("section",{className:"rounded-lg border border-edge/70 bg-panel/60 p-3 text-[12px] text-fg-3",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx(W,{size:12}),e.jsx("span",{children:a(i,"无需授权 — 可直接使用。","No authentication required — ready to use.")})]})})]})]})}function He({item:s,onClick:t,locale:n,animationDelay:l}){return e.jsx("button",{type:"button",onClick:t,className:"animate-in-up group relative flex min-h-[112px] w-full flex-col overflow-hidden rounded-lg border border-[var(--edge-subtle)] bg-[var(--surface-2)] p-4 text-left transition-[background,border-color] duration-200 hover:border-[var(--edge-default)] hover:bg-[var(--surface-3)] focus-visible:outline-none focus-visible:shadow-[0_0_0_3px_var(--brand-glow-a)]",style:{animationDelay:l},children:e.jsxs("div",{className:"relative flex items-start gap-3",children:[e.jsx(T,{iconSlug:s.iconSlug,iconUrl:s.iconUrl,name:s.name,size:36}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center justify-between gap-2",children:[e.jsx("div",{className:"truncate text-[13px] font-semibold text-fg",children:s.name}),xe({state:s.state,locale:n})]}),e.jsxs("div",{className:"mt-0.5 truncate text-[11.5px] text-fg-5",children:[s.version?e.jsxs("span",{className:"font-mono",children:["v",s.version]}):null,s.version&&s.authDetail?" · ":null,s.authDetail]}),e.jsx("div",{className:"mt-1 truncate text-[11.5px] text-fg-4",children:n==="zh-CN"?s.descriptionZh:s.description})]})]})})}function Ge({item:s,onClick:t,locale:n,animationDelay:l}){const i=s.state==="not_installed"?a(n,"安装","Install"):s.state==="installed_not_auth"?a(n,"登录","Sign in"):a(n,"查看","Details");return e.jsxs("button",{type:"button",onClick:t,className:"animate-in-up group relative flex min-h-[112px] w-full flex-col overflow-hidden rounded-lg border border-[var(--edge-subtle)] bg-[var(--surface-2)] p-4 text-left transition-[background,border-color] duration-200 hover:border-[var(--edge-default)] hover:bg-[var(--surface-3)] focus-visible:outline-none focus-visible:shadow-[0_0_0_3px_var(--brand-glow-a)]",style:{animationDelay:l},children:[e.jsxs("div",{className:"flex items-start gap-3",children:[e.jsx(T,{iconSlug:s.iconSlug,iconUrl:s.iconUrl,name:s.name,size:32}),e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"truncate text-[13px] font-semibold text-fg",children:s.name}),s.homepage&&e.jsx("a",{href:s.homepage,target:"_blank",rel:"noreferrer",onClick:c=>c.stopPropagation(),className:"text-fg-5 transition-colors hover:text-primary",children:e.jsx(D,{})})]}),e.jsx("div",{className:"mt-0.5 truncate text-[11.5px] text-fg-4",children:n==="zh-CN"?s.descriptionZh:s.description})]})]}),e.jsxs("div",{className:"mt-auto pt-3 flex items-center justify-between",children:[e.jsx("span",{className:"text-[11px] text-fg-5",children:s.auth.type==="oauth-web"?a(n,"浏览器授权","OAuth"):s.auth.type==="token"?a(n,"Token","Token"):a(n,"免配置","No auth")}),e.jsx("span",{className:"inline-flex items-center gap-1 rounded-md border border-[var(--edge-subtle)] bg-transparent px-2.5 py-1 text-[11px] font-semibold text-fg-3 transition-colors group-hover:border-[var(--edge-default)] group-hover:text-fg",children:i})]})]})}function me({locale:s,scope:t}){const{data:n,loading:l,refresh:i}=Q("pikiloom:cli:catalog",async()=>{const d=await A.getCliCatalog();if(!d.ok)throw new Error(d.error||"failed");return d.items||[]},[]),[c,x]=o.useState(null),u=o.useMemo(()=>{const d=n||[];return d},[n,t]),m=c&&u.find(d=>d.id===c)||null,p=u.filter(d=>d.state==="ready"),b=u.filter(d=>d.state!=="ready"),N=o.useMemo(()=>{const d=new Map;for(const g of b){const f=g.category;d.has(f)||d.set(f,[]),d.get(f).push(g)}return[...d.entries()].sort(([g],[f])=>(Y[g]?.order??99)-(Y[f]?.order??99))},[b]);return e.jsxs("section",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx(J,{children:a(s,"CLI 工具","CLI Tools")}),e.jsxs("div",{className:"flex items-center gap-3 text-[11px] text-fg-5",children:[e.jsxs("span",{children:[p.length," ",a(s,"已登录","signed in")," · ",b.length," ",a(s,"可用","available")]}),e.jsx("button",{type:"button",onClick:()=>{i()},className:"rounded px-2 py-0.5 text-fg-5 transition-colors hover:bg-panel-h hover:text-fg-2",children:l?a(s,"刷新中…","Refreshing…"):a(s,"刷新","Refresh")})]})]}),t==="workspace"&&e.jsx("div",{className:"rounded-lg border border-edge/60 bg-inset/40 px-3 py-2 text-[11.5px] text-fg-4",children:a(s,"CLI 工具安装于机器层面,项目视图下同样可见。","CLI tools are installed machine-wide and are shown here for convenience.")}),p.length>0&&e.jsxs("div",{className:"space-y-3",children:[e.jsxs("div",{className:"flex items-center gap-2 text-[11px] font-semibold text-fg-3",children:[e.jsx("span",{className:"inline-block h-1.5 w-1.5 rounded-full bg-[var(--th-ok)]"}),a(s,"已登录","Signed in")]}),e.jsx("div",{className:"grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3",children:p.map((d,g)=>e.jsx(He,{item:d,locale:s,animationDelay:`${Math.min(g,12)*30}ms`,onClick:()=>x(d.id)},d.id))})]}),b.length>0&&e.jsxs("div",{className:"space-y-4",children:[e.jsxs("div",{className:"flex items-center gap-2 text-[11px] font-semibold text-fg-3",children:[e.jsx("span",{className:"inline-block h-1.5 w-1.5 rounded-full bg-fg-5/50"}),a(s,"推荐工具","Available")]}),N.map(([d,g])=>e.jsxs("div",{className:"space-y-2",children:[e.jsx("div",{className:"text-[10.5px] font-medium uppercase tracking-[0.06em] text-fg-5",children:s==="zh-CN"?Y[d]?.zh||d:Y[d]?.en||d}),e.jsx("div",{className:"grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3",children:g.map((f,I)=>e.jsx(Ge,{item:f,locale:s,animationDelay:`${Math.min(I,12)*30}ms`,onClick:()=>x(f.id)},f.id))})]},d))]}),!l&&u.length===0&&e.jsx(se,{title:a(s,"暂无可用 CLI","No CLI tools available"),subtitle:a(s,"稍后再试,或重启一下服务。","Try again later, or restart the service.")}),e.jsx(De,{cli:m,open:!!m,onClose:()=>x(null),onChanged:()=>{i()},locale:s})]})}function pe({active:s,onChange:t,locale:n,counts:l}){const i=[{id:"mcp",labelZh:"MCP 服务",labelEn:"MCP",icon:e.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("circle",{cx:"12",cy:"12",r:"3"}),e.jsx("circle",{cx:"4",cy:"5",r:"1.6"}),e.jsx("circle",{cx:"20",cy:"5",r:"1.6"}),e.jsx("circle",{cx:"4",cy:"19",r:"1.6"}),e.jsx("circle",{cx:"20",cy:"19",r:"1.6"}),e.jsx("path",{d:"M6 6 l4 4 M18 6 l-4 4 M6 18 l4 -4 M18 18 l-4 -4"})]})},{id:"cli",labelZh:"命令行",labelEn:"CLI",icon:e.jsxs("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:[e.jsx("path",{d:"M4 17 l5 -5 -5 -5"}),e.jsx("path",{d:"M12 19 h8"})]})},{id:"skill",labelZh:"技能包",labelEn:"Skills",icon:e.jsx("svg",{width:"14",height:"14",viewBox:"0 0 24 24",fill:"none",stroke:"currentColor",strokeWidth:"2",strokeLinecap:"round",strokeLinejoin:"round",children:e.jsx("path",{d:"M12 2 l3 7 h7 l-5.5 4.5 2 7.5 L12 17 l-6.5 4 2 -7.5 L2 9 h7 z"})})}];return e.jsx(fe,{className:"w-fit bg-panel/80 backdrop-blur",children:i.map(c=>e.jsxs(ge,{active:s===c.id,onClick:()=>t(c.id),className:"gap-1.5 px-3.5",children:[e.jsx("span",{className:"shrink-0",children:c.icon}),e.jsx("span",{children:n==="zh-CN"?c.labelZh:c.labelEn}),l?.[c.id]!==void 0&&e.jsx("span",{className:U("ml-1 inline-flex h-4 min-w-4 items-center justify-center rounded-full px-1 text-[10px] font-semibold",s===c.id?"bg-primary/12 text-primary":"bg-inset/70 text-fg-5"),children:l[c.id]})]},c.id))})}function Ve({onOpenBrowserSetup:s}){const t=F(u=>u.locale),l=F(u=>u.state)?.config?.workdir||"",[i,c]=o.useState(()=>{try{const u=localStorage.getItem("pikiloom:extensions:tab");return u==="mcp"||u==="cli"||u==="skill"?u:"mcp"}catch{return"mcp"}}),x=o.useCallback(u=>{c(u);try{localStorage.setItem("pikiloom:extensions:tab",u)}catch{}},[]);return e.jsxs("div",{className:"animate-in space-y-6",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-4",children:[e.jsx("div",{className:"space-y-1",children:e.jsx("div",{className:"text-[13px] leading-relaxed text-fg-4",children:a(t,"管理一次授权即可全局复用的服务与工具。项目专属的扩展请在工作台侧栏配置。","One-time authorization, use everywhere. Project-specific extensions live in the Workbench sidebar.")})}),e.jsx(pe,{active:i,onChange:x,locale:t})]}),e.jsxs("div",{className:"animate-in-fade",children:[i==="mcp"&&e.jsx("div",{className:"space-y-7",children:e.jsx(ce,{scope:"global",workdir:l,locale:t,onOpenBrowserSetup:s})}),i==="cli"&&e.jsx(me,{locale:t,scope:"global"}),i==="skill"&&e.jsx(de,{scope:"global",workdir:l,locale:t})]},i)]})}function Ye({workdir:s}){const t=F(c=>c.locale),[n,l]=o.useState(()=>{try{const c=localStorage.getItem("pikiloom:extensions-ws:tab");return c==="mcp"||c==="cli"||c==="skill"?c:"mcp"}catch{return"mcp"}}),i=o.useCallback(c=>{l(c);try{localStorage.setItem("pikiloom:extensions-ws:tab",c)}catch{}},[]);return e.jsxs("div",{className:"space-y-6",children:[e.jsxs("div",{className:"flex flex-wrap items-end justify-between gap-3",children:[e.jsx("div",{className:"text-[13px] leading-relaxed text-fg-4",children:a(t,"仅对当前工作区生效 — 依赖项目目录的本地服务和专属技能包。","Scoped to this workspace — local services that depend on project context and project-specific skill packs.")}),e.jsx(pe,{active:n,onChange:i,locale:t})]}),e.jsxs("div",{className:"animate-in-fade",children:[n==="mcp"&&e.jsx(ce,{scope:"workspace",workdir:s,locale:t}),n==="cli"&&e.jsx(me,{locale:t,scope:"workspace"}),n==="skill"&&e.jsx(de,{scope:"workspace",workdir:s,locale:t})]},n)]})}export{Ve as ExtensionsTab,Ye as WorkspaceExtensionsBody};