Docs/Plugin index.js Context API

Plugin index.js Context API

Universal Developer

Plugin index.js Context API

Overview

Plugin lifecycle handlers receive a typed IPluginContext. New plugins should use context.utils as the canonical capability surface; the host builds it for the verified calling plugin and applies SDK-version and permission policy before protected operations run.

Legacy globalThis utilities remain available to existing CommonJS plugins, but they are a compatibility projection of the same host-owned capabilities. Do not import CoreApp internals or construct raw transport channels to bypass the context facade.

Canonical lifecycle context

EXAMPLE.TYPESCRIPT
import type { IPluginLifecycle } from '@talex-touch/utils/plugin/sdk'

const lifecycle: IPluginLifecycle = {
async onInit(context) {
const {
logger,
http,
storage,
secret,
clipboard,
channel,
dialog,
box,
feature,
quickActions,
quickOps,
intelligence,
screenshot,
system,
i18n,
lexicon,
power,
recommend,
divisionBox,
openUrl
} = context.utils

    logger.info(`Loaded ${context.pluginName}`)
    void [
      http,
      storage,
      secret,
      clipboard,
      channel,
      dialog,
      box,
      feature,
      quickActions,
      quickOps,
      intelligence,
      screenshot,
      system,
      i18n,
      lexicon,
      power,
      recommend,
      divisionBox,
      openUrl
    ]
  },

    onFeatureTriggered(featureId, query) {
      // `query` can be a string or a TuffQuery with text/image/files/html inputs.
      console.log(featureId, query)
    }

}

## export default lifecycle

context also includes pluginPath and plugin config. Store secrets through context.utils.secret, not normal storage or logs.

Capability groups

Context fieldPurposePrimary documentation
box, featureCoreBox window and result-item lifecycleBox, Feature
clipboard, storage, secretClipboard, plugin data, and protected credentialsClipboard, Storage
intelligenceAI capability discovery, invoke, and streamIntelligence
screenshotPermission-gated display, cursor, and region captureScreenshot
systemActive app and permission-gated selected textClipboard
i18n, lexiconHost locale and plugin-scoped Domain LexiconLocalization
quickActions, quickOpsGlobal actions and bounded built-in toolsQuick Actions, QuickOps
divisionBox, channelIndependent windows and plugin transportDivisionBox, Channel
power, recommendLow-power adaptation and recommendation providersPower, Recommend

Protected capabilities still require the matching manifest.json permission declaration and current grant. A field being present on context.utils is not proof that every operation is authorized.

Legacy global compatibility

Existing index.js plugins may still read utilities such as logger, clipboard, storage, feature, box, and openUrl from globalThis. New lifecycle code should capture context.utils in onInit instead, because it exposes the complete typed SDK surface, including secret, intelligence, screenshot, system, i18n, and lexicon.


logger

Plugin logger - logs are saved to the plugin's log directory.

EXAMPLE.JAVASCRIPT
logger.info('Info message', { extra: 'data' })
logger.warn('Warning message')
logger.error('Error message', error)
logger.debug('Debug message')

http

HTTP request library (axios-based):

EXAMPLE.JAVASCRIPT
// GET request
const response = await http.get('https://api.example.com/data', {
headers: { 'Authorization': 'Bearer token' },
signal // AbortSignal for cancellation
})

// POST request
const result = await http.post('https://api.example.com/submit', {
data: 'payload'
}, { signal })

clipboard

Clipboard operations:

EXAMPLE.JAVASCRIPT
// Write text
clipboard.writeText('Copied content')

// Read text
const text = clipboard.readText()

// Read image
const image = clipboard.readImage()

// Write image
clipboard.writeImage(nativeImage)

storage

Plugin-specific storage (10MB limit per plugin):

EXAMPLE.JAVASCRIPT
// Read config file
const config = storage.getFile('providers_config')

// Save config file
storage.setFile('providers_config', { key: 'value' })

// Delete config file
storage.deleteFile('old_config')

// List all files
const files = storage.listFiles() // ['file1', 'file2']

// Watch for config changes
const unsubscribe = storage.onDidChange('providers_config', (newConfig) => {
console.log('Config updated:', newConfig)
})

// Unsubscribe
unsubscribe()

power

PowerSDK for low-power adaptation:

EXAMPLE.JAVASCRIPT
// Read current low-power status
const status = await power.getLowPowerStatus({ threshold: 25 })

if (status.lowPower) {
logger.info('Skip expensive background tasks')
}

// Listen to status changes
const disposePower = power.onLowPowerChanged((nextStatus) => {
logger.info('Low power changed', nextStatus)
})

// Optional: stop listening
disposePower()

Note: In index.js context, power.onLowPowerChanged currently uses polling (about 60s), and strict real-time push is pending.


recommend

RecommendSDK for registering custom recommendation providers with CoreBox. registerProvider and unregisterProvider both return a Promise and must be awaited; a provider must implement onExecute or registration throws.

EXAMPLE.JAVASCRIPT
// Register recommendation provider
const dispose = await recommend.registerProvider({
id: 'my-recommendation',
name: 'My Recommendations',
canProvide(context) {
return context.time.timeSlot === 'morning'
},
getCandidates(context) {
return [{
id: 'morning-tip',
title: 'Morning Reminder',
subtitle: 'Start a new day',
icon: { type: 'emoji', value: '☀️' },
priority: 75,
action: 'show-morning-tip'
}]
},
// true or undefined means the major action was accepted and the host records
// one execution; false or a thrown error means failure and nothing is counted.
async onExecute(candidate) {
if (candidate.action !== 'show-morning-tip') return false
return await showMorningTip()
}
})

// Unregister provider
await dispose()
// or
await recommend.unregisterProvider('my-recommendation')

See RecommendSDK API for full documentation.


feature

Feature SDK for managing search results:

EXAMPLE.JAVASCRIPT
// Push search results
feature.pushItems([
new TuffItemBuilder('item-1')
.setTitle('Search Result Title')
.setSubtitle('Subtitle')
.setIcon({ type: 'file', value: 'assets/icon.svg' })
.build()
])

// Clear current plugin's search results
feature.clearItems()

// Get current plugin's search results
const items = feature.getItems()

box

CoreBox control SDK:

EXAMPLE.JAVASCRIPT
// Hide CoreBox
box.hide()

// Show CoreBox
box.show()

// Set input content
box.setInput('New input content')

// Get input content
const input = box.getInput()

boxItems

BoxItem management SDK (new API):

EXAMPLE.JAVASCRIPT
// Push single item
boxItems.push(item)

// Push multiple items
boxItems.pushItems([item1, item2])

// Update specific item
boxItems.update('item-id', { title: 'New Title' })

// Remove specific item
boxItems.remove('item-id')

// Clear all items for this plugin
boxItems.clear()

// Get all items for this plugin
const items = boxItems.getItems()

quickActions / meta

QuickActions SDK registers MetaK / Quick Actions global actions and can call native share from those actions. meta is a compatibility alias that points to the same SDK instance. New plugins should prefer quickActions.

EXAMPLE.JAVASCRIPT
quickActions.registerAction({
id: 'share-current-item',
render: {
basic: {
title: 'Share current item',
subtitle: 'Use the current platform native share target',
icon: { type: 'class', value: 'i-ri-share-line' }
},
group: 'Share'
}
})

quickActions.onActionExecute(async ({ actionId, item }) => {
if (actionId !== 'share-current-item') return

    const result = await quickActions.shareItem(item, {
      preferredTargets: ['airdrop', 'system-share', 'mail']
    })

    if (!result.success) {
      logger.warn('Native share failed', result.error)
    }

## })

Common methods:

MethodDescription
registerAction(action)Register a MetaK / Quick Actions global action
onActionExecute(handler)Listen for actions registered by this plugin
getNativeShareTargets(payloadType?)Read native share targets available on this platform
resolveNativeShareTarget(options?)Resolve a target by payload type and preference order
nativeShare(payload, options?)Run native share through Flow Transfer
createSharePayloadFromItem(item, options?)Convert a CoreBox item into a Flow payload
shareItem(item, options?)Build item payload, resolve target, and share in one call

See QuickActions SDK for full documentation.


plugin

Current plugin info API:

EXAMPLE.JAVASCRIPT
// Get complete plugin info
const info = plugin.getInfo()
// { name, version, desc, readme, dev, status, features, issues, ... }

// Get plugin path
const path = plugin.getPath()

// Get data directory
const dataPath = plugin.getDataPath()

// Get config directory
const configPath = plugin.getConfigPath()

// Get logs directory
const logsPath = plugin.getLogsPath()

// Get temp directory
const tempPath = plugin.getTempPath()

// Get current status
const status = plugin.getStatus()

// Get dev configuration
const devInfo = plugin.getDevInfo()

// Get platform support info
const platforms = plugin.getPlatforms()

plugins

Other plugins API (read-only access):

EXAMPLE.JAVASCRIPT
// Get all plugins list
const allPlugins = await plugins.list()

// Get specific plugin info
const otherPlugin = await plugins.get('other-plugin-name')

// Get plugin status
const status = await plugins.getStatus('other-plugin-name')

features

Dynamic Feature management:

EXAMPLE.JAVASCRIPT
// Add Feature dynamically
features.addFeature({
id: 'dynamic-feature',
name: 'Dynamic Feature',
desc: 'Runtime-added feature',
icon: { type: 'file', value: 'assets/icon.svg' },
push: true,
commands: [{ type: 'over', value: ['dynamic'] }],
priority: 5
})

// Remove Feature
features.removeFeature('dynamic-feature')

// Get all Features
const allFeatures = features.getFeatures()

// Get specific Feature
const feature = features.getFeature('feature-id')

// Set priority
features.setPriority('feature-id', 10)

// Get priority
const priority = features.getPriority('feature-id')

// Get sorted by priority
const sorted = features.getFeaturesByPriority()

Runtime-added features with icon.type: 'file' are initialized by the host. Relative values are resolved against the owning plugin root; traversal and missing targets fail closed instead of leaving an unresolved relative path in CoreBox.


channel

IPC channel bridge:

EXAMPLE.JAVASCRIPT
// Send message to main process
const result = await channel.sendToMain('event-name', { data: 'payload' })

// Send message to renderer process
await channel.sendToRenderer('event-name', { data: 'payload' })

// Listen to main process messages
const dispose = channel.onMain('event-name', (data) => {
console.log('Received from main:', data)
})

// Listen to renderer process messages
const dispose = channel.onRenderer('event-name', (data) => {
console.log('Received from renderer:', data)
})

// Access raw channel object
channel.raw

$event

Feature event listeners:

EXAMPLE.JAVASCRIPT
// Listen to Feature lifecycle
$event.onFeatureLifeCycle('feature-id', {
onLaunch: (feature) => { console.log('Launched', feature) },
onFeatureTriggered: (data, feature) => { console.log('Triggered', data) },
onInputChanged: (input) => { console.log('Input changed', input) },
onClose: (feature) => { console.log('Closed', feature) }
})

// Remove listener
$event.offFeatureLifeCycle('feature-id', callback)

dialog

System dialogs:

EXAMPLE.JAVASCRIPT
// Message dialog
await dialog.showMessageBox({
type: 'info',
title: 'Title',
message: 'Message content',
buttons: ['OK', 'Cancel']
})

// Open file dialog
const result = await dialog.showOpenDialog({
properties: ['openFile', 'multiSelections'],
filters: [{ name: 'Images', extensions: ['jpg', 'png'] }]
})

// Save file dialog
const result = await dialog.showSaveDialog({
defaultPath: 'file.txt'
})

divisionBox

DivisionBox SDK for creating independent windows:

EXAMPLE.JAVASCRIPT
// Open DivisionBox
const session = await divisionBox.open({
url: 'plugin://my-plugin/index.html',
title: 'Independent Window',
size: 'medium', // 'compact' | 'medium' | 'expanded'
keepAlive: true
})

// Close DivisionBox
await divisionBox.close(session.sessionId)

// Listen to state changes
divisionBox.onStateChange(session.sessionId, (state) => {
console.log('State changed:', state)
})

TuffItemBuilder

Search result builder:

EXAMPLE.JAVASCRIPT
const item = new TuffItemBuilder('unique-id')
.setSource('plugin', 'plugin-features')
.setTitle('Title')
.setSubtitle('Subtitle')
.setIcon({ type: 'file', value: 'assets/icon.svg' })
.createAndAddAction('action-id', 'copy', 'Copy', 'Content to copy')
.addTag('Tag', 'blue')
.setMeta({
pluginName: 'my-plugin',
featureId: 'my-feature',
customData: 'any value'
})
.build()

openUrl

Open external links:

EXAMPLE.JAVASCRIPT
openUrl('https://example.com')

Lifecycle Hooks

Plugin index.js must export a lifecycle hooks object:

EXAMPLE.JAVASCRIPT
const pluginLifecycle = {
/\*\*
_ Called when a Feature is triggered
_ @param {string} featureId - Feature ID
_ @param {string|TuffQuery} query - Query content
_ @param {IPluginFeature} feature - Feature definition
_ @param {AbortSignal} signal - For cancellation
_/
async onFeatureTriggered(featureId, query, feature, signal) {
// Compatibility: query can be string or TuffQuery object
const queryText = typeof query === 'string' ? query : query?.text

      // Handle Feature logic...
    },

    /**
     * Called when a search result item is clicked
     * @param {TuffItem} item - The clicked item
     */
    async onItemAction(item) {
      if (item.meta?.defaultAction === 'copy') {
        const copyAction = item.actions.find(a => a.type === 'copy')
        if (copyAction?.payload) {
          clipboard.writeText(copyAction.payload)
          box.hide()
        }
      }
    }

}

## module.exports = pluginLifecycle

Technical Notes

  • Context objects are injected by the main process into the plugin sandbox runtime.
  • Capability limits and permission checks are enforced before exposing APIs.

Best Practices

  1. Use AbortSignal: Pass signal parameter in async operations for cancellation support
  2. Error Handling: Wrap all async operations with try-catch
  3. Logging: Use logger instead of console for debugging and collection
  4. Storage Limits: Mind the 10MB storage limit, use tempPath for large files
  5. TuffQuery Compatibility: Handle query as both string and object formats