codexmate 0.0.18 → 0.0.19
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/README.en.md +28 -15
- package/README.md +28 -23
- package/cli.js +498 -319
- package/package.json +1 -1
- package/web-ui/app.js +36 -260
- package/web-ui/index.html +274 -122
- package/web-ui/modules/config-mode.computed.mjs +1 -0
- package/web-ui/modules/skills.computed.mjs +26 -1
- package/web-ui/modules/skills.methods.mjs +154 -23
- package/web-ui/session-helpers.mjs +12 -0
- package/web-ui/styles.css +171 -2
- package/doc/CHANGELOG.md +0 -32
- package/doc/CHANGELOG.zh-CN.md +0 -34
package/web-ui/index.html
CHANGED
|
@@ -96,6 +96,16 @@
|
|
|
96
96
|
:class="{ active: isMainTabNavActive('sessions') }"
|
|
97
97
|
@pointerdown="onMainTabPointerDown('sessions', $event)"
|
|
98
98
|
@click="onMainTabClick('sessions', $event)">会话浏览</button>
|
|
99
|
+
<button class="top-tab"
|
|
100
|
+
id="tab-market"
|
|
101
|
+
role="tab"
|
|
102
|
+
data-main-tab="market"
|
|
103
|
+
:tabindex="mainTab === 'market' ? 0 : -1"
|
|
104
|
+
:aria-selected="mainTab === 'market'"
|
|
105
|
+
aria-controls="panel-market"
|
|
106
|
+
:class="{ active: isMainTabNavActive('market') }"
|
|
107
|
+
@pointerdown="onMainTabPointerDown('market', $event)"
|
|
108
|
+
@click="onMainTabClick('market', $event)">技能市场</button>
|
|
99
109
|
<button class="top-tab"
|
|
100
110
|
id="tab-settings"
|
|
101
111
|
role="tab"
|
|
@@ -215,6 +225,26 @@
|
|
|
215
225
|
</button>
|
|
216
226
|
</div>
|
|
217
227
|
|
|
228
|
+
<div class="side-section" role="tablist" aria-label="技能市场">
|
|
229
|
+
<div class="side-section-title">技能市场</div>
|
|
230
|
+
<button
|
|
231
|
+
role="tab"
|
|
232
|
+
id="side-tab-market"
|
|
233
|
+
data-main-tab="market"
|
|
234
|
+
aria-controls="panel-market"
|
|
235
|
+
:tabindex="mainTab === 'market' ? 0 : -1"
|
|
236
|
+
:aria-selected="mainTab === 'market'"
|
|
237
|
+
:class="['side-item', { active: isMainTabNavActive('market') }]"
|
|
238
|
+
@pointerdown="onMainTabPointerDown('market', $event)"
|
|
239
|
+
@click="onMainTabClick('market', $event)">
|
|
240
|
+
<div class="side-item-title">市场</div>
|
|
241
|
+
<div class="side-item-meta">
|
|
242
|
+
<span>{{ skillsTargetLabel }} / 本地 Skills</span>
|
|
243
|
+
<span>已装 {{ skillsList.length }} · 可导入 {{ skillsImportList.length }}</span>
|
|
244
|
+
</div>
|
|
245
|
+
</button>
|
|
246
|
+
</div>
|
|
247
|
+
|
|
218
248
|
<div class="side-section" role="tablist" aria-label="设置">
|
|
219
249
|
<div class="side-section-title">设置</div>
|
|
220
250
|
<button
|
|
@@ -237,7 +267,7 @@
|
|
|
237
267
|
<main class="main-panel">
|
|
238
268
|
<div class="panel-header" v-if="!sessionStandalone">
|
|
239
269
|
<h1 class="main-title">
|
|
240
|
-
{{ mainTab === 'config' ? '配置中心' : (mainTab === 'sessions' ? '会话浏览' : '设置') }}
|
|
270
|
+
{{ mainTab === 'config' ? '配置中心' : (mainTab === 'sessions' ? '会话浏览' : (mainTab === 'market' ? '技能市场' : '设置')) }}
|
|
241
271
|
</h1>
|
|
242
272
|
<p class="subtitle" v-if="mainTab === 'config'">
|
|
243
273
|
配置中枢:管理 Codex / Claude / OpenClaw
|
|
@@ -246,6 +276,9 @@
|
|
|
246
276
|
<p class="subtitle" v-else-if="mainTab === 'sessions'">
|
|
247
277
|
浏览、导出或独立查看 Codex / Claude 会话记录。
|
|
248
278
|
</p>
|
|
279
|
+
<p class="subtitle" v-else-if="mainTab === 'market'">
|
|
280
|
+
统一管理 Codex / Claude Skills,并聚焦本地导入与分发。
|
|
281
|
+
</p>
|
|
249
282
|
</div>
|
|
250
283
|
|
|
251
284
|
<div class="status-strip" v-if="!sessionStandalone && mainTab === 'config'">
|
|
@@ -298,16 +331,34 @@
|
|
|
298
331
|
<span class="value">{{ sessionsList.length }}</span>
|
|
299
332
|
</div>
|
|
300
333
|
</div>
|
|
334
|
+
<div class="status-strip" v-else-if="!sessionStandalone && mainTab === 'market'">
|
|
335
|
+
<div class="status-chip">
|
|
336
|
+
<span class="label">当前目标</span>
|
|
337
|
+
<span class="value">{{ skillsTargetLabel }}</span>
|
|
338
|
+
</div>
|
|
339
|
+
<div class="status-chip">
|
|
340
|
+
<span class="label">本地 Skills</span>
|
|
341
|
+
<span class="value">{{ skillsList.length }}</span>
|
|
342
|
+
</div>
|
|
343
|
+
<div class="status-chip">
|
|
344
|
+
<span class="label">可导入</span>
|
|
345
|
+
<span class="value">{{ skillsImportList.length }}</span>
|
|
346
|
+
</div>
|
|
347
|
+
<div class="status-chip">
|
|
348
|
+
<span class="label">可直接导入</span>
|
|
349
|
+
<span class="value">{{ skillsImportConfiguredCount }}</span>
|
|
350
|
+
</div>
|
|
351
|
+
</div>
|
|
301
352
|
<div
|
|
302
|
-
v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError &&
|
|
353
|
+
v-if="!sessionStandalone && mainTab === 'config' && isProviderConfigMode && forceCompactLayout && !loading && !initError && displayProvidersList.length > 1"
|
|
303
354
|
class="provider-fast-switch">
|
|
304
355
|
<label class="provider-fast-switch-label" for="provider-fast-switch-select">快速切换提供商</label>
|
|
305
356
|
<select
|
|
306
357
|
id="provider-fast-switch-select"
|
|
307
358
|
class="provider-fast-switch-select"
|
|
308
|
-
:value="
|
|
359
|
+
:value="displayCurrentProvider"
|
|
309
360
|
@change="quickSwitchProvider($event.target.value)">
|
|
310
|
-
<option v-for="provider in
|
|
361
|
+
<option v-for="provider in displayProvidersList" :key="'quick-switch-' + provider.name" :value="provider.name">
|
|
311
362
|
{{ provider.name }}
|
|
312
363
|
</option>
|
|
313
364
|
</select>
|
|
@@ -381,7 +432,7 @@
|
|
|
381
432
|
Codex 配置需先改模板,再手动应用。
|
|
382
433
|
</div>
|
|
383
434
|
<div class="config-template-hint" v-else-if="activeProviderBridgeHint">
|
|
384
|
-
{{ activeProviderBridgeHint }}
|
|
435
|
+
{{ activeProviderBridgeHint }} 模板仅在 Codex 模式下可编辑。
|
|
385
436
|
</div>
|
|
386
437
|
<button class="btn-tool btn-template-editor" v-if="isCodexConfigMode" @click="openConfigTemplateEditor" :disabled="loading || !!initError">
|
|
387
438
|
打开 Config 模板编辑器
|
|
@@ -430,8 +481,8 @@
|
|
|
430
481
|
<div class="selector-header">
|
|
431
482
|
<span class="selector-title">Skills 管理</span>
|
|
432
483
|
</div>
|
|
433
|
-
<div class="config-template-hint skills-hint-line"
|
|
434
|
-
<button class="btn-tool" @click="openSkillsManager" :disabled="loading || !!initError || skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
484
|
+
<div class="config-template-hint skills-hint-line">默认打开 Codex skills 管理;市场页已支持切换到 Claude Code 并查看本地概览与导入来源。</div>
|
|
485
|
+
<button class="btn-tool" @click="openSkillsManager({ targetApp: 'codex' })" :disabled="loading || !!initError || skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
435
486
|
{{ skillsLoading ? '加载中...' : '打开 Skills 管理' }}
|
|
436
487
|
</button>
|
|
437
488
|
</div>
|
|
@@ -446,102 +497,6 @@
|
|
|
446
497
|
</button>
|
|
447
498
|
</div>
|
|
448
499
|
|
|
449
|
-
<div class="selector-section">
|
|
450
|
-
<div class="selector-header">
|
|
451
|
-
<span class="selector-title">Codex 认证文件</span>
|
|
452
|
-
</div>
|
|
453
|
-
<div class="config-template-hint hint-single-line" title="上传 JSON 切换账号(写入 ~/.codex/auth.json)。">
|
|
454
|
-
上传 JSON 切换账号(写入 <code>~/.codex/auth.json</code>)。
|
|
455
|
-
</div>
|
|
456
|
-
<button class="btn-tool" @click="triggerCodexAuthUpload" :disabled="codexAuthImportLoading || loading || !!initError">
|
|
457
|
-
{{ codexAuthImportLoading ? '上传中...' : '上传认证文件' }}
|
|
458
|
-
</button>
|
|
459
|
-
<input
|
|
460
|
-
ref="codexAuthImportInput"
|
|
461
|
-
type="file"
|
|
462
|
-
accept=".json,application/json"
|
|
463
|
-
style="display:none"
|
|
464
|
-
@change="handleCodexAuthImportChange">
|
|
465
|
-
<div v-if="codexAuthProfiles.length === 0" class="form-hint">暂无认证文件。</div>
|
|
466
|
-
<div v-else class="auth-profile-list">
|
|
467
|
-
<div class="auth-profile-item" v-for="profile in codexAuthProfiles" :key="'auth-' + profile.name">
|
|
468
|
-
<div class="auth-profile-header">
|
|
469
|
-
<div class="auth-profile-main">
|
|
470
|
-
<div class="auth-profile-title">{{ profile.name }}</div>
|
|
471
|
-
<div class="auth-profile-meta">
|
|
472
|
-
<span class="provider-source">{{ profile.type || 'unknown' }}</span>
|
|
473
|
-
<span :class="['pill', profile.current ? 'configured' : 'empty']">
|
|
474
|
-
{{ profile.current ? '当前' : '备用' }}
|
|
475
|
-
</span>
|
|
476
|
-
</div>
|
|
477
|
-
</div>
|
|
478
|
-
<div class="auth-profile-actions">
|
|
479
|
-
<button
|
|
480
|
-
class="btn-mini"
|
|
481
|
-
:disabled="profile.current || codexAuthSwitching[profile.name]"
|
|
482
|
-
@click="switchCodexAuthProfile(profile.name)">
|
|
483
|
-
{{ codexAuthSwitching[profile.name] ? '切换中...' : (profile.current ? '当前使用' : '切换账号') }}
|
|
484
|
-
</button>
|
|
485
|
-
<button
|
|
486
|
-
class="btn-mini delete"
|
|
487
|
-
:disabled="codexAuthDeleting[profile.name]"
|
|
488
|
-
@click="deleteCodexAuthProfile(profile.name)">
|
|
489
|
-
{{ codexAuthDeleting[profile.name] ? '删除中...' : '删除' }}
|
|
490
|
-
</button>
|
|
491
|
-
</div>
|
|
492
|
-
</div>
|
|
493
|
-
<div class="auth-profile-grid">
|
|
494
|
-
<div v-if="profile.email" class="auth-profile-row">
|
|
495
|
-
<span class="auth-profile-key">email</span>
|
|
496
|
-
<span class="auth-profile-value">{{ profile.email }}</span>
|
|
497
|
-
</div>
|
|
498
|
-
<div v-if="profile.accountId" class="auth-profile-row">
|
|
499
|
-
<span class="auth-profile-key">account_id</span>
|
|
500
|
-
<span class="auth-profile-value">{{ profile.accountId }}</span>
|
|
501
|
-
</div>
|
|
502
|
-
<div v-if="profile.expired" class="auth-profile-row">
|
|
503
|
-
<span class="auth-profile-key">expired</span>
|
|
504
|
-
<span class="auth-profile-value">{{ profile.expired }}</span>
|
|
505
|
-
</div>
|
|
506
|
-
</div>
|
|
507
|
-
</div>
|
|
508
|
-
</div>
|
|
509
|
-
</div>
|
|
510
|
-
|
|
511
|
-
<div class="selector-section">
|
|
512
|
-
<div class="selector-header">
|
|
513
|
-
<span class="selector-title">内建代理</span>
|
|
514
|
-
</div>
|
|
515
|
-
<button class="btn-mini" @click="showProxyAdvanced = !showProxyAdvanced">
|
|
516
|
-
{{ showProxyAdvanced ? '收起高级设置' : '展开高级设置(端口/鉴权)' }}
|
|
517
|
-
</button>
|
|
518
|
-
<div v-if="showProxyAdvanced">
|
|
519
|
-
<div class="list-row">
|
|
520
|
-
<label class="form-label">上游 Provider</label>
|
|
521
|
-
<select class="form-input" v-model="proxySettings.provider" @change="saveProxySettings({ silent: true })">
|
|
522
|
-
<option value="">自动(当前 provider)</option>
|
|
523
|
-
<option v-for="name in proxyProviderOptions" :key="'proxy-provider-' + name" :value="name">{{ name }}</option>
|
|
524
|
-
</select>
|
|
525
|
-
<label class="form-label">端口</label>
|
|
526
|
-
<input v-model.number="proxySettings.port" class="form-input" type="number" min="1" max="65535" placeholder="8318" @change="saveProxySettings({ silent: true })">
|
|
527
|
-
</div>
|
|
528
|
-
<div class="list-row">
|
|
529
|
-
<label class="form-label">鉴权来源</label>
|
|
530
|
-
<select class="form-input" v-model="proxySettings.authSource" @change="saveProxySettings({ silent: true })">
|
|
531
|
-
<option value="provider">provider 优先</option>
|
|
532
|
-
<option value="profile">仅当前认证文件</option>
|
|
533
|
-
<option value="none">不注入鉴权头</option>
|
|
534
|
-
</select>
|
|
535
|
-
<label class="form-label">超时(ms)</label>
|
|
536
|
-
<input v-model.number="proxySettings.timeoutMs" class="form-input" type="number" min="1000" step="500" placeholder="30000" @change="saveProxySettings({ silent: true })">
|
|
537
|
-
</div>
|
|
538
|
-
<div class="form-hint">高级参数修改后自动保存。</div>
|
|
539
|
-
</div>
|
|
540
|
-
<div class="config-template-hint" v-if="proxyRuntime">
|
|
541
|
-
运行中:入口 <code>{{ proxyRuntimeDisplayProvider }}</code>
|
|
542
|
-
<span v-if="proxyRuntime.upstreamProvider">(上游 <code>{{ proxyRuntime.upstreamProvider }}</code>)</span>
|
|
543
|
-
</div>
|
|
544
|
-
</div>
|
|
545
500
|
</template>
|
|
546
501
|
|
|
547
502
|
<div class="selector-section">
|
|
@@ -554,18 +509,18 @@
|
|
|
554
509
|
</div>
|
|
555
510
|
|
|
556
511
|
<div v-if="!loading && !initError" class="card-list">
|
|
557
|
-
<div v-for="provider in
|
|
558
|
-
:class="['card', { active:
|
|
512
|
+
<div v-for="provider in displayProvidersList" :key="provider.name"
|
|
513
|
+
:class="['card', { active: displayCurrentProvider === provider.name }]"
|
|
559
514
|
@click="switchProvider(provider.name)">
|
|
560
515
|
<div class="card-leading">
|
|
561
516
|
<div class="card-icon">{{ provider.name.charAt(0).toUpperCase() }}</div>
|
|
562
517
|
<div class="card-content">
|
|
563
518
|
<div class="card-title">
|
|
564
519
|
<span>{{ provider.name }}</span>
|
|
565
|
-
<span v-if="provider.readOnly" class="provider-readonly-badge"
|
|
520
|
+
<span v-if="provider.readOnly" class="provider-readonly-badge">系统</span>
|
|
566
521
|
</div>
|
|
567
522
|
<div class="card-subtitle">
|
|
568
|
-
{{ provider.
|
|
523
|
+
{{ provider.url || '未设置 URL' }}
|
|
569
524
|
</div>
|
|
570
525
|
</div>
|
|
571
526
|
</div>
|
|
@@ -1231,6 +1186,181 @@
|
|
|
1231
1186
|
</div>
|
|
1232
1187
|
</div>
|
|
1233
1188
|
|
|
1189
|
+
<div
|
|
1190
|
+
v-show="mainTab === 'market'"
|
|
1191
|
+
class="mode-content"
|
|
1192
|
+
id="panel-market"
|
|
1193
|
+
role="tabpanel"
|
|
1194
|
+
aria-labelledby="tab-market">
|
|
1195
|
+
<div class="selector-section market-overview-section">
|
|
1196
|
+
<div class="selector-header market-overview-header">
|
|
1197
|
+
<div>
|
|
1198
|
+
<span class="selector-title">Skills 市场概览</span>
|
|
1199
|
+
<div class="skills-panel-note">当前市场页只保留本地 skills 能力:切换 Codex / Claude Code 安装目标,查看已安装项,执行跨应用导入与 ZIP 分发。</div>
|
|
1200
|
+
</div>
|
|
1201
|
+
<div class="settings-tab-actions market-header-actions">
|
|
1202
|
+
<button class="btn-tool btn-tool-compact" @click="loadSkillsMarketOverview({ forceRefresh: true, silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1203
|
+
{{ skillsMarketLoading ? '刷新中...' : '刷新概览' }}
|
|
1204
|
+
</button>
|
|
1205
|
+
<button class="btn-tool btn-tool-compact" @click="openSkillsManager" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1206
|
+
打开 Skills 管理
|
|
1207
|
+
</button>
|
|
1208
|
+
</div>
|
|
1209
|
+
</div>
|
|
1210
|
+
|
|
1211
|
+
<div class="market-target-switch" role="group" aria-label="选择 Skills 安装目标">
|
|
1212
|
+
<button
|
|
1213
|
+
type="button"
|
|
1214
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'codex' }]"
|
|
1215
|
+
:aria-pressed="skillsTargetApp === 'codex'"
|
|
1216
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
1217
|
+
@click="setSkillsTargetApp('codex', { silent: false })">
|
|
1218
|
+
Codex
|
|
1219
|
+
</button>
|
|
1220
|
+
<button
|
|
1221
|
+
type="button"
|
|
1222
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'claude' }]"
|
|
1223
|
+
:aria-pressed="skillsTargetApp === 'claude'"
|
|
1224
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
1225
|
+
@click="setSkillsTargetApp('claude', { silent: false })">
|
|
1226
|
+
Claude Code
|
|
1227
|
+
</button>
|
|
1228
|
+
</div>
|
|
1229
|
+
|
|
1230
|
+
<div class="skills-root-box market-root-box">{{ skillsRootPath || skillsDefaultRootPath }}</div>
|
|
1231
|
+
|
|
1232
|
+
<div class="skills-summary-strip market-summary-strip">
|
|
1233
|
+
<div class="skills-summary-item">
|
|
1234
|
+
<span class="skills-summary-label">安装目标</span>
|
|
1235
|
+
<strong class="skills-summary-value">{{ skillsTargetLabel }}</strong>
|
|
1236
|
+
</div>
|
|
1237
|
+
<div class="skills-summary-item">
|
|
1238
|
+
<span class="skills-summary-label">本地总数</span>
|
|
1239
|
+
<strong class="skills-summary-value">{{ skillsList.length }}</strong>
|
|
1240
|
+
</div>
|
|
1241
|
+
<div class="skills-summary-item">
|
|
1242
|
+
<span class="skills-summary-label">含 SKILL.md</span>
|
|
1243
|
+
<strong class="skills-summary-value">{{ skillsConfiguredCount }}</strong>
|
|
1244
|
+
</div>
|
|
1245
|
+
<div class="skills-summary-item">
|
|
1246
|
+
<span class="skills-summary-label">缺少 SKILL.md</span>
|
|
1247
|
+
<strong class="skills-summary-value">{{ skillsMissingSkillFileCount }}</strong>
|
|
1248
|
+
</div>
|
|
1249
|
+
<div class="skills-summary-item">
|
|
1250
|
+
<span class="skills-summary-label">可导入</span>
|
|
1251
|
+
<strong class="skills-summary-value">{{ skillsImportList.length }}</strong>
|
|
1252
|
+
</div>
|
|
1253
|
+
<div class="skills-summary-item">
|
|
1254
|
+
<span class="skills-summary-label">可直接导入</span>
|
|
1255
|
+
<strong class="skills-summary-value">{{ skillsImportConfiguredCount }}</strong>
|
|
1256
|
+
</div>
|
|
1257
|
+
</div>
|
|
1258
|
+
</div>
|
|
1259
|
+
|
|
1260
|
+
<div class="market-grid">
|
|
1261
|
+
<div class="skills-panel market-panel">
|
|
1262
|
+
<div class="skills-panel-header">
|
|
1263
|
+
<div class="skills-panel-title-wrap">
|
|
1264
|
+
<div class="skills-panel-title">已安装 Skills</div>
|
|
1265
|
+
<div class="skills-panel-note">展示当前已落地到 <code>{{ skillsRootPath || skillsDefaultRootPath }}</code> 的前 6 个目录,可继续进入管理弹窗做筛选、导出和删除。</div>
|
|
1266
|
+
</div>
|
|
1267
|
+
<button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1268
|
+
{{ skillsLoading ? '刷新中...' : '刷新本地' }}
|
|
1269
|
+
</button>
|
|
1270
|
+
</div>
|
|
1271
|
+
<div v-if="skillsLoading && !skillsMarketLocalLoadedOnce" class="skills-empty-state">正在加载本地 Skills...</div>
|
|
1272
|
+
<div v-else-if="skillsList.length === 0" class="skills-empty-state">当前还没有已安装 skill,可通过 ZIP 或跨应用导入补充。</div>
|
|
1273
|
+
<div v-else class="market-preview-list">
|
|
1274
|
+
<div v-for="skill in skillsMarketInstalledPreview" :key="'market-local-' + skill.name" class="market-preview-item">
|
|
1275
|
+
<div class="market-preview-main">
|
|
1276
|
+
<div class="market-preview-title">{{ skill.displayName || skill.name }}</div>
|
|
1277
|
+
<div class="market-preview-meta">{{ skill.description || skill.path }}</div>
|
|
1278
|
+
</div>
|
|
1279
|
+
<span :class="['pill', skill.hasSkillFile ? 'configured' : 'empty']">
|
|
1280
|
+
{{ skill.hasSkillFile ? '已验证' : '待补 SKILL.md' }}
|
|
1281
|
+
</span>
|
|
1282
|
+
</div>
|
|
1283
|
+
</div>
|
|
1284
|
+
</div>
|
|
1285
|
+
|
|
1286
|
+
<div class="skills-panel market-panel">
|
|
1287
|
+
<div class="skills-panel-header">
|
|
1288
|
+
<div class="skills-panel-title-wrap">
|
|
1289
|
+
<div class="skills-panel-title">可导入来源</div>
|
|
1290
|
+
<div class="skills-panel-note">扫描其他应用下未托管的 skill,先确认来源和目录,再批量导入到当前 {{ skillsTargetLabel }} skills 目录。</div>
|
|
1291
|
+
</div>
|
|
1292
|
+
<button class="btn-mini" @click="scanImportableSkills({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1293
|
+
{{ skillsScanningImports ? '扫描中...' : '扫描来源' }}
|
|
1294
|
+
</button>
|
|
1295
|
+
</div>
|
|
1296
|
+
<div v-if="skillsScanningImports && !skillsMarketImportLoadedOnce" class="skills-empty-state">正在扫描可导入 skill...</div>
|
|
1297
|
+
<div v-else-if="skillsImportList.length === 0" class="skills-empty-state">还没有扫描到可导入 skill,可点击“扫描来源”重新读取。</div>
|
|
1298
|
+
<div v-else class="market-preview-list">
|
|
1299
|
+
<div v-for="skill in skillsMarketImportPreview" :key="'market-import-' + buildSkillImportKey(skill)" class="market-preview-item">
|
|
1300
|
+
<div class="market-preview-main">
|
|
1301
|
+
<div class="market-preview-title">{{ skill.displayName || skill.name }}</div>
|
|
1302
|
+
<div class="market-preview-meta">{{ skill.sourceLabel }} · {{ skill.sourcePath }}</div>
|
|
1303
|
+
</div>
|
|
1304
|
+
<span :class="['pill', skill.hasSkillFile ? 'configured' : 'empty']">
|
|
1305
|
+
{{ skill.hasSkillFile ? '可直接导入' : '缺少 SKILL.md' }}
|
|
1306
|
+
</span>
|
|
1307
|
+
</div>
|
|
1308
|
+
</div>
|
|
1309
|
+
</div>
|
|
1310
|
+
|
|
1311
|
+
<div class="skills-panel market-panel market-actions-panel">
|
|
1312
|
+
<div class="skills-panel-header">
|
|
1313
|
+
<div class="skills-panel-title-wrap">
|
|
1314
|
+
<div class="skills-panel-title">分发入口</div>
|
|
1315
|
+
<div class="skills-panel-note">市场页聚焦本地落地:本地管理、跨应用导入、ZIP 分发,全部作用到当前安装目标。</div>
|
|
1316
|
+
</div>
|
|
1317
|
+
</div>
|
|
1318
|
+
<div class="market-action-grid">
|
|
1319
|
+
<button class="market-action-card" @click="openSkillsManager" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1320
|
+
<span class="market-action-title">本地 Skills 管理</span>
|
|
1321
|
+
<span class="market-action-copy">查看、筛选、导出、删除当前 {{ skillsTargetLabel }} 已安装 skills</span>
|
|
1322
|
+
</button>
|
|
1323
|
+
<button class="market-action-card" @click="scanImportableSkills({ silent: false })" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1324
|
+
<span class="market-action-title">跨应用导入</span>
|
|
1325
|
+
<span class="market-action-copy">扫描其他应用目录并导入到当前 {{ skillsTargetLabel }}</span>
|
|
1326
|
+
</button>
|
|
1327
|
+
<button class="market-action-card" @click="triggerSkillsZipImport" :disabled="loading || !!initError || skillsMarketBusy">
|
|
1328
|
+
<span class="market-action-title">ZIP 导入</span>
|
|
1329
|
+
<span class="market-action-copy">从压缩包分发并安装到当前目标宿主</span>
|
|
1330
|
+
</button>
|
|
1331
|
+
</div>
|
|
1332
|
+
</div>
|
|
1333
|
+
|
|
1334
|
+
<div class="skills-panel market-panel market-panel-wide">
|
|
1335
|
+
<div class="skills-panel-header">
|
|
1336
|
+
<div class="skills-panel-title-wrap">
|
|
1337
|
+
<div class="skills-panel-title">市场说明</div>
|
|
1338
|
+
</div>
|
|
1339
|
+
</div>
|
|
1340
|
+
<div class="market-preview-list">
|
|
1341
|
+
<div class="market-preview-item">
|
|
1342
|
+
<div class="market-preview-main">
|
|
1343
|
+
<div class="market-preview-title">目标宿主切换</div>
|
|
1344
|
+
<div class="market-preview-meta">在 Codex 和 Claude Code 之间切换后,后续扫描、导入、导出、删除都会落到当前 {{ skillsTargetLabel }} 目录。</div>
|
|
1345
|
+
</div>
|
|
1346
|
+
</div>
|
|
1347
|
+
<div class="market-preview-item">
|
|
1348
|
+
<div class="market-preview-main">
|
|
1349
|
+
<div class="market-preview-title">跨应用导入</div>
|
|
1350
|
+
<div class="market-preview-meta">扫描 `Codex`、`Claude Code` 与 `Agents` 目录里的未托管 skills,筛选后批量导入到当前宿主。</div>
|
|
1351
|
+
</div>
|
|
1352
|
+
</div>
|
|
1353
|
+
<div class="market-preview-item">
|
|
1354
|
+
<div class="market-preview-main">
|
|
1355
|
+
<div class="market-preview-title">ZIP 分发</div>
|
|
1356
|
+
<div class="market-preview-meta">通过压缩包在不同环境间分发技能目录,保持本地可控,不依赖外部目录服务。</div>
|
|
1357
|
+
</div>
|
|
1358
|
+
</div>
|
|
1359
|
+
</div>
|
|
1360
|
+
</div>
|
|
1361
|
+
</div>
|
|
1362
|
+
</div>
|
|
1363
|
+
|
|
1234
1364
|
<!-- 加载状态 -->
|
|
1235
1365
|
<div v-if="loading" class="state-message">
|
|
1236
1366
|
加载配置中...
|
|
@@ -1296,8 +1426,6 @@
|
|
|
1296
1426
|
<span :class="['value', 'tone-' + inspectorHealthTone]">{{ inspectorHealthStatus }}</span>
|
|
1297
1427
|
<span class="key">模型加载</span>
|
|
1298
1428
|
<span class="value">{{ inspectorModelLoadStatus }}</span>
|
|
1299
|
-
<span class="key">代理状态</span>
|
|
1300
|
-
<span class="value">{{ inspectorProxyStatus }}</span>
|
|
1301
1429
|
</div>
|
|
1302
1430
|
</section>
|
|
1303
1431
|
</aside>
|
|
@@ -1702,7 +1830,7 @@
|
|
|
1702
1830
|
<div class="structured-card">
|
|
1703
1831
|
<div class="structured-card-title">Providers(只读)</div>
|
|
1704
1832
|
<div v-if="openclawProviders.length === 0" class="form-hint">
|
|
1705
|
-
未发现 providers
|
|
1833
|
+
未发现 providers 配置(可能使用环境变量或外部配置)。
|
|
1706
1834
|
</div>
|
|
1707
1835
|
<div v-else class="provider-list">
|
|
1708
1836
|
<div class="provider-item" v-for="(provider, index) in openclawProviders" :key="provider.key + '-' + provider.source + '-' + index">
|
|
@@ -1905,9 +2033,27 @@
|
|
|
1905
2033
|
<div class="modal-header skills-modal-header">
|
|
1906
2034
|
<div>
|
|
1907
2035
|
<div class="modal-title">Skills 管理</div>
|
|
1908
|
-
<div class="skills-modal-subtitle"
|
|
2036
|
+
<div class="skills-modal-subtitle">集中管理当前宿主的本地技能目录,支持检索筛选、多选删除、跨应用导入、ZIP 导入与导出。</div>
|
|
1909
2037
|
</div>
|
|
1910
2038
|
<div class="modal-header-actions skills-modal-actions">
|
|
2039
|
+
<div class="market-target-switch market-target-switch-compact" role="group" aria-label="选择 Skills 管理目标">
|
|
2040
|
+
<button
|
|
2041
|
+
type="button"
|
|
2042
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'codex' }]"
|
|
2043
|
+
:aria-pressed="skillsTargetApp === 'codex'"
|
|
2044
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
2045
|
+
@click="setSkillsTargetApp('codex', { silent: false })">
|
|
2046
|
+
Codex
|
|
2047
|
+
</button>
|
|
2048
|
+
<button
|
|
2049
|
+
type="button"
|
|
2050
|
+
:class="['market-target-chip', { active: skillsTargetApp === 'claude' }]"
|
|
2051
|
+
:aria-pressed="skillsTargetApp === 'claude'"
|
|
2052
|
+
:disabled="loading || !!initError || skillsMarketBusy"
|
|
2053
|
+
@click="setSkillsTargetApp('claude', { silent: false })">
|
|
2054
|
+
Claude Code
|
|
2055
|
+
</button>
|
|
2056
|
+
</div>
|
|
1911
2057
|
<button class="btn-mini" @click="refreshSkillsList({ silent: false })" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
1912
2058
|
{{ skillsLoading ? '刷新中...' : '刷新' }}
|
|
1913
2059
|
</button>
|
|
@@ -1915,11 +2061,15 @@
|
|
|
1915
2061
|
</div>
|
|
1916
2062
|
|
|
1917
2063
|
<div class="form-group skills-root-group">
|
|
1918
|
-
<label class="form-label">Skills
|
|
1919
|
-
<div class="skills-root-box">{{ skillsRootPath ||
|
|
2064
|
+
<label class="form-label">Skills 目录({{ skillsTargetLabel }})</label>
|
|
2065
|
+
<div class="skills-root-box">{{ skillsRootPath || skillsDefaultRootPath }}</div>
|
|
1920
2066
|
</div>
|
|
1921
2067
|
|
|
1922
2068
|
<div class="skills-summary-strip">
|
|
2069
|
+
<div class="skills-summary-item">
|
|
2070
|
+
<span class="skills-summary-label">安装目标</span>
|
|
2071
|
+
<strong class="skills-summary-value">{{ skillsTargetLabel }}</strong>
|
|
2072
|
+
</div>
|
|
1923
2073
|
<div class="skills-summary-item">
|
|
1924
2074
|
<span class="skills-summary-label">本地总数</span>
|
|
1925
2075
|
<strong class="skills-summary-value">{{ skillsList.length }}</strong>
|
|
@@ -1942,7 +2092,7 @@
|
|
|
1942
2092
|
<div class="skills-panel-header">
|
|
1943
2093
|
<div class="skills-panel-title-wrap">
|
|
1944
2094
|
<div class="skills-panel-title">本地 Skills</div>
|
|
1945
|
-
<div class="skills-panel-note"
|
|
2095
|
+
<div class="skills-panel-note">支持关键词检索与状态筛选,勾选后可批量删除当前 {{ skillsTargetLabel }} 本地 skill。</div>
|
|
1946
2096
|
</div>
|
|
1947
2097
|
<button
|
|
1948
2098
|
class="btn-mini"
|
|
@@ -2001,8 +2151,8 @@
|
|
|
2001
2151
|
<div class="skills-panel skills-import-block">
|
|
2002
2152
|
<div class="skills-panel-header">
|
|
2003
2153
|
<div class="skills-panel-title-wrap">
|
|
2004
|
-
<div class="skills-import-title"
|
|
2005
|
-
<div class="skills-panel-note"
|
|
2154
|
+
<div class="skills-import-title">跨应用导入</div>
|
|
2155
|
+
<div class="skills-panel-note">从其他应用扫描并导入未托管技能,支持多选批量导入到当前 {{ skillsTargetLabel }}。</div>
|
|
2006
2156
|
</div>
|
|
2007
2157
|
<button class="btn-mini" @click="scanImportableSkills" :disabled="skillsLoading || skillsDeleting || skillsScanningImports || skillsImporting || skillsZipImporting || skillsExporting">
|
|
2008
2158
|
{{ skillsScanningImports ? '扫描中...' : '扫描可导入' }}
|
|
@@ -2053,15 +2203,16 @@
|
|
|
2053
2203
|
{{ skillsDeleting ? '删除中...' : '删除选中' }}
|
|
2054
2204
|
</button>
|
|
2055
2205
|
</div>
|
|
2056
|
-
<input
|
|
2057
|
-
ref="skillsZipImportInput"
|
|
2058
|
-
type="file"
|
|
2059
|
-
accept=".zip,application/zip"
|
|
2060
|
-
style="display:none"
|
|
2061
|
-
@change="handleSkillsZipImportChange">
|
|
2062
2206
|
</div>
|
|
2063
2207
|
</div>
|
|
2064
2208
|
|
|
2209
|
+
<input
|
|
2210
|
+
ref="skillsZipImportInput"
|
|
2211
|
+
type="file"
|
|
2212
|
+
accept=".zip,application/zip"
|
|
2213
|
+
style="display:none"
|
|
2214
|
+
@change="handleSkillsZipImportChange">
|
|
2215
|
+
|
|
2065
2216
|
<div v-if="showConfirmDialog" class="modal-overlay" @click.self="closeConfirmDialog">
|
|
2066
2217
|
<div
|
|
2067
2218
|
class="modal confirm-dialog"
|
|
@@ -2076,6 +2227,7 @@
|
|
|
2076
2227
|
<button class="btn btn-cancel" @click="closeConfirmDialog">{{ confirmDialogCancelText }}</button>
|
|
2077
2228
|
<button
|
|
2078
2229
|
:class="['btn', 'btn-confirm', confirmDialogDanger ? 'btn-danger' : '']"
|
|
2230
|
+
:disabled="isConfirmDialogDisabled()"
|
|
2079
2231
|
@click="resolveConfirmDialog(true)">
|
|
2080
2232
|
{{ confirmDialogConfirmText }}
|
|
2081
2233
|
</button>
|
|
@@ -49,6 +49,7 @@ export function createConfigModeComputed() {
|
|
|
49
49
|
inspectorMainTabLabel() {
|
|
50
50
|
if (this.mainTab === 'config') return '配置中心';
|
|
51
51
|
if (this.mainTab === 'sessions') return '会话浏览';
|
|
52
|
+
if (this.mainTab === 'market') return '技能市场';
|
|
52
53
|
if (this.mainTab === 'settings') return '设置';
|
|
53
54
|
return '未知';
|
|
54
55
|
},
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
export function createSkillsComputed() {
|
|
2
2
|
return {
|
|
3
|
+
skillsTargetLabel() {
|
|
4
|
+
return this.skillsTargetApp === 'claude' ? 'Claude Code' : 'Codex';
|
|
5
|
+
},
|
|
6
|
+
skillsDefaultRootPath() {
|
|
7
|
+
return this.skillsTargetApp === 'claude' ? '~/.claude/skills' : '~/.codex/skills';
|
|
8
|
+
},
|
|
3
9
|
filteredSkillsList() {
|
|
4
10
|
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
5
11
|
const keyword = typeof this.skillsKeyword === 'string' ? this.skillsKeyword.trim().toLowerCase() : '';
|
|
@@ -77,6 +83,25 @@
|
|
|
77
83
|
skillsImportMissingSkillFileCount() {
|
|
78
84
|
const list = Array.isArray(this.skillsImportList) ? this.skillsImportList : [];
|
|
79
85
|
return list.filter((item) => !(item && item.hasSkillFile)).length;
|
|
86
|
+
},
|
|
87
|
+
skillsMarketBusy() {
|
|
88
|
+
return !!(
|
|
89
|
+
this.skillsMarketLoading
|
|
90
|
+
|| this.skillsLoading
|
|
91
|
+
|| this.skillsDeleting
|
|
92
|
+
|| this.skillsScanningImports
|
|
93
|
+
|| this.skillsImporting
|
|
94
|
+
|| this.skillsZipImporting
|
|
95
|
+
|| this.skillsExporting
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
skillsMarketInstalledPreview() {
|
|
99
|
+
const list = Array.isArray(this.skillsList) ? this.skillsList : [];
|
|
100
|
+
return list.slice(0, 6);
|
|
101
|
+
},
|
|
102
|
+
skillsMarketImportPreview() {
|
|
103
|
+
const list = Array.isArray(this.skillsImportList) ? this.skillsImportList : [];
|
|
104
|
+
return list.slice(0, 6);
|
|
80
105
|
}
|
|
81
106
|
};
|
|
82
|
-
}
|
|
107
|
+
}
|