@talex-touch/utils 1.0.40 → 1.0.44
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/.eslintcache +1 -0
- package/__tests__/cloud-sync-sdk.test.ts +442 -0
- package/__tests__/icons/icons.test.ts +84 -0
- package/__tests__/plugin-sdk-lifecycle.test.ts +130 -0
- package/__tests__/power-sdk.test.ts +143 -0
- package/__tests__/preset-export-types.test.ts +108 -0
- package/__tests__/search/fuzzy-match.test.ts +137 -0
- package/__tests__/transport/port-policy.test.ts +44 -0
- package/__tests__/transport-domain-sdks.test.ts +152 -0
- package/__tests__/types/update.test.ts +67 -0
- package/account/account-sdk.ts +915 -0
- package/account/index.ts +2 -0
- package/account/types.ts +321 -0
- package/analytics/client.ts +136 -0
- package/analytics/index.ts +2 -0
- package/analytics/types.ts +156 -0
- package/animation/auto-resize.ts +322 -0
- package/animation/window-node.ts +26 -19
- package/auth/clerk-types.ts +12 -30
- package/auth/index.ts +0 -2
- package/auth/useAuthState.ts +6 -14
- package/base/index.ts +2 -0
- package/base/log-level.ts +105 -0
- package/channel/index.ts +170 -69
- package/cloud-sync/cloud-sync-sdk.ts +450 -0
- package/cloud-sync/index.ts +1 -0
- package/common/file-scan-utils.ts +17 -9
- package/common/index.ts +4 -0
- package/common/logger/index.ts +46 -0
- package/common/logger/logger-manager.ts +303 -0
- package/common/logger/module-logger.ts +270 -0
- package/common/logger/transport-logger.ts +234 -0
- package/common/logger/types.ts +93 -0
- package/common/search/gather.ts +48 -6
- package/common/search/index.ts +8 -0
- package/common/storage/constants.ts +13 -0
- package/common/storage/entity/app-settings.ts +245 -0
- package/common/storage/entity/index.ts +3 -0
- package/common/storage/entity/layout-atom-types.ts +147 -0
- package/common/storage/entity/openers.ts +1 -0
- package/common/storage/entity/preset-cloud-api.ts +132 -0
- package/common/storage/entity/preset-export-types.ts +256 -0
- package/common/storage/entity/shortcut-settings.ts +1 -0
- package/common/storage/shortcut-storage.ts +11 -0
- package/common/utils/clone-diagnostics.ts +105 -0
- package/common/utils/file.ts +16 -8
- package/common/utils/index.ts +6 -2
- package/common/utils/payload-preview.ts +173 -0
- package/common/utils/polling.ts +167 -13
- package/common/utils/safe-path.ts +103 -0
- package/common/utils/safe-shell.ts +115 -0
- package/common/utils/task-queue.ts +4 -1
- package/core-box/builder/tuff-builder.ts +0 -1
- package/core-box/index.ts +1 -1
- package/core-box/recommendation.ts +38 -1
- package/core-box/tuff/tuff-dsl.ts +97 -0
- package/electron/download-manager.ts +10 -7
- package/electron/env-tool.ts +42 -40
- package/electron/index.ts +0 -1
- package/env/index.ts +156 -0
- package/eslint.config.js +55 -0
- package/i18n/index.ts +62 -0
- package/i18n/locales/en.json +226 -0
- package/i18n/locales/zh.json +226 -0
- package/i18n/message-keys.ts +236 -0
- package/i18n/resolver.ts +181 -0
- package/icons/index.ts +257 -0
- package/icons/svg.ts +69 -0
- package/index.ts +9 -1
- package/intelligence/client.ts +72 -42
- package/market/constants.ts +21 -3
- package/market/index.ts +1 -1
- package/market/types.ts +20 -5
- package/package.json +15 -5
- package/permission/index.ts +143 -46
- package/permission/legacy.ts +26 -0
- package/permission/registry.ts +304 -0
- package/permission/types.ts +164 -0
- package/plugin/channel.ts +68 -39
- package/plugin/index.ts +82 -8
- package/plugin/install.ts +3 -0
- package/plugin/log/types.ts +22 -5
- package/plugin/node/logger-manager.ts +11 -3
- package/plugin/node/logger.ts +24 -17
- package/plugin/preload.ts +25 -2
- package/plugin/providers/index.ts +4 -0
- package/plugin/providers/market-client.ts +218 -0
- package/plugin/providers/npm-provider.ts +228 -0
- package/plugin/providers/tpex-provider.ts +297 -0
- package/plugin/providers/tpex-types.ts +34 -0
- package/plugin/sdk/box-items.ts +14 -0
- package/plugin/sdk/box-sdk.ts +64 -0
- package/plugin/sdk/channel.ts +119 -4
- package/plugin/sdk/clipboard.ts +26 -12
- package/plugin/sdk/cloud-sync.ts +113 -0
- package/plugin/sdk/common.ts +19 -11
- package/plugin/sdk/core-box.ts +6 -15
- package/plugin/sdk/division-box.ts +160 -65
- package/plugin/sdk/examples/storage-onDidChange-example.js +5 -2
- package/plugin/sdk/feature-sdk.ts +111 -76
- package/plugin/sdk/flow.ts +146 -45
- package/plugin/sdk/hooks/bridge.ts +113 -49
- package/plugin/sdk/hooks/life-cycle.ts +35 -16
- package/plugin/sdk/index.ts +14 -3
- package/plugin/sdk/intelligence.ts +87 -0
- package/plugin/sdk/meta/README.md +179 -0
- package/plugin/sdk/meta-sdk.ts +244 -0
- package/plugin/sdk/notification.ts +9 -0
- package/plugin/sdk/performance.ts +1 -16
- package/plugin/sdk/plugin-info.ts +64 -0
- package/plugin/sdk/power.ts +155 -0
- package/plugin/sdk/recommend.ts +21 -0
- package/plugin/sdk/service/index.ts +12 -8
- package/plugin/sdk/sqlite.ts +141 -0
- package/plugin/sdk/storage.ts +2 -6
- package/plugin/sdk/system.ts +2 -9
- package/plugin/sdk/temp-files.ts +41 -0
- package/plugin/sdk/touch-sdk.ts +18 -0
- package/plugin/sdk/types.ts +44 -4
- package/plugin/sdk/window/index.ts +12 -9
- package/plugin/sdk-version.ts +231 -0
- package/preload/renderer.ts +3 -2
- package/renderer/hooks/arg-mapper.ts +34 -6
- package/renderer/hooks/index.ts +13 -0
- package/renderer/hooks/initialize.ts +2 -1
- package/renderer/hooks/use-agent-market-sdk.ts +7 -0
- package/renderer/hooks/use-agent-market.ts +106 -0
- package/renderer/hooks/use-agents-sdk.ts +7 -0
- package/renderer/hooks/use-app-sdk.ts +7 -0
- package/renderer/hooks/use-channel.ts +33 -4
- package/renderer/hooks/use-download-sdk.ts +21 -0
- package/renderer/hooks/use-intelligence-sdk.ts +7 -0
- package/renderer/hooks/use-intelligence-stats.ts +290 -0
- package/renderer/hooks/use-intelligence.ts +202 -104
- package/renderer/hooks/use-market-sdk.ts +16 -0
- package/renderer/hooks/use-notification-sdk.ts +7 -0
- package/renderer/hooks/use-permission-sdk.ts +7 -0
- package/renderer/hooks/use-permission.ts +325 -0
- package/renderer/hooks/use-platform-sdk.ts +7 -0
- package/renderer/hooks/use-plugin-sdk.ts +16 -0
- package/renderer/hooks/use-settings-sdk.ts +7 -0
- package/renderer/hooks/use-update-sdk.ts +21 -0
- package/renderer/index.ts +1 -0
- package/renderer/ref.ts +19 -10
- package/renderer/shared/components/SharedPluginDetailContent.vue +84 -0
- package/renderer/shared/components/SharedPluginDetailHeader.vue +116 -0
- package/renderer/shared/components/SharedPluginDetailMetaList.vue +39 -0
- package/renderer/shared/components/SharedPluginDetailReadme.vue +45 -0
- package/renderer/shared/components/SharedPluginDetailVersions.vue +98 -0
- package/renderer/shared/components/index.ts +5 -0
- package/renderer/shared/components/shims-vue.d.ts +5 -0
- package/renderer/shared/index.ts +2 -0
- package/renderer/shared/plugin-detail.ts +62 -0
- package/renderer/storage/app-settings.ts +3 -1
- package/renderer/storage/base-storage.ts +508 -82
- package/renderer/storage/intelligence-storage.ts +37 -46
- package/renderer/storage/openers.ts +3 -1
- package/renderer/storage/storage-subscription.ts +126 -42
- package/renderer/touch-sdk/env.ts +10 -10
- package/renderer/touch-sdk/index.ts +114 -18
- package/renderer/touch-sdk/terminal.ts +24 -13
- package/search/feature-matcher.ts +279 -0
- package/search/fuzzy-match.ts +64 -34
- package/search/index.ts +10 -0
- package/search/levenshtein-utils.ts +17 -11
- package/transport/errors.ts +310 -0
- package/transport/event/builder.ts +378 -0
- package/transport/event/index.ts +7 -0
- package/transport/event/types.ts +292 -0
- package/transport/events/index.ts +2670 -0
- package/transport/events/meta-overlay.ts +79 -0
- package/transport/events/types/agents.ts +177 -0
- package/transport/events/types/app-index.ts +9 -0
- package/transport/events/types/app.ts +475 -0
- package/transport/events/types/box-item.ts +222 -0
- package/transport/events/types/clipboard.ts +80 -0
- package/transport/events/types/core-box.ts +534 -0
- package/transport/events/types/device-idle.ts +7 -0
- package/transport/events/types/division-box.ts +99 -0
- package/transport/events/types/download.ts +115 -0
- package/transport/events/types/file-index.ts +73 -0
- package/transport/events/types/flow.ts +149 -0
- package/transport/events/types/index.ts +70 -0
- package/transport/events/types/market.ts +39 -0
- package/transport/events/types/meta-overlay.ts +184 -0
- package/transport/events/types/notification.ts +140 -0
- package/transport/events/types/permission.ts +90 -0
- package/transport/events/types/platform.ts +8 -0
- package/transport/events/types/plugin.ts +620 -0
- package/transport/events/types/sentry.ts +20 -0
- package/transport/events/types/storage.ts +208 -0
- package/transport/events/types/transport.ts +60 -0
- package/transport/events/types/tray.ts +16 -0
- package/transport/events/types/update.ts +78 -0
- package/transport/index.ts +139 -0
- package/transport/main.ts +2 -0
- package/transport/sdk/constants.ts +29 -0
- package/transport/sdk/domains/agents-market.ts +47 -0
- package/transport/sdk/domains/agents.ts +62 -0
- package/transport/sdk/domains/app.ts +48 -0
- package/transport/sdk/domains/disposable.ts +35 -0
- package/transport/sdk/domains/download.ts +139 -0
- package/transport/sdk/domains/index.ts +13 -0
- package/transport/sdk/domains/intelligence.ts +616 -0
- package/transport/sdk/domains/market.ts +35 -0
- package/transport/sdk/domains/notification.ts +62 -0
- package/transport/sdk/domains/permission.ts +85 -0
- package/transport/sdk/domains/platform.ts +19 -0
- package/transport/sdk/domains/plugin.ts +144 -0
- package/transport/sdk/domains/settings.ts +92 -0
- package/transport/sdk/domains/update.ts +64 -0
- package/transport/sdk/index.ts +60 -0
- package/transport/sdk/main-transport.ts +710 -0
- package/transport/sdk/main.ts +9 -0
- package/transport/sdk/plugin-transport.ts +654 -0
- package/transport/sdk/port-policy.ts +38 -0
- package/transport/sdk/renderer-transport.ts +1165 -0
- package/transport/types.ts +605 -0
- package/types/agent.ts +399 -0
- package/types/cloud-sync.ts +157 -0
- package/types/division-box.ts +47 -27
- package/types/download.ts +1 -0
- package/types/flow.ts +63 -12
- package/types/icon.ts +2 -1
- package/types/index.ts +5 -0
- package/types/intelligence.ts +1492 -81
- package/types/modules/base.ts +2 -0
- package/types/path-browserify.d.ts +5 -0
- package/types/platform.ts +12 -0
- package/types/startup-info.ts +32 -0
- package/types/touch-app-core.ts +8 -8
- package/types/update.ts +94 -1
- package/vitest.config.ts +25 -0
- package/auth/useClerkConfig.ts +0 -40
- package/auth/useClerkProvider.ts +0 -52
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import type { IManifest } from '..'
|
|
2
|
+
import type {
|
|
3
|
+
PluginInstallRequest,
|
|
4
|
+
PluginInstallResult,
|
|
5
|
+
PluginProvider,
|
|
6
|
+
PluginProviderContext,
|
|
7
|
+
} from './types'
|
|
8
|
+
import { Buffer } from 'node:buffer'
|
|
9
|
+
import process from 'node:process'
|
|
10
|
+
import { PluginProviderType } from './types'
|
|
11
|
+
|
|
12
|
+
const NPM_REGISTRY = 'https://registry.npmjs.org'
|
|
13
|
+
const TUFF_PLUGIN_PREFIX = 'tuff-plugin-'
|
|
14
|
+
const TUFF_PLUGIN_SCOPE = '@tuff/'
|
|
15
|
+
|
|
16
|
+
export interface NpmPackageInfo {
|
|
17
|
+
name: string
|
|
18
|
+
version: string
|
|
19
|
+
description?: string
|
|
20
|
+
author?: string | { name: string, email?: string }
|
|
21
|
+
main?: string
|
|
22
|
+
keywords?: string[]
|
|
23
|
+
dist: {
|
|
24
|
+
tarball: string
|
|
25
|
+
shasum: string
|
|
26
|
+
integrity?: string
|
|
27
|
+
}
|
|
28
|
+
tuff?: {
|
|
29
|
+
icon?: string
|
|
30
|
+
activationKeywords?: string[]
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface NpmPackageVersions {
|
|
35
|
+
'name': string
|
|
36
|
+
'dist-tags': {
|
|
37
|
+
latest: string
|
|
38
|
+
[tag: string]: string
|
|
39
|
+
}
|
|
40
|
+
'versions': Record<string, NpmPackageInfo>
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Parse NPM source string to extract package name and optional version
|
|
45
|
+
* Formats:
|
|
46
|
+
* - "npm:package-name"
|
|
47
|
+
* - "npm:package-name@version"
|
|
48
|
+
* - "npm:@scope/package-name"
|
|
49
|
+
* - "npm:@scope/package-name@version"
|
|
50
|
+
* - "tuff-plugin-xxx" (when hintType is NPM)
|
|
51
|
+
* - "@tuff/xxx" (when hintType is NPM)
|
|
52
|
+
*/
|
|
53
|
+
function parseNpmSource(source: string): { packageName: string, version?: string } | null {
|
|
54
|
+
const npmMatch = source.match(/^npm:(@?[a-z0-9][\w\-.]*(?:\/[a-z0-9][\w\-.]*)?)(?:@(.+))?$/i)
|
|
55
|
+
if (npmMatch) {
|
|
56
|
+
const packageName = npmMatch[1]
|
|
57
|
+
if (!packageName)
|
|
58
|
+
return null
|
|
59
|
+
const version = npmMatch[2]
|
|
60
|
+
return version ? { packageName, version } : { packageName }
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const scopedMatch = source.match(/^(@tuff\/[a-z0-9][\w\-.]*)(?:@(.+))?$/i)
|
|
64
|
+
if (scopedMatch) {
|
|
65
|
+
const packageName = scopedMatch[1]
|
|
66
|
+
if (!packageName)
|
|
67
|
+
return null
|
|
68
|
+
const version = scopedMatch[2]
|
|
69
|
+
return version ? { packageName, version } : { packageName }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const prefixMatch = source.match(/^(tuff-plugin-[a-z0-9][\w\-.]*)(?:@(.+))?$/i)
|
|
73
|
+
if (prefixMatch) {
|
|
74
|
+
const packageName = prefixMatch[1]
|
|
75
|
+
if (!packageName)
|
|
76
|
+
return null
|
|
77
|
+
const version = prefixMatch[2]
|
|
78
|
+
return version ? { packageName, version } : { packageName }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Check if a package name looks like a Tuff plugin
|
|
86
|
+
*/
|
|
87
|
+
function isTuffPluginPackage(name: string): boolean {
|
|
88
|
+
return name.startsWith(TUFF_PLUGIN_PREFIX) || name.startsWith(TUFF_PLUGIN_SCOPE)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export class NpmProvider implements PluginProvider {
|
|
92
|
+
readonly type = PluginProviderType.NPM
|
|
93
|
+
private registry: string
|
|
94
|
+
|
|
95
|
+
constructor(registry: string = NPM_REGISTRY) {
|
|
96
|
+
this.registry = registry.replace(/\/$/, '')
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
canHandle(request: PluginInstallRequest): boolean {
|
|
100
|
+
if (request.hintType === PluginProviderType.NPM) {
|
|
101
|
+
return parseNpmSource(request.source) !== null
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (request.source.startsWith('npm:')) {
|
|
105
|
+
return true
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const parsed = parseNpmSource(request.source)
|
|
109
|
+
return parsed !== null && isTuffPluginPackage(parsed.packageName)
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
async install(
|
|
113
|
+
request: PluginInstallRequest,
|
|
114
|
+
context?: PluginProviderContext,
|
|
115
|
+
): Promise<PluginInstallResult> {
|
|
116
|
+
const parsed = parseNpmSource(request.source)
|
|
117
|
+
if (!parsed) {
|
|
118
|
+
throw new Error(`Invalid NPM source format: ${request.source}`)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const { packageName, version } = parsed
|
|
122
|
+
const packageInfo = await this.getPackageInfo(packageName, version)
|
|
123
|
+
|
|
124
|
+
if (!packageInfo) {
|
|
125
|
+
throw new Error(`Package not found: ${packageName}`)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
const tarballUrl = packageInfo.dist.tarball
|
|
129
|
+
const downloadRes = await fetch(tarballUrl)
|
|
130
|
+
if (!downloadRes.ok) {
|
|
131
|
+
throw new Error(`Failed to download package: ${downloadRes.statusText}`)
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const arrayBuffer = await downloadRes.arrayBuffer()
|
|
135
|
+
const tempDir = context?.tempDir ?? '/tmp'
|
|
136
|
+
const safePackageName = packageName.replace(/[@/]/g, '-')
|
|
137
|
+
const fileName = `${safePackageName}-${packageInfo.version}.tgz`
|
|
138
|
+
const filePath = `${tempDir}/${fileName}`
|
|
139
|
+
|
|
140
|
+
if (typeof process !== 'undefined') {
|
|
141
|
+
const fs = await import('node:fs/promises')
|
|
142
|
+
await fs.writeFile(filePath, Buffer.from(arrayBuffer))
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const authorName = typeof packageInfo.author === 'string'
|
|
146
|
+
? packageInfo.author
|
|
147
|
+
: packageInfo.author?.name ?? 'Unknown'
|
|
148
|
+
|
|
149
|
+
const manifest: IManifest = {
|
|
150
|
+
id: packageInfo.name,
|
|
151
|
+
name: packageInfo.name.replace(TUFF_PLUGIN_PREFIX, '').replace(TUFF_PLUGIN_SCOPE, ''),
|
|
152
|
+
version: packageInfo.version,
|
|
153
|
+
description: packageInfo.description ?? '',
|
|
154
|
+
author: authorName,
|
|
155
|
+
main: packageInfo.main ?? 'index.js',
|
|
156
|
+
icon: packageInfo.tuff?.icon,
|
|
157
|
+
activationKeywords: packageInfo.tuff?.activationKeywords ?? packageInfo.keywords,
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
provider: PluginProviderType.NPM,
|
|
162
|
+
filePath,
|
|
163
|
+
official: packageName.startsWith(TUFF_PLUGIN_SCOPE),
|
|
164
|
+
manifest,
|
|
165
|
+
metadata: {
|
|
166
|
+
packageName: packageInfo.name,
|
|
167
|
+
version: packageInfo.version,
|
|
168
|
+
tarball: tarballUrl,
|
|
169
|
+
shasum: packageInfo.dist.shasum,
|
|
170
|
+
integrity: packageInfo.dist.integrity,
|
|
171
|
+
},
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Get package info from npm registry
|
|
177
|
+
*/
|
|
178
|
+
async getPackageInfo(packageName: string, version?: string): Promise<NpmPackageInfo | null> {
|
|
179
|
+
const encodedName = encodeURIComponent(packageName).replace('%40', '@')
|
|
180
|
+
const res = await fetch(`${this.registry}/${encodedName}`)
|
|
181
|
+
|
|
182
|
+
if (!res.ok) {
|
|
183
|
+
if (res.status === 404)
|
|
184
|
+
return null
|
|
185
|
+
throw new Error(`Failed to fetch package info: ${res.statusText}`)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const data: NpmPackageVersions = await res.json()
|
|
189
|
+
const targetVersion = version ?? data['dist-tags'].latest
|
|
190
|
+
|
|
191
|
+
return data.versions[targetVersion] ?? null
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Search for Tuff plugins on npm
|
|
196
|
+
*/
|
|
197
|
+
async searchPlugins(keyword?: string): Promise<NpmPackageInfo[]> {
|
|
198
|
+
const searchTerms = [
|
|
199
|
+
`keywords:tuff-plugin`,
|
|
200
|
+
keyword ? `${keyword}` : '',
|
|
201
|
+
].filter(Boolean).join('+')
|
|
202
|
+
|
|
203
|
+
const res = await fetch(
|
|
204
|
+
`${this.registry}/-/v1/search?text=${encodeURIComponent(searchTerms)}&size=100`,
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
if (!res.ok) {
|
|
208
|
+
throw new Error(`Failed to search packages: ${res.statusText}`)
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const data = await res.json() as {
|
|
212
|
+
objects: Array<{ package: NpmPackageInfo }>
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return data.objects
|
|
216
|
+
.map(obj => obj.package)
|
|
217
|
+
.filter(pkg => isTuffPluginPackage(pkg.name))
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* List all available Tuff plugins from npm
|
|
222
|
+
*/
|
|
223
|
+
async listPlugins(): Promise<NpmPackageInfo[]> {
|
|
224
|
+
return this.searchPlugins()
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
export const npmProvider = new NpmProvider()
|
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
import type { IManifest } from '..'
|
|
2
|
+
import type {
|
|
3
|
+
PluginInstallRequest,
|
|
4
|
+
PluginInstallResult,
|
|
5
|
+
PluginProvider,
|
|
6
|
+
PluginProviderContext,
|
|
7
|
+
} from './types'
|
|
8
|
+
import { Buffer } from 'node:buffer'
|
|
9
|
+
import process from 'node:process'
|
|
10
|
+
import { NEXUS_BASE_URL } from '../../env'
|
|
11
|
+
import { PluginProviderType } from './types'
|
|
12
|
+
|
|
13
|
+
const DEFAULT_TPEX_API = NEXUS_BASE_URL
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Check if source is a .tpex file path or URL
|
|
17
|
+
*/
|
|
18
|
+
function isTpexFile(source: string): boolean {
|
|
19
|
+
return source.trim().toLowerCase().endsWith('.tpex')
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if source is a remote URL
|
|
24
|
+
*/
|
|
25
|
+
function isRemoteUrl(source: string): boolean {
|
|
26
|
+
return /^https?:\/\//i.test(source)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface TpexPluginInfo {
|
|
30
|
+
id: string
|
|
31
|
+
slug: string
|
|
32
|
+
name: string
|
|
33
|
+
summary: string
|
|
34
|
+
category: string
|
|
35
|
+
installs: number
|
|
36
|
+
homepage?: string | null
|
|
37
|
+
isOfficial: boolean
|
|
38
|
+
badges: string[]
|
|
39
|
+
author?: { name: string, avatarColor?: string } | null
|
|
40
|
+
iconUrl?: string | null
|
|
41
|
+
latestVersion?: {
|
|
42
|
+
id: string
|
|
43
|
+
version: string
|
|
44
|
+
channel: string
|
|
45
|
+
packageUrl: string
|
|
46
|
+
packageSize: number
|
|
47
|
+
signature?: string
|
|
48
|
+
manifest?: Record<string, unknown> | null
|
|
49
|
+
changelog?: string | null
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface TpexListResponse {
|
|
54
|
+
plugins: TpexPluginInfo[]
|
|
55
|
+
total: number
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface TpexDetailResponse {
|
|
59
|
+
plugin: TpexPluginInfo & {
|
|
60
|
+
versions?: Array<{
|
|
61
|
+
id: string
|
|
62
|
+
version: string
|
|
63
|
+
channel: string
|
|
64
|
+
packageUrl: string
|
|
65
|
+
packageSize: number
|
|
66
|
+
signature?: string
|
|
67
|
+
manifest?: Record<string, unknown> | null
|
|
68
|
+
changelog?: string | null
|
|
69
|
+
}>
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Parse TPEX source string to extract slug and optional version
|
|
75
|
+
* Formats: "tpex:slug", "tpex:slug@version", "slug" (when hintType is TPEX)
|
|
76
|
+
*/
|
|
77
|
+
function parseTpexSource(source: string): { slug: string, version?: string } | null {
|
|
78
|
+
const tpexMatch = source.match(/^tpex:([a-z0-9][\w\-.]{1,62}[a-z0-9])(?:@(.+))?$/i)
|
|
79
|
+
if (tpexMatch) {
|
|
80
|
+
const slug = tpexMatch[1]
|
|
81
|
+
if (!slug)
|
|
82
|
+
return null
|
|
83
|
+
const version = tpexMatch[2]
|
|
84
|
+
return version ? { slug, version } : { slug }
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const slugMatch = source.match(/^([a-z0-9][\w\-.]{1,62}[a-z0-9])(?:@(.+))?$/i)
|
|
88
|
+
if (slugMatch) {
|
|
89
|
+
const slug = slugMatch[1]
|
|
90
|
+
if (!slug)
|
|
91
|
+
return null
|
|
92
|
+
const version = slugMatch[2]
|
|
93
|
+
return version ? { slug, version } : { slug }
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export class TpexProvider implements PluginProvider {
|
|
100
|
+
readonly type = PluginProviderType.TPEX
|
|
101
|
+
private apiBase: string
|
|
102
|
+
|
|
103
|
+
constructor(apiBase: string = DEFAULT_TPEX_API) {
|
|
104
|
+
this.apiBase = apiBase.replace(/\/$/, '')
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
canHandle(request: PluginInstallRequest): boolean {
|
|
108
|
+
// Handle .tpex file paths (local or remote URL)
|
|
109
|
+
if (isTpexFile(request.source)) {
|
|
110
|
+
return true
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
if (request.hintType === PluginProviderType.TPEX) {
|
|
114
|
+
return parseTpexSource(request.source) !== null
|
|
115
|
+
}
|
|
116
|
+
return request.source.startsWith('tpex:')
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async install(
|
|
120
|
+
request: PluginInstallRequest,
|
|
121
|
+
context?: PluginProviderContext,
|
|
122
|
+
): Promise<PluginInstallResult> {
|
|
123
|
+
// Handle .tpex file directly (local path or remote URL)
|
|
124
|
+
if (isTpexFile(request.source)) {
|
|
125
|
+
return this.installFromFile(request, context)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Handle tpex:slug format - fetch from API
|
|
129
|
+
return this.installFromRegistry(request, context)
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Install from a .tpex file (local path or remote URL)
|
|
134
|
+
*/
|
|
135
|
+
private async installFromFile(
|
|
136
|
+
request: PluginInstallRequest,
|
|
137
|
+
context?: PluginProviderContext,
|
|
138
|
+
): Promise<PluginInstallResult> {
|
|
139
|
+
let filePath = request.source
|
|
140
|
+
let arrayBuffer: ArrayBuffer | undefined
|
|
141
|
+
|
|
142
|
+
if (isRemoteUrl(request.source)) {
|
|
143
|
+
// Download remote .tpex file
|
|
144
|
+
const downloadRes = await fetch(request.source)
|
|
145
|
+
if (!downloadRes.ok) {
|
|
146
|
+
throw new Error(`Failed to download TPEX file: ${downloadRes.statusText}`)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
arrayBuffer = await downloadRes.arrayBuffer()
|
|
150
|
+
const tempDir = context?.tempDir ?? '/tmp'
|
|
151
|
+
const fileName = `tpex-${Date.now()}.tpex`
|
|
152
|
+
filePath = `${tempDir}/${fileName}`
|
|
153
|
+
|
|
154
|
+
if (typeof process !== 'undefined') {
|
|
155
|
+
const fs = await import('node:fs/promises')
|
|
156
|
+
await fs.writeFile(filePath, Buffer.from(arrayBuffer))
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// For local files, just return the path - manifest extraction happens in core-app
|
|
161
|
+
return {
|
|
162
|
+
provider: PluginProviderType.TPEX,
|
|
163
|
+
filePath,
|
|
164
|
+
official: false,
|
|
165
|
+
metadata: {
|
|
166
|
+
sourceType: 'file',
|
|
167
|
+
originalSource: request.source,
|
|
168
|
+
},
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Install from TPEX registry (tpex:slug format)
|
|
174
|
+
*/
|
|
175
|
+
private async installFromRegistry(
|
|
176
|
+
request: PluginInstallRequest,
|
|
177
|
+
context?: PluginProviderContext,
|
|
178
|
+
): Promise<PluginInstallResult> {
|
|
179
|
+
const parsed = parseTpexSource(request.source)
|
|
180
|
+
if (!parsed) {
|
|
181
|
+
throw new Error(`Invalid TPEX source format: ${request.source}`)
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const { slug, version } = parsed
|
|
185
|
+
|
|
186
|
+
const detailRes = await fetch(`${this.apiBase}/api/market/plugins/${slug}`)
|
|
187
|
+
if (!detailRes.ok) {
|
|
188
|
+
if (detailRes.status === 404) {
|
|
189
|
+
throw new Error(`Plugin not found: ${slug}`)
|
|
190
|
+
}
|
|
191
|
+
throw new Error(`Failed to fetch plugin details: ${detailRes.statusText}`)
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const detail: TpexDetailResponse = await detailRes.json()
|
|
195
|
+
const plugin = detail.plugin
|
|
196
|
+
|
|
197
|
+
let targetVersion = plugin.latestVersion
|
|
198
|
+
if (version && plugin.versions) {
|
|
199
|
+
targetVersion = plugin.versions.find(v => v.version === version) ?? targetVersion
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (!targetVersion?.packageUrl) {
|
|
203
|
+
throw new Error(`No downloadable version found for plugin: ${slug}`)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const downloadUrl = targetVersion.packageUrl.startsWith('http')
|
|
207
|
+
? targetVersion.packageUrl
|
|
208
|
+
: `${this.apiBase}${targetVersion.packageUrl}`
|
|
209
|
+
|
|
210
|
+
const downloadRes = await fetch(downloadUrl)
|
|
211
|
+
if (!downloadRes.ok) {
|
|
212
|
+
throw new Error(`Failed to download plugin package: ${downloadRes.statusText}`)
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const arrayBuffer = await downloadRes.arrayBuffer()
|
|
216
|
+
const tempDir = context?.tempDir ?? '/tmp'
|
|
217
|
+
const fileName = `${slug}-${targetVersion.version}.tpex`
|
|
218
|
+
const filePath = `${tempDir}/${fileName}`
|
|
219
|
+
|
|
220
|
+
if (typeof process !== 'undefined') {
|
|
221
|
+
const fs = await import('node:fs/promises')
|
|
222
|
+
await fs.writeFile(filePath, Buffer.from(arrayBuffer))
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
const manifest: IManifest | undefined = targetVersion.manifest
|
|
226
|
+
? {
|
|
227
|
+
id: plugin.slug,
|
|
228
|
+
name: plugin.name,
|
|
229
|
+
version: targetVersion.version,
|
|
230
|
+
description: plugin.summary,
|
|
231
|
+
author: plugin.author?.name ?? 'Unknown',
|
|
232
|
+
main: (targetVersion.manifest as Record<string, unknown>).main as string ?? 'index.js',
|
|
233
|
+
icon: plugin.iconUrl ?? undefined,
|
|
234
|
+
...targetVersion.manifest,
|
|
235
|
+
}
|
|
236
|
+
: undefined
|
|
237
|
+
|
|
238
|
+
return {
|
|
239
|
+
provider: PluginProviderType.TPEX,
|
|
240
|
+
filePath,
|
|
241
|
+
official: plugin.isOfficial,
|
|
242
|
+
manifest,
|
|
243
|
+
metadata: {
|
|
244
|
+
sourceType: 'registry',
|
|
245
|
+
slug: plugin.slug,
|
|
246
|
+
version: targetVersion.version,
|
|
247
|
+
channel: targetVersion.channel,
|
|
248
|
+
packageSize: targetVersion.packageSize,
|
|
249
|
+
installs: plugin.installs,
|
|
250
|
+
},
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* List all available plugins from TPEX registry
|
|
256
|
+
*/
|
|
257
|
+
async listPlugins(): Promise<TpexPluginInfo[]> {
|
|
258
|
+
const res = await fetch(`${this.apiBase}/api/market/plugins`)
|
|
259
|
+
if (!res.ok) {
|
|
260
|
+
throw new Error(`Failed to fetch plugin list: ${res.statusText}`)
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const data: TpexListResponse = await res.json()
|
|
264
|
+
return data.plugins
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Get plugin details by slug
|
|
269
|
+
*/
|
|
270
|
+
async getPlugin(slug: string): Promise<TpexDetailResponse['plugin'] | null> {
|
|
271
|
+
const res = await fetch(`${this.apiBase}/api/market/plugins/${slug}`)
|
|
272
|
+
if (!res.ok) {
|
|
273
|
+
if (res.status === 404)
|
|
274
|
+
return null
|
|
275
|
+
throw new Error(`Failed to fetch plugin: ${res.statusText}`)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const data: TpexDetailResponse = await res.json()
|
|
279
|
+
return data.plugin
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Search plugins by keyword
|
|
284
|
+
*/
|
|
285
|
+
async searchPlugins(keyword: string): Promise<TpexPluginInfo[]> {
|
|
286
|
+
const plugins = await this.listPlugins()
|
|
287
|
+
const lowerKeyword = keyword.toLowerCase()
|
|
288
|
+
|
|
289
|
+
return plugins.filter(plugin =>
|
|
290
|
+
plugin.name.toLowerCase().includes(lowerKeyword)
|
|
291
|
+
|| plugin.slug.toLowerCase().includes(lowerKeyword)
|
|
292
|
+
|| plugin.summary.toLowerCase().includes(lowerKeyword),
|
|
293
|
+
)
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
export const tpexProvider = new TpexProvider()
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Metadata extracted from a .tpex package file
|
|
3
|
+
*/
|
|
4
|
+
export interface TpexMetadata {
|
|
5
|
+
readmeMarkdown?: string | null
|
|
6
|
+
manifest?: Record<string, unknown> | null
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Result of package preview operation
|
|
11
|
+
*/
|
|
12
|
+
export interface TpexPackagePreviewResult {
|
|
13
|
+
manifest: Record<string, unknown> | null
|
|
14
|
+
readmeMarkdown: string | null
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Extracted manifest fields from tpex package
|
|
19
|
+
*/
|
|
20
|
+
export interface TpexExtractedManifest {
|
|
21
|
+
id?: string
|
|
22
|
+
name?: string
|
|
23
|
+
description?: string
|
|
24
|
+
version?: string
|
|
25
|
+
homepage?: string
|
|
26
|
+
changelog?: string
|
|
27
|
+
channel?: string
|
|
28
|
+
category?: string
|
|
29
|
+
icon?: {
|
|
30
|
+
type?: string
|
|
31
|
+
value?: string
|
|
32
|
+
}
|
|
33
|
+
[key: string]: unknown
|
|
34
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { hasWindow } from '../../env'
|
|
2
|
+
|
|
3
|
+
export type BoxItemsAPI = Record<string, any>
|
|
4
|
+
|
|
5
|
+
const DEFAULT_BOX_ITEMS_ERROR
|
|
6
|
+
= '[Feature SDK] boxItems API not available. Make sure this is called in a plugin context.'
|
|
7
|
+
|
|
8
|
+
export function useBoxItems(errorMessage = DEFAULT_BOX_ITEMS_ERROR): BoxItemsAPI {
|
|
9
|
+
const boxItemsApi = hasWindow() ? (window as any)?.$boxItems as BoxItemsAPI | undefined : undefined
|
|
10
|
+
if (!boxItemsApi) {
|
|
11
|
+
throw new Error(errorMessage)
|
|
12
|
+
}
|
|
13
|
+
return boxItemsApi
|
|
14
|
+
}
|
package/plugin/sdk/box-sdk.ts
CHANGED
|
@@ -184,6 +184,39 @@ export interface BoxSDK {
|
|
|
184
184
|
* ```
|
|
185
185
|
*/
|
|
186
186
|
allowClipboard: (types: number) => Promise<void>
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Set CoreBox window to a specific height (60-800px)
|
|
190
|
+
*
|
|
191
|
+
* @param height - Target height in pixels (will be clamped to 60-800)
|
|
192
|
+
*
|
|
193
|
+
* @example
|
|
194
|
+
* ```typescript
|
|
195
|
+
* // Set to custom height
|
|
196
|
+
* await plugin.box.setHeight(400)
|
|
197
|
+
* ```
|
|
198
|
+
*/
|
|
199
|
+
setHeight: (height: number) => Promise<void>
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Set CoreBox vertical position as percentage from top (0.1-0.9)
|
|
203
|
+
*
|
|
204
|
+
* @param topPercent - Position from top (0.1 = 10%, 0.5 = 50%, etc.)
|
|
205
|
+
*
|
|
206
|
+
* @example
|
|
207
|
+
* ```typescript
|
|
208
|
+
* // Position at 40% from top
|
|
209
|
+
* await plugin.box.setPositionOffset(0.4)
|
|
210
|
+
* ```
|
|
211
|
+
*/
|
|
212
|
+
setPositionOffset: (topPercent: number) => Promise<void>
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Get current CoreBox window bounds
|
|
216
|
+
*
|
|
217
|
+
* @returns Window bounds { x, y, width, height }
|
|
218
|
+
*/
|
|
219
|
+
getBounds: () => Promise<{ x: number, y: number, width: number, height: number }>
|
|
187
220
|
}
|
|
188
221
|
|
|
189
222
|
/**
|
|
@@ -298,6 +331,37 @@ export function createBoxSDK(channel: ITouchClientChannel): BoxSDK {
|
|
|
298
331
|
throw error
|
|
299
332
|
}
|
|
300
333
|
},
|
|
334
|
+
|
|
335
|
+
async setHeight(height: number): Promise<void> {
|
|
336
|
+
try {
|
|
337
|
+
await channel.send('core-box:set-height', { height })
|
|
338
|
+
}
|
|
339
|
+
catch (error) {
|
|
340
|
+
console.error('[Box SDK] Failed to set height:', error)
|
|
341
|
+
throw error
|
|
342
|
+
}
|
|
343
|
+
},
|
|
344
|
+
|
|
345
|
+
async setPositionOffset(topPercent: number): Promise<void> {
|
|
346
|
+
try {
|
|
347
|
+
await channel.send('core-box:set-position-offset', { topPercent })
|
|
348
|
+
}
|
|
349
|
+
catch (error) {
|
|
350
|
+
console.error('[Box SDK] Failed to set position offset:', error)
|
|
351
|
+
throw error
|
|
352
|
+
}
|
|
353
|
+
},
|
|
354
|
+
|
|
355
|
+
async getBounds(): Promise<{ x: number, y: number, width: number, height: number }> {
|
|
356
|
+
try {
|
|
357
|
+
const result = await channel.send('core-box:get-bounds')
|
|
358
|
+
return result.bounds
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
console.error('[Box SDK] Failed to get bounds:', error)
|
|
362
|
+
throw error
|
|
363
|
+
}
|
|
364
|
+
},
|
|
301
365
|
}
|
|
302
366
|
}
|
|
303
367
|
|