#!/usr/bin/env node import { createHash } from 'node:crypto'; import { OnePanelClient } from './client.js'; import { getModule, listModules } from './index.js'; type Flags = Record; async function main(): Promise { const args = process.argv.slice(2); const [command, ...rest] = args; if (!command || command === '--help' || command === '-h' || command === 'help') { printHelp(); return; } switch (command) { case 'modules': handleModules(); return; case 'actions': handleActions(rest); return; case 'sign': handleSign(rest); return; case 'request': await handleRequest(rest); return; case 'run': await handleRun(rest); return; default: throw new Error(`Unknown command: ${command}`); } } function printHelp(): void { console.log(`1panel-skills Usage: 1panel-skills modules 1panel-skills actions 1panel-skills sign [--base-url URL] [--api-key KEY] 1panel-skills request [--body-json JSON] [--query-json JSON] [--node NODE] 1panel-skills run [--input-json JSON] [--input-file FILE] [--node NODE] Environment: ONEPANEL_BASE_URL ONEPANEL_API_KEY ONEPANEL_TIMEOUT_MS ONEPANEL_SKIP_TLS_VERIFY Examples: 1panel-skills modules 1panel-skills actions monitoring 1panel-skills request GET /api/v2/dashboard/base/os 1panel-skills run monitoring getCurrentNode 1panel-skills run websites searchWebsites --input-json '{"page":1,"pageSize":20}' `); } function handleModules(): void { console.log( JSON.stringify( listModules().map((moduleDef) => ({ id: moduleDef.id, title: moduleDef.title, description: moduleDef.description, })), null, 2, ), ); } function handleActions(args: string[]): void { const [moduleId] = args; if (!moduleId) { throw new Error('Missing module name'); } const moduleDef = getRequiredModule(moduleId); console.log( JSON.stringify( { module: moduleDef.id, actions: Object.values(moduleDef.actions).map((action) => ({ id: action.id, summary: action.summary, method: action.method, path: action.path, nodeAware: Boolean(action.nodeAware), })), reservedMutations: moduleDef.reservedMutations, }, null, 2, ), ); } function handleSign(args: string[]): void { const { flags } = parseArgs(args); const config = resolveClientConfig(flags); const timestamp = `${Math.floor(Date.now() / 1000)}`; const token = computeToken(config.apiKey, timestamp); console.log( JSON.stringify( { baseUrl: config.baseUrl, headers: { '1Panel-Timestamp': timestamp, '1Panel-Token': token, }, }, null, 2, ), ); } async function handleRequest(args: string[]): Promise { const [method, path, ...rest] = args; if (!method || !path) { throw new Error('Usage: request [--body-json JSON] [--query-json JSON] [--node NODE]'); } const { flags } = parseArgs(rest); const client = createClient(flags); const body = parseOptionalJson(flags['body-json']); const query = parseOptionalJson(flags['query-json']); const operateNode = readOptionalString(flags.node); const response = await client.request({ method: method.toUpperCase() as 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', path, body, query: isRecord(query) ? (query as Record) : undefined, operateNode, }); printResponse(response.status, response.data, response.rawBody); } async function handleRun(args: string[]): Promise { const [moduleId, actionId, ...rest] = args; if (!moduleId || !actionId) { throw new Error('Usage: run [--input-json JSON] [--input-file FILE] [--node NODE]'); } const { flags } = parseArgs(rest); const moduleDef = getRequiredModule(moduleId); const action = moduleDef.actions[actionId]; if (!action) { throw new Error(`Unknown action "${actionId}" in module "${moduleId}"`); } const input = await loadInput(flags); const operateNode = readOptionalString(flags.node); const finalInput = mergeOperateNode(input, operateNode); const client = createClient(flags); const response = await action.execute(client, finalInput); printResponse(response.status, response.data, response.rawBody); } function parseArgs(args: string[]): { positionals: string[]; flags: Flags } { const positionals: string[] = []; const flags: Flags = {}; for (let index = 0; index < args.length; index += 1) { const current = args[index]; if (!current.startsWith('--')) { positionals.push(current); continue; } const key = current.slice(2); const next = args[index + 1]; if (!next || next.startsWith('--')) { flags[key] = true; continue; } flags[key] = next; index += 1; } return { positionals, flags }; } function resolveClientConfig(flags: Flags): { baseUrl: string; apiKey: string } { const baseUrl = readOptionalString(flags['base-url']) || process.env.ONEPANEL_BASE_URL; const apiKey = readOptionalString(flags['api-key']) || process.env.ONEPANEL_API_KEY; if (!baseUrl) { throw new Error('Missing ONEPANEL_BASE_URL or --base-url'); } if (!apiKey) { throw new Error('Missing ONEPANEL_API_KEY or --api-key'); } return { baseUrl, apiKey }; } function createClient(flags: Flags): OnePanelClient { const config = resolveClientConfig(flags); return OnePanelClient.fromEnv({ ...process.env, ONEPANEL_BASE_URL: config.baseUrl, ONEPANEL_API_KEY: config.apiKey, }); } function computeToken(apiKey: string, timestamp: string): string { return createHash('md5').update(`1panel${apiKey}${timestamp}`).digest('hex'); } async function loadInput(flags: Flags): Promise { const inputFile = readOptionalString(flags['input-file']); const inputJson = readOptionalString(flags['input-json']); if (inputFile && inputJson) { throw new Error('Use only one of --input-file or --input-json'); } if (inputFile) { const fs = await import('node:fs/promises'); const content = await fs.readFile(inputFile, 'utf8'); return JSON.parse(content); } if (inputJson) { return JSON.parse(inputJson); } return {}; } function mergeOperateNode(input: unknown, operateNode?: string): unknown { if (!operateNode) { return input; } if (!isRecord(input)) { return { operateNode }; } return { ...input, operateNode, }; } function parseOptionalJson(value: string | boolean | undefined): unknown { const stringValue = readOptionalString(value); if (!stringValue) { return undefined; } return JSON.parse(stringValue); } function readOptionalString(value: string | boolean | undefined): string | undefined { return typeof value === 'string' ? value : undefined; } function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value); } function getRequiredModule(moduleId: string) { const moduleDef = getModule(moduleId); if (!moduleDef) { throw new Error(`Unknown module: ${moduleId}`); } return moduleDef; } function printResponse(status: number, data: unknown, rawBody: string): void { const output = { status, ok: status >= 200 && status < 300, data, }; console.log(JSON.stringify(output, null, 2)); if (status >= 400) { if (!data && rawBody) { console.error(rawBody); } process.exitCode = 1; } } main().catch((error) => { console.error( JSON.stringify( { ok: false, error: error instanceof Error ? error.message : String(error), }, null, 2, ), ); process.exit(1); });