@talex-touch/utils 1.0.39 → 1.0.42
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/core-box/tuff/tuff-dsl.ts +133 -54
- package/intelligence/client.ts +8 -8
- package/market/constants.ts +14 -0
- package/market/types.ts +1 -1
- package/package.json +1 -1
- package/plugin/index.ts +7 -1
- package/plugin/providers/index.ts +4 -0
- package/plugin/providers/market-client.ts +215 -0
- package/plugin/providers/npm-provider.ts +213 -0
- package/plugin/providers/tpex-provider.ts +283 -0
- package/plugin/providers/tpex-types.ts +34 -0
- package/plugin/sdk/README.md +54 -6
- package/plugin/sdk/clipboard.ts +196 -23
- package/plugin/sdk/enum/bridge-event.ts +1 -0
- package/plugin/sdk/feature-sdk.ts +85 -6
- package/plugin/sdk/flow.ts +246 -0
- package/plugin/sdk/hooks/bridge.ts +113 -39
- package/plugin/sdk/index.ts +2 -0
- package/plugin/sdk/performance.ts +186 -0
- package/plugin/widget.ts +5 -0
- package/renderer/hooks/arg-mapper.ts +20 -6
- package/renderer/hooks/use-intelligence.ts +291 -34
- package/renderer/storage/base-storage.ts +98 -15
- package/renderer/storage/intelligence-storage.ts +9 -9
- package/renderer/storage/storage-subscription.ts +17 -9
- package/search/fuzzy-match.ts +254 -0
- package/types/division-box.ts +20 -0
- package/types/flow.ts +283 -0
- package/types/index.ts +1 -0
- package/types/intelligence.ts +1496 -78
|
@@ -803,6 +803,65 @@ export interface TuffContext {
|
|
|
803
803
|
tags?: string[]
|
|
804
804
|
}
|
|
805
805
|
|
|
806
|
+
// ==================== Footer Hints 配置 ====================
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* Footer hint 单项配置
|
|
810
|
+
*/
|
|
811
|
+
export interface TuffFooterHintItem {
|
|
812
|
+
/** 快捷键显示文本 */
|
|
813
|
+
key: string
|
|
814
|
+
/** 提示标签 */
|
|
815
|
+
label: string
|
|
816
|
+
/** 是否显示此提示 */
|
|
817
|
+
visible?: boolean
|
|
818
|
+
/** 触发的事件名称 */
|
|
819
|
+
event?: string
|
|
820
|
+
/** 事件携带的数据 */
|
|
821
|
+
eventData?: Record<string, unknown>
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Footer hints 配置
|
|
826
|
+
* @description 控制 CoreBox 底部快捷键提示的显示和行为
|
|
827
|
+
*/
|
|
828
|
+
export interface TuffFooterHints {
|
|
829
|
+
/**
|
|
830
|
+
* 主操作提示(回车键)
|
|
831
|
+
* @description 自定义回车键的文案,如 "发送"、"执行"、"打开" 等
|
|
832
|
+
*/
|
|
833
|
+
primary?: {
|
|
834
|
+
/** 自定义标签文案 */
|
|
835
|
+
label?: string
|
|
836
|
+
/** 是否显示 */
|
|
837
|
+
visible?: boolean
|
|
838
|
+
}
|
|
839
|
+
|
|
840
|
+
/**
|
|
841
|
+
* 辅助操作提示(Meta+K)
|
|
842
|
+
* @description 控制 Meta+K 快捷键的显示和行为
|
|
843
|
+
*/
|
|
844
|
+
secondary?: {
|
|
845
|
+
/** 自定义标签文案 */
|
|
846
|
+
label?: string
|
|
847
|
+
/** 是否显示,默认 false(隐藏) */
|
|
848
|
+
visible?: boolean
|
|
849
|
+
/** 触发的事件名称 */
|
|
850
|
+
event?: string
|
|
851
|
+
/** 事件携带的数据 */
|
|
852
|
+
eventData?: Record<string, unknown>
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
/**
|
|
856
|
+
* 快速选择提示(Meta+1-0)
|
|
857
|
+
* @description 控制快速选择快捷键的显示
|
|
858
|
+
*/
|
|
859
|
+
quickSelect?: {
|
|
860
|
+
/** 是否显示,默认根据场景自动判断 */
|
|
861
|
+
visible?: boolean
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
806
865
|
// ==================== 扩展元数据 ====================
|
|
807
866
|
|
|
808
867
|
/**
|
|
@@ -1005,15 +1064,37 @@ export interface TuffMeta {
|
|
|
1005
1064
|
permissions?: string[]
|
|
1006
1065
|
}
|
|
1007
1066
|
|
|
1067
|
+
/** TuffIntelligence */
|
|
1068
|
+
intelligence?: any
|
|
1069
|
+
|
|
1070
|
+
/** Keep CoreBox open after action execution */
|
|
1071
|
+
keepCoreBoxOpen?: boolean
|
|
1072
|
+
|
|
1073
|
+
/** 固定配置 */
|
|
1074
|
+
pinned?: {
|
|
1075
|
+
isPinned: boolean
|
|
1076
|
+
pinnedAt?: number
|
|
1077
|
+
order?: number
|
|
1078
|
+
}
|
|
1079
|
+
|
|
1080
|
+
/** 推荐来源标记 */
|
|
1081
|
+
recommendation?: {
|
|
1082
|
+
source: 'frequent' | 'recent' | 'time-based' | 'trending' | 'pinned' | 'context'
|
|
1083
|
+
score?: number
|
|
1084
|
+
}
|
|
1085
|
+
|
|
1008
1086
|
/**
|
|
1009
|
-
*
|
|
1087
|
+
* 额外关键词
|
|
1088
|
+
* @description 用于搜索索引的额外关键词,会参与关键字索引匹配
|
|
1089
|
+
* @warning 不建议添加太多关键词(建议 <= 10),过多会影响搜索性能
|
|
1010
1090
|
*/
|
|
1011
|
-
|
|
1091
|
+
keywords?: string[]
|
|
1012
1092
|
|
|
1013
1093
|
/**
|
|
1014
|
-
*
|
|
1094
|
+
* Footer hints 配置
|
|
1095
|
+
* @description 控制 CoreBox 底部快捷键提示的显示和行为
|
|
1015
1096
|
*/
|
|
1016
|
-
|
|
1097
|
+
footerHints?: TuffFooterHints
|
|
1017
1098
|
}
|
|
1018
1099
|
|
|
1019
1100
|
// ==================== 前端展示结构 ====================
|
|
@@ -1267,71 +1348,69 @@ export interface SortStat {
|
|
|
1267
1348
|
duration: number
|
|
1268
1349
|
}
|
|
1269
1350
|
|
|
1270
|
-
/**
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
*/
|
|
1351
|
+
/** 容器布局配置 */
|
|
1352
|
+
export interface TuffContainerLayout {
|
|
1353
|
+
/** 布局模式 */
|
|
1354
|
+
mode: 'list' | 'grid'
|
|
1355
|
+
/** 宫格配置 */
|
|
1356
|
+
grid?: {
|
|
1357
|
+
/** 列数 */
|
|
1358
|
+
columns: number
|
|
1359
|
+
/** 间距(px) */
|
|
1360
|
+
gap?: number
|
|
1361
|
+
/** 单项尺寸 */
|
|
1362
|
+
itemSize?: 'small' | 'medium' | 'large'
|
|
1363
|
+
}
|
|
1364
|
+
/** 分组配置 */
|
|
1365
|
+
sections?: TuffSection[]
|
|
1366
|
+
}
|
|
1367
|
+
|
|
1368
|
+
/** 分组元数据 */
|
|
1369
|
+
export interface TuffSectionMeta {
|
|
1370
|
+
/** 是否为智能推荐分组 */
|
|
1371
|
+
intelligence?: boolean
|
|
1372
|
+
/** 扩展字段 */
|
|
1373
|
+
[key: string]: unknown
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
/** 分组定义 */
|
|
1377
|
+
export interface TuffSection {
|
|
1378
|
+
id: string
|
|
1379
|
+
title?: string
|
|
1380
|
+
layout: 'list' | 'grid'
|
|
1381
|
+
/** 该分组的 item ids */
|
|
1382
|
+
itemIds: string[]
|
|
1383
|
+
collapsed?: boolean
|
|
1384
|
+
/** 分组元数据 */
|
|
1385
|
+
meta?: TuffSectionMeta
|
|
1386
|
+
}
|
|
1387
|
+
|
|
1388
|
+
/** 搜索结果结构 */
|
|
1277
1389
|
export interface TuffSearchResult {
|
|
1278
|
-
/**
|
|
1279
|
-
* A unique identifier for this specific search operation.
|
|
1280
|
-
* This is crucial for the streaming model to associate updates with the correct search instance.
|
|
1281
|
-
*/
|
|
1390
|
+
/** 搜索会话 ID */
|
|
1282
1391
|
sessionId?: string
|
|
1283
|
-
|
|
1284
|
-
/**
|
|
1285
|
-
* 结果项目
|
|
1286
|
-
* @description 匹配的TuffItem列表
|
|
1287
|
-
* @required
|
|
1288
|
-
*/
|
|
1392
|
+
/** 结果项目 */
|
|
1289
1393
|
items: TuffItem[]
|
|
1290
1394
|
|
|
1291
|
-
/**
|
|
1292
|
-
* 查询信息
|
|
1293
|
-
* @description 原始查询参数
|
|
1294
|
-
* @required
|
|
1295
|
-
*/
|
|
1395
|
+
/** 原始查询参数 */
|
|
1296
1396
|
query: TuffQuery
|
|
1297
|
-
|
|
1298
|
-
/**
|
|
1299
|
-
* 搜索耗时
|
|
1300
|
-
* @description 搜索执行的毫秒数
|
|
1301
|
-
* @required
|
|
1302
|
-
*/
|
|
1397
|
+
/** 搜索耗时(ms) */
|
|
1303
1398
|
duration: number
|
|
1304
|
-
|
|
1305
|
-
/**
|
|
1306
|
-
* 来源统计
|
|
1307
|
-
* @description 各数据来源的结果统计
|
|
1308
|
-
* @required
|
|
1309
|
-
*/
|
|
1399
|
+
/** 来源统计 */
|
|
1310
1400
|
sources: Array<{
|
|
1311
|
-
/** Provider's unique ID. */
|
|
1312
1401
|
providerId: string
|
|
1313
|
-
/** Provider's display name. */
|
|
1314
1402
|
providerName: string
|
|
1315
|
-
/** Search duration in milliseconds. */
|
|
1316
1403
|
duration: number
|
|
1317
|
-
/** Number of results returned. */
|
|
1318
1404
|
resultCount: number
|
|
1319
|
-
/** Status of the search operation. */
|
|
1320
1405
|
status: 'success' | 'timeout' | 'error'
|
|
1321
1406
|
}>
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
* @description AI生成的搜索建议
|
|
1326
|
-
*/
|
|
1407
|
+
/** 容器布局配置 */
|
|
1408
|
+
containerLayout?: TuffContainerLayout
|
|
1409
|
+
/** AI 推荐建议 */
|
|
1327
1410
|
suggestions?: string[]
|
|
1328
|
-
|
|
1329
|
-
/**
|
|
1330
|
-
* The provider(s) to activate after this search result.
|
|
1331
|
-
*/
|
|
1411
|
+
/** 激活的 provider */
|
|
1332
1412
|
activate?: IProviderActivate[]
|
|
1333
|
-
|
|
1334
|
-
/** Optional statistics about the sorting process. */
|
|
1413
|
+
/** 排序统计 */
|
|
1335
1414
|
sort_stats?: SortStat[]
|
|
1336
1415
|
}
|
|
1337
1416
|
|
package/intelligence/client.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { IntelligenceInvokeOptions, IntelligenceInvokeResult, IntelligenceProviderConfig } from '../types/intelligence'
|
|
2
2
|
|
|
3
3
|
export interface IntelligenceClientChannel {
|
|
4
4
|
send: (eventName: string, payload: unknown) => Promise<any>
|
|
@@ -50,10 +50,10 @@ async function assertResponse<T>(promise: Promise<ChannelResponse<T>>): Promise<
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
export interface IntelligenceClient {
|
|
53
|
-
invoke: <T = any>(capabilityId: string, payload: any, options?:
|
|
54
|
-
testProvider: (config:
|
|
53
|
+
invoke: <T = any>(capabilityId: string, payload: any, options?: IntelligenceInvokeOptions) => Promise<IntelligenceInvokeResult<T>>
|
|
54
|
+
testProvider: (config: IntelligenceProviderConfig) => Promise<unknown>
|
|
55
55
|
testCapability: (params: Record<string, any>) => Promise<unknown>
|
|
56
|
-
fetchModels: (config:
|
|
56
|
+
fetchModels: (config: IntelligenceProviderConfig) => Promise<{ success: boolean, models?: string[], message?: string }>
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export function createIntelligenceClient(channel?: IntelligenceClientChannel, resolvers?: IntelligenceChannelResolver[]): IntelligenceClient {
|
|
@@ -63,12 +63,12 @@ export function createIntelligenceClient(channel?: IntelligenceClientChannel, re
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
return {
|
|
66
|
-
invoke<T = any>(capabilityId: string, payload: any, options?:
|
|
67
|
-
return assertResponse<
|
|
66
|
+
invoke<T = any>(capabilityId: string, payload: any, options?: IntelligenceInvokeOptions) {
|
|
67
|
+
return assertResponse<IntelligenceInvokeResult<T>>(
|
|
68
68
|
resolvedChannel.send('intelligence:invoke', { capabilityId, payload, options }),
|
|
69
69
|
)
|
|
70
70
|
},
|
|
71
|
-
testProvider(config:
|
|
71
|
+
testProvider(config: IntelligenceProviderConfig) {
|
|
72
72
|
return assertResponse(
|
|
73
73
|
resolvedChannel.send('intelligence:test-provider', { provider: config }),
|
|
74
74
|
)
|
|
@@ -78,7 +78,7 @@ export function createIntelligenceClient(channel?: IntelligenceClientChannel, re
|
|
|
78
78
|
resolvedChannel.send('intelligence:test-capability', params),
|
|
79
79
|
)
|
|
80
80
|
},
|
|
81
|
-
fetchModels(config:
|
|
81
|
+
fetchModels(config: IntelligenceProviderConfig) {
|
|
82
82
|
return assertResponse<{ success: boolean, models?: string[], message?: string }>(
|
|
83
83
|
resolvedChannel.send('intelligence:fetch-models', { provider: config }),
|
|
84
84
|
)
|
package/market/constants.ts
CHANGED
|
@@ -21,6 +21,20 @@ function defineProvider(
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export const DEFAULT_MARKET_PROVIDERS: MarketProviderDefinition[] = [
|
|
24
|
+
defineProvider({
|
|
25
|
+
id: 'tuff-nexus',
|
|
26
|
+
name: 'Tuff Nexus',
|
|
27
|
+
type: 'tpexApi',
|
|
28
|
+
url: 'https://tuff.tagzxia.com',
|
|
29
|
+
description: 'Tuff 官方插件市场,提供经过审核的插件。',
|
|
30
|
+
enabled: true,
|
|
31
|
+
priority: 110,
|
|
32
|
+
trustLevel: 'official',
|
|
33
|
+
readOnly: true,
|
|
34
|
+
config: {
|
|
35
|
+
apiUrl: 'https://tuff.tagzxia.com/api/market/plugins',
|
|
36
|
+
},
|
|
37
|
+
}),
|
|
24
38
|
defineProvider({
|
|
25
39
|
id: 'talex-official',
|
|
26
40
|
name: 'Talex Official',
|
package/market/types.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { StorageList } from '../common/storage/constants'
|
|
2
2
|
|
|
3
|
-
export type MarketProviderType = 'repository' | 'nexusStore' | 'npmPackage'
|
|
3
|
+
export type MarketProviderType = 'repository' | 'nexusStore' | 'npmPackage' | 'tpexApi'
|
|
4
4
|
|
|
5
5
|
export type MarketProviderTrustLevel = 'official' | 'verified' | 'unverified'
|
|
6
6
|
|
package/package.json
CHANGED
package/plugin/index.ts
CHANGED
|
@@ -134,7 +134,8 @@ export interface ITouchPlugin extends IPluginBaseInfo {
|
|
|
134
134
|
export interface IFeatureCommand {
|
|
135
135
|
type: 'match' | 'contain' | 'regex' | 'function' | 'over' | 'image' | 'files' | 'directory' | 'window'
|
|
136
136
|
value: string | string[] | RegExp | Function
|
|
137
|
-
|
|
137
|
+
/** Optional trigger callback - not serialized over IPC */
|
|
138
|
+
onTrigger?: () => void
|
|
138
139
|
}
|
|
139
140
|
|
|
140
141
|
export interface IPluginFeature {
|
|
@@ -142,10 +143,15 @@ export interface IPluginFeature {
|
|
|
142
143
|
name: string
|
|
143
144
|
desc: string
|
|
144
145
|
icon: ITuffIcon
|
|
146
|
+
keywords?: string[]
|
|
145
147
|
push: boolean
|
|
146
148
|
platform: IPlatform
|
|
147
149
|
commands: IFeatureCommand[]
|
|
148
150
|
interaction?: IFeatureInteraction
|
|
151
|
+
/**
|
|
152
|
+
* Internal search tokens generated at runtime for better matching
|
|
153
|
+
*/
|
|
154
|
+
searchTokens?: string[]
|
|
149
155
|
/**
|
|
150
156
|
* Priority of the feature for sorting in search results
|
|
151
157
|
* Higher numbers have higher priority (displayed first)
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import type { NpmPackageInfo } from './npm-provider'
|
|
2
|
+
import type { TpexPluginInfo } from './tpex-provider'
|
|
3
|
+
import { NpmProvider } from './npm-provider'
|
|
4
|
+
import { TpexProvider } from './tpex-provider'
|
|
5
|
+
import { PluginProviderType } from './types'
|
|
6
|
+
|
|
7
|
+
export type PluginSourceType = 'tpex' | 'npm' | 'all'
|
|
8
|
+
|
|
9
|
+
export interface MarketPluginInfo {
|
|
10
|
+
id: string
|
|
11
|
+
name: string
|
|
12
|
+
slug: string
|
|
13
|
+
version: string
|
|
14
|
+
description: string
|
|
15
|
+
author: string
|
|
16
|
+
icon?: string
|
|
17
|
+
source: PluginSourceType
|
|
18
|
+
isOfficial: boolean
|
|
19
|
+
downloads?: number
|
|
20
|
+
category?: string
|
|
21
|
+
keywords?: string[]
|
|
22
|
+
homepage?: string
|
|
23
|
+
packageUrl?: string
|
|
24
|
+
raw: TpexPluginInfo | NpmPackageInfo
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface MarketSearchOptions {
|
|
28
|
+
keyword?: string
|
|
29
|
+
source?: PluginSourceType
|
|
30
|
+
category?: string
|
|
31
|
+
limit?: number
|
|
32
|
+
offset?: number
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface MarketSearchResult {
|
|
36
|
+
plugins: MarketPluginInfo[]
|
|
37
|
+
total: number
|
|
38
|
+
sources: {
|
|
39
|
+
tpex: number
|
|
40
|
+
npm: number
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function normalizeTpexPlugin(plugin: TpexPluginInfo): MarketPluginInfo {
|
|
45
|
+
return {
|
|
46
|
+
id: plugin.id,
|
|
47
|
+
name: plugin.name,
|
|
48
|
+
slug: plugin.slug,
|
|
49
|
+
version: plugin.latestVersion?.version ?? '0.0.0',
|
|
50
|
+
description: plugin.summary,
|
|
51
|
+
author: plugin.author?.name ?? 'Unknown',
|
|
52
|
+
icon: plugin.iconUrl ?? undefined,
|
|
53
|
+
source: 'tpex',
|
|
54
|
+
isOfficial: plugin.isOfficial,
|
|
55
|
+
downloads: plugin.installs,
|
|
56
|
+
category: plugin.category,
|
|
57
|
+
homepage: plugin.homepage ?? undefined,
|
|
58
|
+
packageUrl: plugin.latestVersion?.packageUrl,
|
|
59
|
+
raw: plugin,
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function normalizeNpmPlugin(pkg: NpmPackageInfo): MarketPluginInfo {
|
|
64
|
+
const authorName = typeof pkg.author === 'string'
|
|
65
|
+
? pkg.author
|
|
66
|
+
: pkg.author?.name ?? 'Unknown'
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
id: pkg.name,
|
|
70
|
+
name: pkg.name.replace(/^tuff-plugin-/, '').replace(/^@tuff\//, ''),
|
|
71
|
+
slug: pkg.name,
|
|
72
|
+
version: pkg.version,
|
|
73
|
+
description: pkg.description ?? '',
|
|
74
|
+
author: authorName,
|
|
75
|
+
icon: pkg.tuff?.icon,
|
|
76
|
+
source: 'npm',
|
|
77
|
+
isOfficial: pkg.name.startsWith('@tuff/'),
|
|
78
|
+
keywords: pkg.keywords,
|
|
79
|
+
packageUrl: pkg.dist.tarball,
|
|
80
|
+
raw: pkg,
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Unified plugin market client supporting multiple sources
|
|
86
|
+
*/
|
|
87
|
+
export class PluginMarketClient {
|
|
88
|
+
private tpexProvider: TpexProvider
|
|
89
|
+
private npmProvider: NpmProvider
|
|
90
|
+
|
|
91
|
+
constructor(options?: {
|
|
92
|
+
tpexApiBase?: string
|
|
93
|
+
npmRegistry?: string
|
|
94
|
+
}) {
|
|
95
|
+
this.tpexProvider = new TpexProvider(options?.tpexApiBase)
|
|
96
|
+
this.npmProvider = new NpmProvider(options?.npmRegistry)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Search plugins from all sources
|
|
101
|
+
*/
|
|
102
|
+
async search(options: MarketSearchOptions = {}): Promise<MarketSearchResult> {
|
|
103
|
+
const { keyword, source = 'all', limit = 50, offset = 0 } = options
|
|
104
|
+
const results: MarketPluginInfo[] = []
|
|
105
|
+
let tpexCount = 0
|
|
106
|
+
let npmCount = 0
|
|
107
|
+
|
|
108
|
+
if (source === 'all' || source === 'tpex') {
|
|
109
|
+
try {
|
|
110
|
+
const tpexPlugins = keyword
|
|
111
|
+
? await this.tpexProvider.searchPlugins(keyword)
|
|
112
|
+
: await this.tpexProvider.listPlugins()
|
|
113
|
+
|
|
114
|
+
const normalized = tpexPlugins.map(normalizeTpexPlugin)
|
|
115
|
+
results.push(...normalized)
|
|
116
|
+
tpexCount = normalized.length
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.warn('[MarketClient] TPEX search failed:', error)
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (source === 'all' || source === 'npm') {
|
|
124
|
+
try {
|
|
125
|
+
const npmPlugins = await this.npmProvider.searchPlugins(keyword)
|
|
126
|
+
const normalized = npmPlugins.map(normalizeNpmPlugin)
|
|
127
|
+
results.push(...normalized)
|
|
128
|
+
npmCount = normalized.length
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
console.warn('[MarketClient] NPM search failed:', error)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const sorted = results.sort((a, b) => {
|
|
136
|
+
if (a.isOfficial !== b.isOfficial) return a.isOfficial ? -1 : 1
|
|
137
|
+
return (b.downloads ?? 0) - (a.downloads ?? 0)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
const paginated = sorted.slice(offset, offset + limit)
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
plugins: paginated,
|
|
144
|
+
total: results.length,
|
|
145
|
+
sources: {
|
|
146
|
+
tpex: tpexCount,
|
|
147
|
+
npm: npmCount,
|
|
148
|
+
},
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Get plugin details by identifier
|
|
154
|
+
*/
|
|
155
|
+
async getPlugin(identifier: string, source?: PluginSourceType): Promise<MarketPluginInfo | null> {
|
|
156
|
+
if (source === 'tpex' || (!source && !identifier.includes('/'))) {
|
|
157
|
+
try {
|
|
158
|
+
const plugin = await this.tpexProvider.getPlugin(identifier)
|
|
159
|
+
if (plugin) return normalizeTpexPlugin(plugin)
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Fall through to npm
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (source === 'npm' || !source) {
|
|
167
|
+
try {
|
|
168
|
+
const pkg = await this.npmProvider.getPackageInfo(identifier)
|
|
169
|
+
if (pkg) return normalizeNpmPlugin(pkg)
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
// Not found
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return null
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Get install source string for a plugin
|
|
181
|
+
*/
|
|
182
|
+
getInstallSource(plugin: MarketPluginInfo): string {
|
|
183
|
+
if (plugin.source === 'tpex') {
|
|
184
|
+
return `tpex:${plugin.slug}`
|
|
185
|
+
}
|
|
186
|
+
return `npm:${plugin.id}`
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* Get provider type for a plugin
|
|
191
|
+
*/
|
|
192
|
+
getProviderType(plugin: MarketPluginInfo): PluginProviderType {
|
|
193
|
+
return plugin.source === 'tpex'
|
|
194
|
+
? PluginProviderType.TPEX
|
|
195
|
+
: PluginProviderType.NPM
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* List all plugins from official source (TPEX)
|
|
200
|
+
*/
|
|
201
|
+
async listOfficialPlugins(): Promise<MarketPluginInfo[]> {
|
|
202
|
+
const plugins = await this.tpexProvider.listPlugins()
|
|
203
|
+
return plugins.map(normalizeTpexPlugin)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* List all plugins from npm
|
|
208
|
+
*/
|
|
209
|
+
async listNpmPlugins(): Promise<MarketPluginInfo[]> {
|
|
210
|
+
const plugins = await this.npmProvider.listPlugins()
|
|
211
|
+
return plugins.map(normalizeNpmPlugin)
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export const defaultMarketClient = new PluginMarketClient()
|