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;
|
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);
|
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
|
* `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.
|
* 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) {
|
async function cmdStage(args) {
|
||||||
const stage = args[0];
|
const stage = args[0];
|
||||||
if (!stage || stage.startsWith("--")) {
|
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)",
|
"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"],
|
["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":
|
case "stage":
|
||||||
return cmdStage(rest);
|
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":
|
case "open":
|
||||||
return cmdOpen();
|
return cmdOpen();
|
||||||
case "version":
|
case "version":
|
||||||
|
|||||||
Reference in New Issue
Block a user