@tablation/crew 0.51.1 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +140 -31
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1435,8 +1435,8 @@ var TOP_LEVEL = /* @__PURE__ */ new Set([
|
|
|
1435
1435
|
"labels",
|
|
1436
1436
|
"release"
|
|
1437
1437
|
]);
|
|
1438
|
-
var BRANCH_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote"]);
|
|
1439
|
-
var PLACEHOLDER = /\{(key|number|slug|role|prefix|tag)\}/g;
|
|
1438
|
+
var BRANCH_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote", "lowercase"]);
|
|
1439
|
+
var PLACEHOLDER = /\{(key|number|slug|role|prefix|tag|issue)\}/g;
|
|
1440
1440
|
var WORKTREE_PLACEHOLDERS = ["dir", "issue", "number", "key", "slug", "role", "prefix", "tag"];
|
|
1441
1441
|
function worktreeNameProblem(template) {
|
|
1442
1442
|
const valid = `Valid placeholders: ${WORKTREE_PLACEHOLDERS.map((p) => `{${p}}`).join(" ")}.`;
|
|
@@ -1528,7 +1528,7 @@ function parseRepoConfig(text, file) {
|
|
|
1528
1528
|
if (!PLACEHOLDER.test(branchName)) {
|
|
1529
1529
|
PLACEHOLDER.lastIndex = 0;
|
|
1530
1530
|
throw new RepoConfigError(
|
|
1531
|
-
`${file}: branch.name ("${branchName}") names no placeholder \u2014 every ticket would get the same branch. Use at least one of {key} {number} {slug} {role}.`
|
|
1531
|
+
`${file}: branch.name ("${branchName}") names no placeholder \u2014 every ticket would get the same branch. Use at least one of {key} {number} {slug} {role} {prefix} {tag} {issue}.`
|
|
1532
1532
|
);
|
|
1533
1533
|
}
|
|
1534
1534
|
PLACEHOLDER.lastIndex = 0;
|
|
@@ -1536,7 +1536,8 @@ function parseRepoConfig(text, file) {
|
|
|
1536
1536
|
base: String(raw.branch?.base ?? "main"),
|
|
1537
1537
|
name: branchName,
|
|
1538
1538
|
push: String(raw.branch?.push ?? branchName),
|
|
1539
|
-
remote: String(raw.branch?.remote ?? "origin")
|
|
1539
|
+
remote: String(raw.branch?.remote ?? "origin"),
|
|
1540
|
+
...raw.branch?.lowercase !== void 0 ? { lowercase: parseLowercase(raw.branch.lowercase, file) } : {}
|
|
1540
1541
|
};
|
|
1541
1542
|
const copyRaw = raw.worktrees?.copy;
|
|
1542
1543
|
if (copyRaw !== void 0 && !Array.isArray(copyRaw)) {
|
|
@@ -1733,9 +1734,13 @@ function slugify(title, maxLength = 40) {
|
|
|
1733
1734
|
const s = title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, maxLength).replace(/-+$/, "");
|
|
1734
1735
|
return s || "work";
|
|
1735
1736
|
}
|
|
1737
|
+
function parseLowercase(v, where) {
|
|
1738
|
+
if (typeof v !== "boolean") throw new RepoConfigError(`${where}: branch.lowercase must be true or false`);
|
|
1739
|
+
return v;
|
|
1740
|
+
}
|
|
1736
1741
|
function renderBranchName(template, ctx) {
|
|
1737
1742
|
const number = ctx.key.replace(/^\D+/, "");
|
|
1738
|
-
|
|
1743
|
+
const rendered = template.replace(PLACEHOLDER, (_m, name) => {
|
|
1739
1744
|
switch (name) {
|
|
1740
1745
|
case "key":
|
|
1741
1746
|
return ctx.key;
|
|
@@ -1749,14 +1754,16 @@ function renderBranchName(template, ctx) {
|
|
|
1749
1754
|
return ctx.tag ?? ctx.key;
|
|
1750
1755
|
case "prefix":
|
|
1751
1756
|
return ctx.prefix ?? "";
|
|
1757
|
+
case "issue":
|
|
1758
|
+
return (ctx.tag ?? ctx.key).toLowerCase();
|
|
1752
1759
|
default:
|
|
1753
1760
|
return "";
|
|
1754
1761
|
}
|
|
1755
1762
|
});
|
|
1763
|
+
return ctx.lowercase ? rendered.toLowerCase() : rendered;
|
|
1756
1764
|
}
|
|
1757
1765
|
function renderWorktreeName(template, ctx) {
|
|
1758
|
-
|
|
1759
|
-
return renderBranchName(template.replace(/\{dir\}/g, ctx.dir).replace(/\{issue\}/g, issue), ctx);
|
|
1766
|
+
return renderBranchName(template.replace(/\{dir\}/g, ctx.dir), ctx);
|
|
1760
1767
|
}
|
|
1761
1768
|
function defaultWorktreePrefix(dir) {
|
|
1762
1769
|
const name = basename2(dir.replace(/[/\\]+$/, ""));
|
|
@@ -1856,7 +1863,8 @@ function resolveRepoConfig(repo, ship, dir) {
|
|
|
1856
1863
|
repo?.branch.remote === "origin" ? void 0 : repo?.branch.remote,
|
|
1857
1864
|
ship?.branch?.remote,
|
|
1858
1865
|
"origin"
|
|
1859
|
-
)
|
|
1866
|
+
),
|
|
1867
|
+
...pick("branch.lowercase", repo?.branch.lowercase, ship?.branch?.lowercase, false) ? { lowercase: true } : {}
|
|
1860
1868
|
},
|
|
1861
1869
|
// Unlike every other field here, platform has no ship-level fallback: what
|
|
1862
1870
|
// a repo needs of a host is a fact about that repo alone (its build
|
|
@@ -2055,7 +2063,7 @@ function parseWorktreeName(v, where, file) {
|
|
|
2055
2063
|
return String(v);
|
|
2056
2064
|
}
|
|
2057
2065
|
var REPO_ENTRY_KEYS = /* @__PURE__ */ new Set(["dir", "hooks", "labels", "release", "branch", "hostPassengers", "worktreeName", "toolchain"]);
|
|
2058
|
-
var BRANCH_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote"]);
|
|
2066
|
+
var BRANCH_OVERRIDE_KEYS = /* @__PURE__ */ new Set(["base", "name", "push", "remote", "lowercase"]);
|
|
2059
2067
|
var RELEASE_OVERRIDE_KEYS = /* @__PURE__ */ new Set([
|
|
2060
2068
|
"mode",
|
|
2061
2069
|
"ci",
|
|
@@ -2139,8 +2147,12 @@ function parseBranchOverride(raw, where, file) {
|
|
|
2139
2147
|
const name = raw.name !== void 0 ? String(raw.name) : void 0;
|
|
2140
2148
|
const push = raw.push !== void 0 ? String(raw.push) : void 0;
|
|
2141
2149
|
const remote = raw.remote !== void 0 ? String(raw.remote) : void 0;
|
|
2142
|
-
if (
|
|
2143
|
-
|
|
2150
|
+
if (raw.lowercase !== void 0 && typeof raw.lowercase !== "boolean") {
|
|
2151
|
+
throw new ConfigError(`${file}: ${where}.branch.lowercase must be true or false`);
|
|
2152
|
+
}
|
|
2153
|
+
const lowercase = raw.lowercase;
|
|
2154
|
+
if (base === void 0 && name === void 0 && push === void 0 && remote === void 0 && lowercase === void 0) return void 0;
|
|
2155
|
+
return { base, name, push, remote, ...lowercase !== void 0 ? { lowercase } : {} };
|
|
2144
2156
|
}
|
|
2145
2157
|
function mergeRouteRelease(route, override) {
|
|
2146
2158
|
const provider = override?.ci?.provider ?? route.ci?.provider;
|
|
@@ -6355,13 +6367,13 @@ ${waiting.map((t) => `| ${displayKey2(t)} | ${t.status} | ${blockers(t, i)} |`).
|
|
|
6355
6367
|
|
|
6356
6368
|
// src/ticket-branch.ts
|
|
6357
6369
|
import { basename as basename5 } from "path";
|
|
6358
|
-
function branchRenderer(t, role) {
|
|
6370
|
+
function branchRenderer(t, role, lowercase) {
|
|
6359
6371
|
const { tag, prefix } = ticketBranchContext(t);
|
|
6360
|
-
return (template) => renderBranchName(template, { key: t.issue_id, title: t.title ?? void 0, role, tag, prefix });
|
|
6372
|
+
return (template) => renderBranchName(template, { key: t.issue_id, title: t.title ?? void 0, role, tag, prefix, lowercase });
|
|
6361
6373
|
}
|
|
6362
6374
|
function worktreeForTicket(dir, cfg, t, role) {
|
|
6363
6375
|
const { prefix } = ticketBranchContext(t);
|
|
6364
|
-
const branchName = branchRenderer(t, role)(effectiveBranchTemplate(cfg, prefix));
|
|
6376
|
+
const branchName = branchRenderer(t, role, cfg.branch.lowercase)(effectiveBranchTemplate(cfg, prefix));
|
|
6365
6377
|
const number = t.issue_id.replace(/^\D+/, "");
|
|
6366
6378
|
const { tag } = ticketBranchContext(t);
|
|
6367
6379
|
return effectiveWorktreeDirName(cfg, dir, branchName, number, (template) => renderWorktreeName(template, {
|
|
@@ -6380,10 +6392,10 @@ function branchTemplates(cfg, t) {
|
|
|
6380
6392
|
return { name, push };
|
|
6381
6393
|
}
|
|
6382
6394
|
function pushedBranchForTicket(cfg, t, role) {
|
|
6383
|
-
return branchRenderer(t, role)(branchTemplates(cfg, t).push);
|
|
6395
|
+
return branchRenderer(t, role, cfg.branch.lowercase)(branchTemplates(cfg, t).push);
|
|
6384
6396
|
}
|
|
6385
6397
|
function existingBranchForTicket(dir, cfg, t, role) {
|
|
6386
|
-
return branchForIssue(dir, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role));
|
|
6398
|
+
return branchForIssue(dir, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role, cfg.branch.lowercase));
|
|
6387
6399
|
}
|
|
6388
6400
|
function locateBranchForTicket(dir, cfg, t, role, fetchedDirs) {
|
|
6389
6401
|
const local = existingBranchForTicket(dir, cfg, t, role);
|
|
@@ -6394,7 +6406,7 @@ function locateBranchForTicket(dir, cfg, t, role, fetchedDirs) {
|
|
|
6394
6406
|
fetchedDirs.add(dir);
|
|
6395
6407
|
fetchRemote(dir, remote);
|
|
6396
6408
|
}
|
|
6397
|
-
const found = remoteBranchForIssue(dir, remote, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role));
|
|
6409
|
+
const found = remoteBranchForIssue(dir, remote, t.issue_id, branchTemplates(cfg, t), branchRenderer(t, role, cfg.branch.lowercase));
|
|
6398
6410
|
return found ? { branch: found, where: "remote" } : null;
|
|
6399
6411
|
}
|
|
6400
6412
|
function materializeBranchForTicket(dir, cfg, t, role, onProblem) {
|
|
@@ -6712,14 +6724,14 @@ function writeDigest(o, d, role, stateDir) {
|
|
|
6712
6724
|
};
|
|
6713
6725
|
const fetchedDirs = /* @__PURE__ */ new Set();
|
|
6714
6726
|
const dirFor = (t) => dirForRepo(o.route, t.repo_id);
|
|
6715
|
-
const render2 = (t) => branchRenderer(t, role);
|
|
6727
|
+
const render2 = (t, lowercase) => branchRenderer(t, role, lowercase);
|
|
6716
6728
|
const input = {
|
|
6717
6729
|
dirFor,
|
|
6718
6730
|
repoStop: repoStopFor(openShipAttention(o.state), o.route),
|
|
6719
6731
|
branchFor: (t) => {
|
|
6720
6732
|
const cfg = repoFor(dirFor(t) ?? o.route.dir);
|
|
6721
6733
|
const { prefix } = ticketBranchContext(t);
|
|
6722
|
-
return render2(t)(effectiveBranchTemplate(cfg, prefix));
|
|
6734
|
+
return render2(t, cfg.branch.lowercase)(effectiveBranchTemplate(cfg, prefix));
|
|
6723
6735
|
},
|
|
6724
6736
|
// ISSUE-1028: the agent previously had to derive this itself from the
|
|
6725
6737
|
// Environment section's own worked example, which is rendered before
|
|
@@ -7210,7 +7222,7 @@ function repoPrefix(repo) {
|
|
|
7210
7222
|
function branchExample(repo, key) {
|
|
7211
7223
|
const prefix = repoPrefix(repo);
|
|
7212
7224
|
const template = effectiveBranchTemplate(repo, prefix);
|
|
7213
|
-
return renderBranchName(template, { key, title: "Fix the widget", role: "dev", prefix });
|
|
7225
|
+
return renderBranchName(template, { key, title: "Fix the widget", role: "dev", prefix, lowercase: repo.branch.lowercase });
|
|
7214
7226
|
}
|
|
7215
7227
|
function repoSection(r, key, heading) {
|
|
7216
7228
|
const repo = r.config;
|
|
@@ -8957,7 +8969,7 @@ var describeRunWorktreePlan = (p) => {
|
|
|
8957
8969
|
var planIsUsable = (p) => p.action !== "leave-dirty" && p.action !== "leave-unpushed";
|
|
8958
8970
|
function newBranchName(cfg, t, role) {
|
|
8959
8971
|
const { prefix } = ticketBranchContext(t);
|
|
8960
|
-
return branchRenderer(t, role)(effectiveBranchTemplate(cfg, prefix));
|
|
8972
|
+
return branchRenderer(t, role, cfg.branch.lowercase)(effectiveBranchTemplate(cfg, prefix));
|
|
8961
8973
|
}
|
|
8962
8974
|
function planRunWorktree(dir, cfg, t, role, opts = {}) {
|
|
8963
8975
|
const { remote, base } = cfg.branch;
|
|
@@ -9721,9 +9733,9 @@ function reviewProblems(repo, contract) {
|
|
|
9721
9733
|
if (!reviewApplies(repo, contract)) return [];
|
|
9722
9734
|
const problems = [];
|
|
9723
9735
|
const template = repo.branch.push ?? repo.branch.name;
|
|
9724
|
-
if (!/\{(key|number|tag)\}/.test(template)) {
|
|
9736
|
+
if (!/\{(key|number|tag|issue)\}/.test(template)) {
|
|
9725
9737
|
problems.push(
|
|
9726
|
-
`branch.push "${template}" names none of {key}, {number} or {
|
|
9738
|
+
`branch.push "${template}" names none of {key}, {number}, {tag} or {issue} \u2014 with release.mode "external" and a reviewing status, the pushed branch is the only link back to its ticket`
|
|
9727
9739
|
);
|
|
9728
9740
|
}
|
|
9729
9741
|
return problems;
|
|
@@ -11608,7 +11620,8 @@ function keyIsAdmin(route) {
|
|
|
11608
11620
|
return role === void 0 || role === "WORKSPACE_ADMIN" || role === "PLATFORM_ADMIN";
|
|
11609
11621
|
}
|
|
11610
11622
|
var MEMBER_NO_ROW = "has no linked Agents row \u2014 a workspace admin's `crew agents sync` creates the persona rows; run it here afterwards to link this ship's seats";
|
|
11611
|
-
var MEMBER_NO_KEY =
|
|
11623
|
+
var MEMBER_NO_KEY = 'no agent key for this seat \u2014 run `crew agents sync` (needs the "Mint agent keys" capability on your role)';
|
|
11624
|
+
var FORBIDDEN_LINE = 'seat keys: not minted \u2014 your role does not grant "Mint agent keys"; ask a workspace admin to add it to your project role (or run sync with an admin key)';
|
|
11612
11625
|
function seatKeySlug(workspaceSlug, agentId) {
|
|
11613
11626
|
return `${workspaceSlug}#agent:${agentId}`;
|
|
11614
11627
|
}
|
|
@@ -11712,6 +11725,10 @@ async function mintMissingSeatKeys(route, o) {
|
|
|
11712
11725
|
},
|
|
11713
11726
|
body: JSON.stringify({ name: `crew ${o.shipName ?? "ship"} ${role}` })
|
|
11714
11727
|
});
|
|
11728
|
+
if (res.status === 403) {
|
|
11729
|
+
out.push({ role, action: "forbidden" });
|
|
11730
|
+
break;
|
|
11731
|
+
}
|
|
11715
11732
|
if (!res.ok) {
|
|
11716
11733
|
out.push({ role, action: "failed", detail: `${res.status} ${res.statusText}` });
|
|
11717
11734
|
continue;
|
|
@@ -11722,13 +11739,15 @@ async function mintMissingSeatKeys(route, o) {
|
|
|
11722
11739
|
continue;
|
|
11723
11740
|
}
|
|
11724
11741
|
try {
|
|
11725
|
-
|
|
11742
|
+
const session = {
|
|
11726
11743
|
apiKey: body.key,
|
|
11727
11744
|
shipId: o.shipName ?? "",
|
|
11728
11745
|
workspaceId: resolved.workspaceId,
|
|
11729
11746
|
identityId: agentId,
|
|
11730
|
-
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
11731
|
-
|
|
11747
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
11748
|
+
...body.id ? { keyId: body.id } : {}
|
|
11749
|
+
};
|
|
11750
|
+
await store.set(host, slug, session);
|
|
11732
11751
|
} catch (e) {
|
|
11733
11752
|
let revoked = false;
|
|
11734
11753
|
if (body.id) {
|
|
@@ -11764,10 +11783,92 @@ function describeMintOutcome(o) {
|
|
|
11764
11783
|
return `${o.role}: agent key already stored`;
|
|
11765
11784
|
case "no_agent":
|
|
11766
11785
|
return `${o.role}: no linked Agents row \u2014 writes as the ship until \`crew agents sync\` links one`;
|
|
11786
|
+
case "forbidden":
|
|
11787
|
+
return FORBIDDEN_LINE;
|
|
11767
11788
|
case "failed":
|
|
11768
11789
|
return `${o.role}: could not mint an agent key (${o.detail ?? "unknown"}) \u2014 writes as the ship`;
|
|
11769
11790
|
}
|
|
11770
11791
|
}
|
|
11792
|
+
async function revokeSeatKeys(route, o) {
|
|
11793
|
+
const out = [];
|
|
11794
|
+
if (!route.resolved) return out;
|
|
11795
|
+
const resolved = route.resolved;
|
|
11796
|
+
const doFetch = o.fetchImpl ?? fetch;
|
|
11797
|
+
const targets = [
|
|
11798
|
+
...SEAT_KEY_ROLES.filter((r) => resolved.seats[r]).map((role) => ({ role, agentId: seatAgentId(route, role) })),
|
|
11799
|
+
...resolved.releaseSeat ? [{ role: "release", agentId: resolved.releaseAgentId }] : []
|
|
11800
|
+
];
|
|
11801
|
+
let store;
|
|
11802
|
+
try {
|
|
11803
|
+
store = (o.getStore ?? getSessionStore2)();
|
|
11804
|
+
} catch (e) {
|
|
11805
|
+
return targets.map(({ role }) => ({ role, action: "failed", detail: describeStoreError(e) }));
|
|
11806
|
+
}
|
|
11807
|
+
const host = hostFromUrl2(route.baseUrl);
|
|
11808
|
+
const base = route.baseUrl.replace(/\/+$/, "");
|
|
11809
|
+
for (const { role, agentId } of targets) {
|
|
11810
|
+
if (!agentId) {
|
|
11811
|
+
out.push({ role, action: "no_agent" });
|
|
11812
|
+
continue;
|
|
11813
|
+
}
|
|
11814
|
+
const slug = slugFor(route, agentId);
|
|
11815
|
+
try {
|
|
11816
|
+
const session = await store.get(host, slug);
|
|
11817
|
+
if (!session?.apiKey) {
|
|
11818
|
+
out.push({ role, action: "not_stored" });
|
|
11819
|
+
continue;
|
|
11820
|
+
}
|
|
11821
|
+
if (!session.keyId) {
|
|
11822
|
+
out.push({ role, action: "failed", detail: "no key id was recorded when this key was minted \u2014 revoke it from the seat's Agents row" });
|
|
11823
|
+
continue;
|
|
11824
|
+
}
|
|
11825
|
+
if (o.dryRun) {
|
|
11826
|
+
out.push({ role, action: "revoked", detail: "dry run" });
|
|
11827
|
+
continue;
|
|
11828
|
+
}
|
|
11829
|
+
const del2 = await doFetch(`${base}/api/workspaces/${resolved.workspaceId}/agents/${agentId}/api-keys/${session.keyId}`, {
|
|
11830
|
+
method: "DELETE",
|
|
11831
|
+
headers: { Authorization: `Bearer ${resolveApiKey(route)}`, "User-Agent": o.userAgent ?? "Mozilla/5.0 TablationCrewAgent/1.0" }
|
|
11832
|
+
});
|
|
11833
|
+
if (!del2.ok && del2.status !== 404) {
|
|
11834
|
+
out.push({ role, action: "failed", detail: `${del2.status} ${del2.statusText}` });
|
|
11835
|
+
continue;
|
|
11836
|
+
}
|
|
11837
|
+
await store.clear(host, slug);
|
|
11838
|
+
out.push({ role, action: "revoked", detail: del2.ok ? void 0 : "server already had no such key" });
|
|
11839
|
+
} catch (e) {
|
|
11840
|
+
out.push({ role, action: "failed", detail: /Command failed|security|sk_|-w /i.test(e.message) ? describeStoreError(e) : e.message });
|
|
11841
|
+
}
|
|
11842
|
+
}
|
|
11843
|
+
return out;
|
|
11844
|
+
}
|
|
11845
|
+
function describeRevokeOutcome(o) {
|
|
11846
|
+
switch (o.action) {
|
|
11847
|
+
case "revoked":
|
|
11848
|
+
return `${o.role}: agent key revoked and cleared from the keychain${o.detail ? ` (${o.detail})` : ""}`;
|
|
11849
|
+
case "not_stored":
|
|
11850
|
+
return `${o.role}: no agent key stored on this ship`;
|
|
11851
|
+
case "no_agent":
|
|
11852
|
+
return `${o.role}: no linked Agents row`;
|
|
11853
|
+
case "failed":
|
|
11854
|
+
return `${o.role}: could not revoke the agent key (${o.detail ?? "unknown"}) \u2014 left in the keychain`;
|
|
11855
|
+
}
|
|
11856
|
+
}
|
|
11857
|
+
async function canMintAgentKeys(route, o = {}) {
|
|
11858
|
+
const resolved = route.resolved;
|
|
11859
|
+
if (!resolved) return void 0;
|
|
11860
|
+
const agentId = seatAgentId(route, "dev") ?? SEAT_KEY_ROLES.map((r) => seatAgentId(route, r)).find(Boolean);
|
|
11861
|
+
if (!agentId) return void 0;
|
|
11862
|
+
try {
|
|
11863
|
+
const res = await (o.fetchImpl ?? fetch)(`${route.baseUrl.replace(/\/+$/, "")}/api/workspaces/${resolved.workspaceId}/agents/${agentId}/api-keys`, {
|
|
11864
|
+
headers: { Authorization: `Bearer ${resolveApiKey(route)}`, "User-Agent": o.userAgent ?? "Mozilla/5.0 TablationCrewAgent/1.0" }
|
|
11865
|
+
});
|
|
11866
|
+
if (res.ok) return true;
|
|
11867
|
+
return res.status === 403 ? false : void 0;
|
|
11868
|
+
} catch {
|
|
11869
|
+
return void 0;
|
|
11870
|
+
}
|
|
11871
|
+
}
|
|
11771
11872
|
|
|
11772
11873
|
// src/logbook.ts
|
|
11773
11874
|
import { createHash as createHash3 } from "crypto";
|
|
@@ -13666,7 +13767,7 @@ function usage() {
|
|
|
13666
13767
|
crew inbox [--member NAME] your tickets across every workspace (or a colleague's)
|
|
13667
13768
|
crew connect resolve a workspace's ids into a crew.yaml block (--no-mcp skips registering the Tablation MCP server with Claude Code; --mcp replace overwrites a different one)
|
|
13668
13769
|
crew disconnect remove what connect installed for Pair (Claude MCP; Codex/Gemini instructions block and MCP entry per ship.pair)
|
|
13669
|
-
crew agents sync [route] [--force] push crew's personas AND skill files into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona or skill; never one marked Customize prompt)
|
|
13770
|
+
crew agents sync [route] [--force] [--revoke-seat-keys] push crew's personas AND skill files into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona or skill; never one marked Customize prompt)
|
|
13670
13771
|
crew agents prompt R [route] print one persona's current prompt (e.g. R=pair, for a SessionStart hook; --harness H adds which CLI it serves)
|
|
13671
13772
|
crew skills sync [route] push only crew's skill files (e.g. grill-me) into the workspace Agent Skills table
|
|
13672
13773
|
crew repos add [ROUTE] [NAME] [PATH] attach a local checkout to a route in crew.yaml (--dry-run previews)
|
|
@@ -15187,7 +15288,7 @@ ${changed2.map((c) => ` - ${c}
|
|
|
15187
15288
|
break;
|
|
15188
15289
|
}
|
|
15189
15290
|
if (sub !== "sync") {
|
|
15190
|
-
process.stderr.write("crew agents sync [route] [--force] push crew's personas (Developer/Design/QA/Triage/Pair) AND skill files (e.g. grill-me) into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona or skill; never one marked Customize prompt)\n");
|
|
15291
|
+
process.stderr.write("crew agents sync [route] [--force] push crew's personas (Developer/Design/QA/Triage/Pair) AND skill files (e.g. grill-me) into the workspace Agents/Agent Skills tables (--force overwrites even a diverged persona or skill; never one marked Customize prompt; --revoke-seat-keys deletes this ship's seat keys server-side and from the keychain, so the next sync mints fresh ones)\n");
|
|
15191
15292
|
process.stderr.write(`crew agents prompt <role> [route] print a persona's current prompt (roles: ${ROLE_NAMES.join(", ")})
|
|
15192
15293
|
`);
|
|
15193
15294
|
process.exit(2);
|
|
@@ -15270,7 +15371,12 @@ ${changed2.map((c) => ` - ${c}
|
|
|
15270
15371
|
}
|
|
15271
15372
|
if (readOnly) {
|
|
15272
15373
|
process.stdout.write("personas and skills: not synced (this key is not a workspace admin; the workspace's rows are used as they are)\n");
|
|
15273
|
-
|
|
15374
|
+
}
|
|
15375
|
+
if (flag("revoke-seat-keys")) {
|
|
15376
|
+
for (const o of await revokeSeatKeys(target, { userAgent: cfg.ship.userAgent, dryRun })) {
|
|
15377
|
+
process.stdout.write(`${describeRevokeOutcome(o)}
|
|
15378
|
+
`);
|
|
15379
|
+
}
|
|
15274
15380
|
} else {
|
|
15275
15381
|
for (const o of await mintMissingSeatKeys(target, { userAgent: cfg.ship.userAgent, dryRun, shipName: cfg.ship.name })) {
|
|
15276
15382
|
process.stdout.write(`${describeMintOutcome(o)}
|
|
@@ -16045,7 +16151,7 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
16045
16151
|
const branch = found.branch ?? branchForIssue(target.dir, `ISSUE-${n}`, {
|
|
16046
16152
|
name: target.config.branch.name,
|
|
16047
16153
|
push: target.config.branch.push
|
|
16048
|
-
}, (t) => renderBranchName(t, { key: `ISSUE-${n}
|
|
16154
|
+
}, (t) => renderBranchName(t, { key: `ISSUE-${n}`, lowercase: target.config.branch.lowercase }));
|
|
16049
16155
|
const pids = pidsInWorktree(wt, num);
|
|
16050
16156
|
if (pids.length) {
|
|
16051
16157
|
emit.emit(`${dryRun ? "would kill" : "killing"} processes still inside ${wt}: ${pids.join(" ")}`);
|
|
@@ -16675,6 +16781,9 @@ routes: ${cfg.routes.map((c) => c.route).join(", ")}
|
|
|
16675
16781
|
const keyRole = route.resolved?.role ?? await fetchKeyRole(route, cfg.ship.userAgent);
|
|
16676
16782
|
const roleRoute = route.resolved && keyRole ? { ...route, resolved: { ...route.resolved, role: keyRole } } : route;
|
|
16677
16783
|
process.stdout.write(`api key role: ${keyRole ?? "unknown"}
|
|
16784
|
+
`);
|
|
16785
|
+
const canMint = await canMintAgentKeys(roleRoute, { userAgent: cfg.ship.userAgent });
|
|
16786
|
+
process.stdout.write(`can mint agent keys: ${canMint === void 0 ? "unknown" : canMint ? "yes" : "no"}
|
|
16678
16787
|
`);
|
|
16679
16788
|
for (const role of SEAT_KEY_ROLES) {
|
|
16680
16789
|
if (!route.resolved?.seats[role]) continue;
|
package/package.json
CHANGED