feat(ui): call lanes "workspaces" in the UI, CLI output and docs
Copy-only rename (workspace redesign, phase 0). The word a user reads changes — "Lane" → "Workspace", "Add lane" → "Add workspace" — in both locales, in agenthub CLI prose and help, in the server's error messages and in the docs. Every identifier stays: /api/lanes, `agenthub lanes`, --lane, AGENTHUB_LANE_ID, error codes, tmux names, DB tables, file, component and i18n key names. docs/LANES.md keeps its name, retitled "Workspaces (lanes)".
This commit is contained in:
+66
-54
@@ -1629,19 +1629,21 @@ async function cmdLanesAdd(args) {
|
||||
({ lane } = await get(`/api/lanes/${provisioning.id}`));
|
||||
}
|
||||
if (lane.status === "provisioning") {
|
||||
console.error(`✖ Timed out waiting for worktree lane #${lane.id} to finish provisioning.`);
|
||||
console.error(
|
||||
`✖ Timed out waiting for worktree workspace #${lane.id} to finish provisioning.`
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
if (lane.status === "failed") {
|
||||
console.error(
|
||||
`✖ Worktree lane #${lane.id} failed: ${lane.notes || "no failure details reported"}`
|
||||
`✖ Worktree workspace #${lane.id} failed: ${lane.notes || "no failure details reported"}`
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`${c.green("✔")} Worktree lane #${lane.id} ready: ${lane.title || lane.cwd} (status: ${lane.status})`
|
||||
`${c.green("✔")} Worktree workspace #${lane.id} ready: ${lane.title || lane.cwd} (status: ${lane.status})`
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -1656,7 +1658,7 @@ async function cmdLanesAdd(args) {
|
||||
if (pipeline) body.pipeline = pipeline;
|
||||
if (tool) body.tool = tool;
|
||||
const { lane } = await post("/api/lanes", body);
|
||||
console.log(`${c.green("✔")} Created lane #${lane.id}: ${lane.title}`);
|
||||
console.log(`${c.green("✔")} Created workspace #${lane.id}: ${lane.title}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1795,7 +1797,7 @@ async function cmdLanesLifecycle(action, args) {
|
||||
}
|
||||
|
||||
const preflight = await get(`/api/lanes/${id}/preflight?action=${action}`);
|
||||
printLaneFacts(`Preflight for ${action} lane #${id}:`, preflight, fields);
|
||||
printLaneFacts(`Preflight for ${action} workspace #${id}:`, preflight, fields);
|
||||
const keepDb = action === "reset" && args.includes("--keep-db");
|
||||
if (preflight.database) {
|
||||
const fate = action === "remove" ? "kept" : keepDb ? "kept as-is" : "dropped and recreated";
|
||||
@@ -1812,7 +1814,9 @@ async function cmdLanesLifecycle(action, args) {
|
||||
}
|
||||
|
||||
if (!args.includes("--yes")) {
|
||||
console.error(`Refusing to ${action} lane #${id} without --yes. Re-run with --yes to proceed.`);
|
||||
console.error(
|
||||
`Refusing to ${action} workspace #${id} without --yes. Re-run with --yes to proceed.`
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
@@ -1826,7 +1830,7 @@ async function cmdLanesLifecycle(action, args) {
|
||||
preflight.blocked.includes("adopted")
|
||||
) {
|
||||
console.error(
|
||||
`Lane #${id} points at a directory you own. It cannot be reset; use \`agenthub lanes remove ${id} --yes\` to drop only the dashboard's record of it.`
|
||||
`Workspace #${id} points at a directory you own. It cannot be reset; use \`agenthub lanes remove ${id} --yes\` to drop only the dashboard's record of it.`
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
@@ -1839,7 +1843,7 @@ async function cmdLanesLifecycle(action, args) {
|
||||
const result = await post(`/api/lanes/${id}/${action}`, body, { allowError: true });
|
||||
if (result.status) {
|
||||
const error = result.data?.error || {};
|
||||
console.error(`✖ ${action} lane #${id} → ${error.message || `HTTP ${result.status}`}`);
|
||||
console.error(`✖ ${action} workspace #${id} → ${error.message || `HTTP ${result.status}`}`);
|
||||
if (result.status === 409 && error.code === "ESTALE") {
|
||||
printLaneFacts("Expected preflight:", error.expected || {}, fields);
|
||||
printLaneFacts("Current preflight:", error.current || {}, fields);
|
||||
@@ -1848,12 +1852,12 @@ async function cmdLanesLifecycle(action, args) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (action === "reset") console.log(`${c.green("✔")} Reset lane #${id}.`);
|
||||
if (action === "remove") console.log(`${c.green("✔")} Removed lane #${id}.`);
|
||||
if (action === "reset") console.log(`${c.green("✔")} Reset workspace #${id}.`);
|
||||
if (action === "remove") console.log(`${c.green("✔")} Removed workspace #${id}.`);
|
||||
if (action === "purge") {
|
||||
const purged = result.purged || {};
|
||||
console.log(
|
||||
`${c.green("✔")} Purged lane #${id}: ${purged.sessions} sessions, ${purged.events} events, ${purged.tokenRows} token rows.`
|
||||
`${c.green("✔")} Purged workspace #${id}: ${purged.sessions} sessions, ${purged.events} events, ${purged.tokenRows} token rows.`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1893,7 +1897,9 @@ function detectionLeadsDeclaration(l) {
|
||||
async function cmdLanes() {
|
||||
const { lanes, counts } = await get("/api/lanes");
|
||||
if (!lanes.length) {
|
||||
console.log("no lanes yet — create one with: agenthub lanes add --cwd <path> --title <text>");
|
||||
console.log(
|
||||
"no workspaces yet — create one with: agenthub lanes add --cwd <path> --title <text>"
|
||||
);
|
||||
return;
|
||||
}
|
||||
for (const l of lanes) {
|
||||
@@ -1906,7 +1912,7 @@ async function cmdLanes() {
|
||||
);
|
||||
}
|
||||
console.log(
|
||||
`\n${counts.total} lanes · ${counts.running} running · ${counts.needs_you} need you · ${counts.dead} dead`
|
||||
`\n${counts.total} workspaces · ${counts.running} running · ${counts.needs_you} need you · ${counts.dead} dead`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1971,7 +1977,7 @@ async function resolveLaneArg(args) {
|
||||
.filter((l) => cwd === l.cwd || cwd.startsWith(`${l.cwd}/`))
|
||||
.sort((a, b) => b.cwd.length - a.cwd.length);
|
||||
if (!owning.length) {
|
||||
console.error(`no lane owns ${cwd} — create one with: agenthub lanes add --cwd ${cwd}`);
|
||||
console.error(`no workspace owns ${cwd} — create one with: agenthub lanes add --cwd ${cwd}`);
|
||||
process.exitCode = 1;
|
||||
return null;
|
||||
}
|
||||
@@ -1981,7 +1987,7 @@ async function resolveLaneArg(args) {
|
||||
const candidates = tool ? nearest.filter((l) => (l.tool || "claude") === tool) : nearest;
|
||||
if (!candidates.length) {
|
||||
console.error(
|
||||
`no ${tool} lane owns ${owning[0].cwd} — lanes here: ${nearest
|
||||
`no ${tool} workspace owns ${owning[0].cwd} — workspaces here: ${nearest
|
||||
.map((l) => `#${l.id} (${l.tool || "claude"})`)
|
||||
.join(", ")}`
|
||||
);
|
||||
@@ -1990,7 +1996,7 @@ async function resolveLaneArg(args) {
|
||||
}
|
||||
if (candidates.length > 1) {
|
||||
console.error(
|
||||
`${candidates.length} lanes own ${owning[0].cwd} — pass --tool <${candidates
|
||||
`${candidates.length} workspaces own ${owning[0].cwd} — pass --tool <${candidates
|
||||
.map((l) => l.tool || "claude")
|
||||
.join("|")}> or --lane <id>: ${candidates
|
||||
.map((l) => `#${l.id} (${l.tool || "claude"})`)
|
||||
@@ -2005,7 +2011,7 @@ async function resolveLaneArg(args) {
|
||||
/** One line per declared port: name, number, listening, and any base drift. */
|
||||
function printRuntime(runtime) {
|
||||
if (!runtime.available) {
|
||||
console.log("no .agenthub/profile for this lane — nothing to run.");
|
||||
console.log("no .agenthub/profile for this workspace — nothing to run.");
|
||||
if (runtime.searched) for (const p of runtime.searched) console.log(` looked in ${p}`);
|
||||
return;
|
||||
}
|
||||
@@ -2064,14 +2070,14 @@ async function cmdLanesRuntime(sub, args) {
|
||||
if (laneArgs.includes("--qc")) body.qc = true;
|
||||
const result = await post(`/api/lanes/${laneId}/up`, body, { allowError: true });
|
||||
if (result.status) {
|
||||
console.error(`✖ up lane #${laneId} → ${result.data?.error?.message || result.status}`);
|
||||
console.error(`✖ up workspace #${laneId} → ${result.data?.error?.message || result.status}`);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
// The server answers 202 and boots in the background; poll until the stack
|
||||
// reports healthy or a boot error lands, so the command exits on a real
|
||||
// outcome rather than on "accepted".
|
||||
console.log(`lane #${laneId} booting…`);
|
||||
console.log(`workspace #${laneId} booting…`);
|
||||
const deadline = Date.now() + 15 * 60 * 1000;
|
||||
for (;;) {
|
||||
await new Promise((r) => setTimeout(r, 2000));
|
||||
@@ -2098,11 +2104,13 @@ async function cmdLanesRuntime(sub, args) {
|
||||
if (sub === "down") {
|
||||
const result = await post(`/api/lanes/${laneId}/down`, {}, { allowError: true });
|
||||
if (result.status) {
|
||||
console.error(`✖ down lane #${laneId} → ${result.data?.error?.message || result.status}`);
|
||||
console.error(
|
||||
`✖ down workspace #${laneId} → ${result.data?.error?.message || result.status}`
|
||||
);
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
console.log(`lane #${laneId} down (${result.killed?.length || 0} processes stopped)`);
|
||||
console.log(`workspace #${laneId} down (${result.killed?.length || 0} processes stopped)`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -2117,7 +2125,7 @@ async function cmdLanesRuntime(sub, args) {
|
||||
const query = i > -1 && laneArgs[i + 1] ? `?tail=${encodeURIComponent(laneArgs[i + 1])}` : "";
|
||||
const log = await get(`/api/lanes/${laneId}/logs/${encodeURIComponent(svc)}${query}`);
|
||||
if (!log.available) {
|
||||
console.log("no logs for this lane yet.");
|
||||
console.log("no logs for this workspace yet.");
|
||||
return;
|
||||
}
|
||||
if (log.truncated) console.log(`… (showing the tail of ${log.size} bytes)`);
|
||||
@@ -2143,7 +2151,7 @@ async function cmdLanesRuntime(sub, args) {
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`lane #${laneId} running hook ${name} — follow it with: agenthub lanes logs ${laneId} ${name}`
|
||||
`workspace #${laneId} running hook ${name} — follow it with: agenthub lanes logs ${laneId} ${name}`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2166,7 +2174,7 @@ async function cmdLanesRuntime(sub, args) {
|
||||
}
|
||||
|
||||
if (result.code === 5) {
|
||||
console.error(`✖ lane #${laneId} — MIGRATION NUMBER COLLISION (nothing merged):`);
|
||||
console.error(`✖ workspace #${laneId} — MIGRATION NUMBER COLLISION (nothing merged):`);
|
||||
for (const c of result.collisions) {
|
||||
console.error(` ${c.file} collides with ${c.collidesWith} — rename to ${c.suggestion}`);
|
||||
}
|
||||
@@ -2175,7 +2183,7 @@ async function cmdLanesRuntime(sub, args) {
|
||||
}
|
||||
|
||||
if (result.code === 4) {
|
||||
console.error(`✖ lane #${laneId} — MERGE CONFLICT (left in place).`);
|
||||
console.error(`✖ workspace #${laneId} — MERGE CONFLICT (left in place).`);
|
||||
console.error(` conflicted: ${result.conflictedFiles.join(", ")}`);
|
||||
console.error(" resolve, then: git add <resolved files> && git commit --no-edit");
|
||||
console.error(
|
||||
@@ -2202,12 +2210,12 @@ async function cmdLanesRuntime(sub, args) {
|
||||
console.log("DEV_OVERLAP: none");
|
||||
}
|
||||
}
|
||||
console.log(`lane #${laneId} preflight vs origin/development: OK`);
|
||||
console.log(`workspace #${laneId} preflight vs origin/development: OK`);
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(
|
||||
`lane #${laneId} — synced with origin/development (re-enter the pipeline at the gates)`
|
||||
`workspace #${laneId} — synced with origin/development (re-enter the pipeline at the gates)`
|
||||
);
|
||||
return;
|
||||
}
|
||||
@@ -2382,7 +2390,7 @@ async function cmdFeatureActivate(args) {
|
||||
title: flag("title"),
|
||||
});
|
||||
console.log(
|
||||
`${c.green("✔")} lane #${lane.id} now on feature "${feature.slug}" (stage: ${feature.stage}, ${feature.progress}%)`
|
||||
`${c.green("✔")} workspace #${lane.id} now on feature "${feature.slug}" (stage: ${feature.stage}, ${feature.progress}%)`
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2444,13 +2452,13 @@ async function cmdLanesIntegration(args) {
|
||||
* mirrors the derived state: 0 success, 1 failure or any error, 3 pending. */
|
||||
async function cmdLanesCi(args) {
|
||||
if (args[0] !== "status") {
|
||||
console.error("usage: agenthub lanes ci status <branch-or-sha> [<lane-id>]");
|
||||
console.error("usage: agenthub lanes ci status <branch-or-sha> [<id>]");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
const ref = args[1];
|
||||
if (!ref) {
|
||||
console.error("usage: agenthub lanes ci status <branch-or-sha> [<lane-id>]");
|
||||
console.error("usage: agenthub lanes ci status <branch-or-sha> [<id>]");
|
||||
process.exitCode = 1;
|
||||
return;
|
||||
}
|
||||
@@ -2515,7 +2523,7 @@ async function cmdLanesNode(args) {
|
||||
const rest = positional.filter((p) => p !== String(resolved.laneId));
|
||||
const key = rest[0];
|
||||
const listNodes = () => {
|
||||
console.log(`lane #${lane.id} → ${lane.pipeline} (${nodes.length} nodes)`);
|
||||
console.log(`workspace #${lane.id} → ${lane.pipeline} (${nodes.length} nodes)`);
|
||||
for (const n of nodes) {
|
||||
const mark = n.state === "current" ? c.cyan("→") : " ";
|
||||
console.log(
|
||||
@@ -2562,7 +2570,7 @@ async function cmdLanesNode(args) {
|
||||
}
|
||||
|
||||
console.log(
|
||||
`${c.dim(`lane #${lane.id} · node ${node.id} (${node.label})`)}\n` +
|
||||
`${c.dim(`workspace #${lane.id} · node ${node.id} (${node.label})`)}\n` +
|
||||
`${c.cyan("→")} ${argv.map((a) => (/\s/.test(a) ? JSON.stringify(a) : a)).join(" ")}`
|
||||
);
|
||||
const child = spawn(argv[0], argv.slice(1), { stdio: "inherit", cwd: lane.cwd });
|
||||
@@ -2593,7 +2601,7 @@ async function cmdLanesPipeline(args) {
|
||||
if (!target) {
|
||||
const { lane } = await get(`/api/lanes/${resolved.laneId}`);
|
||||
const { pipelines } = await get("/api/lanes/pipelines");
|
||||
console.log(`lane #${lane.id} → ${lane.pipeline} (${lane.pipeline_nodes.length} nodes)`);
|
||||
console.log(`workspace #${lane.id} → ${lane.pipeline} (${lane.pipeline_nodes.length} nodes)`);
|
||||
if (lane.pipeline_skill) console.log(`driver: ${lane.pipeline_skill} <requirement>`);
|
||||
console.log(`available: ${pipelines.map((p) => `${p.id} (${p.nodes.length})`).join(", ")}`);
|
||||
return;
|
||||
@@ -2601,7 +2609,7 @@ async function cmdLanesPipeline(args) {
|
||||
|
||||
const { lane } = await patch(`/api/lanes/${resolved.laneId}`, { pipeline: target });
|
||||
console.log(
|
||||
`${c.green("✔")} lane #${lane.id} → pipeline ${lane.pipeline} ` +
|
||||
`${c.green("✔")} workspace #${lane.id} → pipeline ${lane.pipeline} ` +
|
||||
`(${lane.pipeline_nodes.length} nodes, stage ${lane.stage}, ${lane.progress}%)`
|
||||
);
|
||||
// Switching templates re-resolves the SAME declared stage string against a
|
||||
@@ -2610,7 +2618,7 @@ async function cmdLanesPipeline(args) {
|
||||
if (!lane.pipeline_nodes.some((n) => n.state === "current")) {
|
||||
console.error(
|
||||
c.yellow(
|
||||
`! the lane's current stage "${lane.stage}" matches no node in "${lane.pipeline}" — ` +
|
||||
`! the workspace's current stage "${lane.stage}" matches no node in "${lane.pipeline}" — ` +
|
||||
`declare one of: ${lane.pipeline_nodes.map((n) => n.id).join(", ")}`
|
||||
)
|
||||
);
|
||||
@@ -2694,7 +2702,7 @@ async function cmdStage(args) {
|
||||
note: flag("note"),
|
||||
result: flag("result"),
|
||||
});
|
||||
console.log(`lane #${lane.id} → ${lane.stage} (${lane.progress}%)`);
|
||||
console.log(`workspace #${lane.id} → ${lane.stage} (${lane.progress}%)`);
|
||||
}
|
||||
|
||||
// ── Command catalog ─────────────────────────────────────────────────────────
|
||||
@@ -2791,18 +2799,18 @@ const COMMAND_GROUPS = [
|
||||
],
|
||||
],
|
||||
[
|
||||
"Lanes",
|
||||
"Workspaces",
|
||||
[
|
||||
["lanes", "", "List lanes with stage, liveness and progress"],
|
||||
["lanes", "", "List workspaces with stage, liveness and progress"],
|
||||
[
|
||||
"lanes add",
|
||||
"--cwd <path> --title <text> [--tool <id>]",
|
||||
"Create a new lane bound to a directory (--tool: an AI Tools id, default claude)",
|
||||
"Create a new workspace bound to a directory (--tool: an AI Tools id, default claude)",
|
||||
],
|
||||
[
|
||||
"lanes add",
|
||||
"--repo <path> [--title <text>] [--base <branch>] [--slug <slug>] [--tool <id>]",
|
||||
"Provision a managed worktree lane",
|
||||
"Provision a managed worktree workspace",
|
||||
],
|
||||
[
|
||||
"lanes profile init",
|
||||
@@ -2812,40 +2820,40 @@ const COMMAND_GROUPS = [
|
||||
[
|
||||
"lanes profile check",
|
||||
"[<path>]",
|
||||
"Validate a profile (path defaults to cwd, not a lane id)",
|
||||
"Validate a profile (path defaults to cwd, not a workspace id)",
|
||||
],
|
||||
[
|
||||
"lanes pipeline",
|
||||
"[<template-id>] [<id>]",
|
||||
"Show, or switch, which pipeline template a lane renders against",
|
||||
"Show, or switch, which pipeline template a workspace renders against",
|
||||
],
|
||||
[
|
||||
"lanes node",
|
||||
"[<node-id>] [extra…] [<id>] [--print]",
|
||||
"Run ONE pipeline node's own command in the lane's cwd (no id: list every node and its command)",
|
||||
"Run ONE pipeline node's own command in the workspace's cwd (no id: list every node and its command)",
|
||||
],
|
||||
[
|
||||
"lanes shell",
|
||||
"[<id>]",
|
||||
"Attach a real terminal to the exact tmux session the dashboard uses for a lane's run",
|
||||
"Attach a real terminal to the exact tmux session the dashboard uses for a workspace's run",
|
||||
],
|
||||
[
|
||||
"lanes reset|remove|purge",
|
||||
"<id> [--force] [--keep-db] --yes",
|
||||
"Show preflight facts, then perform a destructive lane action (--keep-db: reset only; remove drops the dashboard's record only — worktree, branch and database are left alone)",
|
||||
"Show preflight facts, then perform a destructive workspace action (--keep-db: reset only; remove drops the dashboard's record only — worktree, branch and database are left alone)",
|
||||
],
|
||||
[
|
||||
"lanes up|down",
|
||||
"[<id>] [--no-build] [--qc]",
|
||||
"Boot or stop the lane's own app stack (--no-build: up only, skip the build step; --qc: up only, inject QC_BOOT_ENV for a deterministic stack; id defaults to the lane owning this directory)",
|
||||
"Boot or stop the workspace's own app stack (--no-build: up only, skip the build step; --qc: up only, inject QC_BOOT_ENV for a deterministic stack; id defaults to the workspace owning this directory)",
|
||||
],
|
||||
["lanes runtime", "[<id>]", "Slot, ports, service health and the last boot error"],
|
||||
["lanes logs", "[<id>] <svc> [--tail N]", "Tail one of the lane's hook or service logs"],
|
||||
["lanes logs", "[<id>] <svc> [--tail N]", "Tail one of the workspace's hook or service logs"],
|
||||
["lanes hook", "[<id>] <name> [args…]", "Run a profile hook (ci-gate, e2e, migrate, …)"],
|
||||
[
|
||||
"lanes sync-base",
|
||||
"[<id>] [--check|--continue] [branch]",
|
||||
"Fetch + migration-collision preflight, or merge origin/development into the feature branch (--check: read-only; --continue: finish after a resolved conflict; bare: merge, branch defaults to the lane's current branch)",
|
||||
"Fetch + migration-collision preflight, or merge origin/development into the feature branch (--check: read-only; --continue: finish after a resolved conflict; bare: merge, branch defaults to the workspace's current branch)",
|
||||
],
|
||||
[
|
||||
"lanes proof-link",
|
||||
@@ -2855,12 +2863,12 @@ const COMMAND_GROUPS = [
|
||||
[
|
||||
"lanes mcp sync",
|
||||
"[<id>]",
|
||||
"Relocate the source repo's MCP servers into <lane>/.mcp.json and seed Chromium profiles",
|
||||
"Relocate the source repo's MCP servers into <workspace>/.mcp.json and seed Chromium profiles",
|
||||
],
|
||||
[
|
||||
"lanes integration",
|
||||
"<name> [<id>]",
|
||||
"Check whether a named integration (tracker, dev_qc, ci_wait) is on for this lane — exit 0/1",
|
||||
"Check whether a named integration (tracker, dev_qc, ci_wait) is on for this workspace — exit 0/1",
|
||||
],
|
||||
[
|
||||
"lanes ci status",
|
||||
@@ -2870,19 +2878,23 @@ const COMMAND_GROUPS = [
|
||||
[
|
||||
"lanes gc",
|
||||
"[--dry-run]",
|
||||
"Reap orphaned Playwright MCP processes + cap oversized hook logs, across every lane",
|
||||
"Reap orphaned Playwright MCP processes + cap oversized hook logs, across every workspace",
|
||||
],
|
||||
[
|
||||
"lock status|acquire|release",
|
||||
"[<name>] [--holder X] [--timeout N]",
|
||||
"Cross-lane named lock (serialize builds/e2e across all lanes; holder defaults to the calling lane)",
|
||||
"Cross-workspace named lock (serialize builds/e2e across all workspaces; holder defaults to the calling workspace)",
|
||||
],
|
||||
[
|
||||
"stage <stage> [flags]",
|
||||
"",
|
||||
"Report the current pipeline stage for a lane (--tool <id> when one cwd has lanes for two tools)",
|
||||
"Report the current pipeline stage for a workspace (--tool <id> when one cwd has workspaces for two tools)",
|
||||
],
|
||||
[
|
||||
"feature list",
|
||||
"[<id>]",
|
||||
"List every feature this workspace has activated, archived or live",
|
||||
],
|
||||
["feature list", "[<id>]", "List every feature this lane has activated, archived or live"],
|
||||
[
|
||||
"feature activate",
|
||||
"<slug> [--title text] [<id>]",
|
||||
|
||||
Reference in New Issue
Block a user