@tokenoftrust/cli 3.7.2 → 3.7.3

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/bin/tot.mjs CHANGED
@@ -133,7 +133,12 @@ async function dispatch(cmd, rest, ctx) {
133
133
  // root's preview branch into it. Refuse them there, naming the space's own path.
134
134
  if (SPACE_REFUSED_COMMANDS.has(cmd)) {
135
135
  const { recordedCheckout } = await import("../src/commands/git-credential.mjs");
136
- const space = recordedCheckout(ctx.cwd);
136
+ let space;
137
+ try { space = recordedCheckout(ctx.cwd); }
138
+ catch (error) {
139
+ process.stderr.write(`tot: could not read this checkout's git config, so it is not safe to continue.\n${error instanceof Error ? error.message : error}\n`);
140
+ return 1;
141
+ }
137
142
  if (space?.kind === "publication-space") {
138
143
  process.stderr.write(spaceCheckoutRefusal(cmd, space.spaceId));
139
144
  return 2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tokenoftrust/cli",
3
- "version": "3.7.2",
3
+ "version": "3.7.3",
4
4
  "description": "Token of Trust developer CLI — clone a tenant store, run it locally with save→reload, and submit it for preview. Installs the `tot` command.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "Token of Trust",
@@ -128,7 +128,14 @@ export async function mintOrCacheCredential({
128
128
  } = {}) {
129
129
  let authority;
130
130
  try { authority = selectedAuthority(env, { cwd }); }
131
- catch { console.error("tot: this checkout has an invalid MCP authority. Run tot login --mcp <url> in this checkout."); return null; }
131
+ catch (error) {
132
+ if (error instanceof Error && error.message.startsWith("invalid MCP authority")) {
133
+ console.error("tot: this checkout has an invalid MCP authority. Run tot login --mcp <url> in this checkout.");
134
+ return null;
135
+ }
136
+ console.error(formatError(error));
137
+ return null;
138
+ }
132
139
  env = { ...env, TOT_SELECTED_MCP_URL: authority, MCP_BASE_URL: authority };
133
140
  const report = (e) => {
134
141
  console.error(e instanceof AuthUnavailableError
@@ -142,7 +149,9 @@ export async function mintOrCacheCredential({
142
149
  `tot: Token of Trust's entitlement service didn't answer — retrying for up to ${Math.round(ms / 1000)}s…`,
143
150
  ),
144
151
  });
145
- const recorded = recordedCheckout(cwd);
152
+ let recorded;
153
+ try { recorded = recordedCheckout(cwd); }
154
+ catch (e) { report(e); return null; }
146
155
  if (recorded?.kind === "invalid") return null; // a half-recorded space mints nothing, never the store's
147
156
  if (recorded?.kind === PUBLICATION_SPACE_KIND) {
148
157
  if (expectedHost && !hostMatches(recorded.originUrl, expectedHost)) return null;
@@ -167,7 +176,9 @@ export async function mintOrCacheCredential({
167
176
  return null;
168
177
  }
169
178
  }
170
- const kit = kitCheckout(cwd);
179
+ let kit;
180
+ try { kit = kitCheckout(cwd); }
181
+ catch (e) { report(e); return null; }
171
182
  if (kit) {
172
183
  if (expectedHost && !hostMatches(kit.originUrl, expectedHost)) return null;
173
184
  const alias = resolveReadOnlyAlias(kit.target);
@@ -256,7 +267,8 @@ export async function mintOrCacheCredential({
256
267
 
257
268
  /**
258
269
  * The publication-space checkout `cwd` is in (or a half-recorded one: `{ kind: "invalid" }`),
259
- * from the kind `tot clone` recorded; null for anything else. Never throws.
270
+ * from the kind `tot clone` recorded; null for anything else. An unreadable git
271
+ * config throws — it is not "not a space".
260
272
  * @param {string} cwd
261
273
  * @returns {{ kind: "publication-space", tenant: string, spaceId: string, repo: string, originUrl: string|null }
262
274
  * | { kind: "invalid" } | null}
@@ -264,8 +276,7 @@ export async function mintOrCacheCredential({
264
276
  export function recordedCheckout(cwd) {
265
277
  const git = (cargs) =>
266
278
  execFileSync("git", ["-C", cwd, ...cargs], { stdio: ["ignore", "pipe", "pipe"] }).toString();
267
- let kind;
268
- try { kind = readCheckoutKind(git); } catch { return null; }
279
+ const kind = readCheckoutKind(git);
269
280
  if (kind.kind === "invalid") return { kind: "invalid" };
270
281
  if (kind.kind !== PUBLICATION_SPACE_KIND) return null;
271
282
  let originUrl = null;
@@ -275,7 +286,7 @@ export function recordedCheckout(cwd) {
275
286
 
276
287
  /**
277
288
  * The read-only kit checkout `cwd` is in, from the kind `tot clone` recorded, or null when
278
- * it is not one (a store, a loose dir, not a repo). Never throws.
289
+ * it is not one (a store, a loose dir, not a repo). An unreadable git config throws.
279
290
  * @param {string} cwd
280
291
  * @returns {{ target: string, originUrl: string|null }|null}
281
292
  */
@@ -21,7 +21,7 @@ import { homedir } from "node:os";
21
21
  import { createHash } from "node:crypto";
22
22
  import { join, dirname } from "node:path";
23
23
  import { mkdirSync, writeFileSync, chmodSync, rmSync } from "node:fs";
24
- import { readCredentials, writeCredentials, selectedAuthority, profileSlug, readSessionCredentials } from "./token-store.mjs";
24
+ import { readCredentials, writeCredentials, selectedAuthority, profileSlug, readSessionCredentials, isAbsentGitConfig } from "./token-store.mjs";
25
25
 
26
26
  /** The git config value that routes credential requests through `tot` — the
27
27
  * `!` tells git to run this as a shell command (git appends the operation,
@@ -54,8 +54,9 @@ export function splitAuthedRemote(remoteUrl) {
54
54
  if (!token) return null;
55
55
  const username = u.username ? decodeURIComponent(u.username) : "";
56
56
  return { publicUrl: `${u.protocol}//${u.host}${u.pathname}`, username, token };
57
- } catch {
58
- return null;
57
+ } catch (error) {
58
+ if (error instanceof TypeError || error instanceof URIError) return null;
59
+ throw error;
59
60
  }
60
61
  }
61
62
 
@@ -205,7 +206,11 @@ export function recordPublicationSpaceCheckout(git, { tenant, spaceId, repo }) {
205
206
  */
206
207
  export function readCheckoutKind(git) {
207
208
  const get = (key) => {
208
- try { return String(git(["config", "--local", "--get", key])).trim(); } catch { return ""; }
209
+ try { return String(git(["config", "--local", "--get", key])).trim(); }
210
+ catch (error) {
211
+ if (isAbsentGitConfig(error)) return "";
212
+ throw error;
213
+ }
209
214
  };
210
215
  const kind = get(CHECKOUT_KIND_KEY);
211
216
  if (kind === PUBLICATION_SPACE_KIND) {
@@ -54,6 +54,21 @@ export function normalizeAuthority(value) {
54
54
  }
55
55
  }
56
56
 
57
+ /**
58
+ * `git config --get` exit 1 means the key is unset. Exit 128 is "not a checkout"
59
+ * only when stderr says so. Permission denied and a missing git binary are not
60
+ * "no authority" — callers must not fall through to another session.
61
+ * @param {unknown} error
62
+ */
63
+ export function isAbsentGitConfig(error) {
64
+ const status = error && typeof error === "object" ? /** @type {{ status?: number }} */ (error).status : undefined;
65
+ if (status === 1) return true;
66
+ if (status !== 128) return false;
67
+ const stderr = `${/** @type {{ stderr?: unknown, message?: unknown }} */ (error).stderr ?? ""}\n${/** @type {{ message?: unknown }} */ (error).message ?? ""}`;
68
+ if (/Permission denied|unable to access/i.test(stderr)) return false;
69
+ return /not a git repository|can only be used inside a git repository/i.test(stderr);
70
+ }
71
+
57
72
  /** Local checkout provenance, never inferred from a shared forge hostname. */
58
73
  export function repositoryAuthority(cwd = process.cwd()) {
59
74
  let value;
@@ -61,7 +76,10 @@ export function repositoryAuthority(cwd = process.cwd()) {
61
76
  value = execFileSync("git", ["-C", cwd, "config", "--local", "--get", MCP_AUTHORITY_KEY], {
62
77
  encoding: "utf8", stdio: ["ignore", "pipe", "pipe"],
63
78
  }).trim();
64
- } catch { return null; }
79
+ } catch (error) {
80
+ if (isAbsentGitConfig(error)) return null;
81
+ throw error;
82
+ }
65
83
  return value ? normalizeAuthority(value) : null;
66
84
  }
67
85
 
@@ -147,8 +165,13 @@ export function defaultCredentialsPath(env = process.env, mcpUrl = undefined) {
147
165
  export function readSessionCredentials(env = process.env, mcpUrl = undefined) {
148
166
  const authority = selectedAuthority(env, { mcpUrl });
149
167
  const creds = readCredentials(defaultCredentialsPath(env, authority));
150
- try { return creds && normalizeAuthority(creds.mcpUrl) === authority ? creds : null; }
151
- catch { return null; }
168
+ if (!creds || typeof creds.mcpUrl !== "string") return null;
169
+ try {
170
+ return normalizeAuthority(creds.mcpUrl) === authority ? creds : null;
171
+ } catch (error) {
172
+ if (error instanceof Error && error.message.startsWith("invalid MCP authority")) return null;
173
+ throw error;
174
+ }
152
175
  }
153
176
 
154
177
  /**
@@ -165,7 +188,10 @@ export function readCredentials(filePath) {
165
188
  try {
166
189
  const parsed = JSON.parse(text);
167
190
  return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : null;
168
- } catch { return null; }
191
+ } catch (error) {
192
+ if (error instanceof SyntaxError) return null;
193
+ throw error;
194
+ }
169
195
  }
170
196
 
171
197
  /**
@@ -61,11 +61,14 @@ function parseConfig(tenantDir) {
61
61
  }
62
62
  }
63
63
 
64
- /** The checkout's `.tot/config.json`, or null when absent or unreadable. */
64
+ /** The checkout's `.tot/config.json`, or null when it is absent or malformed. Unreadable throws. */
65
65
  function readTotConfig(tenantDir) {
66
66
  try {
67
67
  return JSON.parse(readFileSync(join(tenantDir, ".tot", "config.json"), "utf8"));
68
- } catch { return null; }
68
+ } catch (error) {
69
+ if (error?.code === "ENOENT" || error instanceof SyntaxError) return null;
70
+ throw error;
71
+ }
69
72
  }
70
73
 
71
74
  function allowedNavigation(value, allowlist) {