kiwivm-cli 0.1.0 → 0.2.0

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 (52) hide show
  1. package/README.md +62 -36
  2. package/dist/index.d.mts.map +1 -1
  3. package/dist/index.mjs +370 -50
  4. package/dist/index.mjs.map +1 -1
  5. package/package.json +1 -1
  6. package/src/commands/admin.test.ts +135 -22
  7. package/src/commands/admin.ts +57 -19
  8. package/src/commands/backup.test.ts +26 -23
  9. package/src/commands/backup.ts +13 -15
  10. package/src/commands/help.test.ts +27 -7
  11. package/src/commands/help.ts +61 -26
  12. package/src/commands/info.test.ts +47 -43
  13. package/src/commands/info.ts +11 -13
  14. package/src/commands/iso.test.ts +58 -0
  15. package/src/commands/iso.ts +21 -0
  16. package/src/commands/migrate.test.ts +105 -0
  17. package/src/commands/migrate.ts +38 -0
  18. package/src/commands/network.test.ts +107 -30
  19. package/src/commands/network.ts +56 -18
  20. package/src/commands/power.test.ts +58 -40
  21. package/src/commands/power.ts +27 -16
  22. package/src/commands/shell.test.ts +66 -0
  23. package/src/commands/shell.ts +25 -0
  24. package/src/commands/snapshot.test.ts +141 -71
  25. package/src/commands/snapshot.ts +85 -33
  26. package/src/commands/stats.test.ts +81 -0
  27. package/src/commands/stats.ts +25 -0
  28. package/src/commands/system.test.ts +109 -40
  29. package/src/commands/system.ts +55 -23
  30. package/src/index.test.ts +435 -148
  31. package/src/index.ts +129 -57
  32. package/src/types.ts +57 -1
  33. package/dist/admin-fOud1ZmX.mjs +0 -15
  34. package/dist/admin-fOud1ZmX.mjs.map +0 -1
  35. package/dist/backup-D1UJ4aap.mjs +0 -12
  36. package/dist/backup-D1UJ4aap.mjs.map +0 -1
  37. package/dist/help-Dk-WApoi.mjs +0 -40
  38. package/dist/help-Dk-WApoi.mjs.map +0 -1
  39. package/dist/info-DKExtFYH.mjs +0 -13
  40. package/dist/info-DKExtFYH.mjs.map +0 -1
  41. package/dist/monitoring-BSuv8fj9.mjs +0 -13
  42. package/dist/monitoring-BSuv8fj9.mjs.map +0 -1
  43. package/dist/network-1ycEIJqT.mjs +0 -15
  44. package/dist/network-1ycEIJqT.mjs.map +0 -1
  45. package/dist/power-CDg0Mx1A.mjs +0 -14
  46. package/dist/power-CDg0Mx1A.mjs.map +0 -1
  47. package/dist/snapshot-LO_ufoj5.mjs +0 -23
  48. package/dist/snapshot-LO_ufoj5.mjs.map +0 -1
  49. package/dist/system-Bl-dsqX9.mjs +0 -21
  50. package/dist/system-Bl-dsqX9.mjs.map +0 -1
  51. package/src/commands/monitoring.test.ts +0 -82
  52. package/src/commands/monitoring.ts +0 -20
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../src/types.ts","../src/client.ts","../src/index.ts"],"sourcesContent":["export interface KiwiVMResponse {\n error: number;\n message?: string;\n}\n\nexport class KiwiVMError extends Error {\n public readonly errorCode: number;\n public readonly rawResponse: KiwiVMResponse;\n\n constructor(message: string, errorCode: number, rawResponse: KiwiVMResponse) {\n super(message);\n this.name = \"KiwiVMError\";\n this.errorCode = errorCode;\n this.rawResponse = rawResponse;\n }\n}\n\nexport interface ServiceInfo extends KiwiVMResponse {\n hostname?: string;\n node_alias?: string;\n node_location?: string;\n location_ipv6_ready?: number;\n plan?: string;\n plan_monthly_data?: number;\n plan_disk?: number;\n plan_ram?: number;\n plan_swap?: number;\n os?: string;\n email?: string;\n data_counter?: number;\n data_next_reset?: number;\n ip_addresses?: string[];\n private_ip_addresses?: string[];\n ip_nullroutes?: Record<\n string,\n {\n nullroute_timestamp: number;\n nullroute_duration_s: number;\n log: string;\n }\n >;\n iso1?: string;\n iso2?: string;\n available_isos?: string[];\n plan_max_ipv6s?: number;\n rdns_api_available?: number;\n plan_private_network_available?: number;\n location_private_network_available?: number;\n ptr?: Record<string, string>;\n suspended?: number;\n policy_violation?: number;\n suspension_count?: number;\n total_abuse_points?: number;\n max_abuse_points?: number;\n}\n\nexport interface LiveServiceInfo extends ServiceInfo {\n vm_type?: \"ovz\" | \"kvm\";\n vz_status?: Record<string, unknown>;\n vz_quota?: Record<string, unknown>;\n ve_status?: \"Starting\" | \"Running\" | \"Stopped\";\n ve_mac1?: string;\n ve_used_disk_space_b?: number;\n ve_disk_quota_gb?: number;\n is_cpu_throttled?: number;\n is_disk_throttled?: number;\n ssh_port?: number;\n live_hostname?: string;\n load_average?: string;\n mem_available_kb?: number;\n swap_total_kb?: number;\n swap_available_kb?: number;\n screendump_png_base64?: string;\n}\n\nexport interface Snapshot {\n fileName: string;\n os: string;\n description: string;\n size: number;\n md5: string;\n sticky: number;\n purgesIn: number;\n downloadLink: string;\n downloadLinkSSL: string;\n}\n\nexport interface Backup {\n backupToken: string;\n size: number;\n os: string;\n md5: string;\n timestamp: number;\n}\n","import type { KiwiVMResponse } from \"./types.ts\";\nimport { KiwiVMError } from \"./types.ts\";\n\nconst API_BASE = \"https://api.64clouds.com/v1\";\n\nexport interface ClientOptions {\n veid: string;\n apiKey: string;\n}\n\nexport class KiwiVMClient {\n private veid: string;\n private apiKey: string;\n\n constructor(options: ClientOptions) {\n this.veid = options.veid;\n this.apiKey = options.apiKey;\n }\n\n async call<T extends KiwiVMResponse = KiwiVMResponse>(\n endpoint: string,\n params: Record<string, string | number | boolean | undefined> = {},\n ): Promise<T> {\n const body = new URLSearchParams();\n body.set(\"veid\", this.veid);\n body.set(\"api_key\", this.apiKey);\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n body.set(key, String(value));\n }\n }\n\n const response = await fetch(`${API_BASE}/${endpoint}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as T & KiwiVMResponse;\n\n if (data.error !== 0) {\n throw new KiwiVMError(\n data.message ?? `API error: ${data.error}`,\n data.error,\n data,\n );\n }\n\n return data as T;\n }\n}\n","#!/usr/bin/env node\n\nimport { KiwiVMClient } from \"./client.ts\";\nimport { KiwiVMError } from \"./types.ts\";\n\nfunction parseFlags(args: string[]): Record<string, string> {\n const flags: Record<string, string> = {};\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (!arg?.startsWith(\"--\")) continue;\n const eqIdx = arg.indexOf(\"=\");\n if (eqIdx !== -1) {\n const key = arg.slice(2, eqIdx);\n const value = arg.slice(eqIdx + 1);\n flags[toCamelCase(key)] = value;\n } else {\n const next = args[i + 1];\n if (next !== undefined && !next.startsWith(\"--\")) {\n const key = arg.slice(2);\n flags[toCamelCase(key)] = next;\n i++;\n } else {\n // --flag with no value (treat as boolean/empty string)\n const key = arg.slice(2);\n flags[toCamelCase(key)] = \"\";\n }\n }\n }\n return flags;\n}\n\nfunction toCamelCase(key: string): string {\n // Known mappings: kebab-case flags that need camelCase API param names\n const known: Record<string, string> = {\n \"backup-token\": \"backupToken\",\n \"new-hostname\": \"newHostname\",\n \"ssh-keys\": \"sshKeys\",\n \"source-veid\": \"sourceVeid\",\n \"source-token\": \"sourceToken\",\n \"record-id\": \"recordId\",\n \"api-key\": \"apiKey\",\n \"rate-limit\": \"rateLimit\",\n };\n if (known[key]) return known[key];\n\n // Default: convert --some-flag to someFlag\n return key.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\nfunction getCommandFromArgs(args: string[]): {\n positional: string[];\n flags: Record<string, string>;\n} {\n const positional: string[] = [];\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n if (!arg || arg.startsWith(\"--\")) {\n break;\n }\n positional.push(arg);\n i++;\n }\n const flagArgs = args.slice(i);\n const flags = parseFlags(flagArgs);\n return { positional, flags };\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const { positional, flags } = getCommandFromArgs(args);\n\n const category = positional[0] ?? \"\";\n const action = positional[1] ?? \"\";\n\n // Handle help before auth (no credentials needed)\n if (category === \"\" || category === \"help\") {\n const { run } = await import(\"./commands/help.ts\");\n const text = await run();\n console.log(text as string);\n return;\n }\n\n // Resolve auth: flags first, then env vars\n const flagVeid = flags[\"veid\"];\n const flagApiKey = flags[\"apiKey\"];\n const resolvedVeid = flagVeid || process.env[\"KIWIVM_VEID\"];\n const resolvedApiKey = flagApiKey || process.env[\"KIWIVM_API_KEY\"];\n\n if (!resolvedVeid || !resolvedApiKey) {\n console.error(\n \"Error: VEID and API key are required. Use --veid and --api-key flags, or set KIWIVM_VEID and KIWIVM_API_KEY environment variables.\",\n );\n process.exit(1);\n }\n\n // Strip auth flags before passing to handlers\n const handlerFlags = { ...flags };\n delete handlerFlags[\"veid\"];\n delete handlerFlags[\"apiKey\"];\n\n const client = new KiwiVMClient({\n veid: resolvedVeid,\n apiKey: resolvedApiKey,\n });\n\n try {\n let result: unknown;\n\n switch (category) {\n case \"power\": {\n const { run } = await import(\"./commands/power.ts\");\n result = await run(action, handlerFlags, client);\n break;\n }\n case \"info\": {\n const { run } = await import(\"./commands/info.ts\");\n result = await run(action, handlerFlags, client);\n break;\n }\n case \"snapshot\": {\n const { run } = await import(\"./commands/snapshot.ts\");\n result = await run(action, handlerFlags, client);\n break;\n }\n case \"backup\": {\n const { run } = await import(\"./commands/backup.ts\");\n result = await run(action, handlerFlags, client);\n break;\n }\n case \"system\": {\n const { run } = await import(\"./commands/system.ts\");\n result = await run(action, handlerFlags, client);\n break;\n }\n case \"network\": {\n const { run } = await import(\"./commands/network.ts\");\n result = await run(action, handlerFlags, client);\n break;\n }\n case \"monitoring\": {\n const { run } = await import(\"./commands/monitoring.ts\");\n result = await run(action, handlerFlags, client);\n break;\n }\n case \"admin\": {\n const { run } = await import(\"./commands/admin.ts\");\n result = await run(action, handlerFlags, client);\n break;\n }\n default: {\n console.error(`Unknown category: ${category}`);\n console.error(\"Run 'kiwivm-cli help' for usage.\");\n process.exit(1);\n }\n }\n\n console.log(JSON.stringify(result));\n } catch (error) {\n const message =\n error instanceof KiwiVMError ? error.message : String(error);\n console.error(`Error: ${message}`);\n process.exit(1);\n }\n}\n\nexport { main };\n\n// Only auto-run when executed directly (not when imported by tests)\nif (!process.env[\"VITEST\"]) {\n main();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":["list","help.run","power.start","power.stop","power.restart","power.kill","info.info","info.status","system.hostname","system.password","admin.suspensions","admin.unsuspend","migrate.clone","snapshot.list","snapshot.create","snapshot.deleteSnapshot","snapshot.restore","snapshot.sticky","snapshot.exportSnapshot","snapshot.importSnapshot","backup.list","backup.copy","system.osList","system.osReinstall","system.sshKeyShow","system.sshKeySet","network.rdnsSet","network.ipv6Add","network.ipv6Delete","network.privateIpList","network.privateIpAssign","network.privateIpDelete","iso.mount","iso.unmount","shell.exec","shell.script","migrate.locations","migrate.migrateStart","stats.usage","stats.audit","stats.rateLimit","admin.violationsList","admin.violationsResolve","admin.notificationsGet","admin.notificationsSet"],"sources":["../src/types.ts","../src/client.ts","../src/commands/admin.ts","../src/commands/backup.ts","../src/commands/help.ts","../src/commands/info.ts","../src/commands/iso.ts","../src/commands/migrate.ts","../src/commands/network.ts","../src/commands/power.ts","../src/commands/shell.ts","../src/commands/snapshot.ts","../src/commands/stats.ts","../src/commands/system.ts","../src/index.ts"],"sourcesContent":["export interface KiwiVMResponse {\n error: number;\n message?: string;\n}\n\nexport class KiwiVMError extends Error {\n public readonly errorCode: number;\n public readonly rawResponse: KiwiVMResponse;\n\n constructor(message: string, errorCode: number, rawResponse: KiwiVMResponse) {\n super(message);\n this.name = \"KiwiVMError\";\n this.errorCode = errorCode;\n this.rawResponse = rawResponse;\n }\n}\n\nexport interface ServiceInfo extends KiwiVMResponse {\n vm_type?: \"ovz\" | \"kvm\";\n hostname?: string;\n node_alias?: string;\n node_location?: string;\n location_ipv6_ready?: number;\n monthly_data_multiplier?: number;\n plan?: string;\n plan_monthly_data?: number;\n plan_disk?: number;\n plan_ram?: number;\n plan_swap?: number;\n os?: string;\n email?: string;\n data_counter?: number;\n data_next_reset?: number;\n ip_addresses?: string[];\n private_ip_addresses?: string[];\n ip_nullroutes?: Record<\n string,\n {\n nullroute_timestamp: number;\n nullroute_duration_s: number;\n log: string;\n }\n >;\n iso1?: string;\n iso2?: string;\n available_isos?: string[];\n plan_max_ipv6s?: number;\n rdns_api_available?: number;\n plan_private_network_available?: number;\n location_private_network_available?: number;\n ptr?: Record<string, string>;\n suspended?: number;\n policy_violation?: number;\n suspension_count?: number;\n total_abuse_points?: number;\n max_abuse_points?: number;\n}\n\nexport interface LiveServiceInfo extends ServiceInfo {\n vz_status?: Record<string, unknown>;\n vz_quota?: Record<string, unknown>;\n ve_status?: \"Starting\" | \"Running\" | \"Stopped\";\n ve_mac1?: string;\n ve_used_disk_space_b?: number;\n ve_disk_quota_gb?: number;\n is_cpu_throttled?: number;\n is_disk_throttled?: number;\n ssh_port?: number;\n live_hostname?: string;\n load_average?: string;\n mem_available_kb?: number;\n swap_total_kb?: number;\n swap_available_kb?: number;\n screendump_png_base64?: string;\n}\n\nexport interface Snapshot {\n fileName: string;\n os: string;\n description: string;\n size: number;\n md5: string;\n sticky: number;\n purgesIn: number;\n downloadLink: string;\n downloadLinkSSL: string;\n}\n\nexport interface Backup {\n backupToken: string;\n size: number;\n os: string;\n md5: string;\n timestamp: number;\n}\n\nexport interface RateLimitStatus extends KiwiVMResponse {\n remaining_points_15min: number;\n remaining_points_24h: number;\n}\n\nexport interface SuspensionRecord {\n record_id: number;\n flag: string;\n is_soft: number;\n evidence_record_id: number;\n abuse_points: number;\n}\n\nexport interface SuspensionDetailsResponse extends KiwiVMResponse {\n suspension_count: number;\n total_abuse_points: number;\n max_abuse_points: number;\n suspensions: SuspensionRecord[];\n evidence: Record<string, string>;\n}\n\nexport interface PolicyViolation {\n record_id: number;\n timestamp: number;\n suspend_at: number;\n flag: string;\n is_soft: number;\n abuse_points: number;\n evidence_data: string;\n}\n\nexport interface PolicyViolationsResponse extends KiwiVMResponse {\n total_abuse_points: number;\n max_abuse_points: number;\n policy_violations: PolicyViolation[];\n}\n\nexport interface NotificationPreference {\n id: number;\n name: string;\n description: string;\n value: number;\n}\n\nexport interface NotificationPreferencesResponse extends KiwiVMResponse {\n email_preferences: NotificationPreference[];\n notificationEmail: string;\n}\n\nexport interface NotificationSetResponse extends KiwiVMResponse {\n submitted_email_preferences: Record<string, number>;\n updated_email_preferences: Record<string, number>;\n friendly_descriptions: Record<string, string>;\n}\n","import type { KiwiVMResponse } from \"./types.ts\";\nimport { KiwiVMError } from \"./types.ts\";\n\nconst API_BASE = \"https://api.64clouds.com/v1\";\n\nexport interface ClientOptions {\n veid: string;\n apiKey: string;\n}\n\nexport class KiwiVMClient {\n private veid: string;\n private apiKey: string;\n\n constructor(options: ClientOptions) {\n this.veid = options.veid;\n this.apiKey = options.apiKey;\n }\n\n async call<T extends KiwiVMResponse = KiwiVMResponse>(\n endpoint: string,\n params: Record<string, string | number | boolean | undefined> = {},\n ): Promise<T> {\n const body = new URLSearchParams();\n body.set(\"veid\", this.veid);\n body.set(\"api_key\", this.apiKey);\n for (const [key, value] of Object.entries(params)) {\n if (value !== undefined) {\n body.set(key, String(value));\n }\n }\n\n const response = await fetch(`${API_BASE}/${endpoint}`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n\n const data = (await response.json()) as T & KiwiVMResponse;\n\n if (data.error !== 0) {\n throw new KiwiVMError(\n data.message ?? `API error: ${data.error}`,\n data.error,\n data,\n );\n }\n\n return data as T;\n }\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function suspensions(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"getSuspensionDetails\");\n}\n\nexport async function unsuspend(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const recordId = args[0];\n if (!recordId) {\n throw new Error(\"unsuspend requires a <record-id> argument\");\n }\n return client.call(\"unsuspend\", { record_id: recordId });\n}\n\nexport async function violationsList(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"getPolicyViolations\");\n}\n\nexport async function violationsResolve(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const recordId = args[0];\n if (!recordId) {\n throw new Error(\"violations resolve requires a <record-id> argument\");\n }\n return client.call(\"resolvePolicyViolation\", { record_id: recordId });\n}\n\nexport async function notificationsGet(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"kiwivm/getNotificationPreferences\");\n}\n\nexport async function notificationsSet(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const prefs = args[0];\n if (!prefs) {\n throw new Error(\"notifications set requires a <json> argument\");\n }\n return client.call(\"kiwivm/setNotificationPreferences\", {\n json_notification_preferences: prefs,\n });\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function list(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"backup/list\");\n}\n\nexport async function copy(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const token = args[0];\n if (!token) {\n throw new Error(\"backup copy requires a <token> argument\");\n }\n return client.call(\"backup/copyToSnapshot\", { backupToken: token });\n}\n","const HELP = `Usage: kiwivm-cli <command> [<subcommand>] [args...] [--flags...]\n\nAuth: --veid <VEID> --api-key <KEY> (or KIWIVM_VEID / KIWIVM_API_KEY env vars)\n\nCommands:\n\n start Start the VPS\n stop Stop the VPS\n restart Reboot the VPS\n kill Force-stop a stuck VPS\n\n info Get service info (plan, IPs, bandwidth, etc.)\n status Get live status (CPU, RAM, disk, uptime)\n\n snapshot list List all snapshots\n snapshot create [--desc] Create a new snapshot\n snapshot delete <token> Delete a snapshot\n snapshot restore <token> Restore from snapshot\n snapshot sticky <token> Toggle sticky (--on | --off)\n snapshot export <token> Generate transfer token\n snapshot import <veid> <token> Import snapshot from another instance\n\n backup list List automatic backups\n backup copy <token> Convert backup to restorable snapshot\n\n os list List available OS templates\n os reinstall <template> Reinstall OS\n\n hostname <name> Set new hostname\n password Reset root password\n\n ssh-key Show SSH keys\n ssh-key set <keys> Set SSH keys\n\n rdns set <ip> <ptr> Set reverse DNS record\n\n ipv6 add Assign new IPv6 /64 subnet\n ipv6 delete <subnet> Release IPv6 subnet\n\n private-ip list List available private IPs\n private-ip assign [<ip>] Assign private IP (random if omitted)\n private-ip delete <ip> Release private IP\n\n iso mount <name> Mount ISO for boot (VM must be off)\n iso unmount Unmount ISO, boot from disk\n\n shell exec <command> Execute command synchronously\n shell script <script> Execute script asynchronously\n\n migrate locations List available migration locations\n migrate start <location> Start migration to new location\n clone <ip> <password> [--port] Clone from external server (OVZ only)\n\n stats usage Get detailed usage statistics\n stats audit Get audit log\n stats rate-limit Check API rate limit status\n\n suspensions View suspension details\n unsuspend <record-id> Clear abuse issue and unsuspend\n violations View policy violations\n violations resolve <record-id> Resolve policy violation\n\n notifications Get notification preferences\n notifications set <json> Update notification preferences\n\nOutput: JSON to stdout. Errors to stderr with exit code 1.\n`;\n\nexport async function run(): Promise<string> {\n return HELP;\n}\n","import type { KiwiVMClient } from \"../client.ts\";\nimport type { LiveServiceInfo, ServiceInfo } from \"../types.ts\";\n\nexport async function info(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call<ServiceInfo>(\"getServiceInfo\");\n}\n\nexport async function status(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call<LiveServiceInfo>(\"getLiveServiceInfo\");\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function mount(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const iso = args[0];\n if (!iso) {\n throw new Error(\"iso mount requires a <name> argument\");\n }\n return client.call(\"iso/mount\", { iso });\n}\n\nexport async function unmount(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"iso/unmount\");\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function locations(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"migrate/getLocations\");\n}\n\nexport async function migrateStart(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const location = args[0];\n if (!location) {\n throw new Error(\"migrate start requires a <location> argument\");\n }\n return client.call(\"migrate/start\", { location });\n}\n\nexport async function clone(\n args: string[],\n flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const ip = args[0];\n const password = args[1];\n if (!ip || !password) {\n throw new Error(\"clone requires both <ip> and <password> arguments\");\n }\n return client.call(\"cloneFromExternalServer\", {\n externalServerIP: ip,\n externalServerSSHport: flags[\"port\"] || \"22\",\n externalServerRootPassword: password,\n });\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function rdnsSet(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const ip = args[0];\n const ptr = args[1];\n if (!ip || !ptr) {\n throw new Error(\"rdns set requires both <ip> and <ptr> arguments\");\n }\n return client.call(\"setPTR\", { ip, ptr });\n}\n\nexport async function ipv6Add(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"ipv6/add\");\n}\n\nexport async function ipv6Delete(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const ip = args[0];\n if (!ip) {\n throw new Error(\"ipv6 delete requires a <subnet> argument\");\n }\n return client.call(\"ipv6/delete\", { ip });\n}\n\nexport async function privateIpList(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"privateIp/getAvailableIps\");\n}\n\nexport async function privateIpAssign(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"privateIp/assign\", { ip: args[0] });\n}\n\nexport async function privateIpDelete(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const ip = args[0];\n if (!ip) {\n throw new Error(\"private-ip delete requires an <ip> argument\");\n }\n return client.call(\"privateIp/delete\", { ip });\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function start(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"start\");\n}\n\nexport async function stop(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"stop\");\n}\n\nexport async function restart(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"restart\");\n}\n\nexport async function kill(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"kill\");\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function exec(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const command = args[0];\n if (!command) {\n throw new Error(\"shell exec requires a <command> argument\");\n }\n return client.call(\"basicShell/exec\", { command });\n}\n\nexport async function script(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const scriptContent = args[0];\n if (!scriptContent) {\n throw new Error(\"shell script requires a <script> argument\");\n }\n return client.call(\"shellScript/exec\", { script: scriptContent });\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function list(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"snapshot/list\");\n}\n\nexport async function create(\n _args: string[],\n flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"snapshot/create\", {\n description: flags[\"desc\"] || flags[\"description\"],\n });\n}\n\nexport async function deleteSnapshot(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const token = args[0];\n if (!token) {\n throw new Error(\"snapshot delete requires a <token> argument\");\n }\n return client.call(\"snapshot/delete\", { snapshot: token });\n}\n\nexport async function restore(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const token = args[0];\n if (!token) {\n throw new Error(\"snapshot restore requires a <token> argument\");\n }\n return client.call(\"snapshot/restore\", { snapshot: token });\n}\n\nexport async function sticky(\n args: string[],\n flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const token = args[0];\n if (!token) {\n throw new Error(\"snapshot sticky requires a <token> argument\");\n }\n if (flags[\"on\"] !== undefined && flags[\"off\"] !== undefined) {\n throw new Error(\"snapshot sticky requires exactly one of --on or --off\");\n }\n if (flags[\"on\"] === undefined && flags[\"off\"] === undefined) {\n throw new Error(\"snapshot sticky requires exactly one of --on or --off\");\n }\n const stickyVal = flags[\"on\"] !== undefined ? 1 : 0;\n return client.call(\"snapshot/toggleSticky\", {\n snapshot: token,\n sticky: stickyVal,\n });\n}\n\nexport async function exportSnapshot(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const token = args[0];\n if (!token) {\n throw new Error(\"snapshot export requires a <token> argument\");\n }\n return client.call(\"snapshot/export\", { snapshot: token });\n}\n\nexport async function importSnapshot(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const sourceVeid = args[0];\n const sourceToken = args[1];\n if (!sourceVeid || !sourceToken) {\n throw new Error(\n \"snapshot import requires both <sourceVeid> and <sourceToken> arguments\",\n );\n }\n return client.call(\"snapshot/import\", { sourceVeid, sourceToken });\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function usage(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"getRawUsageStats\");\n}\n\nexport async function audit(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"getAuditLog\");\n}\n\nexport async function rateLimit(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"getRateLimitStatus\");\n}\n","import type { KiwiVMClient } from \"../client.ts\";\n\nexport async function hostname(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const name = args[0];\n if (!name) {\n throw new Error(\"hostname requires a <name> argument\");\n }\n return client.call(\"setHostname\", { newHostname: name });\n}\n\nexport async function password(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"resetRootPassword\");\n}\n\nexport async function osList(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"getAvailableOS\");\n}\n\nexport async function osReinstall(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const os = args[0];\n if (!os) {\n throw new Error(\"os reinstall requires a <template> argument\");\n }\n return client.call(\"reinstallOS\", { os });\n}\n\nexport async function sshKeyShow(\n _args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n return client.call(\"getSshKeys\");\n}\n\nexport async function sshKeySet(\n args: string[],\n _flags: Record<string, string>,\n client: KiwiVMClient,\n): Promise<unknown> {\n const keys = args[0];\n if (!keys) {\n throw new Error(\"ssh-key set requires a <keys> argument\");\n }\n return client.call(\"updateSshKeys\", { ssh_keys: keys });\n}\n","#!/usr/bin/env node\n\nimport { KiwiVMClient } from \"./client.ts\";\nimport * as admin from \"./commands/admin.ts\";\nimport * as backup from \"./commands/backup.ts\";\nimport * as help from \"./commands/help.ts\";\nimport * as info from \"./commands/info.ts\";\nimport * as iso from \"./commands/iso.ts\";\nimport * as migrate from \"./commands/migrate.ts\";\nimport * as network from \"./commands/network.ts\";\nimport * as power from \"./commands/power.ts\";\nimport * as shell from \"./commands/shell.ts\";\nimport * as snapshot from \"./commands/snapshot.ts\";\nimport * as stats from \"./commands/stats.ts\";\nimport * as system from \"./commands/system.ts\";\nimport { KiwiVMError } from \"./types.ts\";\n\ntype Handler = (\n args: string[],\n flags: Record<string, string>,\n client: KiwiVMClient,\n) => Promise<unknown>;\n\ninterface SubcommandRoutes {\n default?: Handler;\n [subcommand: string]: Handler | undefined;\n}\n\nfunction parseFlags(args: string[]): Record<string, string> {\n const flags: Record<string, string> = {};\n for (let i = 0; i < args.length; i++) {\n const arg = args[i];\n if (!arg?.startsWith(\"--\")) continue;\n const eqIdx = arg.indexOf(\"=\");\n if (eqIdx !== -1) {\n const key = arg.slice(2, eqIdx);\n const value = arg.slice(eqIdx + 1);\n flags[toCamelCase(key)] = value;\n } else {\n const next = args[i + 1];\n if (next !== undefined && !next.startsWith(\"--\")) {\n const key = arg.slice(2);\n flags[toCamelCase(key)] = next;\n i++;\n } else {\n const key = arg.slice(2);\n flags[toCamelCase(key)] = \"1\";\n }\n }\n }\n return flags;\n}\n\nfunction toCamelCase(key: string): string {\n const known: Record<string, string> = {\n \"backup-token\": \"backupToken\",\n \"new-hostname\": \"newHostname\",\n \"ssh-keys\": \"sshKeys\",\n \"source-veid\": \"sourceVeid\",\n \"source-token\": \"sourceToken\",\n \"record-id\": \"recordId\",\n \"api-key\": \"apiKey\",\n \"rate-limit\": \"rateLimit\",\n };\n if (known[key]) return known[key];\n\n return key.replace(/-([a-z])/g, (_, c: string) => c.toUpperCase());\n}\n\nfunction getCommandFromArgs(args: string[]): {\n positional: string[];\n flags: Record<string, string>;\n} {\n const positional: string[] = [];\n let i = 0;\n while (i < args.length) {\n const arg = args[i];\n if (!arg || arg.startsWith(\"--\")) {\n break;\n }\n positional.push(arg);\n i++;\n }\n const flagArgs = args.slice(i);\n const flags = parseFlags(flagArgs);\n return { positional, flags };\n}\n\nasync function main() {\n const args = process.argv.slice(2);\n const { positional, flags } = getCommandFromArgs(args);\n\n const command = positional[0] ?? \"\";\n\n if (command === \"\" || command === \"help\") {\n console.log(await help.run());\n return;\n }\n\n const flagVeid = flags[\"veid\"];\n const flagApiKey = flags[\"apiKey\"];\n const resolvedVeid = flagVeid || process.env[\"KIWIVM_VEID\"];\n const resolvedApiKey = flagApiKey || process.env[\"KIWIVM_API_KEY\"];\n\n if (!resolvedVeid || !resolvedApiKey) {\n console.error(\n \"Error: VEID and API key are required. Use --veid and --api-key flags, or set KIWIVM_VEID and KIWIVM_API_KEY environment variables.\",\n );\n process.exit(1);\n }\n\n const handlerFlags = { ...flags };\n delete handlerFlags[\"veid\"];\n delete handlerFlags[\"apiKey\"];\n\n const client = new KiwiVMClient({\n veid: resolvedVeid,\n apiKey: resolvedApiKey,\n });\n\n try {\n let result: unknown;\n\n const ROUTES: Record<string, Handler | SubcommandRoutes> = {\n start: power.start,\n stop: power.stop,\n restart: power.restart,\n kill: power.kill,\n info: info.info,\n status: info.status,\n hostname: system.hostname,\n password: system.password,\n suspensions: admin.suspensions,\n unsuspend: admin.unsuspend,\n clone: migrate.clone,\n\n snapshot: {\n list: snapshot.list,\n create: snapshot.create,\n delete: snapshot.deleteSnapshot,\n restore: snapshot.restore,\n sticky: snapshot.sticky,\n export: snapshot.exportSnapshot,\n import: snapshot.importSnapshot,\n },\n backup: {\n list: backup.list,\n copy: backup.copy,\n },\n os: {\n list: system.osList,\n reinstall: system.osReinstall,\n },\n \"ssh-key\": {\n default: system.sshKeyShow,\n set: system.sshKeySet,\n },\n rdns: {\n set: network.rdnsSet,\n },\n ipv6: {\n add: network.ipv6Add,\n delete: network.ipv6Delete,\n },\n \"private-ip\": {\n list: network.privateIpList,\n assign: network.privateIpAssign,\n delete: network.privateIpDelete,\n },\n iso: {\n mount: iso.mount,\n unmount: iso.unmount,\n },\n shell: {\n exec: shell.exec,\n script: shell.script,\n },\n migrate: {\n locations: migrate.locations,\n start: migrate.migrateStart,\n },\n stats: {\n usage: stats.usage,\n audit: stats.audit,\n \"rate-limit\": stats.rateLimit,\n },\n violations: {\n default: admin.violationsList,\n resolve: admin.violationsResolve,\n },\n notifications: {\n default: admin.notificationsGet,\n set: admin.notificationsSet,\n },\n };\n\n const route = ROUTES[command];\n\n if (!route) {\n console.error(`Unknown command: ${command}`);\n console.error(\"Run 'kiwivm-cli help' for usage.\");\n process.exit(1);\n }\n\n if (typeof route === \"function\") {\n result = await route(positional.slice(1), handlerFlags, client);\n } else {\n const subcommand = positional[1];\n let handler: Handler | undefined;\n\n if (subcommand && subcommand in route) {\n handler = route[subcommand];\n } else if (!subcommand && route.default) {\n handler = route.default;\n }\n\n if (!handler) {\n const valid = Object.keys(route)\n .filter((k) => k !== \"default\")\n .join(\", \");\n const sc = subcommand || \"(none)\";\n console.error(\n `Unknown subcommand for ${command}: ${sc}. Valid: ${valid}`,\n );\n process.exit(1);\n }\n\n result = await handler(positional.slice(2), handlerFlags, client);\n }\n\n console.log(JSON.stringify(result));\n } catch (error) {\n const message =\n error instanceof KiwiVMError ? error.message : String(error);\n console.error(`Error: ${message}`);\n process.exit(1);\n }\n}\n\nexport { main };\n\nif (!process.env[\"VITEST\"]) {\n main();\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kiwivm-cli",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "CLI for managing KiwiVM (BuyVM) VPS instances",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,13 +1,20 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import type { KiwiVMClient } from "../client.ts";
3
- import { run } from "./admin.ts";
3
+ import {
4
+ notificationsGet,
5
+ notificationsSet,
6
+ suspensions,
7
+ unsuspend,
8
+ violationsList,
9
+ violationsResolve,
10
+ } from "./admin.ts";
4
11
 
5
12
  function mockClient() {
6
13
  const call = vi.fn();
7
14
  return { client: { call } as unknown as KiwiVMClient, call };
8
15
  }
9
16
 
10
- describe("admin command", () => {
17
+ describe("admin handlers", () => {
11
18
  describe("suspensions", () => {
12
19
  it("calls getSuspensionDetails", async () => {
13
20
  const { client, call } = mockClient();
@@ -16,50 +23,156 @@ describe("admin command", () => {
16
23
  suspensions: [{ reason: "abuse", createdAt: "2025-01-01" }],
17
24
  });
18
25
 
19
- const result = await run("suspensions", {}, client);
26
+ const result = await suspensions([], {}, client);
20
27
 
21
- expect(client.call).toHaveBeenCalledExactlyOnceWith(
22
- "getSuspensionDetails",
23
- );
28
+ expect(call).toHaveBeenCalledExactlyOnceWith("getSuspensionDetails");
24
29
  expect(result).toMatchObject({
25
30
  suspensions: [{ reason: "abuse" }],
26
31
  });
27
32
  });
33
+
34
+ it("propagates errors from the client", async () => {
35
+ const { client, call } = mockClient();
36
+ call.mockRejectedValueOnce(new Error("API failure"));
37
+
38
+ await expect(suspensions([], {}, client)).rejects.toThrow("API failure");
39
+ });
28
40
  });
29
41
 
30
42
  describe("unsuspend", () => {
31
- it("calls unsuspend with record-id flag", async () => {
43
+ it("calls unsuspend with recordId from args", async () => {
32
44
  const { client, call } = mockClient();
33
45
  call.mockResolvedValueOnce({ error: 0 });
34
46
 
35
- await run("unsuspend", { recordId: "42" }, client);
47
+ await unsuspend(["11851"], {}, client);
36
48
 
37
- expect(client.call).toHaveBeenCalledExactlyOnceWith("unsuspend", {
38
- recordId: "42",
49
+ expect(call).toHaveBeenCalledExactlyOnceWith("unsuspend", {
50
+ record_id: "11851",
39
51
  });
40
52
  });
53
+
54
+ it("throws when no record ID provided", async () => {
55
+ const { client } = mockClient();
56
+
57
+ await expect(unsuspend([], {}, client)).rejects.toThrow("record");
58
+ });
59
+
60
+ it("propagates errors from the client", async () => {
61
+ const { client, call } = mockClient();
62
+ call.mockRejectedValueOnce(new Error("Record not found"));
63
+
64
+ await expect(unsuspend(["999"], {}, client)).rejects.toThrow(
65
+ "Record not found",
66
+ );
67
+ });
41
68
  });
42
69
 
43
- describe("resolve", () => {
44
- it("calls resolvePolicyViolation with record-id flag", async () => {
70
+ describe("violationsList", () => {
71
+ it("calls getPolicyViolations", async () => {
72
+ const { client, call } = mockClient();
73
+ call.mockResolvedValueOnce({
74
+ error: 0,
75
+ violations: [{ id: 14, reason: "abuse" }],
76
+ });
77
+
78
+ const result = await violationsList([], {}, client);
79
+
80
+ expect(call).toHaveBeenCalledExactlyOnceWith("getPolicyViolations");
81
+ expect(result).toMatchObject({
82
+ violations: [{ id: 14, reason: "abuse" }],
83
+ });
84
+ });
85
+
86
+ it("propagates errors from the client", async () => {
87
+ const { client, call } = mockClient();
88
+ call.mockRejectedValueOnce(new Error("API failure"));
89
+
90
+ await expect(violationsList([], {}, client)).rejects.toThrow(
91
+ "API failure",
92
+ );
93
+ });
94
+ });
95
+
96
+ describe("violationsResolve", () => {
97
+ it("calls resolvePolicyViolation with recordId from args", async () => {
45
98
  const { client, call } = mockClient();
46
99
  call.mockResolvedValueOnce({ error: 0 });
47
100
 
48
- await run("resolve", { recordId: "42" }, client);
101
+ await violationsResolve(["14"], {}, client);
102
+
103
+ expect(call).toHaveBeenCalledExactlyOnceWith("resolvePolicyViolation", {
104
+ record_id: "14",
105
+ });
106
+ });
107
+
108
+ it("throws when no record ID provided", async () => {
109
+ const { client } = mockClient();
49
110
 
50
- expect(client.call).toHaveBeenCalledExactlyOnceWith(
51
- "resolvePolicyViolation",
52
- { recordId: "42" },
111
+ await expect(violationsResolve([], {}, client)).rejects.toThrow("record");
112
+ });
113
+
114
+ it("propagates errors from the client", async () => {
115
+ const { client, call } = mockClient();
116
+ call.mockRejectedValueOnce(new Error("Failed to resolve"));
117
+
118
+ await expect(violationsResolve(["14"], {}, client)).rejects.toThrow(
119
+ "Failed to resolve",
120
+ );
121
+ });
122
+ });
123
+
124
+ describe("notificationsGet", () => {
125
+ it("calls kiwivm/getNotificationPreferences", async () => {
126
+ const { client, call } = mockClient();
127
+ call.mockResolvedValueOnce({
128
+ error: 0,
129
+ preferences: { "1": 1, "2": 0 },
130
+ });
131
+
132
+ const result = await notificationsGet([], {}, client);
133
+
134
+ expect(call).toHaveBeenCalledExactlyOnceWith(
135
+ "kiwivm/getNotificationPreferences",
136
+ );
137
+ expect(result).toMatchObject({ preferences: { "1": 1, "2": 0 } });
138
+ });
139
+
140
+ it("propagates errors from the client", async () => {
141
+ const { client, call } = mockClient();
142
+ call.mockRejectedValueOnce(new Error("API failure"));
143
+
144
+ await expect(notificationsGet([], {}, client)).rejects.toThrow(
145
+ "API failure",
53
146
  );
54
147
  });
55
148
  });
56
149
 
57
- it("propagates errors from the client", async () => {
58
- const { client, call } = mockClient();
59
- call.mockRejectedValueOnce(new Error("Record not found"));
150
+ describe("notificationsSet", () => {
151
+ it("calls kiwivm/setNotificationPreferences with JSON from args", async () => {
152
+ const { client, call } = mockClient();
153
+ call.mockResolvedValueOnce({ error: 0 });
154
+
155
+ await notificationsSet(['{"1":1,"2":0}'], {}, client);
156
+
157
+ expect(call).toHaveBeenCalledExactlyOnceWith(
158
+ "kiwivm/setNotificationPreferences",
159
+ { json_notification_preferences: '{"1":1,"2":0}' },
160
+ );
161
+ });
162
+
163
+ it("throws when no preferences JSON provided", async () => {
164
+ const { client } = mockClient();
165
+
166
+ await expect(notificationsSet([], {}, client)).rejects.toThrow(/json/);
167
+ });
168
+
169
+ it("propagates errors from the client", async () => {
170
+ const { client, call } = mockClient();
171
+ call.mockRejectedValueOnce(new Error("Invalid preferences"));
60
172
 
61
- await expect(run("unsuspend", { recordId: "999" }, client)).rejects.toThrow(
62
- "Record not found",
63
- );
173
+ await expect(notificationsSet(['{"1":1}'], {}, client)).rejects.toThrow(
174
+ "Invalid preferences",
175
+ );
176
+ });
64
177
  });
65
178
  });
@@ -1,25 +1,63 @@
1
1
  import type { KiwiVMClient } from "../client.ts";
2
2
 
3
- export async function run(
4
- action: string,
5
- flags: Record<string, string>,
3
+ export async function suspensions(
4
+ _args: string[],
5
+ _flags: Record<string, string>,
6
6
  client: KiwiVMClient,
7
7
  ): Promise<unknown> {
8
- switch (action) {
9
- case "suspensions":
10
- return client.call("getSuspensionDetails");
11
- case "unsuspend":
12
- return client.call("unsuspend", { recordId: flags["recordId"] });
13
- case "resolve":
14
- if (flags["recordId"] !== undefined) {
15
- return client.call("resolvePolicyViolation", {
16
- recordId: flags["recordId"],
17
- });
18
- }
19
- return client.call("getPolicyViolations");
20
- default:
21
- throw new Error(
22
- `Unknown admin action: ${action}. Valid: suspensions, unsuspend, resolve`,
23
- );
8
+ return client.call("getSuspensionDetails");
9
+ }
10
+
11
+ export async function unsuspend(
12
+ args: string[],
13
+ _flags: Record<string, string>,
14
+ client: KiwiVMClient,
15
+ ): Promise<unknown> {
16
+ const recordId = args[0];
17
+ if (!recordId) {
18
+ throw new Error("unsuspend requires a <record-id> argument");
19
+ }
20
+ return client.call("unsuspend", { record_id: recordId });
21
+ }
22
+
23
+ export async function violationsList(
24
+ _args: string[],
25
+ _flags: Record<string, string>,
26
+ client: KiwiVMClient,
27
+ ): Promise<unknown> {
28
+ return client.call("getPolicyViolations");
29
+ }
30
+
31
+ export async function violationsResolve(
32
+ args: string[],
33
+ _flags: Record<string, string>,
34
+ client: KiwiVMClient,
35
+ ): Promise<unknown> {
36
+ const recordId = args[0];
37
+ if (!recordId) {
38
+ throw new Error("violations resolve requires a <record-id> argument");
39
+ }
40
+ return client.call("resolvePolicyViolation", { record_id: recordId });
41
+ }
42
+
43
+ export async function notificationsGet(
44
+ _args: string[],
45
+ _flags: Record<string, string>,
46
+ client: KiwiVMClient,
47
+ ): Promise<unknown> {
48
+ return client.call("kiwivm/getNotificationPreferences");
49
+ }
50
+
51
+ export async function notificationsSet(
52
+ args: string[],
53
+ _flags: Record<string, string>,
54
+ client: KiwiVMClient,
55
+ ): Promise<unknown> {
56
+ const prefs = args[0];
57
+ if (!prefs) {
58
+ throw new Error("notifications set requires a <json> argument");
24
59
  }
60
+ return client.call("kiwivm/setNotificationPreferences", {
61
+ json_notification_preferences: prefs,
62
+ });
25
63
  }
@@ -1,15 +1,15 @@
1
1
  import { describe, expect, it, vi } from "vitest";
2
2
  import type { KiwiVMClient } from "../client.ts";
3
- import { run } from "./backup.ts";
3
+ import { copy, list } from "./backup.ts";
4
4
 
5
5
  function mockClient() {
6
6
  const call = vi.fn();
7
7
  return { client: { call } as unknown as KiwiVMClient, call };
8
8
  }
9
9
 
10
- describe("backup command", () => {
10
+ describe("backup handlers", () => {
11
11
  describe("list", () => {
12
- it("calls backup/list", async () => {
12
+ it("calls client.call('backup/list')", async () => {
13
13
  const { client, call } = mockClient();
14
14
  const backups = {
15
15
  error: 0,
@@ -25,42 +25,45 @@ describe("backup command", () => {
25
25
  };
26
26
  call.mockResolvedValueOnce(backups);
27
27
 
28
- const result = await run("list", {}, client);
28
+ const result = await list([], {}, client);
29
29
 
30
- expect(client.call).toHaveBeenCalledExactlyOnceWith("backup/list");
30
+ expect(call).toHaveBeenCalledExactlyOnceWith("backup/list");
31
31
  expect(result).toEqual(backups);
32
32
  });
33
33
 
34
- it("does not pass any extra params", async () => {
34
+ it("propagates errors from the client", async () => {
35
35
  const { client, call } = mockClient();
36
- call.mockResolvedValueOnce({ error: 0, backups: [] });
36
+ call.mockRejectedValueOnce(new Error("API failure"));
37
37
 
38
- await run("list", {}, client);
39
-
40
- expect(client.call).toHaveBeenCalledExactlyOnceWith("backup/list");
38
+ await expect(list([], {}, client)).rejects.toThrow("API failure");
41
39
  });
42
40
  });
43
41
 
44
42
  describe("copy", () => {
45
- it("calls backup/copyToSnapshot with backupToken", async () => {
43
+ it("calls backup/copyToSnapshot with backup token from args", async () => {
46
44
  const { client, call } = mockClient();
47
45
  call.mockResolvedValueOnce({ error: 0 });
48
46
 
49
- await run("copy", { backupToken: "tok1" }, client);
47
+ await copy(["abc123"], {}, client);
50
48
 
51
- expect(client.call).toHaveBeenCalledExactlyOnceWith(
52
- "backup/copyToSnapshot",
53
- { backupToken: "tok1" },
54
- );
49
+ expect(call).toHaveBeenCalledExactlyOnceWith("backup/copyToSnapshot", {
50
+ backupToken: "abc123",
51
+ });
55
52
  });
56
- });
57
53
 
58
- it("propagates errors from the client", async () => {
59
- const { client, call } = mockClient();
60
- call.mockRejectedValueOnce(new Error("Backup not found"));
54
+ it("throws when no backup token provided", async () => {
55
+ const { client } = mockClient();
56
+
57
+ await expect(copy([], {}, client)).rejects.toThrow("token");
58
+ });
61
59
 
62
- await expect(run("copy", { backupToken: "bad" }, client)).rejects.toThrow(
63
- "Backup not found",
64
- );
60
+ it("propagates errors from the client", async () => {
61
+ const { client, call } = mockClient();
62
+ call.mockRejectedValueOnce(new Error("Backup not found"));
63
+
64
+ await expect(copy(["bad"], {}, client)).rejects.toThrow(
65
+ "Backup not found",
66
+ );
67
+ });
65
68
  });
66
69
  });
@@ -1,23 +1,21 @@
1
1
  import type { KiwiVMClient } from "../client.ts";
2
- import type { Backup, KiwiVMResponse } from "../types.ts";
3
2
 
4
- interface BackupListResponse extends KiwiVMResponse {
5
- backups: Backup[];
3
+ export async function list(
4
+ _args: string[],
5
+ _flags: Record<string, string>,
6
+ client: KiwiVMClient,
7
+ ): Promise<unknown> {
8
+ return client.call("backup/list");
6
9
  }
7
10
 
8
- export async function run(
9
- action: string,
10
- flags: Record<string, string>,
11
+ export async function copy(
12
+ args: string[],
13
+ _flags: Record<string, string>,
11
14
  client: KiwiVMClient,
12
15
  ): Promise<unknown> {
13
- switch (action) {
14
- case "list":
15
- return client.call<BackupListResponse>("backup/list");
16
- case "copy":
17
- return client.call("backup/copyToSnapshot", {
18
- backupToken: flags["backupToken"],
19
- });
20
- default:
21
- throw new Error(`Unknown backup action: ${action}. Valid: list, copy`);
16
+ const token = args[0];
17
+ if (!token) {
18
+ throw new Error("backup copy requires a <token> argument");
22
19
  }
20
+ return client.call("backup/copyToSnapshot", { backupToken: token });
23
21
  }
@@ -14,18 +14,38 @@ describe("help command", () => {
14
14
  expect(result).toContain("kiwivm-cli");
15
15
  });
16
16
 
17
- it("lists available command categories", async () => {
17
+ it("lists key command names in help text", async () => {
18
18
  const result = await run();
19
19
 
20
- // Spot-check a few categories
21
- expect(result).toContain("power");
20
+ // Flat commands
21
+ expect(result).toContain("start");
22
+ expect(result).toContain("stop");
22
23
  expect(result).toContain("info");
24
+ expect(result).toContain("status");
25
+ // Subcommand categories
23
26
  expect(result).toContain("snapshot");
24
27
  expect(result).toContain("backup");
25
- expect(result).toContain("system");
26
- expect(result).toContain("network");
27
- expect(result).toContain("monitoring");
28
- expect(result).toContain("admin");
28
+ // System actions
29
+ expect(result).toContain("os");
30
+ expect(result).toContain("hostname");
31
+ expect(result).toContain("password");
32
+ expect(result).toContain("ssh-key");
33
+ // Network
34
+ expect(result).toContain("rdns");
35
+ expect(result).toContain("ipv6");
36
+ expect(result).toContain("private-ip");
37
+ // New categories
38
+ expect(result).toContain("iso");
39
+ expect(result).toContain("shell");
40
+ expect(result).toContain("migrate");
41
+ expect(result).toContain("clone");
42
+ // Stats
43
+ expect(result).toContain("stats");
44
+ // Admin
45
+ expect(result).toContain("suspensions");
46
+ expect(result).toContain("unsuspend");
47
+ expect(result).toContain("violations");
48
+ expect(result).toContain("notifications");
29
49
  });
30
50
 
31
51
  it("mentions global flags --veid and --api-key", async () => {