#!/usr/bin/env node
"use strict";
// hub-agent CLI — single binary, multiple subcommands. The actual long-
// running agent process is `start` (the default). All other subcommands
// are diagnostic / lifecycle tools that read config + logs + /dev so the
// operator on the box can see what's going on without digging into
// systemctl / launchctl / journalctl manually.
//
// Usage:
//   hub-agent                  start the agent (default)
//   hub-agent start            same
//   hub-agent setup            run the (re)configure wizard
//   hub-agent status           process state + connection state + config summary
//   hub-agent logs [-f] [-n N] tail the agent log
//   hub-agent devices          list configured endpoints + USB candidates on /dev
//   hub-agent config           show config (apiKey masked)
//   hub-agent restart          restart the service via systemctl/launchctl
//   hub-agent version          print package version
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
const child_process_1 = require("child_process");
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const cmd = process.argv[2];
function platformLogPaths() {
    if (process.platform === 'darwin') {
        // LaunchAgent logs to ~/Library/Logs in the install user's home.
        const home = process.env.HOME || `/Users/${process.env.USER || process.env.LOGNAME || ''}`;
        return {
            out: `${home}/Library/Logs/appmint-hub.out.log`,
            err: `${home}/Library/Logs/appmint-hub.err.log`,
            service: 'launchd:io.appmint.hub (LaunchAgent)',
        };
    }
    if (process.platform === 'linux') {
        return { out: 'journalctl', service: 'systemd:appmint-hub' };
    }
    return { out: '<see Event Viewer → AppmintHub>', service: 'windows-service:AppmintHub' };
}
function run(cmdline) {
    try {
        return (0, child_process_1.execSync)(cmdline, { stdio: ['ignore', 'pipe', 'pipe'] }).toString();
    }
    catch (e) {
        return (e?.stdout?.toString() || '') + (e?.stderr?.toString() || '') + (e?.message || '');
    }
}
function processState() {
    if (process.platform === 'darwin') {
        const out = run('launchctl list io.appmint.hub 2>&1');
        if (/Could not find/i.test(out))
            return { running: false, detail: 'service not loaded' };
        const pidLine = out.split('\n').find((l) => /"PID"\s*=\s*\d+/.test(l));
        if (pidLine)
            return { running: true, detail: pidLine.trim() };
        return { running: false, detail: 'service loaded but no PID — probably crashed' };
    }
    if (process.platform === 'linux') {
        const status = run('systemctl is-active appmint-hub 2>&1').trim();
        const running = status === 'active';
        return { running, detail: status };
    }
    return { running: false, detail: '(windows: check Event Viewer / Get-Service AppmintHub)' };
}
function loadCfg() {
    try {
        const { loadConfig } = require('./config');
        return loadConfig();
    }
    catch {
        return null;
    }
}
function maskKey(k) {
    if (!k)
        return '(none)';
    if (k.length <= 12)
        return k.slice(0, 4) + '…';
    return k.slice(0, 8) + '…' + k.slice(-4);
}
function tailLog(lines = 50, follow = false) {
    if (process.platform === 'darwin') {
        const { out, err } = platformLogPaths();
        if (follow) {
            const p = (0, child_process_1.spawn)('tail', ['-n', String(lines), '-F', out, ...(err ? [err] : [])], { stdio: 'inherit' });
            p.on('exit', (code) => process.exit(code || 0));
            return;
        }
        console.log(`--- ${out} ---`);
        console.log(run(`tail -n ${lines} '${out}' 2>&1`));
        if (err) {
            console.log(`--- ${err} ---`);
            console.log(run(`tail -n ${lines} '${err}' 2>&1`));
        }
        return;
    }
    if (process.platform === 'linux') {
        const args = follow
            ? ['-u', 'appmint-hub', '-n', String(lines), '-f']
            : ['-u', 'appmint-hub', '-n', String(lines), '--no-pager'];
        if (follow) {
            const p = (0, child_process_1.spawn)('journalctl', args, { stdio: 'inherit' });
            p.on('exit', (code) => process.exit(code || 0));
            return;
        }
        console.log(run(`journalctl ${args.join(' ')}`));
        return;
    }
    console.log('Windows: open Event Viewer → Applications and Services → AppmintHub.');
}
function statusCmd() {
    const cfg = loadCfg();
    const proc = processState();
    const { service, out, err } = platformLogPaths();
    console.log('appmint-hub status');
    console.log('───────────────────');
    console.log(`platform:  ${process.platform}`);
    console.log(`service:   ${service} — ${proc.running ? 'RUNNING' : 'NOT RUNNING'}`);
    console.log(`           ${proc.detail}`);
    console.log('');
    if (!cfg) {
        console.log('config:    (not found / unreadable)');
        console.log('           Run "hub-agent setup" to create one.');
    }
    else {
        console.log(`config:    hubName=${cfg.hubName}  orgId=${cfg.orgId}`);
        console.log(`           server=${cfg.serverUrl}`);
        console.log(`           apiKey=${maskKey(cfg.apiKey)}`);
        console.log(`           endpoints=${(cfg.endpoints || []).length} configured`);
    }
    console.log('');
    // Recent log — last few connection-related lines so the operator sees
    // online/offline state without scrolling logs.
    console.log('recent log (connection-related):');
    let recent = '';
    if (process.platform === 'darwin') {
        recent = run(`tail -n 200 '${out}' '${err || ''}' 2>&1 | grep -iE 'welcomed|connecting|disconnected|rejected|error' | tail -n 12`);
    }
    else if (process.platform === 'linux') {
        recent = run(`journalctl -u appmint-hub -n 200 --no-pager 2>&1 | grep -iE 'welcomed|connecting|disconnected|rejected|error' | tail -n 12`);
    }
    if (!recent.trim())
        recent = '  (no relevant log lines found in last 200)';
    console.log(recent.split('\n').map((l) => l ? `  ${l}` : '').join('\n'));
    console.log('');
    console.log('next steps:');
    if (!proc.running) {
        console.log('  • Service not running — try "hub-agent restart" (sudo).');
    }
    else if (!cfg) {
        console.log('  • Config missing — run "hub-agent setup" (sudo).');
    }
    else {
        console.log('  • If status is online in admin UI, you\'re good.');
        console.log('  • Otherwise: "hub-agent logs -f" to watch live, or "hub-agent setup" to fix server URL / key.');
    }
}
function devicesCmd() {
    const cfg = loadCfg();
    console.log('appmint-hub devices');
    console.log('────────────────────');
    // Discover first — both sections are derived from what the OS actually
    // sees right now, so "CONNECTED" can't lie about an unplugged device.
    let candidates = [];
    let probeEndpoint;
    try {
        const dispatcher = require('./dispatcher');
        candidates = dispatcher.discoverDevicePaths(cfg?.endpoints || []);
        probeEndpoint = dispatcher.probeEndpoint;
    }
    catch (e) {
        console.error(`(discovery error: ${e?.message})`);
        candidates = [];
        probeEndpoint = () => ({ health: 'unknown', detail: 'probe unavailable' });
    }
    // Probe each configured endpoint for REAL reachability. A CUPS queue
    // existing (USB or Bluetooth) is NOT proof — the probe inspects transport
    // state and only says `online` when the device genuinely answers.
    const configured = cfg?.endpoints || [];
    const probed = configured.map((ep) => ({ ep, ...probeEndpoint(ep) }));
    const online = probed.filter((p) => p.health === 'online');
    const notOnline = probed.filter((p) => p.health !== 'online');
    console.log('CONNECTED (verified reachable right now):');
    if (!configured.length) {
        console.log('  (none — use the admin UI Add peripheral, or call setup)');
    }
    else if (!online.length) {
        console.log('  (none verified — see "NOT CONFIRMED" below)');
    }
    else {
        for (const { ep, detail } of online) {
            const where = ep.config?.path || ep.config?.interface || '—';
            console.log(`  • ${ep.name.padEnd(20)} [${ep.kind.padEnd(9)}]  ${where}`);
            const caps = (ep.capabilities || []).join(', ');
            if (caps)
                console.log(`    capabilities: ${caps}`);
            if (detail)
                console.log(`    ${detail}`);
        }
    }
    console.log('');
    if (notOnline.length) {
        console.log('NOT CONFIRMED (configured, but the device is not answering):');
        for (const { ep, health, detail } of notOnline) {
            const where = ep.config?.path || ep.config?.interface || '—';
            const tag = health === 'offline' ? 'OFFLINE' : 'UNVERIFIED';
            console.log(`  ✗ ${ep.name.padEnd(20)} [${ep.kind.padEnd(9)}]  ${where}  — ${tag}`);
            if (detail)
                console.log(`    ${detail}`);
        }
        console.log('');
    }
    // Section 2: available — what the OS sees plugged in that the agent
    // could attach. Uses the same per-platform discovery the WS RPC does.
    console.log('AVAILABLE (detected on the OS, not yet attached):');
    if (candidates.length === 0) {
        console.log(`  (none detected — platform=${process.platform})`);
        macOsHidNote();
    }
    else {
        for (const c of candidates) {
            const driveable = c.driveable !== false;
            const status = c.inUseBy ? `← attached as ${c.inUseBy}` : driveable ? 'available' : 'info only';
            const name = c.product ? `  ${c.product}` : '';
            console.log(`  ${(c.hintKind || 'unknown').padEnd(10)} ${c.path.padEnd(60)} ${status}${name}`);
            if (c.note)
                console.log(`            ${c.note}`);
        }
        macOsHidNote();
    }
}
// Why a USB scanner / NFC card reader can be missing from the list above.
// macOS does not expose HID input devices as readable device files (HID is
// IOKit-mediated) and the agent has no PC/SC enumeration, so HID-keyboard
// readers and PC/SC readers (ACR122U-class) won't appear as driveable here.
function macOsHidNote() {
    if (process.platform !== 'darwin')
        return;
    console.log('');
    console.log('  macOS note — if your barcode scanner or NFC card reader is not listed:');
    console.log('    • A USB reader in HID-keyboard mode is NOT readable on macOS (IOKit-mediated).');
    console.log('      Switch it to USB-COM / CDC-serial mode (scan the vendor setup barcode in its');
    console.log('      manual) so it shows up as /dev/cu.usbserial* or /dev/cu.usbmodem*, then attach');
    console.log('      it (NFC readers: pick kind "nfc-reader" in the admin UI → Hubs → Diagnose →');
    console.log('      Advanced).');
    console.log('    • PC/SC contactless readers (ACR122U etc.) need pcscd + the nfc-pcsc module and');
    console.log('      are not auto-detected here — drive them on a Linux hub for full read/write.');
    console.log('    • Linux exposes /dev/hidraw* so HID readers work there with no mode change.');
}
function configCmd() {
    const cfg = loadCfg();
    if (!cfg) {
        console.error('config not found. Run "hub-agent setup" to create it.');
        process.exit(1);
    }
    console.log(JSON.stringify({ ...cfg, apiKey: maskKey(cfg.apiKey) }, null, 2));
}
function restartCmd() {
    if (process.platform === 'darwin') {
        console.log(run(`launchctl kickstart -k gui/${process.getuid?.() ?? ''}/io.appmint.hub 2>&1`));
    }
    else if (process.platform === 'linux') {
        console.log(run('systemctl restart appmint-hub 2>&1'));
    }
    else {
        console.log(run('powershell -Command "Restart-Service AppmintHub" 2>&1'));
    }
}
/**
 * Update the agent code in place — pulls latest, reinstalls deps, rebuilds,
 * restarts the service. DOES NOT touch config.json — operator's hubName /
 * orgId / apiKey / serverUrl / endpoints are preserved.
 *
 * Strategy: detect install dir, run git pull (if it's a clone) or skip the
 * pull (if it was tarball-installed; future: download a fresh tarball),
 * reinstall + build with a clean prune, restart.
 */
function updateCmd() {
    const installDirs = process.platform === 'win32'
        ? [`${process.env.ProgramFiles || 'C:\\Program Files'}\\AppmintHub`]
        : ['/opt/appmint-hub'];
    const installDir = installDirs.find((d) => {
        try {
            return fs.existsSync(path.join(d, 'package.json'));
        }
        catch {
            return false;
        }
    });
    if (!installDir) {
        console.error('hub-agent: install directory not found.');
        console.error('Looked in: ' + installDirs.join(', '));
        process.exit(1);
    }
    console.log(`updating agent at ${installDir}`);
    console.log('(config.json is NOT touched — credentials and endpoints are preserved)');
    const isGit = fs.existsSync(path.join(installDir, '.git'));
    const cd = process.platform === 'win32' ? `cd /d "${installDir}"` : `cd "${installDir}"`;
    if (isGit) {
        console.log('— git pull');
        console.log(run(`${cd} && git pull --ff-only 2>&1`));
    }
    else {
        console.log('— skipping git pull (install is not a clone). Future: tarball updater.');
    }
    console.log('— npm ci');
    console.log(run(`${cd} && npm ci 2>&1`));
    console.log('— npm run build');
    console.log(run(`${cd} && npm run build 2>&1`));
    console.log('— npm prune --omit=dev');
    console.log(run(`${cd} && npm prune --omit=dev 2>&1`));
    console.log('— restart service');
    restartCmd();
    console.log('');
    console.log('done. run "hub-agent status" to verify it came back online.');
}
switch (cmd) {
    case 'setup':
        require('./setup');
        break;
    case 'start':
    case undefined: {
        // Refuse to start a second agent if the service is already running —
        // two agents with the same hubName get into an eviction war on the
        // gateway (last writer wins on hubsByName), each kicking the other
        // off every reconnect cycle. Pass --force to override (rare; only
        // useful if you've explicitly stopped the service).
        const force = process.argv.includes('--force');
        const proc = processState();
        if (proc.running && !force) {
            console.error('hub-agent is already running as a service.');
            console.error(`  ${proc.detail}`);
            console.error('');
            console.error('Running another instance with the same hubName would cause both');
            console.error('to disconnect each other every few seconds (the gateway only');
            console.error('keeps one connection per hub).');
            console.error('');
            console.error('What you probably want:');
            console.error('  hub-agent logs -f      Watch the service log live.');
            console.error('  hub-agent status       See connection state.');
            console.error('  hub-agent restart      Bounce the service.');
            console.error('');
            console.error('To start a foreground instance anyway (advanced):');
            console.error('  1. Stop the service first:  sudo launchctl bootout system /Library/LaunchDaemons/io.appmint.hub.plist  (macOS)');
            console.error('                              sudo systemctl stop appmint-hub  (Linux)');
            console.error('  2. Then:  hub-agent start --force');
            process.exit(1);
        }
        require('./index');
        break;
    }
    case 'status':
        statusCmd();
        break;
    case 'logs': {
        const follow = process.argv.includes('-f') || process.argv.includes('--follow');
        const nIdx = Math.max(process.argv.indexOf('-n'), process.argv.indexOf('--lines'));
        const n = nIdx >= 0 ? parseInt(process.argv[nIdx + 1] || '50', 10) || 50 : 50;
        tailLog(n, follow);
        break;
    }
    case 'devices':
        devicesCmd();
        break;
    case 'config':
        configCmd();
        break;
    case 'restart':
        restartCmd();
        break;
    case 'test': {
        // hub-agent test <endpoint> [operation] [json-data]
        // Routes through the same dispatcher the WS receive path uses, so the
        // SAME code path is exercised in CLI tests and real production calls.
        // With multiple printers / drawers / scanners, endpoint name pinpoints
        // which one. With no args, lists what's configured.
        const endpoint = process.argv[3];
        const operation = process.argv[4];
        const dataJson = process.argv[5];
        const { testEndpoint } = require('./test-cmd');
        testEndpoint(endpoint, operation, dataJson).catch((e) => {
            console.error(e?.message || e);
            process.exit(2);
        });
        break;
    }
    case '--version':
    case '-v':
    case 'version':
        // eslint-disable-next-line @typescript-eslint/no-var-requires
        console.log(require('../package.json').version);
        break;
    case '--help':
    case '-h':
    case 'help':
    default:
        if (cmd && cmd !== 'help' && cmd !== '--help' && cmd !== '-h') {
            console.error(`Unknown command: ${cmd}`);
            console.error('');
        }
        console.error('Usage: hub-agent <command>');
        console.error('');
        console.error('  start             Start the agent (default if no command).');
        console.error('  setup             (Re)configure: hubName, orgId, apiKey, serverUrl, endpoints.');
        console.error('  status            Process + connection + config summary + recent log.');
        console.error('  logs [-f] [-n N]  Tail the service log. -f to follow, -n N for last N lines.');
        console.error('  devices           List configured endpoints + USB devices detected on /dev.');
        console.error('  config            Print current config.json (apiKey masked).');
        console.error('  restart           Restart the service (systemctl / launchctl / Restart-Service).');
        console.error('  test <endpoint> [op] [data]   Send <op> to a configured endpoint locally,');
        console.error('                                via the same dispatcher the WS uses. No args = list.');
        console.error('  version           Print agent version.');
        if (cmd && cmd !== 'help' && cmd !== '--help' && cmd !== '-h')
            process.exit(1);
}
//# sourceMappingURL=cli.js.map