Created a vs code extension with Claude
This commit is contained in:
160
vscode-appdaemon/src/adClient.ts
Normal file
160
vscode-appdaemon/src/adClient.ts
Normal file
@@ -0,0 +1,160 @@
|
||||
import * as vscode from 'vscode';
|
||||
import * as https from 'https';
|
||||
import * as http from 'http';
|
||||
import { URL } from 'url';
|
||||
|
||||
/** Ensure URL has http(s):// prefix and no trailing slash */
|
||||
function normalizeUrl(raw: string): string {
|
||||
let url = raw.trim().replace(/\/+$/, '');
|
||||
if (!/^https?:\/\//i.test(url)) {
|
||||
url = 'http://' + url;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
||||
export class ADClient {
|
||||
private productionMode = false;
|
||||
private _onProductionModeChanged = new vscode.EventEmitter<boolean>();
|
||||
readonly onProductionModeChanged = this._onProductionModeChanged.event;
|
||||
private log: vscode.OutputChannel;
|
||||
|
||||
constructor(log: vscode.OutputChannel) {
|
||||
this.log = log;
|
||||
}
|
||||
|
||||
private getConfig() {
|
||||
const config = vscode.workspace.getConfiguration('appdaemon');
|
||||
return {
|
||||
url: normalizeUrl(config.get<string>('adUrl', 'http://localhost:5050'))
|
||||
};
|
||||
}
|
||||
|
||||
private async request<T = unknown>(method: string, path: string, body?: unknown): Promise<T> {
|
||||
const { url } = this.getConfig();
|
||||
const fullUrl = new URL(path, url);
|
||||
const isHttps = fullUrl.protocol === 'https:';
|
||||
const lib = isHttps ? https : http;
|
||||
|
||||
this.log.appendLine(`[AD] ${method} ${fullUrl.href}`);
|
||||
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const options: http.RequestOptions = {
|
||||
hostname: fullUrl.hostname,
|
||||
port: parseInt(fullUrl.port, 10) || (isHttps ? 443 : 5050),
|
||||
path: fullUrl.pathname + fullUrl.search,
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
};
|
||||
|
||||
const req = lib.request(options, (res) => {
|
||||
let data = '';
|
||||
res.on('data', (chunk: Buffer) => { data += chunk; });
|
||||
res.on('end', () => {
|
||||
this.log.appendLine(`[AD] ${res.statusCode} (${data.length} bytes)`);
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
try {
|
||||
resolve(JSON.parse(data) as T);
|
||||
} catch {
|
||||
resolve(data as unknown as T);
|
||||
}
|
||||
} else {
|
||||
reject(new Error(`HTTP ${res.statusCode}: ${data.slice(0, 200)}`));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
req.on('error', (err) => {
|
||||
this.log.appendLine(`[AD] ERROR: ${err.message}`);
|
||||
reject(err);
|
||||
});
|
||||
req.setTimeout(10000, () => {
|
||||
req.destroy();
|
||||
reject(new Error('Request timed out'));
|
||||
});
|
||||
|
||||
if (body) {
|
||||
req.write(JSON.stringify(body));
|
||||
}
|
||||
req.end();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Restart a specific list of AppDaemon apps by name.
|
||||
*/
|
||||
async restartApps(appNames: string[]): Promise<void> {
|
||||
this.log.appendLine(`[AD] Restarting specific apps: ${appNames.join(', ')}`);
|
||||
await Promise.all(appNames.map(app =>
|
||||
this.request('POST', '/api/appdaemon/service/admin/app/restart', { app })
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Reload all AppDaemon apps by fetching the app list then restarting each one.
|
||||
*/
|
||||
async reloadApps(): Promise<void> {
|
||||
// Fetch app names from admin state
|
||||
type AdminState = { state: Record<string, unknown> };
|
||||
const resp = await this.request<AdminState>('GET', '/api/appdaemon/state/admin');
|
||||
const stateMap = resp?.state ?? (resp as unknown as Record<string, unknown>);
|
||||
const appNames = Object.keys(stateMap)
|
||||
.filter(k => k.startsWith('app.'))
|
||||
.map(k => k.slice(4)); // strip "app." prefix
|
||||
|
||||
if (appNames.length === 0) {
|
||||
throw new Error('No apps found in AppDaemon state');
|
||||
}
|
||||
|
||||
this.log.appendLine(`[AD] Restarting ${appNames.length} apps: ${appNames.join(', ')}`);
|
||||
|
||||
await Promise.all(appNames.map(app =>
|
||||
this.request('POST', '/api/appdaemon/service/admin/app/restart', { app })
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Set AppDaemon production mode on/off via the admin API.
|
||||
*/
|
||||
async setProductionMode(mode: boolean): Promise<void> {
|
||||
await this.request('POST', '/api/appdaemon/service/admin/production_mode/set', { mode });
|
||||
this.productionMode = mode;
|
||||
this._onProductionModeChanged.fire(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to read the current production mode from AppDaemon state.
|
||||
*/
|
||||
async fetchProductionMode(): Promise<boolean> {
|
||||
try {
|
||||
const resp = await this.request<{ state: Record<string, { state?: unknown }> }>('GET', '/api/appdaemon/state/admin');
|
||||
const state = resp?.state ?? resp as unknown as Record<string, { state?: unknown }>;
|
||||
if (state && typeof state === 'object') {
|
||||
for (const key of Object.keys(state)) {
|
||||
if (key.includes('production_mode')) {
|
||||
const val = (state[key] as { state?: unknown })?.state;
|
||||
this.productionMode = val === true || val === 'True' || val === 'true';
|
||||
this._onProductionModeChanged.fire(this.productionMode);
|
||||
return this.productionMode;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Fallback to cached value
|
||||
}
|
||||
return this.productionMode;
|
||||
}
|
||||
|
||||
isProductionMode(): boolean {
|
||||
return this.productionMode;
|
||||
}
|
||||
|
||||
async toggleProductionMode(): Promise<void> {
|
||||
await this.setProductionMode(!this.productionMode);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this._onProductionModeChanged.dispose();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user