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
293 lines • 10.7 kB
JavaScript
/**
* `aiwg index views` subcommand router.
*
* Subcommands:
* add <name> — scaffold a definition YAML interactively or via flags
* list — list all views with freshness
* show <name> — print stored results
* build [<name>] — emit dispatch plan(s) for stale or named views
* remove <name> — delete definition + results
*
* `build` does NOT directly invoke RLM. RLM dispatch is an agent skill
* (/rlm-batch). This CLI prints a dispatch plan that the agent reads
* and acts on, then writes the result back via putResult().
*
* @implements #1207
*/
import { join } from 'node:path';
import { computeHash } from '../../rlm/cache/hash.js';
import { parseViewYaml, validate, ViewValidationError } from './definition.js';
import { getDefinition, getResult, listViews, putDefinition, removeView, resolveViewsRoot, } from './store.js';
import { readFileSync } from 'node:fs';
export async function main(args) {
const sub = args[0];
const rest = args.slice(1);
switch (sub) {
case 'add':
handleAdd(rest);
break;
case 'list':
handleList(rest);
break;
case 'show':
handleShow(rest);
break;
case 'build':
handleBuild(rest);
break;
case 'remove':
handleRemove(rest);
break;
default:
printUsage();
if (sub)
throw new Error(`Unknown views subcommand: ${sub}`);
}
}
function asJson(args) {
return args.includes('--json');
}
function flagValue(args, flag) {
const i = args.indexOf(flag);
return i !== -1 && args[i + 1] ? args[i + 1] : undefined;
}
function handleAdd(args) {
const fileFlag = flagValue(args, '--from');
if (fileFlag) {
const yaml = readFileSync(fileFlag, 'utf-8');
let def;
try {
def = parseViewYaml(yaml);
}
catch (err) {
console.error(err instanceof ViewValidationError ? err.message : String(err));
process.exitCode = 1;
return;
}
const root = resolveViewsRoot();
const path = putDefinition(root, def);
console.log(`Added view '${def.name}' from ${fileFlag}`);
console.log(` Definition: ${path}`);
return;
}
// Non-interactive add via flags: --name --producer --glob/--query/--neighbors-of --prompt --aggregate
const name = flagValue(args, '--name');
const producer = (flagValue(args, '--producer') ?? 'rlm-batch');
const prompt = flagValue(args, '--prompt');
const aggregate = (flagValue(args, '--aggregate') ?? 'concat');
const glob = flagValue(args, '--glob');
const query = flagValue(args, '--query');
const neighborsId = flagValue(args, '--neighbors-of');
if (!name || !prompt) {
console.error('Usage: aiwg index views add --name <name> --prompt <prompt> [--producer rlm-batch] [--glob <p>|--query <q>|--neighbors-of <id>] [--aggregate <strategy>]');
console.error(' or: aiwg index views add --from <path/to/view.yaml>');
process.exitCode = 1;
return;
}
const inputs = {};
if (glob)
inputs.glob = glob;
else if (query)
inputs.query = query;
else if (neighborsId) {
inputs.neighborsOf = {
id: neighborsId,
depth: parseInt(flagValue(args, '--depth') ?? '1', 10),
direction: flagValue(args, '--direction') ?? 'both',
...(flagValue(args, '--graph') ? { graph: flagValue(args, '--graph') } : {}),
};
}
else {
console.error('Specify exactly one input source: --glob | --query | --neighbors-of');
process.exitCode = 1;
return;
}
let def;
try {
def = validate({
name,
producer,
inputs: renderInputsForValidate(inputs),
prompt,
aggregate,
refresh: { on_artifact_change: true, schedule: 'never', manual_only: false },
output_format: 'json',
});
}
catch (err) {
console.error(err instanceof ViewValidationError ? err.message : String(err));
process.exitCode = 1;
return;
}
const root = resolveViewsRoot();
const path = putDefinition(root, def);
console.log(`Added view '${def.name}'`);
console.log(` Definition: ${path}`);
console.log('');
console.log('Build with: aiwg index views build ' + def.name);
}
function renderInputsForValidate(inputs) {
if (inputs.glob)
return { glob: inputs.glob };
if (inputs.query)
return { query: inputs.query };
if (inputs.neighborsOf) {
return {
'neighbors-of': {
id: inputs.neighborsOf.id,
depth: inputs.neighborsOf.depth,
direction: inputs.neighborsOf.direction,
...(inputs.neighborsOf.graph ? { graph: inputs.neighborsOf.graph } : {}),
},
};
}
return {};
}
function handleList(args) {
const root = resolveViewsRoot();
const views = listViews(root);
if (asJson(args)) {
console.log(JSON.stringify(views, null, 2));
return;
}
if (views.length === 0) {
console.log('No views defined.');
console.log(`Views root: ${root}`);
return;
}
console.log(`name producer aggregate status built`);
console.log(`──────────────────────────── ────────── ──────────── ──────────────── ─────`);
for (const v of views) {
const status = v.staleReason ?? 'unknown';
const built = v.builtAt ? `${v.ageDays}d ago` : '—';
console.log(`${v.name.padEnd(28).slice(0, 28)} ${v.producer.padEnd(10)} ${v.aggregate.padEnd(12)} ${status.padEnd(16)} ${built}`);
}
console.log(`\n${views.length} views at ${root}`);
}
function handleShow(args) {
const name = args.find((a) => !a.startsWith('--'));
if (!name) {
console.error('Usage: aiwg index views show <name> [--json]');
process.exitCode = 1;
return;
}
const root = resolveViewsRoot();
try {
const r = getResult(root, name);
if (asJson(args)) {
console.log(JSON.stringify(r, null, 2));
}
else {
console.log(`View: ${r.name}`);
console.log(`Built: ${r.meta.builtAt}`);
console.log(`Inputs: ${r.meta.inputCount} (cache hits: ${r.meta.cacheHits}, misses: ${r.meta.cacheMisses})`);
console.log(`Duration: ${r.meta.durationMs}ms`);
console.log('');
console.log(typeof r.result === 'string' ? r.result : JSON.stringify(r.result, null, 2));
}
}
catch (err) {
console.error(err.message);
process.exitCode = 1;
}
}
function handleBuild(args) {
const root = resolveViewsRoot();
const named = args.find((a) => !a.startsWith('--'));
const force = args.includes('--force');
const json = asJson(args);
const targets = named
? [named]
: listViews(root)
.filter((v) => force || v.staleReason !== 'fresh')
.map((v) => v.name);
if (targets.length === 0) {
console.log('No views need building. Use --force to rebuild all, or pass a name.');
return;
}
const plans = [];
for (const name of targets) {
let def;
try {
def = getDefinition(root, name);
}
catch (err) {
console.error(err.message);
process.exitCode = 1;
continue;
}
const cacheKey = computeHash({
inputs: [
{ artifactId: `view:${def.name}`, contentHash: 'pending' },
],
query: def.prompt,
subPrompt: def.prompt,
model: 'claude-sonnet-4-6',
aggregateStrategy: def.aggregate,
});
plans.push({
name: def.name,
producer: def.producer,
inputs: def.inputs,
prompt: def.prompt,
aggregate: def.aggregate,
cacheKey,
outputPath: join(root, 'results', `${def.name}.json`),
metaPath: join(root, 'results', `${def.name}.meta.json`),
});
}
if (json) {
console.log(JSON.stringify({ plans }, null, 2));
return;
}
console.log('NOTE: This command emits dispatch plans. The RLM agent (/rlm-batch or /rlm-query)');
console.log('reads the plan, runs the prompt against the input set, and writes results to outputPath.');
console.log('');
for (const p of plans) {
console.log(`╭─ View: ${p.name}`);
console.log(`│ Producer: ${p.producer}`);
console.log(`│ Inputs: ${describeInputs(p.inputs)}`);
console.log(`│ Aggregate: ${p.aggregate}`);
console.log(`│ Cache key: ${p.cacheKey.slice(0, 16)}...`);
console.log(`│ Output: ${p.outputPath}`);
console.log('╰─');
}
console.log('');
console.log(`${plans.length} plan(s) emitted. Re-run with --json to consume programmatically.`);
}
function describeInputs(i) {
if (i.glob)
return `glob: ${i.glob}`;
if (i.query)
return `query: ${i.query}`;
if (i.neighborsOf)
return `neighbors-of: ${i.neighborsOf.id} (depth ${i.neighborsOf.depth}, ${i.neighborsOf.direction})`;
return '<none>';
}
function handleRemove(args) {
const name = args.find((a) => !a.startsWith('--'));
if (!name) {
console.error('Usage: aiwg index views remove <name>');
process.exitCode = 1;
return;
}
const root = resolveViewsRoot();
const r = removeView(root, name);
if (!r.defRemoved) {
console.log(`No view named '${name}'.`);
return;
}
console.log(`Removed view '${name}' (definition${r.resultRemoved ? ' + results' : ''})`);
}
function printUsage() {
console.log('aiwg index views <subcommand>');
console.log('');
console.log('Subcommands:');
console.log(' add --name <n> --prompt <p> [--glob <g>|--query <q>|--neighbors-of <id>] [--aggregate <s>] [--producer <rlm-batch|rlm-query>]');
console.log(' add --from <path/to/view.yaml>');
console.log(' list [--json]');
console.log(' show <name> [--json]');
console.log(' build [<name>] [--force] [--json]');
console.log(' remove <name>');
}
//# sourceMappingURL=cli.js.map