pluginCliCommands.ts
services/plugins/pluginCliCommands.ts
345
Lines
10894
Bytes
8
Exports
11
Imports
10
Keywords
What this is
This page documents one file from the repository and includes its full source so you can read it without leaving the docs site.
Beginner explanation
This file is one piece of the larger system. Its name, directory, imports, and exports show where it fits. Start by reading the exports and related files first.
How it is used
Start from the exports list and related files. Those are the easiest clues for where this file fits into the system.
Expert explanation
Architecturally, this file intersects with commands, integrations. It contains 345 lines, 11 detected imports, and 8 detected exports.
Important relationships
Detected exports
installPluginuninstallPluginenablePlugindisablePlugindisableAllPluginsupdatePluginCliVALID_INSTALLABLE_SCOPESVALID_UPDATE_SCOPES
Keywords
pluginresultscopemarketplacenameconsolecommandanalyticsmetadata_i_verified_this_is_pii_taggedmessageanalyticsmetadata_i_verified_this_is_not_code_or_filepaths
Detected imports
figures../../utils/errors.js../../utils/gracefulShutdown.js../../utils/log.js../../utils/plugins/managedPlugins.js../../utils/plugins/pluginIdentifier.js../../utils/plugins/schemas.js../../utils/process.js../../utils/telemetry/pluginTelemetry.js../analytics/index.js./pluginOperations.js
Source notes
This page embeds the full file contents. Small or leaf files are still indexed honestly instead of being over-explained.
Full source
/**
* 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)
}
}