@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.
Files changed (235) 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 +97 -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 +21 -3
  72. package/market/index.ts +1 -1
  73. package/market/types.ts +20 -5
  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 +82 -8
  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 -0
  87. package/plugin/providers/market-client.ts +218 -0
  88. package/plugin/providers/npm-provider.ts +228 -0
  89. package/plugin/providers/tpex-provider.ts +297 -0
  90. package/plugin/providers/tpex-types.ts +34 -0
  91. package/plugin/sdk/box-items.ts +14 -0
  92. package/plugin/sdk/box-sdk.ts +64 -0
  93. package/plugin/sdk/channel.ts +119 -4
  94. package/plugin/sdk/clipboard.ts +26 -12
  95. package/plugin/sdk/cloud-sync.ts +113 -0
  96. package/plugin/sdk/common.ts +19 -11
  97. package/plugin/sdk/core-box.ts +6 -15
  98. package/plugin/sdk/division-box.ts +160 -65
  99. package/plugin/sdk/examples/storage-onDidChange-example.js +5 -2
  100. package/plugin/sdk/feature-sdk.ts +111 -76
  101. package/plugin/sdk/flow.ts +146 -45
  102. package/plugin/sdk/hooks/bridge.ts +113 -49
  103. package/plugin/sdk/hooks/life-cycle.ts +35 -16
  104. package/plugin/sdk/index.ts +14 -3
  105. package/plugin/sdk/intelligence.ts +87 -0
  106. package/plugin/sdk/meta/README.md +179 -0
  107. package/plugin/sdk/meta-sdk.ts +244 -0
  108. package/plugin/sdk/notification.ts +9 -0
  109. package/plugin/sdk/performance.ts +1 -16
  110. package/plugin/sdk/plugin-info.ts +64 -0
  111. package/plugin/sdk/power.ts +155 -0
  112. package/plugin/sdk/recommend.ts +21 -0
  113. package/plugin/sdk/service/index.ts +12 -8
  114. package/plugin/sdk/sqlite.ts +141 -0
  115. package/plugin/sdk/storage.ts +2 -6
  116. package/plugin/sdk/system.ts +2 -9
  117. package/plugin/sdk/temp-files.ts +41 -0
  118. package/plugin/sdk/touch-sdk.ts +18 -0
  119. package/plugin/sdk/types.ts +44 -4
  120. package/plugin/sdk/window/index.ts +12 -9
  121. package/plugin/sdk-version.ts +231 -0
  122. package/preload/renderer.ts +3 -2
  123. package/renderer/hooks/arg-mapper.ts +34 -6
  124. package/renderer/hooks/index.ts +13 -0
  125. package/renderer/hooks/initialize.ts +2 -1
  126. package/renderer/hooks/use-agent-market-sdk.ts +7 -0
  127. package/renderer/hooks/use-agent-market.ts +106 -0
  128. package/renderer/hooks/use-agents-sdk.ts +7 -0
  129. package/renderer/hooks/use-app-sdk.ts +7 -0
  130. package/renderer/hooks/use-channel.ts +33 -4
  131. package/renderer/hooks/use-download-sdk.ts +21 -0
  132. package/renderer/hooks/use-intelligence-sdk.ts +7 -0
  133. package/renderer/hooks/use-intelligence-stats.ts +290 -0
  134. package/renderer/hooks/use-intelligence.ts +202 -104
  135. package/renderer/hooks/use-market-sdk.ts +16 -0
  136. package/renderer/hooks/use-notification-sdk.ts +7 -0
  137. package/renderer/hooks/use-permission-sdk.ts +7 -0
  138. package/renderer/hooks/use-permission.ts +325 -0
  139. package/renderer/hooks/use-platform-sdk.ts +7 -0
  140. package/renderer/hooks/use-plugin-sdk.ts +16 -0
  141. package/renderer/hooks/use-settings-sdk.ts +7 -0
  142. package/renderer/hooks/use-update-sdk.ts +21 -0
  143. package/renderer/index.ts +1 -0
  144. package/renderer/ref.ts +19 -10
  145. package/renderer/shared/components/SharedPluginDetailContent.vue +84 -0
  146. package/renderer/shared/components/SharedPluginDetailHeader.vue +116 -0
  147. package/renderer/shared/components/SharedPluginDetailMetaList.vue +39 -0
  148. package/renderer/shared/components/SharedPluginDetailReadme.vue +45 -0
  149. package/renderer/shared/components/SharedPluginDetailVersions.vue +98 -0
  150. package/renderer/shared/components/index.ts +5 -0
  151. package/renderer/shared/components/shims-vue.d.ts +5 -0
  152. package/renderer/shared/index.ts +2 -0
  153. package/renderer/shared/plugin-detail.ts +62 -0
  154. package/renderer/storage/app-settings.ts +3 -1
  155. package/renderer/storage/base-storage.ts +508 -82
  156. package/renderer/storage/intelligence-storage.ts +37 -46
  157. package/renderer/storage/openers.ts +3 -1
  158. package/renderer/storage/storage-subscription.ts +126 -42
  159. package/renderer/touch-sdk/env.ts +10 -10
  160. package/renderer/touch-sdk/index.ts +114 -18
  161. package/renderer/touch-sdk/terminal.ts +24 -13
  162. package/search/feature-matcher.ts +279 -0
  163. package/search/fuzzy-match.ts +64 -34
  164. package/search/index.ts +10 -0
  165. package/search/levenshtein-utils.ts +17 -11
  166. package/transport/errors.ts +310 -0
  167. package/transport/event/builder.ts +378 -0
  168. package/transport/event/index.ts +7 -0
  169. package/transport/event/types.ts +292 -0
  170. package/transport/events/index.ts +2670 -0
  171. package/transport/events/meta-overlay.ts +79 -0
  172. package/transport/events/types/agents.ts +177 -0
  173. package/transport/events/types/app-index.ts +9 -0
  174. package/transport/events/types/app.ts +475 -0
  175. package/transport/events/types/box-item.ts +222 -0
  176. package/transport/events/types/clipboard.ts +80 -0
  177. package/transport/events/types/core-box.ts +534 -0
  178. package/transport/events/types/device-idle.ts +7 -0
  179. package/transport/events/types/division-box.ts +99 -0
  180. package/transport/events/types/download.ts +115 -0
  181. package/transport/events/types/file-index.ts +73 -0
  182. package/transport/events/types/flow.ts +149 -0
  183. package/transport/events/types/index.ts +70 -0
  184. package/transport/events/types/market.ts +39 -0
  185. package/transport/events/types/meta-overlay.ts +184 -0
  186. package/transport/events/types/notification.ts +140 -0
  187. package/transport/events/types/permission.ts +90 -0
  188. package/transport/events/types/platform.ts +8 -0
  189. package/transport/events/types/plugin.ts +620 -0
  190. package/transport/events/types/sentry.ts +20 -0
  191. package/transport/events/types/storage.ts +208 -0
  192. package/transport/events/types/transport.ts +60 -0
  193. package/transport/events/types/tray.ts +16 -0
  194. package/transport/events/types/update.ts +78 -0
  195. package/transport/index.ts +139 -0
  196. package/transport/main.ts +2 -0
  197. package/transport/sdk/constants.ts +29 -0
  198. package/transport/sdk/domains/agents-market.ts +47 -0
  199. package/transport/sdk/domains/agents.ts +62 -0
  200. package/transport/sdk/domains/app.ts +48 -0
  201. package/transport/sdk/domains/disposable.ts +35 -0
  202. package/transport/sdk/domains/download.ts +139 -0
  203. package/transport/sdk/domains/index.ts +13 -0
  204. package/transport/sdk/domains/intelligence.ts +616 -0
  205. package/transport/sdk/domains/market.ts +35 -0
  206. package/transport/sdk/domains/notification.ts +62 -0
  207. package/transport/sdk/domains/permission.ts +85 -0
  208. package/transport/sdk/domains/platform.ts +19 -0
  209. package/transport/sdk/domains/plugin.ts +144 -0
  210. package/transport/sdk/domains/settings.ts +92 -0
  211. package/transport/sdk/domains/update.ts +64 -0
  212. package/transport/sdk/index.ts +60 -0
  213. package/transport/sdk/main-transport.ts +710 -0
  214. package/transport/sdk/main.ts +9 -0
  215. package/transport/sdk/plugin-transport.ts +654 -0
  216. package/transport/sdk/port-policy.ts +38 -0
  217. package/transport/sdk/renderer-transport.ts +1165 -0
  218. package/transport/types.ts +605 -0
  219. package/types/agent.ts +399 -0
  220. package/types/cloud-sync.ts +157 -0
  221. package/types/division-box.ts +47 -27
  222. package/types/download.ts +1 -0
  223. package/types/flow.ts +63 -12
  224. package/types/icon.ts +2 -1
  225. package/types/index.ts +5 -0
  226. package/types/intelligence.ts +1492 -81
  227. package/types/modules/base.ts +2 -0
  228. package/types/path-browserify.d.ts +5 -0
  229. package/types/platform.ts +12 -0
  230. package/types/startup-info.ts +32 -0
  231. package/types/touch-app-core.ts +8 -8
  232. package/types/update.ts +94 -1
  233. package/vitest.config.ts +25 -0
  234. package/auth/useClerkConfig.ts +0 -40
  235. package/auth/useClerkProvider.ts +0 -52
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,10 +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
137
- onTrigger: () => void
182
+ value: string | string[] | RegExp | FeatureCommandMatcher
183
+ /** Optional trigger callback - not serialized over IPC */
184
+ onTrigger?: () => void
138
185
  }
139
186
 
187
+ export type FeatureCommandMatcher = (queryText: string) => boolean
188
+
140
189
  export interface IPluginFeature {
141
190
  id: string
142
191
  name: string
@@ -147,6 +196,10 @@ export interface IPluginFeature {
147
196
  platform: IPlatform
148
197
  commands: IFeatureCommand[]
149
198
  interaction?: IFeatureInteraction
199
+ /**
200
+ * Experimental features are hidden unless the plugin runs in dev mode.
201
+ */
202
+ experimental?: boolean
150
203
  /**
151
204
  * Internal search tokens generated at runtime for better matching
152
205
  */
@@ -174,6 +227,17 @@ export interface IFeatureInteraction {
174
227
  * The relative path to the html file from the plugin root.
175
228
  */
176
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
177
241
  }
178
242
 
179
243
  /**
@@ -390,6 +454,16 @@ export interface IManifest {
390
454
  * Version of the plugin, following semantic versioning (e.g., "1.0.0").
391
455
  */
392
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
393
467
  /**
394
468
  * Short description of the plugin's functionality.
395
469
  */
@@ -499,8 +573,8 @@ export interface IManifest {
499
573
  }
500
574
 
501
575
  export * from './install'
502
- export type { IPluginLogger, LogDataType, LogItem, LogLevel } from './log/types'
503
- export * from './providers'
576
+ export type { IPluginLogger, LogDataType, LogItem } from './log/types'
504
577
  export * from './risk'
505
- 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.
506
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,2 +1,6 @@
1
+ export * from './market-client'
2
+ export * from './npm-provider'
1
3
  export * from './registry'
4
+ export * from './tpex-provider'
5
+ export * from './tpex-types'
2
6
  export * from './types'
@@ -0,0 +1,218 @@
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)
137
+ return a.isOfficial ? -1 : 1
138
+ return (b.downloads ?? 0) - (a.downloads ?? 0)
139
+ })
140
+
141
+ const paginated = sorted.slice(offset, offset + limit)
142
+
143
+ return {
144
+ plugins: paginated,
145
+ total: results.length,
146
+ sources: {
147
+ tpex: tpexCount,
148
+ npm: npmCount,
149
+ },
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Get plugin details by identifier
155
+ */
156
+ async getPlugin(identifier: string, source?: PluginSourceType): Promise<MarketPluginInfo | null> {
157
+ if (source === 'tpex' || (!source && !identifier.includes('/'))) {
158
+ try {
159
+ const plugin = await this.tpexProvider.getPlugin(identifier)
160
+ if (plugin)
161
+ return normalizeTpexPlugin(plugin)
162
+ }
163
+ catch {
164
+ // Fall through to npm
165
+ }
166
+ }
167
+
168
+ if (source === 'npm' || !source) {
169
+ try {
170
+ const pkg = await this.npmProvider.getPackageInfo(identifier)
171
+ if (pkg)
172
+ return normalizeNpmPlugin(pkg)
173
+ }
174
+ catch {
175
+ // Not found
176
+ }
177
+ }
178
+
179
+ return null
180
+ }
181
+
182
+ /**
183
+ * Get install source string for a plugin
184
+ */
185
+ getInstallSource(plugin: MarketPluginInfo): string {
186
+ if (plugin.source === 'tpex') {
187
+ return `tpex:${plugin.slug}`
188
+ }
189
+ return `npm:${plugin.id}`
190
+ }
191
+
192
+ /**
193
+ * Get provider type for a plugin
194
+ */
195
+ getProviderType(plugin: MarketPluginInfo): PluginProviderType {
196
+ return plugin.source === 'tpex'
197
+ ? PluginProviderType.TPEX
198
+ : PluginProviderType.NPM
199
+ }
200
+
201
+ /**
202
+ * List all plugins from official source (TPEX)
203
+ */
204
+ async listOfficialPlugins(): Promise<MarketPluginInfo[]> {
205
+ const plugins = await this.tpexProvider.listPlugins()
206
+ return plugins.map(normalizeTpexPlugin)
207
+ }
208
+
209
+ /**
210
+ * List all plugins from npm
211
+ */
212
+ async listNpmPlugins(): Promise<MarketPluginInfo[]> {
213
+ const plugins = await this.npmProvider.listPlugins()
214
+ return plugins.map(normalizeNpmPlugin)
215
+ }
216
+ }
217
+
218
+ export const defaultMarketClient = new PluginMarketClient()