aiwg
Version:
Deployment tool and support utility for AI context. Copies agents, skills, commands, rules, and behaviors into the paths each AI platform reads (Claude Code, Codex, Copilot, Cursor, Warp, OpenClaw, and 6 more) so one source of truth works across 10 platfo
109 lines • 3.97 kB
JavaScript
/**
* Optional Features — Installation Status Probe
*
* Resolves whether each feature in the catalog is currently available
* by attempting to read its packages from the AIWG install's
* `node_modules`. Pure best-effort — never throws on missing packages,
* just reports them as not installed.
*
* @implements #1219
*/
import { promises as fs } from 'node:fs';
import path from 'node:path';
import { FEATURE_CATALOG } from './catalog.js';
/**
* Resolve the AIWG install root for package probing. Tries:
* 1. AIWG_ROOT env var
* 2. The directory that contains this module's compiled output
* (walks up to find package.json with name "aiwg")
* 3. process.cwd() as a last resort
*/
async function resolveAiwgRoot() {
if (process.env.AIWG_ROOT)
return process.env.AIWG_ROOT;
// Walk up from this module's URL.
// dist layout: dist/src/features/status.js → walk up to package.json
let cur = path.dirname(new URL(import.meta.url).pathname);
for (let i = 0; i < 10; i++) {
try {
const pkgPath = path.join(cur, 'package.json');
const stat = await fs.stat(pkgPath).catch(() => null);
if (stat?.isFile()) {
const pkg = JSON.parse(await fs.readFile(pkgPath, 'utf8'));
if (pkg?.name === 'aiwg')
return cur;
}
}
catch { /* keep walking */ }
const parent = path.dirname(cur);
if (parent === cur)
break;
cur = parent;
}
return process.cwd();
}
/**
* Probe a single package by reading its package.json from
* `<aiwgRoot>/node_modules/<name>/package.json`.
*/
async function probePackage(aiwgRoot, name) {
const candidate = path.join(aiwgRoot, 'node_modules', name, 'package.json');
try {
const raw = await fs.readFile(candidate, 'utf8');
const meta = JSON.parse(raw);
return {
name,
installed: true,
version: meta.version ?? null,
path: candidate,
};
}
catch {
return { name, installed: false, version: null, path: null };
}
}
/** Status of a single named feature. Returns null if the name is unknown. */
export async function getFeatureStatus(name) {
const feature = FEATURE_CATALOG.find(f => f.name === name);
if (!feature)
return null;
const aiwgRoot = await resolveAiwgRoot();
const pkgStatuses = await Promise.all(feature.packages.map(p => probePackage(aiwgRoot, p)));
const missing = pkgStatuses.filter(p => !p.installed).map(p => p.name);
return {
feature,
packages: pkgStatuses,
available: missing.length === 0,
missing,
};
}
/** Status of every feature in the catalog. */
export async function getAllFeatureStatuses() {
const aiwgRoot = await resolveAiwgRoot();
return Promise.all(FEATURE_CATALOG.map(async (feature) => {
const pkgStatuses = await Promise.all(feature.packages.map(p => probePackage(aiwgRoot, p)));
const missing = pkgStatuses.filter(p => !p.installed).map(p => p.name);
return {
feature,
packages: pkgStatuses,
available: missing.length === 0,
missing,
};
}));
}
/**
* Format a single feature status as a one-line "doctor row":
* ✓ embeddings installed (@xenova/transformers 2.17.2, hnswlib-node 3.0.0)
* ○ pty not installed — `aiwg features install pty` to enable
*/
export function formatStatusLine(status, indent = ' ') {
const name = status.feature.name.padEnd(11);
if (status.available) {
const versions = status.packages
.map(p => `${p.name} ${p.version ?? '?'}`)
.join(', ');
return `${indent}OK ${name} installed (${versions})`;
}
return `${indent}- ${name} not installed — \`aiwg features install ${status.feature.name}\` to enable`;
}
//# sourceMappingURL=status.js.map