docs: Ledger Live integration, contract deploy learnings, NEXT_STEPS updates
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
- ADD_CHAIN138_TO_LEDGER_LIVE: Ledger form done; public code review repo bis-innovations/LedgerLive; init/push commands - CONTRACT_DEPLOYMENT_RUNBOOK: Chain 138 gas price 1 gwei, 36-addr check, TransactionMirror workaround - CONTRACT_*: AddressMapper, MirrorManager deployed 2026-02-12; 36-address on-chain check - NEXT_STEPS_FOR_YOU: Ledger done; steps completable now (no LAN); run-completable-tasks-from-anywhere - MASTER_INDEX, OPERATOR_OPTIONAL, SMART_CONTRACTS_INVENTORY_SIMPLE: updates - LEDGER_BLOCKCHAIN_INTEGRATION_COMPLETE: bis-innovations/LedgerLive reference Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
299
unifi-api/src/cli/index.ts
Normal file
299
unifi-api/src/cli/index.ts
Normal file
@@ -0,0 +1,299 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* UniFi API CLI Tool
|
||||
* Command-line interface for UniFi Controller operations
|
||||
*/
|
||||
|
||||
import { Command } from 'commander';
|
||||
import { readFileSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { UnifiClient } from '../client/UnifiClient.js';
|
||||
import { ApiMode } from '../client/ApiMode.js';
|
||||
import { SitesService } from '../services/SitesService.js';
|
||||
import { DevicesService } from '../services/DevicesService.js';
|
||||
import { ClientsService } from '../services/ClientsService.js';
|
||||
import { NetworksService } from '../services/NetworksService.js';
|
||||
import { SystemService } from '../services/SystemService.js';
|
||||
import { FirewallService } from '../services/FirewallService.js';
|
||||
|
||||
// Load environment variables
|
||||
const envPath = join(homedir(), '.env');
|
||||
function loadEnvFile(filePath: string): boolean {
|
||||
try {
|
||||
const envFile = readFileSync(filePath, 'utf8');
|
||||
const envVars = envFile.split('\n').filter(
|
||||
(line) => line.includes('=') && !line.trim().startsWith('#')
|
||||
);
|
||||
for (const line of envVars) {
|
||||
const [key, ...values] = line.split('=');
|
||||
const k = key?.trim();
|
||||
if (k && values.length > 0 && /^[A-Z_][A-Z0-9_]*$/.test(k)) {
|
||||
if (process.env[k] !== undefined) continue; // prefer existing env (e.g. UNIFI_UDM_URL, UNIFI_API_MODE)
|
||||
let value = values.join('=').trim();
|
||||
if (
|
||||
(value.startsWith('"') && value.endsWith('"')) ||
|
||||
(value.startsWith("'") && value.endsWith("'"))
|
||||
) {
|
||||
value = value.slice(1, -1);
|
||||
}
|
||||
process.env[k] = value;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
loadEnvFile(envPath);
|
||||
|
||||
const program = new Command();
|
||||
|
||||
program
|
||||
.name('unifi-cli')
|
||||
.description('CLI tool for UniFi Controller operations')
|
||||
.version('1.0.0');
|
||||
|
||||
function createClient(): UnifiClient {
|
||||
const baseUrl = process.env.UNIFI_UDM_URL || 'https://192.168.1.1';
|
||||
const apiMode = (process.env.UNIFI_API_MODE || 'private') as ApiMode;
|
||||
const siteId = process.env.UNIFI_SITE_ID || 'default';
|
||||
const verifySSL = process.env.UNIFI_VERIFY_SSL !== 'false';
|
||||
|
||||
if (apiMode === ApiMode.OFFICIAL) {
|
||||
const apiKey = process.env.UNIFI_API_KEY;
|
||||
if (!apiKey) {
|
||||
throw new Error('UNIFI_API_KEY is required for Official API mode');
|
||||
}
|
||||
return new UnifiClient({
|
||||
baseUrl,
|
||||
apiMode,
|
||||
apiKey,
|
||||
siteId,
|
||||
verifySSL,
|
||||
});
|
||||
} else {
|
||||
const username = process.env.UNIFI_USERNAME;
|
||||
const password = process.env.UNIFI_PASSWORD;
|
||||
if (!username || !password) {
|
||||
throw new Error('UNIFI_USERNAME and UNIFI_PASSWORD are required for Private API mode');
|
||||
}
|
||||
return new UnifiClient({
|
||||
baseUrl,
|
||||
apiMode,
|
||||
username,
|
||||
password,
|
||||
siteId,
|
||||
verifySSL,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Sites commands
|
||||
program
|
||||
.command('sites')
|
||||
.description('List all sites')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const sitesService = new SitesService(client);
|
||||
const sites = await sitesService.listSites();
|
||||
console.log(JSON.stringify(sites, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Devices commands
|
||||
program
|
||||
.command('devices')
|
||||
.description('List all devices')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const devicesService = new DevicesService(client);
|
||||
const devices = await devicesService.listDevices();
|
||||
console.log(JSON.stringify(devices, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('device <mac>')
|
||||
.description('Get device by MAC address')
|
||||
.action(async (mac: string) => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const devicesService = new DevicesService(client);
|
||||
const device = await devicesService.getDevice(mac);
|
||||
console.log(JSON.stringify(device, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Clients commands
|
||||
program
|
||||
.command('clients')
|
||||
.description('List all active clients')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const clientsService = new ClientsService(client);
|
||||
const clients = await clientsService.listClients();
|
||||
console.log(JSON.stringify(clients, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('client <clientId>')
|
||||
.description('Get client by ID or MAC')
|
||||
.action(async (clientId: string) => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const clientsService = new ClientsService(client);
|
||||
const clientData = await clientsService.getClient(clientId);
|
||||
console.log(JSON.stringify(clientData, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Networks commands
|
||||
program
|
||||
.command('networks')
|
||||
.description('List all networks')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const networksService = new NetworksService(client);
|
||||
const networks = await networksService.listNetworks();
|
||||
console.log(JSON.stringify(networks, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// System / UDM Pro config (Private API)
|
||||
program
|
||||
.command('system-info')
|
||||
.description('Get system/controller info (Private API)')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const systemService = new SystemService(client);
|
||||
const info = await systemService.getSystemInfo();
|
||||
console.log(JSON.stringify(info, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('health')
|
||||
.description('Get site health status (Private API)')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const systemService = new SystemService(client);
|
||||
const health = await systemService.getHealth();
|
||||
console.log(JSON.stringify(health, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('sysinfo')
|
||||
.description('Get system stats / sysinfo (Private API)')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const systemService = new SystemService(client);
|
||||
const stats = await systemService.getSystemStats();
|
||||
console.log(JSON.stringify(stats, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('firewall-rules')
|
||||
.description('List firewall rules (Private API)')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const firewallService = new FirewallService(client);
|
||||
const rules = await firewallService.listFirewallRules();
|
||||
console.log(JSON.stringify(rules, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('port-forward')
|
||||
.description('List port forwarding rules (Private API)')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const systemService = new SystemService(client);
|
||||
const rules = await systemService.getPortForwarding();
|
||||
console.log(JSON.stringify(rules, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
// Config dump: summary of UDM Pro config (Private API)
|
||||
program
|
||||
.command('config')
|
||||
.description('Dump UDM Pro config summary (system, networks, firewall, port-forward)')
|
||||
.action(async () => {
|
||||
try {
|
||||
const client = createClient();
|
||||
const [systemService, networksService, firewallService] = [
|
||||
new SystemService(client),
|
||||
new NetworksService(client),
|
||||
new FirewallService(client),
|
||||
];
|
||||
const [sysinfo, health, portForward, networks, firewallRules] = await Promise.all([
|
||||
systemService.getSystemStats(),
|
||||
systemService.getHealth(),
|
||||
systemService.getPortForwarding(),
|
||||
networksService.listNetworks(),
|
||||
firewallService.listFirewallRules(),
|
||||
]);
|
||||
const summary = {
|
||||
sysinfo,
|
||||
health,
|
||||
networks,
|
||||
firewallRules: firewallRules.length,
|
||||
firewallRulesDetail: firewallRules,
|
||||
portForward: portForward.length,
|
||||
portForwardDetail: portForward,
|
||||
};
|
||||
console.log(JSON.stringify(summary, null, 2));
|
||||
} catch (error) {
|
||||
console.error('Error:', error instanceof Error ? error.message : String(error));
|
||||
process.exit(1);
|
||||
}
|
||||
});
|
||||
|
||||
program.parse();
|
||||
Reference in New Issue
Block a user