@talex-touch/utils 1.0.42 → 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.
Files changed (233) hide show
  1. package/.eslintcache +1 -0
  2. package/__tests__/cloud-sync-sdk.test.ts +442 -0
  3. package/__tests__/icons/icons.test.ts +84 -0
  4. package/__tests__/plugin-sdk-lifecycle.test.ts +130 -0
  5. package/__tests__/power-sdk.test.ts +143 -0
  6. package/__tests__/preset-export-types.test.ts +108 -0
  7. package/__tests__/search/fuzzy-match.test.ts +137 -0
  8. package/__tests__/transport/port-policy.test.ts +44 -0
  9. package/__tests__/transport-domain-sdks.test.ts +152 -0
  10. package/__tests__/types/update.test.ts +67 -0
  11. package/account/account-sdk.ts +915 -0
  12. package/account/index.ts +2 -0
  13. package/account/types.ts +321 -0
  14. package/analytics/client.ts +136 -0
  15. package/analytics/index.ts +2 -0
  16. package/analytics/types.ts +156 -0
  17. package/animation/auto-resize.ts +322 -0
  18. package/animation/window-node.ts +26 -19
  19. package/auth/clerk-types.ts +12 -30
  20. package/auth/index.ts +0 -2
  21. package/auth/useAuthState.ts +6 -14
  22. package/base/index.ts +2 -0
  23. package/base/log-level.ts +105 -0
  24. package/channel/index.ts +170 -69
  25. package/cloud-sync/cloud-sync-sdk.ts +450 -0
  26. package/cloud-sync/index.ts +1 -0
  27. package/common/file-scan-utils.ts +17 -9
  28. package/common/index.ts +4 -0
  29. package/common/logger/index.ts +46 -0
  30. package/common/logger/logger-manager.ts +303 -0
  31. package/common/logger/module-logger.ts +270 -0
  32. package/common/logger/transport-logger.ts +234 -0
  33. package/common/logger/types.ts +93 -0
  34. package/common/search/gather.ts +48 -6
  35. package/common/search/index.ts +8 -0
  36. package/common/storage/constants.ts +13 -0
  37. package/common/storage/entity/app-settings.ts +245 -0
  38. package/common/storage/entity/index.ts +3 -0
  39. package/common/storage/entity/layout-atom-types.ts +147 -0
  40. package/common/storage/entity/openers.ts +1 -0
  41. package/common/storage/entity/preset-cloud-api.ts +132 -0
  42. package/common/storage/entity/preset-export-types.ts +256 -0
  43. package/common/storage/entity/shortcut-settings.ts +1 -0
  44. package/common/storage/shortcut-storage.ts +11 -0
  45. package/common/utils/clone-diagnostics.ts +105 -0
  46. package/common/utils/file.ts +16 -8
  47. package/common/utils/index.ts +6 -2
  48. package/common/utils/payload-preview.ts +173 -0
  49. package/common/utils/polling.ts +167 -13
  50. package/common/utils/safe-path.ts +103 -0
  51. package/common/utils/safe-shell.ts +115 -0
  52. package/common/utils/task-queue.ts +4 -1
  53. package/core-box/builder/tuff-builder.ts +0 -1
  54. package/core-box/index.ts +1 -1
  55. package/core-box/recommendation.ts +38 -1
  56. package/core-box/tuff/tuff-dsl.ts +32 -0
  57. package/electron/download-manager.ts +10 -7
  58. package/electron/env-tool.ts +42 -40
  59. package/electron/index.ts +0 -1
  60. package/env/index.ts +156 -0
  61. package/eslint.config.js +55 -0
  62. package/i18n/index.ts +62 -0
  63. package/i18n/locales/en.json +226 -0
  64. package/i18n/locales/zh.json +226 -0
  65. package/i18n/message-keys.ts +236 -0
  66. package/i18n/resolver.ts +181 -0
  67. package/icons/index.ts +257 -0
  68. package/icons/svg.ts +69 -0
  69. package/index.ts +9 -1
  70. package/intelligence/client.ts +72 -42
  71. package/market/constants.ts +9 -5
  72. package/market/index.ts +1 -1
  73. package/market/types.ts +19 -4
  74. package/package.json +15 -5
  75. package/permission/index.ts +143 -46
  76. package/permission/legacy.ts +26 -0
  77. package/permission/registry.ts +304 -0
  78. package/permission/types.ts +164 -0
  79. package/plugin/channel.ts +68 -39
  80. package/plugin/index.ts +80 -7
  81. package/plugin/install.ts +3 -0
  82. package/plugin/log/types.ts +22 -5
  83. package/plugin/node/logger-manager.ts +11 -3
  84. package/plugin/node/logger.ts +24 -17
  85. package/plugin/preload.ts +25 -2
  86. package/plugin/providers/index.ts +4 -4
  87. package/plugin/providers/market-client.ts +6 -3
  88. package/plugin/providers/npm-provider.ts +22 -7
  89. package/plugin/providers/tpex-provider.ts +22 -8
  90. package/plugin/sdk/box-items.ts +14 -0
  91. package/plugin/sdk/box-sdk.ts +64 -0
  92. package/plugin/sdk/channel.ts +119 -4
  93. package/plugin/sdk/clipboard.ts +26 -12
  94. package/plugin/sdk/cloud-sync.ts +113 -0
  95. package/plugin/sdk/common.ts +19 -11
  96. package/plugin/sdk/core-box.ts +6 -15
  97. package/plugin/sdk/division-box.ts +160 -65
  98. package/plugin/sdk/examples/storage-onDidChange-example.js +5 -2
  99. package/plugin/sdk/feature-sdk.ts +111 -76
  100. package/plugin/sdk/flow.ts +146 -45
  101. package/plugin/sdk/hooks/bridge.ts +13 -6
  102. package/plugin/sdk/hooks/life-cycle.ts +35 -16
  103. package/plugin/sdk/index.ts +14 -3
  104. package/plugin/sdk/intelligence.ts +87 -0
  105. package/plugin/sdk/meta/README.md +179 -0
  106. package/plugin/sdk/meta-sdk.ts +244 -0
  107. package/plugin/sdk/notification.ts +9 -0
  108. package/plugin/sdk/plugin-info.ts +64 -0
  109. package/plugin/sdk/power.ts +155 -0
  110. package/plugin/sdk/recommend.ts +21 -0
  111. package/plugin/sdk/service/index.ts +12 -8
  112. package/plugin/sdk/sqlite.ts +141 -0
  113. package/plugin/sdk/storage.ts +2 -6
  114. package/plugin/sdk/system.ts +2 -9
  115. package/plugin/sdk/temp-files.ts +41 -0
  116. package/plugin/sdk/touch-sdk.ts +18 -0
  117. package/plugin/sdk/types.ts +44 -4
  118. package/plugin/sdk/window/index.ts +12 -9
  119. package/plugin/sdk-version.ts +231 -0
  120. package/preload/renderer.ts +3 -2
  121. package/renderer/hooks/arg-mapper.ts +16 -2
  122. package/renderer/hooks/index.ts +13 -0
  123. package/renderer/hooks/initialize.ts +2 -1
  124. package/renderer/hooks/use-agent-market-sdk.ts +7 -0
  125. package/renderer/hooks/use-agent-market.ts +106 -0
  126. package/renderer/hooks/use-agents-sdk.ts +7 -0
  127. package/renderer/hooks/use-app-sdk.ts +7 -0
  128. package/renderer/hooks/use-channel.ts +33 -4
  129. package/renderer/hooks/use-download-sdk.ts +21 -0
  130. package/renderer/hooks/use-intelligence-sdk.ts +7 -0
  131. package/renderer/hooks/use-intelligence-stats.ts +290 -0
  132. package/renderer/hooks/use-intelligence.ts +55 -214
  133. package/renderer/hooks/use-market-sdk.ts +16 -0
  134. package/renderer/hooks/use-notification-sdk.ts +7 -0
  135. package/renderer/hooks/use-permission-sdk.ts +7 -0
  136. package/renderer/hooks/use-permission.ts +325 -0
  137. package/renderer/hooks/use-platform-sdk.ts +7 -0
  138. package/renderer/hooks/use-plugin-sdk.ts +16 -0
  139. package/renderer/hooks/use-settings-sdk.ts +7 -0
  140. package/renderer/hooks/use-update-sdk.ts +21 -0
  141. package/renderer/index.ts +1 -0
  142. package/renderer/ref.ts +19 -10
  143. package/renderer/shared/components/SharedPluginDetailContent.vue +84 -0
  144. package/renderer/shared/components/SharedPluginDetailHeader.vue +116 -0
  145. package/renderer/shared/components/SharedPluginDetailMetaList.vue +39 -0
  146. package/renderer/shared/components/SharedPluginDetailReadme.vue +45 -0
  147. package/renderer/shared/components/SharedPluginDetailVersions.vue +98 -0
  148. package/renderer/shared/components/index.ts +5 -0
  149. package/renderer/shared/components/shims-vue.d.ts +5 -0
  150. package/renderer/shared/index.ts +2 -0
  151. package/renderer/shared/plugin-detail.ts +62 -0
  152. package/renderer/storage/app-settings.ts +3 -1
  153. package/renderer/storage/base-storage.ts +508 -82
  154. package/renderer/storage/intelligence-storage.ts +31 -40
  155. package/renderer/storage/openers.ts +3 -1
  156. package/renderer/storage/storage-subscription.ts +126 -42
  157. package/renderer/touch-sdk/env.ts +10 -10
  158. package/renderer/touch-sdk/index.ts +114 -18
  159. package/renderer/touch-sdk/terminal.ts +24 -13
  160. package/search/feature-matcher.ts +279 -0
  161. package/search/fuzzy-match.ts +64 -34
  162. package/search/index.ts +10 -0
  163. package/search/levenshtein-utils.ts +17 -11
  164. package/transport/errors.ts +310 -0
  165. package/transport/event/builder.ts +378 -0
  166. package/transport/event/index.ts +7 -0
  167. package/transport/event/types.ts +292 -0
  168. package/transport/events/index.ts +2670 -0
  169. package/transport/events/meta-overlay.ts +79 -0
  170. package/transport/events/types/agents.ts +177 -0
  171. package/transport/events/types/app-index.ts +9 -0
  172. package/transport/events/types/app.ts +475 -0
  173. package/transport/events/types/box-item.ts +222 -0
  174. package/transport/events/types/clipboard.ts +80 -0
  175. package/transport/events/types/core-box.ts +534 -0
  176. package/transport/events/types/device-idle.ts +7 -0
  177. package/transport/events/types/division-box.ts +99 -0
  178. package/transport/events/types/download.ts +115 -0
  179. package/transport/events/types/file-index.ts +73 -0
  180. package/transport/events/types/flow.ts +149 -0
  181. package/transport/events/types/index.ts +70 -0
  182. package/transport/events/types/market.ts +39 -0
  183. package/transport/events/types/meta-overlay.ts +184 -0
  184. package/transport/events/types/notification.ts +140 -0
  185. package/transport/events/types/permission.ts +90 -0
  186. package/transport/events/types/platform.ts +8 -0
  187. package/transport/events/types/plugin.ts +620 -0
  188. package/transport/events/types/sentry.ts +20 -0
  189. package/transport/events/types/storage.ts +208 -0
  190. package/transport/events/types/transport.ts +60 -0
  191. package/transport/events/types/tray.ts +16 -0
  192. package/transport/events/types/update.ts +78 -0
  193. package/transport/index.ts +139 -0
  194. package/transport/main.ts +2 -0
  195. package/transport/sdk/constants.ts +29 -0
  196. package/transport/sdk/domains/agents-market.ts +47 -0
  197. package/transport/sdk/domains/agents.ts +62 -0
  198. package/transport/sdk/domains/app.ts +48 -0
  199. package/transport/sdk/domains/disposable.ts +35 -0
  200. package/transport/sdk/domains/download.ts +139 -0
  201. package/transport/sdk/domains/index.ts +13 -0
  202. package/transport/sdk/domains/intelligence.ts +616 -0
  203. package/transport/sdk/domains/market.ts +35 -0
  204. package/transport/sdk/domains/notification.ts +62 -0
  205. package/transport/sdk/domains/permission.ts +85 -0
  206. package/transport/sdk/domains/platform.ts +19 -0
  207. package/transport/sdk/domains/plugin.ts +144 -0
  208. package/transport/sdk/domains/settings.ts +92 -0
  209. package/transport/sdk/domains/update.ts +64 -0
  210. package/transport/sdk/index.ts +60 -0
  211. package/transport/sdk/main-transport.ts +710 -0
  212. package/transport/sdk/main.ts +9 -0
  213. package/transport/sdk/plugin-transport.ts +654 -0
  214. package/transport/sdk/port-policy.ts +38 -0
  215. package/transport/sdk/renderer-transport.ts +1165 -0
  216. package/transport/types.ts +605 -0
  217. package/types/agent.ts +399 -0
  218. package/types/cloud-sync.ts +157 -0
  219. package/types/division-box.ts +31 -31
  220. package/types/download.ts +1 -0
  221. package/types/flow.ts +63 -12
  222. package/types/icon.ts +2 -1
  223. package/types/index.ts +5 -0
  224. package/types/intelligence.ts +166 -173
  225. package/types/modules/base.ts +2 -0
  226. package/types/path-browserify.d.ts +5 -0
  227. package/types/platform.ts +12 -0
  228. package/types/startup-info.ts +32 -0
  229. package/types/touch-app-core.ts +8 -8
  230. package/types/update.ts +94 -1
  231. package/vitest.config.ts +25 -0
  232. package/auth/useClerkConfig.ts +0 -40
  233. package/auth/useClerkProvider.ts +0 -52
package/plugin/channel.ts CHANGED
@@ -9,25 +9,45 @@ import {
9
9
  ChannelType,
10
10
  DataCode,
11
11
  } from '../channel'
12
+ import { getLogger } from '../common/logger'
13
+ import { findCloneIssue, isCloneError, summarizeClonePayload } from '../common/utils/clone-diagnostics'
14
+ import { formatPayloadPreview } from '../common/utils/payload-preview'
15
+ import { hasWindow } from '../env'
12
16
 
13
- const CHANNEL_DEFAULT_TIMEOUT = 10_000
17
+ const CHANNEL_DEFAULT_TIMEOUT = 60_000
14
18
 
15
19
  let cachedIpcRenderer: IpcRenderer | null = null
20
+ const channelLog = getLogger('plugin-channel')
21
+
22
+ type PluginWindow = Window & {
23
+ $plugin?: { name?: string }
24
+ $channel?: ITouchClientChannel
25
+ electron?: { ipcRenderer?: IpcRenderer }
26
+ }
27
+
28
+ function getPluginWindow(): PluginWindow | undefined {
29
+ return hasWindow() ? (window as unknown as PluginWindow) : undefined
30
+ }
16
31
 
17
32
  // 使用惰性解析避免在打包阶段静态引入 electron
18
33
  function resolveIpcRenderer(): IpcRenderer | null {
19
- if (typeof window !== 'undefined') {
20
- const bridge = (window as any)?.electron
21
- if (bridge?.ipcRenderer)
22
- return bridge.ipcRenderer as IpcRenderer
23
- }
34
+ const globalWindow = getPluginWindow()
35
+ if (globalWindow?.electron?.ipcRenderer)
36
+ return globalWindow.electron.ipcRenderer as IpcRenderer
24
37
 
25
38
  try {
26
- const electron = (globalThis as any)?.electron ?? (eval('require') as any)?.('electron')
27
- if (electron?.ipcRenderer)
28
- return electron.ipcRenderer as IpcRenderer
39
+ const electronFromGlobal = (globalThis as any)?.electron
40
+ if (electronFromGlobal?.ipcRenderer)
41
+ return electronFromGlobal.ipcRenderer as IpcRenderer
42
+
43
+ const requireFromGlobal = (globalThis as any)?.require
44
+ if (typeof requireFromGlobal === 'function') {
45
+ const electron = requireFromGlobal('electron')
46
+ if (electron?.ipcRenderer)
47
+ return electron.ipcRenderer as IpcRenderer
48
+ }
29
49
  }
30
- catch (error) {
50
+ catch {
31
51
  // ignore – will throw below if no ipcRenderer is resolved
32
52
  }
33
53
 
@@ -46,14 +66,17 @@ function ensureIpcRenderer(): IpcRenderer {
46
66
  return cachedIpcRenderer
47
67
  }
48
68
 
69
+ type ChannelListener = (data: StandardChannelData) => unknown
70
+ type PendingCallback = (data: RawStandardChannelData) => void
71
+
49
72
  /**
50
73
  * @deprecated This class is deprecated and will be removed in the future.
51
74
  * Due to the new secret system, ipc message transmission should unique Key, and will inject when ui view attached.
52
75
  */
53
76
  class TouchChannel implements ITouchClientChannel {
54
- channelMap: Map<string, Function[]> = new Map()
77
+ channelMap: Map<string, ChannelListener[]> = new Map()
55
78
 
56
- pendingMap: Map<string, Function> = new Map()
79
+ pendingMap: Map<string, PendingCallback> = new Map()
57
80
 
58
81
  plugin: string
59
82
 
@@ -66,7 +89,7 @@ class TouchChannel implements ITouchClientChannel {
66
89
  }
67
90
 
68
91
  __parse_raw_data(e: IpcRendererEvent | undefined, arg: any): RawStandardChannelData | null {
69
- console.debug('Raw data: ', arg, e)
92
+ channelLog.debug('Raw data', { meta: { payload: formatPayloadPreview(arg) } })
70
93
  if (arg) {
71
94
  const { name, header, code, data, sync } = arg
72
95
 
@@ -86,7 +109,7 @@ class TouchChannel implements ITouchClientChannel {
86
109
  }
87
110
  }
88
111
 
89
- console.error(e, arg)
112
+ channelLog.error('Invalid message payload', { error: { event: e, payload: arg } })
90
113
  return null
91
114
  // throw new Error("Invalid message!");
92
115
  }
@@ -154,7 +177,7 @@ class TouchChannel implements ITouchClientChannel {
154
177
 
155
178
  regChannel(
156
179
  eventName: string,
157
- callback: Function,
180
+ callback: ChannelListener,
158
181
  ): () => void {
159
182
  const listeners = this.channelMap.get(eventName) || []
160
183
 
@@ -176,7 +199,7 @@ class TouchChannel implements ITouchClientChannel {
176
199
  }
177
200
  }
178
201
 
179
- unRegChannel(eventName: string, callback: Function): boolean {
202
+ unRegChannel(eventName: string, callback: ChannelListener): boolean {
180
203
  const listeners = this.channelMap.get(eventName)
181
204
 
182
205
  if (!listeners) {
@@ -200,16 +223,7 @@ class TouchChannel implements ITouchClientChannel {
200
223
  }
201
224
 
202
225
  private formatPayloadPreview(payload: unknown): string {
203
- if (payload === null || payload === undefined)
204
- return String(payload)
205
- if (typeof payload === 'string')
206
- return payload.length > 200 ? `${payload.slice(0, 200)}…` : payload
207
- try {
208
- return JSON.stringify(payload)
209
- }
210
- catch {
211
- return '[unserializable]'
212
- }
226
+ return formatPayloadPreview(payload)
213
227
  }
214
228
 
215
229
  send(eventName: string, arg: any): Promise<any> {
@@ -239,13 +253,20 @@ class TouchChannel implements ITouchClientChannel {
239
253
  }
240
254
  catch (error) {
241
255
  const errorMessage = error instanceof Error ? error.message : String(error)
242
- console.error(
243
- `[PluginChannel] Failed to send "${eventName}": ${errorMessage}`,
244
- { payloadPreview: this.formatPayloadPreview(arg) },
245
- )
256
+ const meta: Record<string, unknown> = {
257
+ payloadPreview: this.formatPayloadPreview(arg)
258
+ }
259
+ if (isCloneError(error)) {
260
+ meta.cloneIssue = findCloneIssue(arg)
261
+ meta.payloadSummary = summarizeClonePayload(arg)
262
+ }
263
+ channelLog.error(`Failed to send \"${eventName}\": ${errorMessage}`, {
264
+ meta,
265
+ error,
266
+ })
246
267
  reject(
247
268
  Object.assign(
248
- new Error(`Failed to send plugin channel message "${eventName}": ${errorMessage}`),
269
+ new Error(`Failed to send plugin channel message \"${eventName}\": ${errorMessage}`),
249
270
  { code: 'plugin_channel_send_failed' },
250
271
  ),
251
272
  )
@@ -258,10 +279,10 @@ class TouchChannel implements ITouchClientChannel {
258
279
  return
259
280
  this.pendingMap.delete(uniqueId)
260
281
  const timeoutError = Object.assign(
261
- new Error(`Plugin channel request "${eventName}" timed out after ${timeoutMs}ms`),
282
+ new Error(`Plugin channel request \"${eventName}\" timed out after ${timeoutMs}ms`),
262
283
  { code: 'plugin_channel_timeout' },
263
284
  )
264
- console.warn(timeoutError.message)
285
+ channelLog.warn(timeoutError.message)
265
286
  reject(timeoutError)
266
287
  }, timeoutMs)
267
288
 
@@ -299,12 +320,19 @@ class TouchChannel implements ITouchClientChannel {
299
320
  }
300
321
  catch (error) {
301
322
  const errorMessage = error instanceof Error ? error.message : String(error)
302
- console.error('[PluginChannel] Failed to sendSync message', {
323
+ const meta: Record<string, unknown> = {
303
324
  eventName,
304
- error: errorMessage,
305
325
  payloadPreview: this.formatPayloadPreview(arg),
326
+ }
327
+ if (isCloneError(error)) {
328
+ meta.cloneIssue = findCloneIssue(arg)
329
+ meta.payloadSummary = summarizeClonePayload(arg)
330
+ }
331
+ channelLog.error('Failed to sendSync message', {
332
+ meta,
333
+ error,
306
334
  })
307
- throw new Error(`Failed to sendSync plugin channel message "${eventName}": ${errorMessage}`)
335
+ throw new Error(`Failed to sendSync plugin channel message \"${eventName}\": ${errorMessage}`)
308
336
  }
309
337
  }
310
338
  }
@@ -313,13 +341,14 @@ let touchChannel: ITouchClientChannel | null = null
313
341
 
314
342
  export function genChannel(): ITouchClientChannel {
315
343
  if (!touchChannel) {
316
- if (typeof window === 'undefined' || !(window as any)?.$plugin?.name) {
344
+ const globalWindow = getPluginWindow()
345
+ const pluginName = globalWindow?.$plugin?.name
346
+ if (!globalWindow || !pluginName) {
317
347
  throw new Error('TouchChannel cannot be initialized outside plugin renderer context')
318
348
  }
319
349
 
320
- const pluginName = (window as any).$plugin.name as string
321
350
  touchChannel = new TouchChannel(pluginName)
322
- ;(window as any).$channel = touchChannel
351
+ globalWindow.$channel = touchChannel
323
352
  }
324
353
 
325
354
  return touchChannel
package/plugin/index.ts CHANGED
@@ -4,7 +4,7 @@ import type { Arch, SupportOS } from './../base/index'
4
4
 
5
5
  import type { IPluginLogger } from './log/types'
6
6
 
7
- import type { PluginInstallRequest, PluginInstallSummary } from './providers'
7
+ import type { PluginInstallRequest, PluginInstallSummary } from './providers/types'
8
8
 
9
9
  export enum PluginStatus {
10
10
  DISABLED,
@@ -33,6 +33,14 @@ export interface PluginIssue {
33
33
  timestamp?: number
34
34
  }
35
35
 
36
+ export interface PluginMeta {
37
+ /**
38
+ * Internal plugins are created in code (no manifest / scanning).
39
+ * They should be hidden in UI unless developer mode is enabled.
40
+ */
41
+ internal?: boolean
42
+ }
43
+
36
44
  export interface DevServerHealthCheckResult {
37
45
  healthy: boolean
38
46
  version?: string
@@ -68,13 +76,44 @@ export interface IPluginDev {
68
76
  source?: boolean
69
77
  }
70
78
 
79
+ /**
80
+ * SDK API version for plugin compatibility checking.
81
+ * Format: YYMMDD (e.g., 251212 = 2025-12-12)
82
+ *
83
+ * Rules:
84
+ * - Not declared or < PERMISSION_ENFORCEMENT_MIN_VERSION: legacy mode (permissions bypassed)
85
+ * - >= PERMISSION_ENFORCEMENT_MIN_VERSION: permissions enforced
86
+ */
87
+ export type SdkApiVersion = number
88
+
71
89
  export interface ITouchPlugin extends IPluginBaseInfo {
72
90
  dev: IPluginDev
73
91
  pluginPath: string
74
92
  logger: IPluginLogger<any>
93
+ /**
94
+ * Category id synced with Nexus (e.g., 'utilities', 'productivity').
95
+ * Used for UI grouping and marketplace filtering.
96
+ */
97
+ category?: string
98
+ meta?: PluginMeta
75
99
  features: IPluginFeature[]
76
100
  issues: PluginIssue[]
77
101
  divisionBoxConfig?: import('../types/division-box').ManifestDivisionBoxConfig
102
+ /**
103
+ * SDK API version declared by the plugin.
104
+ * Used for compatibility checking and permission enforcement.
105
+ * Format: YYMMDD (e.g., 251212)
106
+ */
107
+ sdkapi?: SdkApiVersion
108
+ /**
109
+ * Declared permissions from manifest.
110
+ * Used for permission checking and UI display.
111
+ */
112
+ declaredPermissions?: {
113
+ required: string[]
114
+ optional: string[]
115
+ reasons: Record<string, string>
116
+ }
78
117
 
79
118
  addFeature: (feature: IPluginFeature) => boolean
80
119
  delFeature: (featureId: string) => boolean
@@ -102,14 +141,21 @@ export interface ITouchPlugin extends IPluginBaseInfo {
102
141
  * @param content The content of the file.
103
142
  * @returns The result of the save operation.
104
143
  */
105
- savePluginFile: (fileName: string, content: object) => { success: boolean, error?: string }
144
+ savePluginFile: (
145
+ fileName: string,
146
+ content: object,
147
+ options?: { broadcast?: boolean }
148
+ ) => { success: boolean, error?: string }
106
149
 
107
150
  /**
108
151
  * Delete the plugin file.
109
152
  * @param fileName The name of the file.
110
153
  * @returns The result of the delete operation.
111
154
  */
112
- deletePluginFile: (fileName: string) => { success: boolean, error?: string }
155
+ deletePluginFile: (
156
+ fileName: string,
157
+ options?: { broadcast?: boolean }
158
+ ) => { success: boolean, error?: string }
113
159
 
114
160
  /**
115
161
  * List all files in the plugin.
@@ -133,11 +179,13 @@ export interface ITouchPlugin extends IPluginBaseInfo {
133
179
 
134
180
  export interface IFeatureCommand {
135
181
  type: 'match' | 'contain' | 'regex' | 'function' | 'over' | 'image' | 'files' | 'directory' | 'window'
136
- value: string | string[] | RegExp | Function
182
+ value: string | string[] | RegExp | FeatureCommandMatcher
137
183
  /** Optional trigger callback - not serialized over IPC */
138
184
  onTrigger?: () => void
139
185
  }
140
186
 
187
+ export type FeatureCommandMatcher = (queryText: string) => boolean
188
+
141
189
  export interface IPluginFeature {
142
190
  id: string
143
191
  name: string
@@ -148,6 +196,10 @@ export interface IPluginFeature {
148
196
  platform: IPlatform
149
197
  commands: IFeatureCommand[]
150
198
  interaction?: IFeatureInteraction
199
+ /**
200
+ * Experimental features are hidden unless the plugin runs in dev mode.
201
+ */
202
+ experimental?: boolean
151
203
  /**
152
204
  * Internal search tokens generated at runtime for better matching
153
205
  */
@@ -175,6 +227,17 @@ export interface IFeatureInteraction {
175
227
  * The relative path to the html file from the plugin root.
176
228
  */
177
229
  path?: string
230
+ /**
231
+ * Whether to show the input field in CoreBox when this feature is active.
232
+ * Defaults to true for webcontent type.
233
+ */
234
+ showInput?: boolean
235
+ /**
236
+ * Whether to automatically enable input monitoring for this feature.
237
+ * If true, plugin will receive input change events without calling allowInput().
238
+ * Defaults to true for webcontent features.
239
+ */
240
+ allowInput?: boolean
178
241
  }
179
242
 
180
243
  /**
@@ -391,6 +454,16 @@ export interface IManifest {
391
454
  * Version of the plugin, following semantic versioning (e.g., "1.0.0").
392
455
  */
393
456
  version: string
457
+ /**
458
+ * SDK API version for compatibility checking.
459
+ * Format: YYMMDD (e.g., 251212 = 2025-12-12)
460
+ * Plugins without this field or with version < 251212 will bypass permission enforcement.
461
+ */
462
+ sdkapi?: SdkApiVersion
463
+ /**
464
+ * Category id synced with Nexus (e.g., 'utilities', 'productivity').
465
+ */
466
+ category?: string
394
467
  /**
395
468
  * Short description of the plugin's functionality.
396
469
  */
@@ -500,8 +573,8 @@ export interface IManifest {
500
573
  }
501
574
 
502
575
  export * from './install'
503
- export type { IPluginLogger, LogDataType, LogItem, LogLevel } from './log/types'
504
- export * from './providers'
576
+ export type { IPluginLogger, LogDataType, LogItem } from './log/types'
505
577
  export * from './risk'
506
- export * from './sdk/index'
578
+ export * from './sdk-version'
579
+ // Plugin runtime SDK should be imported from `@talex-touch/utils/plugin/sdk` to avoid root export collisions.
507
580
  export * from './widget'
package/plugin/install.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  export type PluginInstallTaskStage
2
2
  = | 'queued'
3
3
  | 'downloading'
4
+ | 'verifying'
4
5
  | 'awaiting-confirmation'
5
6
  | 'installing'
6
7
  | 'completed'
@@ -20,6 +21,8 @@ export interface PluginInstallProgressEvent {
20
21
  /** 插件唯一标识或名称(由客户端提供)。 */
21
22
  pluginId?: string
22
23
  pluginName?: string
24
+ /** 来源提供者 ID,用于区分不同市场源的同名插件。 */
25
+ providerId?: string
23
26
  /** 队列中的剩余任务数量(包含当前任务)。 */
24
27
  remaining?: number
25
28
  /** 当前任务在队列中的位置(0 表示正在处理)。 */
@@ -1,7 +1,24 @@
1
+ import type { LogLevelString as BaseLogLevelString } from '../../base/log-level'
1
2
  /**
2
- * Defines supported logging levels.
3
+ * Re-export unified LogLevel from base
3
4
  */
4
- export type LogLevel = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG'
5
+ import {
6
+ LogLevel as BaseLogLevel,
7
+
8
+ logLevelToString as baseLogLevelToString,
9
+ stringToLogLevel as baseStringToLogLevel,
10
+ } from '../../base/log-level'
11
+
12
+ export const LogLevel = BaseLogLevel
13
+ export type LogLevelString = BaseLogLevelString
14
+ export const logLevelToString = baseLogLevelToString
15
+ export const stringToLogLevel = baseStringToLogLevel
16
+
17
+ /**
18
+ * Legacy string-based log level type (for backward compatibility)
19
+ * @deprecated Use LogLevel enum instead
20
+ */
21
+ export type LogLevelLegacy = 'INFO' | 'WARN' | 'ERROR' | 'DEBUG'
5
22
 
6
23
  /**
7
24
  * Supported data types for logging arguments.
@@ -14,8 +31,8 @@ export type LogDataType = string | number | boolean | object
14
31
  export interface LogItem {
15
32
  /** ISO timestamp when the log was created */
16
33
  timestamp: string
17
- /** Logging severity level */
18
- level: LogLevel
34
+ /** Logging severity level (uppercase string for storage compatibility) */
35
+ level: LogLevelString
19
36
  /** Plugin name */
20
37
  plugin: string
21
38
  /** Main log message */
@@ -27,7 +44,7 @@ export interface LogItem {
27
44
  }
28
45
 
29
46
  /**
30
- * Minimal contract for plugin loggers so web 端只依赖接口定义
47
+ * Minimal contract for plugin loggers
31
48
  */
32
49
  export interface IPluginLogger<TManager = unknown> {
33
50
  info: (...args: LogDataType[]) => void
@@ -3,6 +3,7 @@ import type { LogItem } from '../log/types'
3
3
  import fs from 'node:fs'
4
4
  import path from 'node:path'
5
5
  import { structuredStrictStringify } from '@talex-touch/utils'
6
+ import { PollingService } from '../../common/utils/polling'
6
7
 
7
8
  /**
8
9
  * PluginLoggerManager is responsible for managing and writing logs for a specific plugin.
@@ -14,7 +15,8 @@ export class PluginLoggerManager {
14
15
  private readonly pluginInfoPath: string
15
16
  private readonly sessionStart: string
16
17
  private buffer: LogItem[] = []
17
- private flushInterval: NodeJS.Timeout
18
+ private readonly pollingService = PollingService.getInstance()
19
+ private readonly flushTaskId: string
18
20
  private onLogAppend?: (log: LogItem) => void
19
21
 
20
22
  /**
@@ -33,9 +35,15 @@ export class PluginLoggerManager {
33
35
  this.pluginLogDir = path.resolve(baseDir, 'logs', sessionFolder)
34
36
  this.sessionLogPath = path.resolve(this.pluginLogDir, 'session.log')
35
37
  this.pluginInfoPath = path.resolve(this.pluginLogDir, 'touch-plugin.info')
38
+ this.flushTaskId = `plugin-logger.flush.${pluginInfo.name}.${Date.now()}`
36
39
 
37
40
  this.ensureLogEnvironment(true)
38
- this.flushInterval = setInterval(() => this.flush(), 5000)
41
+ this.pollingService.register(
42
+ this.flushTaskId,
43
+ () => this.flush(),
44
+ { interval: 5, unit: 'seconds' },
45
+ )
46
+ this.pollingService.start()
39
47
  }
40
48
 
41
49
  /**
@@ -89,7 +97,7 @@ export class PluginLoggerManager {
89
97
  * Stops the flush interval and ensures remaining logs are written.
90
98
  */
91
99
  destroy(): void {
92
- clearInterval(this.flushInterval)
100
+ this.pollingService.unregister(this.flushTaskId)
93
101
  this.flush()
94
102
  }
95
103
 
@@ -1,7 +1,18 @@
1
- import type { IPluginLogger, LogDataType, LogItem, LogLevel } from '../log/types'
1
+ import type { IPluginLogger, LogDataType, LogItem, LogLevelString } from '../log/types'
2
2
  import type { PluginLoggerManager } from './logger-manager'
3
+ import process from 'node:process'
4
+ import { inspect } from 'node:util'
3
5
  import chalk from 'chalk'
4
6
 
7
+ const pluginLogStdout =
8
+ process.env.TALEX_PLUGIN_LOG_STDOUT === '1' || process.env.TALEX_PLUGIN_LOG_STDOUT === 'true'
9
+ const defaultStdoutLevels = new Set<LogLevelString>(['WARN', 'ERROR'])
10
+
11
+ function shouldWriteToStdout(level: LogLevelString): boolean {
12
+ if (pluginLogStdout) return true
13
+ return defaultStdoutLevels.has(level)
14
+ }
15
+
5
16
  /**
6
17
  * PluginLogger provides structured logging capabilities for individual plugins.
7
18
  */
@@ -61,13 +72,13 @@ export class PluginLogger implements IPluginLogger<PluginLoggerManager> {
61
72
  * @param level - The severity level of the log.
62
73
  * @param args - The log message and optional data payload.
63
74
  */
64
- private log(level: LogLevel, ...args: LogDataType[]): void {
75
+ private log(level: LogLevelString, ...args: LogDataType[]): void {
65
76
  const [message, ...data] = args
66
77
 
67
78
  const normalizedLevel = (typeof level === 'string' ? level.toUpperCase() : level) as string
68
- const allowedLevels: LogLevel[] = ['INFO', 'WARN', 'ERROR', 'DEBUG']
69
- const resolvedLevel = (allowedLevels.includes(normalizedLevel as LogLevel)
70
- ? (normalizedLevel as LogLevel)
79
+ const allowedLevels: LogLevelString[] = ['INFO', 'WARN', 'ERROR', 'DEBUG']
80
+ const resolvedLevel = (allowedLevels.includes(normalizedLevel as LogLevelString)
81
+ ? (normalizedLevel as LogLevelString)
71
82
  : 'INFO')
72
83
  if (resolvedLevel === 'INFO' && normalizedLevel !== 'INFO') {
73
84
  console.warn(
@@ -75,11 +86,12 @@ export class PluginLogger implements IPluginLogger<PluginLoggerManager> {
75
86
  )
76
87
  }
77
88
 
78
- const levelColorMap: Record<LogLevel, (input: string) => string> = {
89
+ const levelColorMap: Record<LogLevelString, (input: string) => string> = {
79
90
  INFO: chalk.bgBlue,
80
91
  WARN: chalk.bgYellow,
81
92
  ERROR: chalk.bgRed,
82
93
  DEBUG: chalk.bgGray,
94
+ NONE: chalk.bgBlack,
83
95
  }
84
96
  const colorize = levelColorMap[resolvedLevel] ?? ((input: string) => input)
85
97
 
@@ -93,17 +105,12 @@ export class PluginLogger implements IPluginLogger<PluginLoggerManager> {
93
105
  }
94
106
  this.manager.append(log)
95
107
 
96
- if (resolvedLevel === 'DEBUG') {
97
- console.debug(
98
- `${chalk.bgMagenta('[PluginLog]')} ${colorize(resolvedLevel)} ${this.pluginName} - ${message}`,
99
- ...data,
100
- )
101
- }
102
- else {
103
- console.log(
104
- `${chalk.bgMagenta('[PluginLog]')} ${colorize(resolvedLevel)} ${this.pluginName} - ${message}`,
105
- ...data,
106
- )
108
+ if (shouldWriteToStdout(resolvedLevel)) {
109
+ const baseMessage = `${chalk.bgMagenta('[PluginLog]')} ${colorize(resolvedLevel)} ${this.pluginName} - ${message}`
110
+ const extra = data.length
111
+ ? ` ${data.map(item => (typeof item === 'string' ? item : inspect(item))).join(' ')}`
112
+ : ''
113
+ process.stdout.write(`${baseMessage}${extra}\n`)
107
114
  }
108
115
  }
109
116
  }
package/plugin/preload.ts CHANGED
@@ -1,5 +1,9 @@
1
1
  import type { ITouchClientChannel } from '../channel'
2
+ import type { ITuffTransport } from '../transport'
2
3
  import type { ITouchSDK } from './sdk/index'
4
+ import { getLogger } from '../common/logger'
5
+ import { createPluginTuffTransport } from '../transport'
6
+ import { defineRawEvent } from '../transport/event/builder'
3
7
  // Import SDK for side effects (initializes hooks)
4
8
  import './sdk/index'
5
9
 
@@ -9,8 +13,11 @@ declare global {
9
13
  $plugin: {
10
14
  name: string
11
15
  path: object
16
+ version?: string
17
+ sdkapi?: number
12
18
  }
13
19
  $channel: ITouchClientChannel
20
+ $transport?: ITuffTransport
14
21
  $crash: (message: string, extraData: any) => void
15
22
  $config: {
16
23
  themeStyle: any
@@ -19,14 +26,30 @@ declare global {
19
26
  }
20
27
  }
21
28
 
29
+ const preloadLog = getLogger('plugin-preload')
30
+ const crashEvent = defineRawEvent<Record<string, string | number | boolean | undefined>, void>('crash')
31
+
22
32
  export function initTuff(window: Window) {
23
33
  const plugin = window.$plugin
24
34
  if (!plugin)
25
35
  throw new Error('Plugin has a fatal error! Please check your plugin!')
26
36
 
37
+ if (!window.$transport && window.$channel) {
38
+ window.$transport = createPluginTuffTransport(window.$channel)
39
+ }
40
+ if (window.$transport) {
41
+ window.addEventListener?.('beforeunload', () => {
42
+ window.$transport?.destroy()
43
+ }, { once: true })
44
+ }
45
+
27
46
  window.$crash = function (message, extraData) {
28
- window.$channel.send('crash', { message, ...extraData })
47
+ if (window.$transport) {
48
+ void window.$transport.send(crashEvent, { message, ...extraData })
49
+ return
50
+ }
51
+ window.$channel?.send?.('crash', { message, ...extraData })
29
52
  }
30
53
 
31
- console.log(`%c[Plugin] ${plugin.name} loaded`, 'color: #42b983')
54
+ preloadLog.info(`[Plugin] ${plugin.name} loaded`)
32
55
  }
@@ -1,6 +1,6 @@
1
+ export * from './market-client'
2
+ export * from './npm-provider'
1
3
  export * from './registry'
2
- export * from './types'
3
- export * from './tpex-types'
4
4
  export * from './tpex-provider'
5
- export * from './npm-provider'
6
- export * from './market-client'
5
+ export * from './tpex-types'
6
+ export * from './types'
@@ -133,7 +133,8 @@ export class PluginMarketClient {
133
133
  }
134
134
 
135
135
  const sorted = results.sort((a, b) => {
136
- if (a.isOfficial !== b.isOfficial) return a.isOfficial ? -1 : 1
136
+ if (a.isOfficial !== b.isOfficial)
137
+ return a.isOfficial ? -1 : 1
137
138
  return (b.downloads ?? 0) - (a.downloads ?? 0)
138
139
  })
139
140
 
@@ -156,7 +157,8 @@ export class PluginMarketClient {
156
157
  if (source === 'tpex' || (!source && !identifier.includes('/'))) {
157
158
  try {
158
159
  const plugin = await this.tpexProvider.getPlugin(identifier)
159
- if (plugin) return normalizeTpexPlugin(plugin)
160
+ if (plugin)
161
+ return normalizeTpexPlugin(plugin)
160
162
  }
161
163
  catch {
162
164
  // Fall through to npm
@@ -166,7 +168,8 @@ export class PluginMarketClient {
166
168
  if (source === 'npm' || !source) {
167
169
  try {
168
170
  const pkg = await this.npmProvider.getPackageInfo(identifier)
169
- if (pkg) return normalizeNpmPlugin(pkg)
171
+ if (pkg)
172
+ return normalizeNpmPlugin(pkg)
170
173
  }
171
174
  catch {
172
175
  // Not found