@tablation/crew 0.67.2 → 0.68.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 +82 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -2662,6 +2662,14 @@ function resolveApiKey(r) {
|
|
|
2662
2662
|
`route "${r.route}": no API key (looked for, in order: apiKey; ${r.apiKeyVar ?? "VAR"} in ${r.apiKeyFile ?? "<unset>"}; CREW_API_KEY in the environment)`
|
|
2663
2663
|
);
|
|
2664
2664
|
}
|
|
2665
|
+
function credentialSource(r) {
|
|
2666
|
+
if (r.apiKey) return KEYCHAIN_ELIGIBLE_ROUTES.has(r) ? "keychain" : "inline";
|
|
2667
|
+
if (r.apiKeyFile && r.apiKeyVar && existsSync7(r.apiKeyFile)) {
|
|
2668
|
+
const re = new RegExp(`^${r.apiKeyVar}=`);
|
|
2669
|
+
if (readFileSync6(r.apiKeyFile, "utf8").split("\n").some((l) => re.test(l))) return "apiKeyFile";
|
|
2670
|
+
}
|
|
2671
|
+
return process.env.CREW_API_KEY?.trim() ? "CREW_API_KEY" : "none";
|
|
2672
|
+
}
|
|
2665
2673
|
var KEYCHAIN_ELIGIBLE_ROUTES = /* @__PURE__ */ new WeakSet();
|
|
2666
2674
|
async function hydrateApiKeys(routes, getStore = getSessionStore) {
|
|
2667
2675
|
let store;
|
|
@@ -11674,6 +11682,61 @@ function formatProjectsReport(report) {
|
|
|
11674
11682
|
`;
|
|
11675
11683
|
}
|
|
11676
11684
|
|
|
11685
|
+
// src/whoami.ts
|
|
11686
|
+
async function buildWhoami(routes, deps = {}) {
|
|
11687
|
+
const doFetch = deps.fetch ?? fetch;
|
|
11688
|
+
return Promise.all(routes.map(async (route) => {
|
|
11689
|
+
const credential = credentialSource(route);
|
|
11690
|
+
try {
|
|
11691
|
+
const key = resolveApiKey(route);
|
|
11692
|
+
const q = route.resolved ? `?workspaceId=${encodeURIComponent(route.resolved.workspaceId)}` : "";
|
|
11693
|
+
const res = await doFetch(`${route.baseUrl.replace(/\/+$/, "")}/api/auth/me${q}`, {
|
|
11694
|
+
headers: { Authorization: `Bearer ${key}`, "User-Agent": deps.userAgent ?? "Mozilla/5.0 TablationCrewAgent/1.0" }
|
|
11695
|
+
});
|
|
11696
|
+
if (res.status === 401 || res.status === 403) {
|
|
11697
|
+
return { route: route.route, ok: false, credential, error: `key rejected by ${route.baseUrl} (HTTP ${res.status}): expired or revoked, re-run crew connect` };
|
|
11698
|
+
}
|
|
11699
|
+
if (!res.ok) return { route: route.route, ok: false, credential, error: `/auth/me answered HTTP ${res.status}` };
|
|
11700
|
+
const me = await res.json();
|
|
11701
|
+
const operatorId = route.resolved?.operator ?? null;
|
|
11702
|
+
let operatorName = null;
|
|
11703
|
+
if (operatorId && deps.operatorName) {
|
|
11704
|
+
try {
|
|
11705
|
+
operatorName = await deps.operatorName(route, operatorId) ?? null;
|
|
11706
|
+
} catch {
|
|
11707
|
+
}
|
|
11708
|
+
}
|
|
11709
|
+
return {
|
|
11710
|
+
route: route.route,
|
|
11711
|
+
ok: true,
|
|
11712
|
+
credential,
|
|
11713
|
+
identity: { id: me.id ?? null, email: me.email ?? null, name: me.name ?? null },
|
|
11714
|
+
role: typeof me.role === "string" ? me.role : null,
|
|
11715
|
+
operator: { id: operatorId, name: operatorName }
|
|
11716
|
+
};
|
|
11717
|
+
} catch (e) {
|
|
11718
|
+
return { route: route.route, ok: false, credential, error: e.message };
|
|
11719
|
+
}
|
|
11720
|
+
}));
|
|
11721
|
+
}
|
|
11722
|
+
function formatWhoami(report) {
|
|
11723
|
+
const lines = [];
|
|
11724
|
+
for (const r of report) {
|
|
11725
|
+
lines.push(`route ${r.route}`);
|
|
11726
|
+
if (!r.ok) {
|
|
11727
|
+
lines.push(` error: ${r.error}`, ` credential: ${r.credential}`);
|
|
11728
|
+
continue;
|
|
11729
|
+
}
|
|
11730
|
+
const who2 = [r.identity.name, r.identity.email ? `<${r.identity.email}>` : null].filter(Boolean).join(" ");
|
|
11731
|
+
lines.push(` signed in as: ${who2 || r.identity.id || "(unknown)"}${r.identity.id ? ` (${r.identity.id})` : ""}`);
|
|
11732
|
+
lines.push(` role: ${r.role ?? "(unknown)"}`);
|
|
11733
|
+
lines.push(` operator: ${r.operator.id ? `${r.operator.name ?? "(unnamed)"} (${r.operator.id})` : "(not linked)"}`);
|
|
11734
|
+
lines.push(` credential: ${r.credential}`);
|
|
11735
|
+
}
|
|
11736
|
+
return `${lines.join("\n")}
|
|
11737
|
+
`;
|
|
11738
|
+
}
|
|
11739
|
+
|
|
11677
11740
|
// src/cli.ts
|
|
11678
11741
|
import { StaleWriteError as StaleWriteError9 } from "@tablation/client";
|
|
11679
11742
|
|
|
@@ -15477,6 +15540,7 @@ function usage() {
|
|
|
15477
15540
|
crew watch [route] live view of what the crew is doing
|
|
15478
15541
|
crew status [route] [--json] paused/running state; --json for a machine reader
|
|
15479
15542
|
crew projects [--route R] [--json] the Projects rows each route can read (id, name, prefix, work mode)
|
|
15543
|
+
crew whoami [route] [--json] the signed-in identity, key role, operator Crew row and credential source per route
|
|
15480
15544
|
crew reload make the running daemon re-read crew.yaml (--now: restart it immediately)
|
|
15481
15545
|
crew doctor [route] [--fix] [--offline] [--json] preflight; --fix enables clean routes and offers crew install;
|
|
15482
15546
|
--offline skips push probes; --json prints push-auth and tool checks only
|
|
@@ -15698,7 +15762,7 @@ if (command === "passenger-sync-daemon") {
|
|
|
15698
15762
|
const DAEMON_SUBCOMMANDS = /* @__PURE__ */ new Set(["start", "stop", "status", "restart"]);
|
|
15699
15763
|
const worktreeArgs = command === "worktree" ? splitWorktreeArgs(positional, cfg.routes.map((r) => r.route)) : void 0;
|
|
15700
15764
|
const named = worktreeArgs ? worktreeArgs.route : command === "daemon" && DAEMON_SUBCOMMANDS.has(positional[1] ?? "") ? positional[2] : positional[1];
|
|
15701
|
-
const fleetWide = command === "inbox" || command === "connect" || command === "disconnect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || command === "projects" || releaseFleetWide || command === "pause" || command === "resume" || command === "reload" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1 || worktreeArgs !== void 0 && !named && cfg.routes.length > 1;
|
|
15765
|
+
const fleetWide = command === "inbox" || command === "connect" || command === "disconnect" || command === "agents" || command === "skills" || command === "logbook" || command === "repos" || command === "projects" || command === "whoami" || releaseFleetWide || command === "pause" || command === "resume" || command === "reload" || FLEET_CAPABLE.has(command) && !named && cfg.routes.length > 1 || worktreeArgs !== void 0 && !named && cfg.routes.length > 1;
|
|
15702
15766
|
let route;
|
|
15703
15767
|
try {
|
|
15704
15768
|
route = fleetWide ? cfg.routes[0] : findRoute(cfg, named);
|
|
@@ -18902,6 +18966,23 @@ Signed in as ${result.identity.email} \u2014 minted an API key in workspace "${r
|
|
|
18902
18966
|
if (results.some((r) => !r.ok)) process.exitCode = 1;
|
|
18903
18967
|
break;
|
|
18904
18968
|
}
|
|
18969
|
+
case "whoami": {
|
|
18970
|
+
const only = value("route") ?? positional[1];
|
|
18971
|
+
const wanted = only ? cfg.routes.filter((r) => r.route === only) : cfg.routes;
|
|
18972
|
+
if (only && wanted.length === 0) {
|
|
18973
|
+
process.stderr.write(`crew whoami: no route "${only}" (have: ${cfg.routes.map((r) => r.route).join(", ")})
|
|
18974
|
+
`);
|
|
18975
|
+
process.exit(2);
|
|
18976
|
+
}
|
|
18977
|
+
const report = await buildWhoami(wanted, {
|
|
18978
|
+
userAgent: cfg.ship.userAgent,
|
|
18979
|
+
operatorName: async (r, id) => (await new Tracker(r, cfg.ship).crewRows()).find((c) => c.id === id)?.name
|
|
18980
|
+
});
|
|
18981
|
+
process.stdout.write(flag("json") ? `${JSON.stringify(report.length === 1 && only ? report[0] : report, null, 2)}
|
|
18982
|
+
` : formatWhoami(report));
|
|
18983
|
+
if (report.some((r) => !r.ok)) process.exitCode = 1;
|
|
18984
|
+
break;
|
|
18985
|
+
}
|
|
18905
18986
|
case "projects": {
|
|
18906
18987
|
const only = value("route") ?? positional[1];
|
|
18907
18988
|
const wanted = only ? cfg.routes.filter((r) => r.route === only) : cfg.routes;
|
package/package.json
CHANGED