feat(lanes): add ccam feature list/activate/show CLI (B)
This commit is contained in:
+91
-1
@@ -190,7 +190,7 @@ async function api(method, pathname, body, options = {}) {
|
||||
}
|
||||
return data;
|
||||
}
|
||||
const get = (p) => api("GET", p);
|
||||
const get = (p, b, options) => api("GET", p, undefined, options);
|
||||
const post = (p, b, options) => api("POST", p, b, options);
|
||||
|
||||
/**
|
||||
@@ -2031,6 +2031,74 @@ async function cmdLockRelease(args) {
|
||||
* `state.sh N set stage=…`. A skill calls this at each phase boundary so the
|
||||
* dashboard shows a declared stage instead of an inferred one.
|
||||
*/
|
||||
function fmtFeatureRow(f) {
|
||||
const marker = f.archived_at ? " " : "▶ ";
|
||||
return `${marker}${f.slug.padEnd(24)} ${String(f.stage).padEnd(12)} ${f.progress}%${
|
||||
f.archived_at ? ` (archived ${fmtTime(f.archived_at)})` : ""
|
||||
}`;
|
||||
}
|
||||
|
||||
/** `ccam feature list [<id>] [--cwd path]` — every feature this lane has activated. */
|
||||
async function cmdFeatureList(args) {
|
||||
const resolved = await resolveLaneArg(args);
|
||||
if (!resolved) return;
|
||||
const { features } = await get(`/api/lanes/${resolved.laneId}/features`);
|
||||
if (!features.length) {
|
||||
console.log("no features activated yet — start one with: ccam feature activate <slug>");
|
||||
return;
|
||||
}
|
||||
for (const f of features) console.log(fmtFeatureRow(f));
|
||||
}
|
||||
|
||||
/** `ccam feature activate <slug> [--title X] [<id>] [--cwd path]`. */
|
||||
async function cmdFeatureActivate(args) {
|
||||
const slug = args.find((arg) => !arg.startsWith("--"));
|
||||
if (!slug) {
|
||||
console.error("usage: ccam feature activate <slug> [--title text]");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const flag = (name) => {
|
||||
const i = args.indexOf(`--${name}`);
|
||||
return i > -1 ? args[i + 1] : undefined;
|
||||
};
|
||||
const resolved = await resolveLaneArg(args.filter((a) => a !== slug));
|
||||
if (!resolved) return;
|
||||
const { lane, feature } = await post(`/api/lanes/${resolved.laneId}/features/activate`, {
|
||||
slug,
|
||||
title: flag("title"),
|
||||
});
|
||||
console.log(
|
||||
`${c.green("✔")} lane #${lane.id} now on feature "${feature.slug}" (stage: ${feature.stage}, ${feature.progress}%)`
|
||||
);
|
||||
}
|
||||
|
||||
/** `ccam feature show <slug> [<id>] [--cwd path]` — one feature's saved pipeline. */
|
||||
async function cmdFeatureShow(args) {
|
||||
const slug = args.find((arg) => !arg.startsWith("--"));
|
||||
if (!slug) {
|
||||
console.error("usage: ccam feature show <slug>");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const resolved = await resolveLaneArg(args.filter((a) => a !== slug));
|
||||
if (!resolved) return;
|
||||
const result = await get(
|
||||
`/api/lanes/${resolved.laneId}/features/${encodeURIComponent(slug)}`,
|
||||
undefined,
|
||||
{ allowError: true }
|
||||
);
|
||||
if (result.status) {
|
||||
console.error(`✖ feature "${slug}" → ${result.data?.error?.message || result.status}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const f = result.feature;
|
||||
console.log(`${f.slug} ${f.archived_at ? "(archived)" : "(active)"}`);
|
||||
console.log(` stage: ${f.stage} status: ${f.status} progress: ${f.progress}%`);
|
||||
for (const node of f.pipeline_nodes) console.log(` ${node.state.padEnd(18)} ${node.label}`);
|
||||
}
|
||||
|
||||
async function cmdStage(args) {
|
||||
const stage = args[0];
|
||||
if (!stage || stage.startsWith("--")) {
|
||||
@@ -2188,6 +2256,17 @@ const COMMAND_GROUPS = [
|
||||
"Cross-lane named lock (serialize builds/e2e across all lanes; holder defaults to the calling lane)",
|
||||
],
|
||||
["stage <stage> [flags]", "", "Report the current pipeline stage for a lane"],
|
||||
["feature list", "[<id>]", "List every feature this lane has activated, archived or live"],
|
||||
[
|
||||
"feature activate",
|
||||
"<slug> [--title text] [<id>]",
|
||||
"Switch to a feature by slug, archiving the current one first (echoes the canonicalized slug)",
|
||||
],
|
||||
[
|
||||
"feature show",
|
||||
"<slug> [<id>]",
|
||||
"Show one feature's saved pipeline (works on an archived one too)",
|
||||
],
|
||||
],
|
||||
],
|
||||
[
|
||||
@@ -3002,6 +3081,17 @@ async function runCommand(argv) {
|
||||
}
|
||||
case "stage":
|
||||
return cmdStage(rest);
|
||||
case "feature": {
|
||||
const sub = rest[0];
|
||||
if (sub === "list") return cmdFeatureList(rest.slice(1));
|
||||
if (sub === "activate") return cmdFeatureActivate(rest.slice(1));
|
||||
if (sub === "show") return cmdFeatureShow(rest.slice(1));
|
||||
console.error(
|
||||
"usage: ccam feature list | ccam feature activate <slug> [--title text] | ccam feature show <slug>"
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
case "open":
|
||||
return cmdOpen();
|
||||
case "version":
|
||||
|
||||
Reference in New Issue
Block a user