π File detail
services/plugins/pluginCliCommands.ts
π― Use case
This file lives under βservices/β, which covers long-lived services (LSP, MCP, OAuth, tool execution, memory, compaction, voice, settings sync, β¦). On the API surface it exposes installPlugin, uninstallPlugin, enablePlugin, disablePlugin, and disableAllPlugins (and more) β mainly functions, hooks, or classes. Dependencies touch figures. It composes internal code from utils, analytics, and pluginOperations (relative imports). What the file header says: CLI command wrappers for plugin operations This module provides thin wrappers around the core plugin operations that handle CLI-specific concerns like console output and process exit. For the core operations (without CLI side effects), see pluginOperations.ts.
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
CLI command wrappers for plugin operations This module provides thin wrappers around the core plugin operations that handle CLI-specific concerns like console output and process exit. For the core operations (without CLI side effects), see pluginOperations.ts
π€ Exports (heuristic)
installPluginuninstallPluginenablePlugindisablePlugindisableAllPluginsupdatePluginCliVALID_INSTALLABLE_SCOPESVALID_UPDATE_SCOPES
π External import roots
Package roots from from "β¦" (relative paths omitted).
figures
π₯οΈ Source preview
/**
* CLI command wrappers for plugin operations
*
* This module provides thin wrappers around the core plugin operations
* that handle CLI-specific concerns like console output and process exit.
*
* For the core operations (without CLI side effects), see pluginOperations.ts
*/
import figures from 'figures'
import { errorMessage } from '../../utils/errors.js'
import { gracefulShutdown } from '../../utils/gracefulShutdown.js'
import { logError } from '../../utils/log.js'
import { getManagedPluginNames } from '../../utils/plugins/managedPlugins.js'
import { parsePluginIdentifier } from '../../utils/plugins/pluginIdentifier.js'
import type { PluginScope } from '../../utils/plugins/schemas.js'
import { writeToStdout } from '../../utils/process.js'
import {
buildPluginTelemetryFields,
classifyPluginCommandError,
} from '../../utils/telemetry/pluginTelemetry.js'
import {
type AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
type AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
logEvent,
} from '../analytics/index.js'
import {
disableAllPluginsOp,
disablePluginOp,
enablePluginOp,
type InstallableScope,
installPluginOp,
uninstallPluginOp,
updatePluginOp,
VALID_INSTALLABLE_SCOPES,
VALID_UPDATE_SCOPES,
} from './pluginOperations.js'
export { VALID_INSTALLABLE_SCOPES, VALID_UPDATE_SCOPES }
type PluginCliCommand =
| 'install'
| 'uninstall'
| 'enable'
| 'disable'
| 'disable-all'
| 'update'
/**
* Generic error handler for plugin CLI commands. Emits
* tengu_plugin_command_failed before exit so dashboards can compute a
* success rate against the corresponding success events.
*/
function handlePluginCommandError(
error: unknown,
command: PluginCliCommand,
plugin?: string,
): never {
logError(error)
const operation = plugin
? `${command} plugin "${plugin}"`
: command === 'disable-all'
? 'disable all plugins'
: `${command} plugins`
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.error(
`${figures.cross} Failed to ${operation}: ${errorMessage(error)}`,
)
const telemetryFields = plugin
? (() => {
const { name, marketplace } = parsePluginIdentifier(plugin)
return {
_PROTO_plugin_name:
name as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
...(marketplace && {
_PROTO_marketplace_name:
marketplace as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
}),
...buildPluginTelemetryFields(
name,
marketplace,
getManagedPluginNames(),
),
}
})()
: {}
logEvent('tengu_plugin_command_failed', {
command:
command as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
error_category: classifyPluginCommandError(
error,
) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...telemetryFields,
})
// eslint-disable-next-line custom-rules/no-process-exit
process.exit(1)
}
/**
* CLI command: Install a plugin non-interactively
* @param plugin Plugin identifier (name or plugin@marketplace)
* @param scope Installation scope: user, project, or local (defaults to 'user')
*/
export async function installPlugin(
plugin: string,
scope: InstallableScope = 'user',
): Promise<void> {
try {
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`Installing plugin "${plugin}"...`)
const result = await installPluginOp(plugin, scope)
if (!result.success) {
throw new Error(result.message)
}
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${figures.tick} ${result.message}`)
// _PROTO_* routes to PII-tagged plugin_name/marketplace_name BQ columns.
// Unredacted plugin_id was previously logged to general-access
// additional_metadata for all users β dropped in favor of the privileged
// column route.
const { name, marketplace } = parsePluginIdentifier(
result.pluginId || plugin,
)
logEvent('tengu_plugin_installed_cli', {
_PROTO_plugin_name:
name as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
...(marketplace && {
_PROTO_marketplace_name:
marketplace as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
}),
scope: (result.scope ||
scope) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
install_source:
'cli-explicit' as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...buildPluginTelemetryFields(name, marketplace, getManagedPluginNames()),
})
// eslint-disable-next-line custom-rules/no-process-exit
process.exit(0)
} catch (error) {
handlePluginCommandError(error, 'install', plugin)
}
}
/**
* CLI command: Uninstall a plugin non-interactively
* @param plugin Plugin name or plugin@marketplace identifier
* @param scope Uninstall from scope: user, project, or local (defaults to 'user')
*/
export async function uninstallPlugin(
plugin: string,
scope: InstallableScope = 'user',
keepData = false,
): Promise<void> {
try {
const result = await uninstallPluginOp(plugin, scope, !keepData)
if (!result.success) {
throw new Error(result.message)
}
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${figures.tick} ${result.message}`)
const { name, marketplace } = parsePluginIdentifier(
result.pluginId || plugin,
)
logEvent('tengu_plugin_uninstalled_cli', {
_PROTO_plugin_name:
name as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
...(marketplace && {
_PROTO_marketplace_name:
marketplace as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
}),
scope: (result.scope ||
scope) as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...buildPluginTelemetryFields(name, marketplace, getManagedPluginNames()),
})
// eslint-disable-next-line custom-rules/no-process-exit
process.exit(0)
} catch (error) {
handlePluginCommandError(error, 'uninstall', plugin)
}
}
/**
* CLI command: Enable a plugin non-interactively
* @param plugin Plugin name or plugin@marketplace identifier
* @param scope Optional scope. If not provided, finds the most specific scope for the current project.
*/
export async function enablePlugin(
plugin: string,
scope?: InstallableScope,
): Promise<void> {
try {
const result = await enablePluginOp(plugin, scope)
if (!result.success) {
throw new Error(result.message)
}
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${figures.tick} ${result.message}`)
const { name, marketplace } = parsePluginIdentifier(
result.pluginId || plugin,
)
logEvent('tengu_plugin_enabled_cli', {
_PROTO_plugin_name:
name as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
...(marketplace && {
_PROTO_marketplace_name:
marketplace as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
}),
scope:
result.scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...buildPluginTelemetryFields(name, marketplace, getManagedPluginNames()),
})
// eslint-disable-next-line custom-rules/no-process-exit
process.exit(0)
} catch (error) {
handlePluginCommandError(error, 'enable', plugin)
}
}
/**
* CLI command: Disable a plugin non-interactively
* @param plugin Plugin name or plugin@marketplace identifier
* @param scope Optional scope. If not provided, finds the most specific scope for the current project.
*/
export async function disablePlugin(
plugin: string,
scope?: InstallableScope,
): Promise<void> {
try {
const result = await disablePluginOp(plugin, scope)
if (!result.success) {
throw new Error(result.message)
}
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${figures.tick} ${result.message}`)
const { name, marketplace } = parsePluginIdentifier(
result.pluginId || plugin,
)
logEvent('tengu_plugin_disabled_cli', {
_PROTO_plugin_name:
name as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
...(marketplace && {
_PROTO_marketplace_name:
marketplace as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
}),
scope:
result.scope as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...buildPluginTelemetryFields(name, marketplace, getManagedPluginNames()),
})
// eslint-disable-next-line custom-rules/no-process-exit
process.exit(0)
} catch (error) {
handlePluginCommandError(error, 'disable', plugin)
}
}
/**
* CLI command: Disable all enabled plugins non-interactively
*/
export async function disableAllPlugins(): Promise<void> {
try {
const result = await disableAllPluginsOp()
if (!result.success) {
throw new Error(result.message)
}
// biome-ignore lint/suspicious/noConsole:: intentional console output
console.log(`${figures.tick} ${result.message}`)
logEvent('tengu_plugin_disabled_all_cli', {})
// eslint-disable-next-line custom-rules/no-process-exit
process.exit(0)
} catch (error) {
handlePluginCommandError(error, 'disable-all')
}
}
/**
* CLI command: Update a plugin non-interactively
* @param plugin Plugin name or plugin@marketplace identifier
* @param scope Scope to update
*/
export async function updatePluginCli(
plugin: string,
scope: PluginScope,
): Promise<void> {
try {
writeToStdout(
`Checking for updates for plugin "${plugin}" at ${scope} scopeβ¦\n`,
)
const result = await updatePluginOp(plugin, scope)
if (!result.success) {
throw new Error(result.message)
}
writeToStdout(`${figures.tick} ${result.message}\n`)
if (!result.alreadyUpToDate) {
const { name, marketplace } = parsePluginIdentifier(
result.pluginId || plugin,
)
logEvent('tengu_plugin_updated_cli', {
_PROTO_plugin_name:
name as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
...(marketplace && {
_PROTO_marketplace_name:
marketplace as AnalyticsMetadata_I_VERIFIED_THIS_IS_PII_TAGGED,
}),
old_version: (result.oldVersion ||
'unknown') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
new_version: (result.newVersion ||
'unknown') as AnalyticsMetadata_I_VERIFIED_THIS_IS_NOT_CODE_OR_FILEPATHS,
...buildPluginTelemetryFields(
name,
marketplace,
getManagedPluginNames(),
),
})
}
await gracefulShutdown(0)
} catch (error) {
handlePluginCommandError(error, 'update', plugin)
}
}