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
268 lines (262 loc) • 8.76 kB
JavaScript
/**
* Ops CLI — Subcommand router for `aiwg ops`
*
* Subcommands:
* init — Bootstrap a new ops workspace
* status — Show workspace health
* use <workspace> — Switch active workspace
* list — List registered workspaces
* push — Push workspace repos to remote
*
* @implements #544
*/
import { OpsRegistry } from './registry.js';
/**
* Main CLI entry point for `aiwg ops <subcommand> [args]`
*/
export async function main(args) {
// Extract global flags
let configDir;
const filteredArgs = [];
for (let i = 0; i < args.length; i++) {
if (args[i] === '--config-dir' && i + 1 < args.length) {
configDir = args[i + 1];
i++;
}
else {
filteredArgs.push(args[i]);
}
}
const subcommand = filteredArgs[0];
const subArgs = filteredArgs.slice(1);
const registry = new OpsRegistry(configDir);
switch (subcommand) {
case 'init':
await handleInit(registry, subArgs);
break;
case 'status':
await handleStatus(registry, subArgs);
break;
case 'use':
await handleUse(registry, subArgs);
break;
case 'list':
case 'ls':
await handleList(registry);
break;
case 'push':
await handlePush(registry, subArgs);
break;
case 'discover':
await handleDiscover(registry, subArgs);
break;
case 'adopt':
await handleAdopt(registry, subArgs);
break;
default:
printUsage();
if (subcommand) {
throw new Error(`Unknown ops subcommand: ${subcommand}`);
}
break;
}
}
async function handleInit(registry, args) {
// Parse flags
let silent = false;
let workspace;
let home;
let mode = 'multi-repo';
let extensions = ['sys', 'it', 'dev'];
let prefix;
let provider;
let from;
for (let i = 0; i < args.length; i++) {
switch (args[i]) {
case '--silent':
silent = true;
break;
case '--workspace':
workspace = args[++i];
break;
case '--home':
home = args[++i];
break;
case '--mode':
mode = args[++i];
break;
case '--ext':
extensions = args[++i].split(',');
break;
case '--prefix':
prefix = args[++i];
break;
case '--provider':
provider = args[++i];
break;
case '--from':
from = args[++i];
break;
}
}
if (!workspace) {
workspace = 'default';
}
await registry.initWorkspace({
name: workspace,
home,
mode,
extensions,
prefix,
provider,
silent,
from,
});
}
async function handleAdopt(registry, args) {
let path;
let workspace;
let extensions;
let name;
let silent = false;
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--workspace')
workspace = args[++i];
else if (a === '--ext')
extensions = args[++i].split(',');
else if (a === '--name')
name = args[++i];
else if (a === '--silent')
silent = true;
else if (!a.startsWith('--') && !path)
path = a;
}
if (!path) {
throw new Error('Usage: aiwg ops adopt <path> [--workspace <n>] [--ext <list>] [--name <n>]');
}
await registry.adoptRepo(path, { workspace, extensions, name, silent });
}
async function handleStatus(registry, args) {
const showAll = args.includes('--all');
await registry.showStatus(showAll);
}
async function handleUse(registry, args) {
const workspace = args[0];
if (!workspace) {
throw new Error('Usage: aiwg ops use <workspace>');
}
await registry.switchWorkspace(workspace);
}
async function handleList(registry) {
await registry.listWorkspaces();
}
async function handlePush(registry, args) {
let workspace;
for (let i = 0; i < args.length; i++) {
if (args[i] === '--workspace') {
workspace = args[++i];
}
}
await registry.pushWorkspace(workspace);
}
async function handleDiscover(registry, args) {
const roots = [];
let maxDepth = 3;
let register = false;
let workspaceName = 'discovered';
let asJson = false;
for (let i = 0; i < args.length; i++) {
const a = args[i];
if (a === '--max-depth') {
maxDepth = Number(args[++i]);
}
else if (a === '--register' || a === '--yes' || a === '-y') {
register = true;
}
else if (a === '--workspace') {
workspaceName = args[++i];
}
else if (a === '--json') {
asJson = true;
}
else if (!a.startsWith('--')) {
roots.push(a);
}
}
const candidates = await registry.discoverWorkspaces({
roots: roots.length > 0 ? roots : undefined,
maxDepth,
});
if (asJson) {
console.log(JSON.stringify({ candidates }, null, 2));
}
else if (candidates.length === 0) {
console.log('No ops workspace candidates found.');
}
else {
console.log(`Found ${candidates.length} candidate(s):\n`);
console.log(' STATUS NAME REMOTE PATH');
for (const c of candidates) {
const status = c.alreadyRegistered ? 'KNOWN ' : 'NEW ';
const name = (c.name + ' '.repeat(14)).slice(0, 14);
const remote = (c.remote || '-').slice(0, 40).padEnd(40);
console.log(` ${status} ${name} ${remote} ${c.path}`);
}
console.log('');
}
if (register) {
const result = await registry.registerDiscovered(workspaceName, candidates);
console.log(`Registered ${result.added} new entr${result.added === 1 ? 'y' : 'ies'} ` +
`(skipped ${result.skipped} already-known) into workspace "${workspaceName}".`);
}
else if (candidates.some((c) => !c.alreadyRegistered)) {
console.log('Run with --register to add NEW candidates to the registry.');
}
}
function printUsage() {
console.log(`Usage: aiwg ops <subcommand> [options]
Subcommands:
init Bootstrap a new ops workspace
status [--all] Show workspace health
use <workspace> Switch active workspace
list List registered workspaces
push [--workspace <n>] Push workspace repos to remote
discover [root...] Scan filesystem for orphaned ops-workspace clones
adopt <path> Register an existing local clone as a repo entry
Init options:
--silent Skip interactive prompts
--workspace <name> Workspace name (default: "default")
--home <path> Parent directory for repos
--mode <mode> single-repo or multi-repo (default: multi-repo)
--ext <list> Comma-separated extensions: sys,it,dev,stream
--prefix <name> Repo naming prefix (e.g., "myorg")
--provider <name> Remote provider for auto-push (github, gitea, or URL)
--from <git-url> Clone this URL into the target repo instead of init.
Requires single-repo mode or exactly one --ext value.
Global flags:
--config-dir <path> Override config directory
Examples:
aiwg ops init
aiwg ops init --silent --workspace personal --ext sys,dev
aiwg ops init --mode single-repo --workspace homelab
aiwg ops status
aiwg ops status --all
aiwg ops use client-acme
aiwg ops list
aiwg ops push --workspace personal
aiwg ops discover ~/projects ~/work --max-depth 4
aiwg ops discover --register --workspace home
Discover options:
--max-depth <n> Max walk depth from each root (default: 3)
--register Write NEW candidates into the registry
--yes, -y Alias for --register
--workspace <name> Bucket workspace for registered entries (default: "discovered")
--json Machine-readable output
Adopt options:
--workspace <name> Workspace to attach to (default: "default")
--ext <list> Comma-separated extensions for the adopted repo
--name <name> Override repo name (default: basename of path)
--silent Suppress informational logging`);
}
//# sourceMappingURL=cli.js.map