DesarrolladoresSDK de TypeScript
SDK de Hostwolf para JavaScript & TypeScript
@hostwolf/sdk es el cliente tipado oficial de la API de Hostwolf. Con él controlas tus servidores de juegos desde código: encendido, consola, archivos, copias de seguridad, mods, jugadores, programaciones y más.
npm install https://hostwolf.net/dl/hostwolf-sdk-0.1.8.tgz
El SDK se sirve desde hostwolf.net en lugar del registro de npm. El comando de arriba lo añade a tu package.json como "@hostwolf/sdk": "https://hostwolf.net/dl/hostwolf-sdk-0.1.8.tgz", y lo importas como @hostwolf/sdk. pnpm, yarn y bun aceptan la misma URL. Cada versión tiene su propia URL; consulta Versiones.
Funciona en Node 18+, Bun y Deno, y no tiene dependencias en tiempo de ejecución. Para la consola en vivo en versiones de Node anteriores a la 22, ejecuta también npm install ws.
Ver también: Referencia de la API REST · Servidor MCP · Registro de cambios · Obtener una clave API
Inicio rápido#
Crea una clave API en panel.hostwolf.net/account/api y luego:
import { Hostwolf } from '@hostwolf/sdk';
const hw = new Hostwolf({ apiKey: process.env.HOSTWOLF_API_KEY! });
const { data: servers } = await hw.servers.list();
for (const s of servers) {
const res = await hw.servers.resources(s.identifier);
console.log(s.name, s.egg_name, res.current_state);
}
const id = servers[0].identifier;
await hw.servers.command(id, 'say Backup in 1 minute');
await hw.backups.create(id, { name: 'before-update' });
console.log(await hw.tail(id, { lines: 20 }));
Cada método de servidor recibe como primer argumento el identifier corto del servidor (los 8 caracteres de su URL en el panel) o su UUID. Los objetos de respuesta usan los nombres de campo de la API (snake_case). Sus tipos de TypeScript se exportan, p. ej. Server, Backup y FileObject.
Opciones#
new Hostwolf({
apiKey: 'ptlc_...', // required
baseUrl: 'https://panel.hostwolf.net', // default
timeoutMs: 30_000, // per request
maxRetries: 2, // 429s, and 502/503/504 on GET
maxRetryWaitMs: 10_000, // longer Retry-After waits fail fast instead
fetch: customFetch, // optional
WebSocket: customWebSocket, // optional, for the console
});
Servidores#
REST: Endpoints de servidores
| Método | Qué hace |
|---|---|
servers.list({ page, per_page }) | Una página de tus servidores: { data, pagination } |
servers.listAll() | Iterador asíncrono sobre todos los servidores |
servers.get(id, ['egg', 'subusers']) | Detalles del servidor, con asignaciones y variables de arranque |
servers.resources(id) | Estado en vivo, CPU, memoria, disco, red, tiempo encendido |
servers.power(id, signal) / start / stop / restart / kill | Acciones de encendido |
servers.command(id, command) | Ejecutar un comando de consola |
servers.activity(id) | Registro de actividad (paginado) |
servers.rename(id, name, description?) | Renombrar |
servers.reinstall(id) | Volver a ejecutar el script de instalación |
servers.setDockerImage(id, image) | Cambia la imagen de ejecución (p. ej. la versión de Java) |
servers.websocket(id) | Token de consola y URL del socket (la consola de abajo los usa) |
servers.waitForState(id, 'running', { timeoutMs }) | Espera a que el servidor llegue a un estado, p. ej. después de start(). Devuelve sus recursos |
await hw.servers.start(id);
await hw.servers.waitForState(id, 'running'); // default timeout 5 minutes
const line = await hw.waitForConsole(id, /There are \d+ of a max/, { send: 'list' });
Consola#
const c = await hw.console(id).connect();
c.on('output', (line) => console.log(line));
c.on('stats', (s) => console.log(s.state, s.memory_bytes));
c.requestLogs(); // replay recent output
c.sendCommand('list');
// later
c.close();
// Or just fetch the recent lines (ANSI colours stripped):
const lines = await hw.tail(id, { lines: 50 });
// Or wait for one line: send a command and get its reply.
const reply = await hw.waitForConsole(id, /There are \d+ of a max/, { send: 'list', timeoutMs: 20_000 });
waitForConsole(id, pattern, { send, includeRecent, timeoutMs }) devuelve la primera línea que coincide con un texto o una RegExp. includeRecent también busca en el registro reciente, y falla con el código Timeout tras timeoutMs (30 segundos por defecto).
La consola se conecta directamente al nodo donde se ejecuta tu servidor y renueva su token automáticamente.
Eventos:
readyycloseoutputstatusstatsinstall outputdaemon messageydaemon errorerror
Archivos#
REST: Endpoints de archivos
await hw.files.list(id, '/plugins');
const props = await hw.files.read(id, 'server.properties');
await hw.files.write(id, 'server.properties', props.replace('pvp=true', 'pvp=false'));
await hw.files.rename(id, '/', [{ from: 'world', to: 'world-old' }]);
await hw.files.delete(id, '/logs', ['old.log']);
await hw.files.createFolder(id, '/', 'maps');
await hw.files.compress(id, '/', ['world']); // returns the archive
await hw.files.decompress(id, '/', 'world.tar.gz');
await hw.files.pull(id, 'https://example.com/map.zip', { directory: '/maps' });
await hw.files.exists(id, 'plugins/LuckPerms.jar'); // true or false
// Binary files: upload and download go straight to the server's node.
await hw.files.upload(id, '/plugins', jarBytes, 'MyPlugin.jar'); // Blob, ArrayBuffer, Uint8Array or string
const buf = await hw.files.download(id, 'world.tar.gz'); // ArrayBuffer, in memory
// Or work with the signed URLs yourself, e.g. to stream a big file:
const url = await hw.files.downloadUrl(id, 'world.tar.gz');
const uploadUrl = await hw.files.uploadUrl(id); // POST multipart "files" to `${uploadUrl}&directory=/maps`
Leer un archivo que no existe lanza un HostwolfError con estado 404 y código NotFound. También tienes files.copy(id, path) y files.chmod(id, root, [{ file, mode }]).
Copias de seguridad#
const { data } = await hw.backups.list(id);
const b = await hw.backups.create(id, { name: 'nightly', is_locked: false });
await hw.backups.waitUntilDone(id, b.uuid); // polls until finished; rejects with code BackupFailed if it failed
await hw.backups.get(id, b.uuid);
await hw.backups.restore(id, b.uuid, { truncate: false });
await hw.backups.toggleLock(id, b.uuid);
await hw.backups.downloadUrl(id, b.uuid);
await hw.backups.delete(id, b.uuid);
await hw.backups.smartState(id);
await hw.backups.setSmart(id, true); // automatic backups before risky changes
Puedes crear 2 copias de seguridad cada 10 minutos. Por encima de eso, create() falla al momento con error.retryAfter definido.
Bases de datos#
Cada servidor tiene hasta 5 bases de datos compatibles con MySQL en su propia máquina. La primera también arranca el servicio de bases de datos, lo que tarda unos 40 segundos: la API responde 202 y create() devuelve un resultado pendiente, salvo que pases wait.
import { isDatabasePending } from '@hostwolf/sdk';
// Simplest: wait until the database exists (up to 2 minutes), then use it
const db = await hw.databases.create(id, 'luckperms', { wait: true });
console.log(db.host.address, db.name, db.username, db.relationships?.password?.password);
// Or handle the pending answer yourself
const r = await hw.databases.create(id, 'coreprotect');
if (isDatabasePending(r)) {
const ready = await hw.databases.waitUntilReady(id, 'coreprotect', { timeoutMs: 120_000 });
}
await hw.databases.service(id); // { status, local_address, public_address, other_servers }
await hw.databases.setRemoteAccess(id, db.id, { remoteIps: ['203.0.113.7'], anywhere: false });
await hw.databases.rotatePassword(id, db.id);
const dump = await hw.databases.export(id, db.id); // .sql.gz bytes
await hw.databases.import(id, db.id, dump, { filename: 'luckperms.sql.gz' });
await hw.databases.delete(id, db.id);
Los servidores de juego se conectan a db.host (172.18.0.1:3306). db.name lleva un prefijo, por ejemplo s12_luckperms; waitUntilReady() recibe el nombre que pediste. Los demás métodos reciben db.id. waitUntilReady() falla con el código DatabaseSetupFailed si el servicio no pudo arrancar, o con Timeout. Puedes crear 2 bases de datos por minuto y exportar o importar 6 por minuto.
Juego, jugadores y ajustes#
await hw.game.versions(id); // current + available versions
await hw.game.setVersion(id, '1.21.4'); // switches and reinstalls; { resetWorld, force } for downgrades
await hw.game.settings(id); // friendly settings, grouped by file ({ supported: false } on Java Minecraft)
await hw.game.updateSettings(id, { difficulty: 'hard' }, true);
await hw.game.diagnostics(id); // "Fix my server" checks
await hw.players.info(id); // online count (+ names on Minecraft)
await hw.players.list(id); // live list via RCON/query
await hw.players.kick(id, 'Steve');
await hw.players.ban(id, { steamid: '7656...' });
await hw.players.unban(id, 'Steve');
await hw.players.broadcast(id, 'Restarting in 5 minutes');
Mods, plugins y modpacks#
const profile = await hw.addons.profile(id); // sources, install dir, frameworks
const { items } = await hw.addons.search(id, 'modrinth', 'worldedit');
await hw.addons.install(id, 'modrinth', items[0].id); // newest compatible version
const installed = await hw.addons.installed(id, { checkUpdates: true });
await hw.addons.uninstall(id, installed.addons[0].id);
const packs = await hw.modpacks.search(id, 'cobblemon');
await hw.modpacks.install(id, packs.items[0].id, { freshWorld: false });
Otras llamadas de addons:
addons.versions(id, source, project): versiones, marcadas como compatibles o no.addons.installFeatured(id, project): addons destacados con un clic, desdeprofile.featured.addons.upload(id, blob, name): cuandoprofile.uploadestá definido. Si no, pon los archivos enprofile.directoryconfiles.upload().addons.removeManual(id, file): quita un archivo que añadiste tú.addons.setFramework(id, framework): p. ej. Oxide o Carbon en Rust.addons.workshop/setWorkshop/workshopPreview(juegos con Steam Workshop).addons.cs2Framework(id, { repair }): Metamod + CounterStrikeSharp en CS2.modpacks.current/versions/remove.
Más#
| Espacio de nombres | Métodos |
|---|---|
startup | get(id), setVariable(id, key, value) |
schedules | list, get, create, update, execute, delete, createTask, updateTask, deleteTask |
databases | list(id, { includePassword }), create, waitUntilReady, service, setRemoteAccess, rotatePassword, export, import, delete. Ver Bases de datos |
network | allocations, createAllocation, setNotes, setPrimary, deleteAllocation |
users | list, get, create(id, publicId, permissions), update, delete, permissions(). Las cuentas se añaden por su ID pública (HW-XXXXXX), no por correo |
notifications | Avisos de Discord por servidor: get, update, test |
domain | Dominio propio: get, check, set, remove |
mcNetwork | Redes Velocity/BungeeCord: get, link, unlink, installFabricProxy |
account | get, updateEmail, updatePassword, updateProfile, updateLanguage(locale, currency?), uploadAvatar, deleteAvatar, activity, getNotifications, updateNotifications, testNotification, getCurseforgeKey, setCurseforgeKey, getFactorioCredentials, setFactorioCredentials |
account.apiKeys | list, create(description, allowedIps?) (devuelve la clave completa en token, una sola vez), delete(identifier) |
account.sshKeys | list, create(name, publicKey), delete(fingerprint) |
store | Solo lectura: catalog(), orders(), billing() |
Contratar, cancelar y pagar servidores solo se puede hacer en el panel, así que una clave API nunca puede gastar dinero.
Paginación#
servers.list, backups.list y los métodos activity devuelven { data, pagination }. Para recorrer todos los servidores:
import { collect } from '@hostwolf/sdk';
const all = await collect(hw.servers.listAll());
Errores#
Las llamadas que fallan lanzan un HostwolfError.
import { HostwolfError } from '@hostwolf/sdk';
try {
await hw.game.setVersion(id, '1.16.5');
} catch (e) {
if (e instanceof HostwolfError && e.code === 'downgrade_requires_reset') {
// older versions can't load a newer world: retry with { resetWorld: true } or { force: true }
}
}
Tiene estas propiedades:
status: el estado HTTP, o 0 en errores de red y tiempos agotados.code: el código de error. Además de los códigos de la API, el SDK usaNetworkError(sin respuesta o tiempo agotado),Timeout(un helperwaitFor…owaitUntil…se rindió),BackupFailed,DatabaseSetupFailed,DatabaseNotCreatedyNotFound(un archivo que no existe).retryAfter: en un 429, los segundos que faltan para que la acción vuelva a estar permitida. Las esperas cortas se reintentan por ti; las largas (como el límite de 2 copias por cada 10 minutos) fallan al momento con este valor.message: una descripción legible.errors: la lista de errores sin procesar.body: el cuerpo de la respuesta.- Atajos:
isNotFound,isRateLimitedeisValidation.
Los más comunes:
401: la clave es incorrecta, está incompleta o revocada.403conThis IP address (…) does not have permission: la clave tiene una lista de IP permitidas sin tu dirección.409 downgrade_requires_resety422 unknown_version: degame.setVersion().429: un límite de peticiones; miraretryAfter.- Una escritura que agotó el tiempo (estado 0) puede terminar igualmente en el panel, así que compruébalo antes de reintentarla.
Peticiones directas#
Si un endpoint aún no tiene método, llámalo directamente:
await hw.http.get(`/servers/${id}/players`);
await hw.http.post(`/servers/${id}/command`, { command: 'list' });
Las rutas son relativas a /api/client, y las respuestas se desempaquetan igual que con los métodos incluidos. Consulta todos los endpoints en la referencia de la API.
Versiones#
| Archivo | Uso |
|---|---|
https://hostwolf.net/dl/hostwolf-sdk- | Una versión fija. Úsala en package.json. |
https://hostwolf.net/dl/hostwolf-sdk.tgz | Siempre la versión más reciente. Los gestores de paquetes guardan en caché por URL, así que es mejor usar URLs con versión. |
https://hostwolf.net/dl/index.json | Versiones actuales, con hashes SHA-256 y de integridad de npm. |
Para actualizar, instala la URL con la versión más nueva.
Licencia#
MIT