pikiloop 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +353 -0
- package/README.v2.md +287 -0
- package/README.zh-CN.md +352 -0
- package/dashboard/dist/assets/AgentTab-UZPIhlkr.js +1 -0
- package/dashboard/dist/assets/DirBrowser-Ckcmi-Pi.js +1 -0
- package/dashboard/dist/assets/ExtensionsTab-KZhEDrdu.js +1 -0
- package/dashboard/dist/assets/IMAccessTab-Bd_IY1GQ.js +1 -0
- package/dashboard/dist/assets/Modal-CTeL0y7P.js +1 -0
- package/dashboard/dist/assets/Modals-axftHasy.js +1 -0
- package/dashboard/dist/assets/Select-C8tOdPhe.js +1 -0
- package/dashboard/dist/assets/SessionPanel-C1geSRxw.js +1 -0
- package/dashboard/dist/assets/SystemTab-DBDkaPiO.js +1 -0
- package/dashboard/dist/assets/anthropic-BAdojD7P.ico +0 -0
- package/dashboard/dist/assets/codex-DYadqqp0.png +0 -0
- package/dashboard/dist/assets/deepseek-BeYNZEk0.ico +0 -0
- package/dashboard/dist/assets/doubao-DloFDuFR.png +0 -0
- package/dashboard/dist/assets/feishu-C4OMrjCW.ico +0 -0
- package/dashboard/dist/assets/gemini-BYkEpiWr.svg +1 -0
- package/dashboard/dist/assets/hermes-BAarh-tH.png +0 -0
- package/dashboard/dist/assets/index-CpM4CqZJ.js +23 -0
- package/dashboard/dist/assets/index-DXSohzrE.js +3 -0
- package/dashboard/dist/assets/index-reSbuley.css +1 -0
- package/dashboard/dist/assets/markdown-DxQYQFeH.js +29 -0
- package/dashboard/dist/assets/minimax-PuEGTfrF.ico +0 -0
- package/dashboard/dist/assets/mlx-DhWwjtMw.png +0 -0
- package/dashboard/dist/assets/ollama-Bt9O-2K_.png +0 -0
- package/dashboard/dist/assets/openrouter-CsJ_bD5Q.ico +0 -0
- package/dashboard/dist/assets/playwright-BldPFZgC.ico +0 -0
- package/dashboard/dist/assets/qwen-xykkX0_y.png +0 -0
- package/dashboard/dist/assets/react-vendor-C7Sl8SE7.js +9 -0
- package/dashboard/dist/assets/router-DHISdpPk.js +3 -0
- package/dashboard/dist/assets/shared-BIP_4k4I.js +1 -0
- package/dashboard/dist/favicon.svg +28 -0
- package/dashboard/dist/index.html +17 -0
- package/dist/agent/acp-client.js +261 -0
- package/dist/agent/auto-update.js +432 -0
- package/dist/agent/await-resume.js +50 -0
- package/dist/agent/cli/auth.js +325 -0
- package/dist/agent/cli/catalog.js +40 -0
- package/dist/agent/cli/detector.js +136 -0
- package/dist/agent/cli/index.js +7 -0
- package/dist/agent/cli/registry.js +33 -0
- package/dist/agent/driver.js +39 -0
- package/dist/agent/drivers/claude-tui.js +2297 -0
- package/dist/agent/drivers/claude.js +2689 -0
- package/dist/agent/drivers/codex.js +2210 -0
- package/dist/agent/drivers/gemini.js +1059 -0
- package/dist/agent/drivers/hermes.js +795 -0
- package/dist/agent/goal.js +274 -0
- package/dist/agent/handover.js +130 -0
- package/dist/agent/images.js +355 -0
- package/dist/agent/index.js +50 -0
- package/dist/agent/mcp/bridge.js +791 -0
- package/dist/agent/mcp/extensions.js +637 -0
- package/dist/agent/mcp/oauth.js +353 -0
- package/dist/agent/mcp/registry.js +119 -0
- package/dist/agent/mcp/session-server.js +229 -0
- package/dist/agent/mcp/tools/ask-user.js +113 -0
- package/dist/agent/mcp/tools/await-resume.js +77 -0
- package/dist/agent/mcp/tools/goal.js +144 -0
- package/dist/agent/mcp/tools/types.js +12 -0
- package/dist/agent/mcp/tools/workspace.js +212 -0
- package/dist/agent/npm.js +31 -0
- package/dist/agent/session.js +1206 -0
- package/dist/agent/skill-installer.js +160 -0
- package/dist/agent/skills.js +257 -0
- package/dist/agent/stream.js +743 -0
- package/dist/agent/types.js +13 -0
- package/dist/agent/utils.js +687 -0
- package/dist/bot/bot.js +2499 -0
- package/dist/bot/command-ui.js +633 -0
- package/dist/bot/commands.js +513 -0
- package/dist/bot/headless-bot.js +36 -0
- package/dist/bot/host.js +192 -0
- package/dist/bot/human-loop.js +168 -0
- package/dist/bot/menu.js +48 -0
- package/dist/bot/orchestration.js +79 -0
- package/dist/bot/render-shared.js +309 -0
- package/dist/bot/session-hub.js +361 -0
- package/dist/bot/session-status.js +55 -0
- package/dist/bot/streaming.js +309 -0
- package/dist/browser-profile.js +579 -0
- package/dist/browser-supervisor.js +249 -0
- package/dist/catalog/cli-tools.js +421 -0
- package/dist/catalog/index.js +21 -0
- package/dist/catalog/local-models.js +94 -0
- package/dist/catalog/mcp-servers.js +315 -0
- package/dist/catalog/skill-repos.js +173 -0
- package/dist/channels/base.js +55 -0
- package/dist/channels/dingtalk/bot.js +549 -0
- package/dist/channels/dingtalk/channel.js +268 -0
- package/dist/channels/discord/bot.js +552 -0
- package/dist/channels/discord/channel.js +245 -0
- package/dist/channels/feishu/bot.js +1275 -0
- package/dist/channels/feishu/channel.js +911 -0
- package/dist/channels/feishu/markdown.js +91 -0
- package/dist/channels/feishu/render.js +619 -0
- package/dist/channels/health.js +109 -0
- package/dist/channels/slack/bot.js +554 -0
- package/dist/channels/slack/channel.js +283 -0
- package/dist/channels/states.js +6 -0
- package/dist/channels/telegram/bot.js +1310 -0
- package/dist/channels/telegram/channel.js +820 -0
- package/dist/channels/telegram/directory.js +111 -0
- package/dist/channels/telegram/live-preview.js +220 -0
- package/dist/channels/telegram/render.js +384 -0
- package/dist/channels/wecom/bot.js +558 -0
- package/dist/channels/wecom/channel.js +479 -0
- package/dist/channels/weixin/api.js +520 -0
- package/dist/channels/weixin/bot.js +1000 -0
- package/dist/channels/weixin/channel.js +222 -0
- package/dist/cli/autostart.js +262 -0
- package/dist/cli/channel-supervisor.js +313 -0
- package/dist/cli/channels.js +54 -0
- package/dist/cli/main.js +726 -0
- package/dist/cli/onboarding.js +227 -0
- package/dist/cli/run.js +308 -0
- package/dist/cli/setup-wizard.js +235 -0
- package/dist/core/config/runtime-config.js +201 -0
- package/dist/core/config/user-config.js +510 -0
- package/dist/core/config/validation.js +521 -0
- package/dist/core/constants.js +400 -0
- package/dist/core/git.js +145 -0
- package/dist/core/legacy-compat.js +60 -0
- package/dist/core/logging.js +101 -0
- package/dist/core/platform.js +59 -0
- package/dist/core/process-control.js +315 -0
- package/dist/core/secrets/index.js +42 -0
- package/dist/core/secrets/inline-seal.js +60 -0
- package/dist/core/secrets/ref.js +33 -0
- package/dist/core/secrets/resolver.js +65 -0
- package/dist/core/secrets/store.js +63 -0
- package/dist/core/utils.js +233 -0
- package/dist/core/version.js +15 -0
- package/dist/dashboard/platform.js +219 -0
- package/dist/dashboard/routes/agents.js +450 -0
- package/dist/dashboard/routes/cli.js +174 -0
- package/dist/dashboard/routes/config.js +523 -0
- package/dist/dashboard/routes/extensions.js +745 -0
- package/dist/dashboard/routes/local-models.js +290 -0
- package/dist/dashboard/routes/models.js +324 -0
- package/dist/dashboard/routes/sessions.js +838 -0
- package/dist/dashboard/runtime.js +410 -0
- package/dist/dashboard/server.js +237 -0
- package/dist/dashboard/session-control.js +347 -0
- package/dist/model/catalog.js +104 -0
- package/dist/model/index.js +20 -0
- package/dist/model/injector.js +272 -0
- package/dist/model/provider-models.js +112 -0
- package/dist/model/store.js +212 -0
- package/dist/model/types.js +13 -0
- package/dist/model/validation.js +203 -0
- package/package.json +82 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Process-level singleton supervisor for the managed browser.
|
|
3
|
+
*
|
|
4
|
+
* Owns the lifecycle decisions for the managed Chrome instance so that all
|
|
5
|
+
* agent streams in this pikiloop process share one browser. Replaces the old
|
|
6
|
+
* per-stream `prepareManagedBrowserForAutomation` call inside the MCP bridge,
|
|
7
|
+
* which was relaunching Chrome at the start of every task.
|
|
8
|
+
*
|
|
9
|
+
* Three operations:
|
|
10
|
+
* - probe(): non-launching health check; returns the current CDP endpoint
|
|
11
|
+
* iff a managed Chrome is already reachable.
|
|
12
|
+
* - ensure(): idempotent prepare with singleflight; launches Chrome only
|
|
13
|
+
* when no healthy instance is reachable. Caches the result for re-use
|
|
14
|
+
* across streams.
|
|
15
|
+
* - invalidate(): drop the cache after a confirmed downstream failure
|
|
16
|
+
* (e.g. CDP socket closed mid-stream).
|
|
17
|
+
*/
|
|
18
|
+
import { forceCloseManagedBrowser, getConfiguredRemoteCdpUrl, getManagedBrowserProfileDir, prepareManagedBrowserForAutomation, } from './browser-profile.js';
|
|
19
|
+
import { PIKILOOP_BROWSER_CDP_URL_ENV } from './core/constants.js';
|
|
20
|
+
import { writeScopedLog } from './core/logging.js';
|
|
21
|
+
const HEALTH_CACHE_MS = 30_000;
|
|
22
|
+
const CDP_PROBE_TIMEOUT_MS = 1_500;
|
|
23
|
+
let cached = null;
|
|
24
|
+
let inflight = null;
|
|
25
|
+
function log(message, level = 'debug') {
|
|
26
|
+
writeScopedLog('browser-supervisor', message, { level, stream: 'stderr' });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The user-configured remote endpoint from {@link PIKILOOP_BROWSER_CDP_URL_ENV}.
|
|
30
|
+
* When set, every supervisor codepath bypasses local Chrome launching. Aliased
|
|
31
|
+
* to the shared `getConfiguredRemoteCdpUrl` so the bridge and supervisor read
|
|
32
|
+
* the same normalized value.
|
|
33
|
+
*/
|
|
34
|
+
const getRemoteCdpUrl = getConfiguredRemoteCdpUrl;
|
|
35
|
+
/**
|
|
36
|
+
* Snapshot for the remote-CDP path. The URL is taken on trust as configured —
|
|
37
|
+
* health is verified by {@link pingCdpEndpoint} before caching, and by the
|
|
38
|
+
* usual freshness check on every reuse.
|
|
39
|
+
*/
|
|
40
|
+
async function snapshotRemote(remoteUrl, now) {
|
|
41
|
+
if (cached?.cdpEndpoint === remoteUrl && now - cached.validatedAt < HEALTH_CACHE_MS) {
|
|
42
|
+
return snapshotFromCache(cached);
|
|
43
|
+
}
|
|
44
|
+
const healthy = await pingCdpEndpoint(remoteUrl);
|
|
45
|
+
if (!healthy) {
|
|
46
|
+
if (cached?.cdpEndpoint === remoteUrl)
|
|
47
|
+
cached = null;
|
|
48
|
+
log(`remote CDP endpoint ${remoteUrl} not reachable`, 'warn');
|
|
49
|
+
return { cdpEndpoint: null, connectionMode: 'unavailable' };
|
|
50
|
+
}
|
|
51
|
+
cached = { cdpEndpoint: remoteUrl, connectionMode: 'attach', validatedAt: now };
|
|
52
|
+
log(`using remote CDP endpoint ${remoteUrl} (from ${PIKILOOP_BROWSER_CDP_URL_ENV})`);
|
|
53
|
+
return snapshotFromCache(cached);
|
|
54
|
+
}
|
|
55
|
+
function snapshotFromCache(state) {
|
|
56
|
+
return { cdpEndpoint: state.cdpEndpoint, connectionMode: state.connectionMode };
|
|
57
|
+
}
|
|
58
|
+
async function fetchWithTimeout(url, timeoutMs) {
|
|
59
|
+
const controller = new AbortController();
|
|
60
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
61
|
+
try {
|
|
62
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
63
|
+
return response;
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
clearTimeout(timer);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Two-stage health check.
|
|
74
|
+
*
|
|
75
|
+
* Stage 1 (`/json/version`) hits Chrome's static info handler — confirms the
|
|
76
|
+
* process is alive and the debug HTTP server is bound. Stage 2 (`/json`) reads
|
|
77
|
+
* from the target manager, exercising the CDP dispatch loop. Chrome can end up
|
|
78
|
+
* in a state where stage 1 still answers but stage 2 hangs, typically after
|
|
79
|
+
* many stale CDP sessions accumulate; treating stage-1-only responses as
|
|
80
|
+
* healthy lets that state poison every subsequent agent run.
|
|
81
|
+
*
|
|
82
|
+
* Both stages share the same `CDP_PROBE_TIMEOUT_MS`. This probe does NOT catch
|
|
83
|
+
* the case where stage 2 succeeds but the *first page target* is itself stuck
|
|
84
|
+
* (e.g. a heavy SPA frozen mid-init); that has to be handled reactively from
|
|
85
|
+
* the MCP-tool-error path.
|
|
86
|
+
*/
|
|
87
|
+
async function pingCdpEndpoint(endpoint) {
|
|
88
|
+
if (!endpoint)
|
|
89
|
+
return false;
|
|
90
|
+
const versionResp = await fetchWithTimeout(`${endpoint}/json/version`, CDP_PROBE_TIMEOUT_MS);
|
|
91
|
+
if (!versionResp || !versionResp.ok)
|
|
92
|
+
return false;
|
|
93
|
+
const versionPayload = await versionResp.json().catch(() => null);
|
|
94
|
+
if (typeof versionPayload?.webSocketDebuggerUrl !== 'string')
|
|
95
|
+
return false;
|
|
96
|
+
const targetsResp = await fetchWithTimeout(`${endpoint}/json`, CDP_PROBE_TIMEOUT_MS);
|
|
97
|
+
if (!targetsResp || !targetsResp.ok)
|
|
98
|
+
return false;
|
|
99
|
+
const targets = await targetsResp.json().catch(() => null);
|
|
100
|
+
return Array.isArray(targets);
|
|
101
|
+
}
|
|
102
|
+
async function freshenCacheIfPossible(now) {
|
|
103
|
+
if (!cached?.cdpEndpoint)
|
|
104
|
+
return null;
|
|
105
|
+
if (now - cached.validatedAt < HEALTH_CACHE_MS)
|
|
106
|
+
return snapshotFromCache(cached);
|
|
107
|
+
const healthy = await pingCdpEndpoint(cached.cdpEndpoint);
|
|
108
|
+
if (healthy) {
|
|
109
|
+
cached.validatedAt = now;
|
|
110
|
+
return snapshotFromCache(cached);
|
|
111
|
+
}
|
|
112
|
+
log(`cached endpoint ${cached.cdpEndpoint} no longer reachable; clearing cache`, 'warn');
|
|
113
|
+
cached = null;
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Non-launching probe. Returns the current CDP endpoint iff a managed Chrome
|
|
118
|
+
* is already reachable. Never starts a new Chrome process.
|
|
119
|
+
*/
|
|
120
|
+
export async function probeManagedBrowser() {
|
|
121
|
+
const remoteUrl = getRemoteCdpUrl();
|
|
122
|
+
if (remoteUrl)
|
|
123
|
+
return snapshotRemote(remoteUrl, Date.now());
|
|
124
|
+
const fresh = await freshenCacheIfPossible(Date.now());
|
|
125
|
+
if (fresh)
|
|
126
|
+
return fresh;
|
|
127
|
+
return { cdpEndpoint: null, connectionMode: 'unavailable' };
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Idempotent prepare. Returns a healthy CDP endpoint, launching Chrome only
|
|
131
|
+
* when no reachable managed instance is available. Concurrent callers share
|
|
132
|
+
* one in-flight preparation promise (singleflight).
|
|
133
|
+
*
|
|
134
|
+
* When {@link PIKILOOP_BROWSER_CDP_URL_ENV} is set we skip the local-launch
|
|
135
|
+
* branch entirely and just verify the remote endpoint — no `findChromeExecutable`
|
|
136
|
+
* lookup, no SIGKILL of detected pids on restart.
|
|
137
|
+
*/
|
|
138
|
+
export async function ensureManagedBrowser(opts = {}) {
|
|
139
|
+
const { headless = false, force = false } = opts;
|
|
140
|
+
const now = Date.now();
|
|
141
|
+
const remoteUrl = getRemoteCdpUrl();
|
|
142
|
+
if (remoteUrl) {
|
|
143
|
+
if (force && cached?.cdpEndpoint === remoteUrl)
|
|
144
|
+
cached = null;
|
|
145
|
+
return snapshotRemote(remoteUrl, now);
|
|
146
|
+
}
|
|
147
|
+
if (!force) {
|
|
148
|
+
const fresh = await freshenCacheIfPossible(now);
|
|
149
|
+
if (fresh)
|
|
150
|
+
return fresh;
|
|
151
|
+
}
|
|
152
|
+
if (inflight)
|
|
153
|
+
return inflight;
|
|
154
|
+
inflight = (async () => {
|
|
155
|
+
try {
|
|
156
|
+
const result = await prepareManagedBrowserForAutomation(getManagedBrowserProfileDir(), { headless });
|
|
157
|
+
const state = {
|
|
158
|
+
cdpEndpoint: result.cdpEndpoint,
|
|
159
|
+
connectionMode: result.cdpEndpoint ? result.connectionMode : 'unavailable',
|
|
160
|
+
validatedAt: Date.now(),
|
|
161
|
+
};
|
|
162
|
+
if (state.cdpEndpoint) {
|
|
163
|
+
cached = state;
|
|
164
|
+
log(`prepared managed browser: mode=${state.connectionMode} endpoint=${state.cdpEndpoint}`);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
cached = null;
|
|
168
|
+
log(`managed browser unavailable (mode=${result.connectionMode}); will fall back to upstream-managed launch`, 'warn');
|
|
169
|
+
}
|
|
170
|
+
return snapshotFromCache(state);
|
|
171
|
+
}
|
|
172
|
+
catch (err) {
|
|
173
|
+
cached = null;
|
|
174
|
+
log(`ensure failed: ${err?.message || err}`, 'error');
|
|
175
|
+
return { cdpEndpoint: null, connectionMode: 'unavailable' };
|
|
176
|
+
}
|
|
177
|
+
finally {
|
|
178
|
+
inflight = null;
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
181
|
+
return inflight;
|
|
182
|
+
}
|
|
183
|
+
const RESTART_COOLDOWN_MS = 30_000;
|
|
184
|
+
let lastRestartAt = 0;
|
|
185
|
+
let restartInflight = null;
|
|
186
|
+
/**
|
|
187
|
+
* Reactive recovery for a confirmed CDP-layer failure: drops the supervisor
|
|
188
|
+
* cache, SIGKILLs Chrome, wipes session-restore. The next `ensureManagedBrowser`
|
|
189
|
+
* will detect no CDP endpoint and relaunch Chrome cold.
|
|
190
|
+
*
|
|
191
|
+
* The current stream is not rescued — its MCP child has already lost stdio,
|
|
192
|
+
* and Claude CLI's MCP client won't reconnect mid-tool-call. The benefit lands
|
|
193
|
+
* on the next agent turn, which will attach to a fresh browser instead of
|
|
194
|
+
* inheriting the wedged one.
|
|
195
|
+
*
|
|
196
|
+
* Cooldown-throttled and singleflight: a single agent run can spit many
|
|
197
|
+
* "Connection closed" lines as the MCP child stays dead; we only act once.
|
|
198
|
+
*/
|
|
199
|
+
export function restartManagedBrowser(reason) {
|
|
200
|
+
const now = Date.now();
|
|
201
|
+
if (restartInflight)
|
|
202
|
+
return restartInflight;
|
|
203
|
+
if (now - lastRestartAt < RESTART_COOLDOWN_MS) {
|
|
204
|
+
log(`restart skipped (within ${RESTART_COOLDOWN_MS / 1000}s cooldown): ${reason}`, 'debug');
|
|
205
|
+
return Promise.resolve();
|
|
206
|
+
}
|
|
207
|
+
lastRestartAt = now;
|
|
208
|
+
// Remote CDP path: we don't own the Chrome process (it's a sidecar / external
|
|
209
|
+
// service), so don't SIGKILL anything — just drop the cache so the next
|
|
210
|
+
// ensure re-probes the endpoint.
|
|
211
|
+
const remoteUrl = getRemoteCdpUrl();
|
|
212
|
+
if (remoteUrl) {
|
|
213
|
+
log(`invalidating remote CDP cache (${remoteUrl}): ${reason}`, 'warn');
|
|
214
|
+
cached = null;
|
|
215
|
+
return Promise.resolve();
|
|
216
|
+
}
|
|
217
|
+
log(`restarting managed browser: ${reason}`, 'warn');
|
|
218
|
+
restartInflight = (async () => {
|
|
219
|
+
try {
|
|
220
|
+
cached = null;
|
|
221
|
+
const killed = await forceCloseManagedBrowser();
|
|
222
|
+
log(`forced close complete; killed pids=[${killed.join(',')}]`);
|
|
223
|
+
}
|
|
224
|
+
catch (err) {
|
|
225
|
+
log(`force-close failed: ${err?.message || err}`, 'error');
|
|
226
|
+
}
|
|
227
|
+
finally {
|
|
228
|
+
restartInflight = null;
|
|
229
|
+
}
|
|
230
|
+
})();
|
|
231
|
+
return restartInflight;
|
|
232
|
+
}
|
|
233
|
+
/** Drop any cached endpoint, e.g. after a confirmed CDP failure mid-stream. */
|
|
234
|
+
export function invalidateManagedBrowser() {
|
|
235
|
+
if (cached)
|
|
236
|
+
log(`invalidating cached endpoint ${cached.cdpEndpoint}`);
|
|
237
|
+
cached = null;
|
|
238
|
+
}
|
|
239
|
+
/** Synchronous accessor for the cached endpoint without any I/O. */
|
|
240
|
+
export function getCachedManagedBrowserEndpoint() {
|
|
241
|
+
return cached?.cdpEndpoint || null;
|
|
242
|
+
}
|
|
243
|
+
/** Test-only: reset module state. */
|
|
244
|
+
export function _resetManagedBrowserSupervisor() {
|
|
245
|
+
cached = null;
|
|
246
|
+
inflight = null;
|
|
247
|
+
lastRestartAt = 0;
|
|
248
|
+
restartInflight = null;
|
|
249
|
+
}
|
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI tool catalog — single source of truth for what the Dashboard shows under
|
|
3
|
+
* Extensions → CLI.
|
|
4
|
+
*
|
|
5
|
+
* ─── How this plugs into the rest of the stack ───────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* Dashboard → GET /api/extensions/cli/catalog
|
|
8
|
+
* → dashboard/routes/cli.ts
|
|
9
|
+
* → agent/cli/catalog.ts (merge + live detection)
|
|
10
|
+
* → agent/cli/registry.ts (types + re-exports this array)
|
|
11
|
+
* ← src/catalog/cli-tools.ts ← YOU ARE HERE
|
|
12
|
+
*
|
|
13
|
+
* The registry module defines the `RecommendedCli` type and other helpers; the
|
|
14
|
+
* detector spawns `which <binary>` + the status command, and the auth runner
|
|
15
|
+
* streams the login sub-process. This file owns only the *data*.
|
|
16
|
+
*
|
|
17
|
+
* ─── How to add a CLI ────────────────────────────────────────────────────────
|
|
18
|
+
*
|
|
19
|
+
* 1. Append an entry to `CLI_TOOLS`.
|
|
20
|
+
* 2. Pick an `auth.type`:
|
|
21
|
+
* - 'oauth-web' if the CLI has a first-party `<cli> ... login --web`
|
|
22
|
+
* flavor (gh, wrangler, vercel, …). The runner spawns that command,
|
|
23
|
+
* streams stdout/stderr to the UI, and polls `statusArgv` until it
|
|
24
|
+
* reports authed.
|
|
25
|
+
* - 'token' if the user pastes an API key. Implement the write-path in
|
|
26
|
+
* `agent/cli/auth.ts` → `applyCliToken` for that id (keeps secret
|
|
27
|
+
* handling explicit per CLI).
|
|
28
|
+
* - 'none' for CLIs that don't need sign-in (docker, pnpm).
|
|
29
|
+
* 3. Fill `install.{darwin,linux,win}` with canonical commands. The UI shows
|
|
30
|
+
* the current OS's list and always renders a "Copy" button. We deliberately
|
|
31
|
+
* do NOT auto-run installers — package managers often need sudo or
|
|
32
|
+
* interactive confirmation and we'd rather keep the user in charge.
|
|
33
|
+
*/
|
|
34
|
+
export const CLI_TOOLS = [
|
|
35
|
+
// ── Dev: source control & platforms ────────────────────────────────────────
|
|
36
|
+
{
|
|
37
|
+
id: 'gh',
|
|
38
|
+
binary: 'gh',
|
|
39
|
+
name: 'GitHub CLI',
|
|
40
|
+
description: 'Manage pull requests, issues, and Actions from the terminal',
|
|
41
|
+
descriptionZh: '终端直管 PR、Issue 和 Actions',
|
|
42
|
+
category: 'dev',
|
|
43
|
+
iconSlug: 'github',
|
|
44
|
+
homepage: 'https://cli.github.com/',
|
|
45
|
+
recommendedScope: 'global',
|
|
46
|
+
versionArgv: ['gh', '--version'],
|
|
47
|
+
install: {
|
|
48
|
+
docs: 'https://github.com/cli/cli#installation',
|
|
49
|
+
darwin: [{ label: 'Homebrew', cmd: 'brew install gh' }],
|
|
50
|
+
linux: [
|
|
51
|
+
{ label: 'apt (Debian/Ubuntu)', cmd: 'sudo apt install gh' },
|
|
52
|
+
{ label: 'dnf (Fedora)', cmd: 'sudo dnf install gh' },
|
|
53
|
+
],
|
|
54
|
+
win: [{ label: 'winget', cmd: 'winget install GitHub.cli' }],
|
|
55
|
+
},
|
|
56
|
+
auth: {
|
|
57
|
+
type: 'oauth-web',
|
|
58
|
+
statusArgv: ['gh', 'auth', 'status'],
|
|
59
|
+
loginArgv: ['gh', 'auth', 'login', '--web', '--git-protocol', 'https'],
|
|
60
|
+
logoutArgv: ['gh', 'auth', 'logout', '--hostname', 'github.com'],
|
|
61
|
+
loginHint: 'Opens a browser for GitHub OAuth.',
|
|
62
|
+
loginHintZh: '将打开浏览器完成 GitHub OAuth 授权。',
|
|
63
|
+
},
|
|
64
|
+
},
|
|
65
|
+
// ── Cloud platforms ────────────────────────────────────────────────────────
|
|
66
|
+
{
|
|
67
|
+
id: 'wrangler',
|
|
68
|
+
binary: 'wrangler',
|
|
69
|
+
name: 'Cloudflare Wrangler',
|
|
70
|
+
description: 'Build and deploy Cloudflare Workers, Pages, and D1',
|
|
71
|
+
descriptionZh: '构建并部署 Workers / Pages / D1',
|
|
72
|
+
category: 'cloud',
|
|
73
|
+
iconSlug: 'cloudflare',
|
|
74
|
+
homepage: 'https://developers.cloudflare.com/workers/wrangler/',
|
|
75
|
+
recommendedScope: 'global',
|
|
76
|
+
versionArgv: ['wrangler', '--version'],
|
|
77
|
+
install: {
|
|
78
|
+
docs: 'https://developers.cloudflare.com/workers/wrangler/install-and-update/',
|
|
79
|
+
darwin: [{ label: 'npm', cmd: 'npm i -g wrangler' }],
|
|
80
|
+
linux: [{ label: 'npm', cmd: 'npm i -g wrangler' }],
|
|
81
|
+
win: [{ label: 'npm', cmd: 'npm i -g wrangler' }],
|
|
82
|
+
},
|
|
83
|
+
auth: {
|
|
84
|
+
type: 'oauth-web',
|
|
85
|
+
statusArgv: ['wrangler', 'whoami'],
|
|
86
|
+
loginArgv: ['wrangler', 'login'],
|
|
87
|
+
logoutArgv: ['wrangler', 'logout'],
|
|
88
|
+
loginHint: 'Opens a browser for Cloudflare OAuth.',
|
|
89
|
+
loginHintZh: '将打开浏览器完成 Cloudflare OAuth 授权。',
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
id: 'vercel',
|
|
94
|
+
binary: 'vercel',
|
|
95
|
+
name: 'Vercel CLI',
|
|
96
|
+
description: 'Deploy Next.js / frontend projects to Vercel',
|
|
97
|
+
descriptionZh: '将 Next.js / 前端项目部署到 Vercel',
|
|
98
|
+
category: 'cloud',
|
|
99
|
+
iconSlug: 'vercel',
|
|
100
|
+
homepage: 'https://vercel.com/docs/cli',
|
|
101
|
+
recommendedScope: 'global',
|
|
102
|
+
versionArgv: ['vercel', '--version'],
|
|
103
|
+
install: {
|
|
104
|
+
docs: 'https://vercel.com/docs/cli#installing-vercel-cli',
|
|
105
|
+
darwin: [{ label: 'npm', cmd: 'npm i -g vercel' }],
|
|
106
|
+
linux: [{ label: 'npm', cmd: 'npm i -g vercel' }],
|
|
107
|
+
win: [{ label: 'npm', cmd: 'npm i -g vercel' }],
|
|
108
|
+
},
|
|
109
|
+
auth: {
|
|
110
|
+
type: 'oauth-web',
|
|
111
|
+
statusArgv: ['vercel', 'whoami'],
|
|
112
|
+
loginArgv: ['vercel', 'login'],
|
|
113
|
+
logoutArgv: ['vercel', 'logout'],
|
|
114
|
+
// `vercel login` opens an interactive picker (email / GitHub / GitLab /
|
|
115
|
+
// Bitbucket / SAML) that needs a real TTY. Surface the official command.
|
|
116
|
+
manualLoginCommands: [
|
|
117
|
+
{ label: 'Sign in to Vercel', cmd: 'vercel login' },
|
|
118
|
+
],
|
|
119
|
+
loginHint: 'Run `vercel login` in your own terminal — it interactively asks which login method to use, which doesn\'t render well here. After signing in, click "Re-check status".',
|
|
120
|
+
loginHintZh: '请在自己的终端里运行 `vercel login` —— 该命令需要交互式选择登录方式(邮箱 / GitHub / GitLab 等),不适合在此面板里完成。登录后回到这里点击「重新检测状态」。',
|
|
121
|
+
},
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
id: 'netlify',
|
|
125
|
+
binary: 'netlify',
|
|
126
|
+
name: 'Netlify CLI',
|
|
127
|
+
description: 'Deploy and manage Netlify sites, Functions, and Edge',
|
|
128
|
+
descriptionZh: '部署并管理 Netlify 站点、Functions、Edge',
|
|
129
|
+
category: 'cloud',
|
|
130
|
+
iconSlug: 'netlify',
|
|
131
|
+
homepage: 'https://docs.netlify.com/cli/get-started/',
|
|
132
|
+
recommendedScope: 'global',
|
|
133
|
+
versionArgv: ['netlify', '--version'],
|
|
134
|
+
install: {
|
|
135
|
+
docs: 'https://docs.netlify.com/cli/get-started/#installation',
|
|
136
|
+
darwin: [{ label: 'npm', cmd: 'npm i -g netlify-cli' }],
|
|
137
|
+
linux: [{ label: 'npm', cmd: 'npm i -g netlify-cli' }],
|
|
138
|
+
win: [{ label: 'npm', cmd: 'npm i -g netlify-cli' }],
|
|
139
|
+
},
|
|
140
|
+
auth: {
|
|
141
|
+
type: 'oauth-web',
|
|
142
|
+
statusArgv: ['netlify', 'status'],
|
|
143
|
+
loginArgv: ['netlify', 'login'],
|
|
144
|
+
logoutArgv: ['netlify', 'logout'],
|
|
145
|
+
loginHint: 'Opens a browser for Netlify OAuth.',
|
|
146
|
+
loginHintZh: '将打开浏览器完成 Netlify OAuth 授权。',
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
{
|
|
150
|
+
id: 'supabase',
|
|
151
|
+
binary: 'supabase',
|
|
152
|
+
name: 'Supabase CLI',
|
|
153
|
+
description: 'Manage Supabase projects, Postgres, and Edge Functions',
|
|
154
|
+
descriptionZh: '管理 Supabase 项目、Postgres 与 Edge Functions',
|
|
155
|
+
category: 'data',
|
|
156
|
+
iconSlug: 'supabase',
|
|
157
|
+
homepage: 'https://supabase.com/docs/guides/cli',
|
|
158
|
+
recommendedScope: 'global',
|
|
159
|
+
versionArgv: ['supabase', '--version'],
|
|
160
|
+
install: {
|
|
161
|
+
docs: 'https://supabase.com/docs/guides/cli/getting-started',
|
|
162
|
+
darwin: [{ label: 'Homebrew', cmd: 'brew install supabase/tap/supabase' }],
|
|
163
|
+
linux: [{ label: 'Script', cmd: 'curl -sL https://supabase.com/install.sh | sh' }],
|
|
164
|
+
win: [{ label: 'scoop', cmd: 'scoop bucket add supabase https://github.com/supabase/scoop-bucket.git && scoop install supabase' }],
|
|
165
|
+
},
|
|
166
|
+
auth: {
|
|
167
|
+
type: 'oauth-web',
|
|
168
|
+
statusArgv: ['supabase', 'projects', 'list'],
|
|
169
|
+
loginArgv: ['supabase', 'login'],
|
|
170
|
+
logoutArgv: ['supabase', 'logout'],
|
|
171
|
+
loginHint: 'Opens a browser for Supabase OAuth.',
|
|
172
|
+
loginHintZh: '将打开浏览器完成 Supabase OAuth 授权。',
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
id: 'heroku',
|
|
177
|
+
binary: 'heroku',
|
|
178
|
+
name: 'Heroku CLI',
|
|
179
|
+
description: 'Create and manage Heroku apps and dynos',
|
|
180
|
+
descriptionZh: '创建与管理 Heroku 应用和 dyno',
|
|
181
|
+
category: 'cloud',
|
|
182
|
+
iconSlug: 'heroku',
|
|
183
|
+
homepage: 'https://devcenter.heroku.com/articles/heroku-cli',
|
|
184
|
+
recommendedScope: 'global',
|
|
185
|
+
versionArgv: ['heroku', '--version'],
|
|
186
|
+
install: {
|
|
187
|
+
docs: 'https://devcenter.heroku.com/articles/heroku-cli#install-the-heroku-cli',
|
|
188
|
+
darwin: [{ label: 'Homebrew', cmd: 'brew tap heroku/brew && brew install heroku' }],
|
|
189
|
+
linux: [{ label: 'Snap', cmd: 'sudo snap install --classic heroku' }],
|
|
190
|
+
win: [{ label: 'Installer', cmd: 'winget install --id=Heroku.HerokuCLI' }],
|
|
191
|
+
},
|
|
192
|
+
auth: {
|
|
193
|
+
type: 'oauth-web',
|
|
194
|
+
statusArgv: ['heroku', 'whoami'],
|
|
195
|
+
loginArgv: ['heroku', 'login'],
|
|
196
|
+
logoutArgv: ['heroku', 'logout'],
|
|
197
|
+
// `heroku login` prompts "Press any key to open up the browser to login
|
|
198
|
+
// or q to exit" — a hard TTY dependency. Surface the official command.
|
|
199
|
+
manualLoginCommands: [
|
|
200
|
+
{ label: 'Sign in to Heroku', cmd: 'heroku login' },
|
|
201
|
+
],
|
|
202
|
+
loginHint: 'Run `heroku login` in your own terminal — the CLI prompts "Press any key to open the browser", which requires a real TTY. After signing in, click "Re-check status".',
|
|
203
|
+
loginHintZh: '请在自己的终端里运行 `heroku login` —— 该命令会提示「Press any key」,必须在交互式终端里完成。登录后回到这里点击「重新检测状态」。',
|
|
204
|
+
},
|
|
205
|
+
},
|
|
206
|
+
// ── Commerce & payments ────────────────────────────────────────────────────
|
|
207
|
+
{
|
|
208
|
+
id: 'stripe',
|
|
209
|
+
binary: 'stripe',
|
|
210
|
+
name: 'Stripe CLI',
|
|
211
|
+
description: 'Test webhooks, trigger events, inspect API logs',
|
|
212
|
+
descriptionZh: '调试 Webhook、触发事件、查看 API 日志',
|
|
213
|
+
category: 'commerce',
|
|
214
|
+
iconSlug: 'stripe',
|
|
215
|
+
homepage: 'https://stripe.com/docs/stripe-cli',
|
|
216
|
+
recommendedScope: 'global',
|
|
217
|
+
versionArgv: ['stripe', '--version'],
|
|
218
|
+
install: {
|
|
219
|
+
docs: 'https://docs.stripe.com/stripe-cli#install',
|
|
220
|
+
darwin: [{ label: 'Homebrew', cmd: 'brew install stripe/stripe-cli/stripe' }],
|
|
221
|
+
linux: [{ label: 'apt', cmd: "curl -s https://packages.stripe.dev/api/security/keypair/stripe-cli-gpg/public | gpg --dearmor | sudo tee /usr/share/keyrings/stripe.gpg >/dev/null && echo 'deb [signed-by=/usr/share/keyrings/stripe.gpg] https://packages.stripe.dev/stripe-cli-debian-local stable main' | sudo tee /etc/apt/sources.list.d/stripe.list && sudo apt update && sudo apt install stripe" }],
|
|
222
|
+
win: [{ label: 'scoop', cmd: 'scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git && scoop install stripe' }],
|
|
223
|
+
},
|
|
224
|
+
auth: {
|
|
225
|
+
type: 'oauth-web',
|
|
226
|
+
statusArgv: ['stripe', 'config', '--list'],
|
|
227
|
+
loginArgv: ['stripe', 'login'],
|
|
228
|
+
logoutArgv: ['stripe', 'logout'],
|
|
229
|
+
loginHint: 'Opens a browser and writes credentials to ~/.config/stripe/.',
|
|
230
|
+
loginHintZh: '打开浏览器完成授权,凭据写入 ~/.config/stripe/。',
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
// ── Cloud giants (token-based profile flows) ───────────────────────────────
|
|
234
|
+
{
|
|
235
|
+
id: 'aws',
|
|
236
|
+
binary: 'aws',
|
|
237
|
+
name: 'AWS CLI',
|
|
238
|
+
description: 'Interact with AWS services (S3, Lambda, IAM, …)',
|
|
239
|
+
descriptionZh: '访问 AWS 的 S3 / Lambda / IAM 等服务',
|
|
240
|
+
category: 'cloud',
|
|
241
|
+
iconSlug: 'aws',
|
|
242
|
+
homepage: 'https://aws.amazon.com/cli/',
|
|
243
|
+
recommendedScope: 'global',
|
|
244
|
+
versionArgv: ['aws', '--version'],
|
|
245
|
+
install: {
|
|
246
|
+
docs: 'https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html',
|
|
247
|
+
darwin: [{ label: 'Homebrew', cmd: 'brew install awscli' }],
|
|
248
|
+
linux: [{ label: 'Bundled installer', cmd: 'curl "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip" -o awscli.zip && unzip awscli.zip && sudo ./aws/install' }],
|
|
249
|
+
win: [{ label: 'MSI', cmd: 'msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi' }],
|
|
250
|
+
},
|
|
251
|
+
auth: {
|
|
252
|
+
type: 'token',
|
|
253
|
+
statusArgv: ['aws', 'sts', 'get-caller-identity'],
|
|
254
|
+
tokenFields: [
|
|
255
|
+
{ key: 'AWS_ACCESS_KEY_ID', label: 'Access key ID', labelZh: 'Access Key ID', required: true },
|
|
256
|
+
{ key: 'AWS_SECRET_ACCESS_KEY', label: 'Secret key', labelZh: 'Secret Access Key', secret: true, required: true },
|
|
257
|
+
{ key: 'AWS_DEFAULT_REGION', label: 'Region', labelZh: '默认区域', placeholder: 'us-east-1' },
|
|
258
|
+
],
|
|
259
|
+
loginHint: 'Writes credentials to ~/.aws/credentials under [default].',
|
|
260
|
+
loginHintZh: '凭据会写入 ~/.aws/credentials 的 [default] 下。',
|
|
261
|
+
},
|
|
262
|
+
},
|
|
263
|
+
{
|
|
264
|
+
id: 'gcloud',
|
|
265
|
+
binary: 'gcloud',
|
|
266
|
+
name: 'Google Cloud CLI',
|
|
267
|
+
description: 'Manage GCP projects, compute, storage, and IAM',
|
|
268
|
+
descriptionZh: '管理 GCP 项目、计算、存储与 IAM',
|
|
269
|
+
category: 'cloud',
|
|
270
|
+
iconSlug: 'google-cloud',
|
|
271
|
+
homepage: 'https://cloud.google.com/sdk/gcloud',
|
|
272
|
+
recommendedScope: 'global',
|
|
273
|
+
versionArgv: ['gcloud', '--version'],
|
|
274
|
+
install: {
|
|
275
|
+
docs: 'https://cloud.google.com/sdk/docs/install',
|
|
276
|
+
darwin: [{ label: 'Homebrew Cask', cmd: 'brew install --cask google-cloud-sdk' }],
|
|
277
|
+
linux: [{ label: 'Bundled installer', cmd: 'curl https://sdk.cloud.google.com | bash' }],
|
|
278
|
+
win: [{ label: 'Installer', cmd: 'winget install Google.CloudSDK' }],
|
|
279
|
+
},
|
|
280
|
+
auth: {
|
|
281
|
+
type: 'oauth-web',
|
|
282
|
+
// `gcloud auth list ... --format=value(account)` exits 0 even when no
|
|
283
|
+
// account is signed in (just prints an empty line). Require non-empty
|
|
284
|
+
// output so an empty list isn't mistaken for ready.
|
|
285
|
+
statusArgv: ['gcloud', 'auth', 'list', '--filter=status:ACTIVE', '--format=value(account)'],
|
|
286
|
+
statusReadyPattern: '\\S',
|
|
287
|
+
loginArgv: ['gcloud', 'auth', 'login'],
|
|
288
|
+
logoutArgv: ['gcloud', 'auth', 'revoke'],
|
|
289
|
+
// `gcloud auth login` orchestrates a local callback server + interactive
|
|
290
|
+
// prompts — needs a real TTY. ADC for SDK code is a separate command.
|
|
291
|
+
manualLoginCommands: [
|
|
292
|
+
{ label: 'Sign in (user credentials)', cmd: 'gcloud auth login' },
|
|
293
|
+
{ label: 'Optional: Application Default Credentials', cmd: 'gcloud auth application-default login' },
|
|
294
|
+
],
|
|
295
|
+
loginHint: 'Run `gcloud auth login` in your own terminal — it relies on a local callback server and interactive prompts that don\'t work here. SDK code paths use Application Default Credentials, which is a separate command. After signing in, click "Re-check status".',
|
|
296
|
+
loginHintZh: '请在自己的终端里运行 `gcloud auth login` —— 它依赖本地回调服务器与交互式提示,不能在此面板里完成。SDK 用的 Application Default 凭证是另一个命令。登录后回到这里点击「重新检测状态」。',
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
// ── Social & messaging ─────────────────────────────────────────────────────
|
|
300
|
+
{
|
|
301
|
+
id: 'lark-cli',
|
|
302
|
+
binary: 'lark-cli',
|
|
303
|
+
name: 'Lark CLI',
|
|
304
|
+
description: 'Official Lark/Feishu CLI — Messenger, Docs, Base, Calendar, Tasks (200+ commands, Agent-native)',
|
|
305
|
+
descriptionZh: '飞书官方 CLI — 消息、文档、多维表格、日历、任务等 200+ 命令,专为 Agent 设计',
|
|
306
|
+
category: 'social',
|
|
307
|
+
iconSlug: 'lark',
|
|
308
|
+
homepage: 'https://github.com/larksuite/cli',
|
|
309
|
+
recommendedScope: 'global',
|
|
310
|
+
versionArgv: ['lark-cli', '--version'],
|
|
311
|
+
install: {
|
|
312
|
+
docs: 'https://github.com/larksuite/cli#installation--quick-start',
|
|
313
|
+
darwin: [{ label: 'npm', cmd: 'npm i -g @larksuite/cli' }],
|
|
314
|
+
linux: [{ label: 'npm', cmd: 'npm i -g @larksuite/cli' }],
|
|
315
|
+
win: [{ label: 'npm', cmd: 'npm i -g @larksuite/cli' }],
|
|
316
|
+
},
|
|
317
|
+
auth: {
|
|
318
|
+
type: 'oauth-web',
|
|
319
|
+
statusArgv: ['lark-cli', 'auth', 'status'],
|
|
320
|
+
loginArgv: ['lark-cli', 'auth', 'login', '--recommend'],
|
|
321
|
+
logoutArgv: ['lark-cli', 'auth', 'logout'],
|
|
322
|
+
// Lark CLI sign-in has two ordered steps and renders a terminal QR that
|
|
323
|
+
// doesn't fit a streamed-output panel. Both commands run in the user's
|
|
324
|
+
// own terminal; only after step 2 does `lark-cli auth status` exit 0,
|
|
325
|
+
// so re-check naturally rejects "only step 1 completed".
|
|
326
|
+
manualLoginCommands: [
|
|
327
|
+
{ label: 'Step 1 — Register a Feishu app (one-time setup)', cmd: 'lark-cli config init --new' },
|
|
328
|
+
{ label: 'Step 2 — Sign in via Device Flow OAuth', cmd: 'lark-cli auth login --recommend' },
|
|
329
|
+
],
|
|
330
|
+
loginHint: 'Lark CLI sign-in has two steps. Run them in order in your own terminal — `lark-cli` prints a QR/Device-Flow URL that doesn\'t render well in this panel. Both steps must complete before authorization counts as successful. After step 2 finishes, click "Re-check status" below. Tip: `npx skills add larksuite/cli -y -g` installs the matching Skills.',
|
|
331
|
+
loginHintZh: '飞书 CLI 登录分两步,请在自己的终端里依序运行:先用第 1 步注册飞书应用,再用第 2 步完成 OAuth 授权 —— 两步全部完成后才算授权成功。`lark-cli` 登录时打印的二维码 / Device Flow URL 不适合在此面板内展示。完成第 2 步后回到这里点击「重新检测状态」。建议同步安装配套 Skills:`npx skills add larksuite/cli -y -g`。',
|
|
332
|
+
},
|
|
333
|
+
},
|
|
334
|
+
// ── Content & creation ─────────────────────────────────────────────────────
|
|
335
|
+
{
|
|
336
|
+
id: 'mocli',
|
|
337
|
+
binary: 'mocli',
|
|
338
|
+
name: 'Mowen CLI',
|
|
339
|
+
description: 'Talk to 墨问 (Mowen) — query notes, search users, view activity from your agent',
|
|
340
|
+
descriptionZh: '墨问官方 CLI — 查我的笔记、搜用户、看动态,给 Agent 一手接入墨问内容',
|
|
341
|
+
category: 'content',
|
|
342
|
+
iconUrl: 'https://github.com/mowenxd.png?size=80',
|
|
343
|
+
homepage: 'https://github.com/mowenxd/cli',
|
|
344
|
+
recommendedScope: 'global',
|
|
345
|
+
versionArgv: ['mocli', '--version'],
|
|
346
|
+
install: {
|
|
347
|
+
docs: 'https://github.com/mowenxd/cli#安装与快速开始',
|
|
348
|
+
darwin: [{ label: 'npm', cmd: 'npm i -g @mowenxd/cli' }],
|
|
349
|
+
linux: [{ label: 'npm', cmd: 'npm i -g @mowenxd/cli' }],
|
|
350
|
+
win: [{ label: 'npm', cmd: 'npm i -g @mowenxd/cli' }],
|
|
351
|
+
},
|
|
352
|
+
auth: {
|
|
353
|
+
type: 'token',
|
|
354
|
+
statusArgv: ['mocli', 'auth', 'info'],
|
|
355
|
+
logoutArgv: ['mocli', 'auth', 'logout'],
|
|
356
|
+
tokenFields: [
|
|
357
|
+
{ key: 'MOWEN_API_KEY', label: 'Mowen API Key', labelZh: '墨问 API Key', secret: true, required: true },
|
|
358
|
+
],
|
|
359
|
+
loginHint: 'Get your API Key in the Mowen mini-program → 我的 → 开发者 → 我的 API Key.',
|
|
360
|
+
loginHintZh: '在墨问小程序里:右下角「我的」→「开发者」→「我的 API Key」,复制粘贴到这里。装完后建议再跑 `npx skills add mowenxd/cli -y -g` 安装配套 Skills。',
|
|
361
|
+
},
|
|
362
|
+
},
|
|
363
|
+
// ── No-auth utilities (detect only) ────────────────────────────────────────
|
|
364
|
+
{
|
|
365
|
+
id: 'opencli',
|
|
366
|
+
binary: 'opencli',
|
|
367
|
+
name: 'OpenCLI',
|
|
368
|
+
description: 'Universal CLI hub — call 100+ websites and local binaries as agent-friendly commands via a browser bridge',
|
|
369
|
+
descriptionZh: '通用 CLI Hub — 借助浏览器桥接,把 100+ 网站与本地二进制变成 Agent 可调用的命令',
|
|
370
|
+
category: 'dev',
|
|
371
|
+
iconUrl: 'https://github.com/jackwener.png?size=80',
|
|
372
|
+
homepage: 'https://github.com/jackwener/opencli',
|
|
373
|
+
recommendedScope: 'global',
|
|
374
|
+
versionArgv: ['opencli', '--version'],
|
|
375
|
+
install: {
|
|
376
|
+
docs: 'https://github.com/jackwener/opencli#installation',
|
|
377
|
+
darwin: [{ label: 'npm', cmd: 'npm i -g @jackwener/opencli' }],
|
|
378
|
+
linux: [{ label: 'npm', cmd: 'npm i -g @jackwener/opencli' }],
|
|
379
|
+
win: [{ label: 'npm', cmd: 'npm i -g @jackwener/opencli' }],
|
|
380
|
+
},
|
|
381
|
+
auth: { type: 'none' },
|
|
382
|
+
},
|
|
383
|
+
{
|
|
384
|
+
id: 'docker',
|
|
385
|
+
binary: 'docker',
|
|
386
|
+
name: 'Docker',
|
|
387
|
+
description: 'Build, run, and ship containers',
|
|
388
|
+
descriptionZh: '构建、运行和分发容器',
|
|
389
|
+
category: 'dev',
|
|
390
|
+
iconSlug: 'docker',
|
|
391
|
+
homepage: 'https://docs.docker.com/engine/install/',
|
|
392
|
+
recommendedScope: 'global',
|
|
393
|
+
versionArgv: ['docker', '--version'],
|
|
394
|
+
install: {
|
|
395
|
+
docs: 'https://docs.docker.com/engine/install/',
|
|
396
|
+
darwin: [{ label: 'Docker Desktop', cmd: 'brew install --cask docker' }],
|
|
397
|
+
linux: [{ label: 'Script', cmd: 'curl -fsSL https://get.docker.com | sh' }],
|
|
398
|
+
win: [{ label: 'Docker Desktop', cmd: 'winget install Docker.DockerDesktop' }],
|
|
399
|
+
},
|
|
400
|
+
auth: { type: 'none' },
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
id: 'pnpm',
|
|
404
|
+
binary: 'pnpm',
|
|
405
|
+
name: 'pnpm',
|
|
406
|
+
description: 'Fast, disk-efficient Node package manager',
|
|
407
|
+
descriptionZh: '更快、更节省磁盘的 Node 包管理器',
|
|
408
|
+
category: 'dev',
|
|
409
|
+
iconSlug: 'pnpm',
|
|
410
|
+
homepage: 'https://pnpm.io/',
|
|
411
|
+
recommendedScope: 'global',
|
|
412
|
+
versionArgv: ['pnpm', '--version'],
|
|
413
|
+
install: {
|
|
414
|
+
docs: 'https://pnpm.io/installation',
|
|
415
|
+
darwin: [{ label: 'npm', cmd: 'npm i -g pnpm' }, { label: 'Homebrew', cmd: 'brew install pnpm' }],
|
|
416
|
+
linux: [{ label: 'npm', cmd: 'npm i -g pnpm' }],
|
|
417
|
+
win: [{ label: 'npm', cmd: 'npm i -g pnpm' }],
|
|
418
|
+
},
|
|
419
|
+
auth: { type: 'none' },
|
|
420
|
+
},
|
|
421
|
+
];
|