@whittlelabs/trivet 0.1.9 → 0.1.10

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.
Files changed (3) hide show
  1. package/bin.js +125 -63
  2. package/bin.js.map +3 -3
  3. package/package.json +1 -1
package/bin.js CHANGED
@@ -67941,6 +67941,10 @@ var require_store = __commonJS({
67941
67941
  await fsp.rename(tmp, this.path);
67942
67942
  await fsp.chmod(this.path, 384);
67943
67943
  }
67944
+ /** Forget the pairing: `revoke` of this machine, whose credential is dead. */
67945
+ async remove() {
67946
+ await fsp.rm(this.path, { force: true });
67947
+ }
67944
67948
  };
67945
67949
  exports2.PairingConfigStore = PairingConfigStore;
67946
67950
  }
@@ -75761,38 +75765,55 @@ var require_pairings = __commonJS({
75761
75765
  Object.defineProperty(exports2, "__esModule", { value: true });
75762
75766
  exports2.listPairings = listPairings;
75763
75767
  exports2.revokePairing = revokePairing;
75768
+ var keep_1 = require_keep();
75769
+ var keep_2 = require_dist4();
75770
+ var version_check_1 = require_version_check();
75771
+ var PAGE_LIMIT = 100;
75764
75772
  async function listPairings(opts) {
75765
- const fetcher = opts.fetcher ?? globalThis.fetch;
75766
- const url = `${opts.brand.keepRegistration.keepApiUrl.replace(/\/$/, "")}/api/me/service-identities?client_id=${encodeURIComponent(opts.brand.keepRegistration.oauthClientId)}`;
75767
- const response = await fetcher(url, {
75768
- method: "GET",
75769
- headers: { Authorization: `Bearer ${opts.accessToken}` }
75770
- });
75771
- return parseJson(response);
75773
+ const machines = [];
75774
+ for (let offset = 0; ; offset += PAGE_LIMIT) {
75775
+ const { pairings, total } = await ask(opts, (keep) => keep.get("/api/pairings", {
75776
+ query: { limit: PAGE_LIMIT, offset },
75777
+ headers: { Authorization: `Bearer ${opts.accessToken}` }
75778
+ }));
75779
+ machines.push(...pairings);
75780
+ if (pairings.length < PAGE_LIMIT || offset + pairings.length >= total)
75781
+ return machines;
75782
+ }
75772
75783
  }
75773
75784
  async function revokePairing(opts) {
75774
- const fetcher = opts.fetcher ?? globalThis.fetch;
75775
- const url = `${opts.brand.keepRegistration.keepApiUrl.replace(/\/$/, "")}/api/service-identities/${encodeURIComponent(opts.identityId)}`;
75776
- const response = await fetcher(url, {
75777
- method: "DELETE",
75785
+ await ask(opts, (keep) => keep.delete("/api/pairings/{pairingId}", {
75786
+ path: { pairingId: opts.pairingId },
75778
75787
  headers: { Authorization: `Bearer ${opts.accessToken}` }
75779
- });
75780
- if (!response.ok) {
75781
- throw new Error(`Revoke failed with status ${response.status}`);
75782
- }
75788
+ }));
75783
75789
  }
75784
- async function parseJson(response) {
75785
- let body2;
75786
- try {
75787
- body2 = await response.json();
75788
- } catch {
75789
- throw new Error(`Keep returned non-JSON (status ${response.status}).`);
75790
- }
75791
- if (!response.ok) {
75792
- const err2 = body2;
75793
- throw new Error(err2.error?.message ?? `Keep returned status ${response.status}.`);
75790
+ async function ask(opts, send) {
75791
+ const fetcher = opts.fetcher ?? globalThis.fetch;
75792
+ let belowFloor;
75793
+ const transport = async (url, init2) => {
75794
+ const response = await fetcher(url, init2);
75795
+ try {
75796
+ (0, version_check_1.checkResponseMinVersion)({
75797
+ response,
75798
+ productId: opts.brand.product.id,
75799
+ currentVersion: opts.brand.product.version,
75800
+ packageName: opts.brand.product.packageName
75801
+ });
75802
+ } catch (err2) {
75803
+ belowFloor = err2;
75804
+ throw err2;
75805
+ }
75806
+ return response;
75807
+ };
75808
+ const result = await send((0, keep_1.createClient)({ baseUrl: opts.keepApiUrl.replace(/\/$/, ""), transport }));
75809
+ if (belowFloor !== void 0)
75810
+ throw belowFloor;
75811
+ if (result.success)
75812
+ return result.data;
75813
+ if (result.error.status === 401) {
75814
+ throw new keep_2.AgentCredentialRevokedError(`Keep refused this machine's token (401): ${result.error.message}`);
75794
75815
  }
75795
- return body2.data;
75816
+ throw new Error(result.error.message || `Keep answered ${result.error.status ?? "nothing"}.`);
75796
75817
  }
75797
75818
  }
75798
75819
  });
@@ -75803,42 +75824,75 @@ var require_list = __commonJS({
75803
75824
  "use strict";
75804
75825
  Object.defineProperty(exports2, "__esModule", { value: true });
75805
75826
  exports2.buildListCommand = buildListCommand;
75827
+ exports2.mintAgentToken = mintAgentToken;
75828
+ exports2.renderPairings = renderPairings;
75806
75829
  var commander_1 = require_commander();
75807
75830
  var keep_1 = require_dist4();
75808
75831
  var apply_1 = require_apply();
75809
75832
  var store_1 = require_store();
75810
75833
  var pairings_1 = require_pairings();
75834
+ var version_check_1 = require_version_check();
75835
+ var typed_exit_1 = require_typed_exit();
75811
75836
  function buildListCommand(brand) {
75812
- return new commander_1.Command("list").description(`List paired ${brand.product.title} machines`).option("-p, --profile <name>", 'Config profile whose pairing credentials to use (default "default")').action(async (options) => {
75837
+ return new commander_1.Command("list").description(`List the machines paired to your account as a ${brand.product.title}`).option("-p, --profile <name>", 'Config profile whose pairing credentials to use (default "default")').option("--json", "Print the pairings as JSON and nothing else").action(async (options) => {
75813
75838
  try {
75814
75839
  const paths = (0, apply_1.resolvePaths)(brand, (0, apply_1.resolveProfile)(brand, options.profile));
75815
- const store = new store_1.PairingConfigStore(paths.configFile);
75816
- const pairing = store.read();
75817
- const tokens = new keep_1.ServiceTokenManager({
75840
+ const pairing = new store_1.PairingConfigStore(paths.configFile).read();
75841
+ const machines = await (0, pairings_1.listPairings)({
75842
+ brand,
75818
75843
  keepApiUrl: pairing.keepApiUrl,
75819
- clientId: pairing.credentials.clientId,
75820
- clientSecret: pairing.credentials.clientSecret
75844
+ accessToken: await mintAgentToken(pairing, brand)
75821
75845
  });
75822
- const accessToken = await tokens.getToken();
75823
- if (!accessToken) {
75824
- throw new Error("Failed to mint service token from stored credentials.");
75825
- }
75826
- const machines = await (0, pairings_1.listPairings)({ brand, accessToken });
75827
- if (machines.length === 0) {
75828
- console.log("No paired machines.");
75846
+ if (options.json) {
75847
+ console.log(JSON.stringify(machines, null, 2));
75829
75848
  return;
75830
75849
  }
75831
- console.log(["id", "name", "hostname", "pairedAt", "lastSeenAt", "active"].join(" "));
75832
- for (const m of machines) {
75833
- console.log([m.id, m.name, m.hostname ?? "-", m.pairedAt ?? "-", m.lastSeenAt ?? "-", m.active].join(" "));
75834
- }
75850
+ for (const line of renderPairings(machines, pairing.serviceIdentity.id))
75851
+ console.log(line);
75835
75852
  } catch (err2) {
75853
+ if ((0, typed_exit_1.isTypedExit)(err2))
75854
+ throw err2;
75836
75855
  const message = err2 instanceof Error ? err2.message : String(err2);
75837
75856
  console.error(`list failed: ${message}`);
75838
75857
  process.exit(1);
75839
75858
  }
75840
75859
  });
75841
75860
  }
75861
+ async function mintAgentToken(pairing, brand) {
75862
+ const token = await new keep_1.AgentTokenManager({
75863
+ keepApiUrl: pairing.keepApiUrl,
75864
+ clientId: pairing.credentials.clientId,
75865
+ clientSecret: pairing.credentials.clientSecret,
75866
+ onResponse: (response) => (0, version_check_1.checkResponseMinVersion)({
75867
+ response,
75868
+ productId: brand.product.id,
75869
+ currentVersion: brand.product.version,
75870
+ packageName: brand.product.packageName
75871
+ })
75872
+ }).getToken();
75873
+ if (!token)
75874
+ throw new Error("This pairing holds no credentials to mint a token with. Run `init` again.");
75875
+ return token;
75876
+ }
75877
+ function renderPairings(machines, self2) {
75878
+ if (machines.length === 0)
75879
+ return ["No paired machines."];
75880
+ const header = ["", "ID", "NAME", "HOST", "PAIRED", "LAST USED"];
75881
+ const rows = machines.map((m) => [
75882
+ m.id === self2 ? "*" : "",
75883
+ m.id,
75884
+ m.displayName,
75885
+ m.hostname ?? "-",
75886
+ m.pairedAt.slice(0, 10),
75887
+ m.lastUsedAt ? m.lastUsedAt.slice(0, 16).replace("T", " ") : "never"
75888
+ ]);
75889
+ const widths = header.map((h, i2) => Math.max(h.length, ...rows.map((r) => r[i2].length)));
75890
+ const line = (cells) => cells.map((cell, i2) => cell.padEnd(widths[i2])).join(" ").trimEnd();
75891
+ const lines = [line(header), ...rows.map(line)];
75892
+ if (machines.some((m) => m.id === self2))
75893
+ lines.push("", "* this machine");
75894
+ return lines;
75895
+ }
75842
75896
  }
75843
75897
  });
75844
75898
 
@@ -75848,43 +75902,51 @@ var require_revoke = __commonJS({
75848
75902
  "use strict";
75849
75903
  Object.defineProperty(exports2, "__esModule", { value: true });
75850
75904
  exports2.buildRevokeCommand = buildRevokeCommand;
75905
+ exports2.pickMachine = pickMachine;
75851
75906
  var commander_1 = require_commander();
75852
- var keep_1 = require_dist4();
75853
75907
  var apply_1 = require_apply();
75854
75908
  var store_1 = require_store();
75855
75909
  var pairings_1 = require_pairings();
75910
+ var list_1 = require_list();
75911
+ var typed_exit_1 = require_typed_exit();
75856
75912
  function buildRevokeCommand(brand) {
75857
- return new commander_1.Command("revoke").description(`Revoke a paired ${brand.product.title}`).argument("<nameOrId>", "Machine name or service-identity id to revoke").option("-p, --profile <name>", 'Config profile whose pairing credentials to use (default "default")').action(async (nameOrId, options) => {
75913
+ return new commander_1.Command("revoke").description(`Revoke a machine paired to your account as a ${brand.product.title} (this one included)`).argument("<machine>", "The machine to revoke: its id, the start of its id, its name, or its host, as `list` shows them").option("-p, --profile <name>", 'Config profile whose pairing credentials to use (default "default")').action(async (machine, options) => {
75858
75914
  try {
75859
75915
  const paths = (0, apply_1.resolvePaths)(brand, (0, apply_1.resolveProfile)(brand, options.profile));
75860
75916
  const store = new store_1.PairingConfigStore(paths.configFile);
75861
75917
  const pairing = store.read();
75862
- const tokens = new keep_1.ServiceTokenManager({
75863
- keepApiUrl: pairing.keepApiUrl,
75864
- clientId: pairing.credentials.clientId,
75865
- clientSecret: pairing.credentials.clientSecret
75866
- });
75867
- const accessToken = await tokens.getToken();
75868
- if (!accessToken) {
75869
- throw new Error("Failed to mint service token from stored credentials.");
75870
- }
75871
- const machines = await (0, pairings_1.listPairings)({ brand, accessToken });
75872
- const target = machines.find((m) => m.id === nameOrId || m.name === nameOrId || m.displayName === nameOrId);
75873
- if (!target) {
75874
- throw new Error(`No paired machine matches "${nameOrId}".`);
75875
- }
75918
+ const api = { brand, keepApiUrl: pairing.keepApiUrl, accessToken: await (0, list_1.mintAgentToken)(pairing, brand) };
75919
+ const target = pickMachine(await (0, pairings_1.listPairings)(api), machine);
75920
+ await (0, pairings_1.revokePairing)({ ...api, pairingId: target.id });
75876
75921
  if (target.id === pairing.serviceIdentity.id) {
75877
- throw new Error("Refusing to revoke the currently-paired identity. Run `init` on a different machine and revoke from there.");
75922
+ await store.remove();
75923
+ console.log(`Revoked this machine, "${target.displayName}" (${target.id}).`);
75924
+ console.log(`Its pairing is removed from ${paths.configFile}; run \`${brand.product.cliBinary} init\` to pair it again.`);
75925
+ return;
75878
75926
  }
75879
- await (0, pairings_1.revokePairing)({ brand, accessToken, identityId: target.id });
75880
- console.log(`Revoked ${target.name} (${target.id}).`);
75927
+ console.log(`Revoked "${target.displayName}" (${target.id}). It can take no more work.`);
75881
75928
  } catch (err2) {
75929
+ if ((0, typed_exit_1.isTypedExit)(err2))
75930
+ throw err2;
75882
75931
  const message = err2 instanceof Error ? err2.message : String(err2);
75883
75932
  console.error(`revoke failed: ${message}`);
75884
75933
  process.exit(1);
75885
75934
  }
75886
75935
  });
75887
75936
  }
75937
+ function pickMachine(machines, query) {
75938
+ const exact = machines.find((m) => m.id === query);
75939
+ if (exact)
75940
+ return exact;
75941
+ const candidates = machines.filter((m) => m.id.startsWith(query) || m.name === query || m.displayName === query || m.hostname === query);
75942
+ if (candidates.length === 1)
75943
+ return candidates[0];
75944
+ if (candidates.length === 0) {
75945
+ throw new Error(`No paired machine matches "${query}". Run \`list\` to see them.`);
75946
+ }
75947
+ const named = candidates.map((m) => ` ${m.id} ${m.displayName}${m.hostname ? ` (${m.hostname})` : ""}`);
75948
+ throw new Error([`"${query}" matches ${candidates.length} machines; name one by its id:`, ...named].join("\n"));
75949
+ }
75888
75950
  }
75889
75951
  });
75890
75952
 
@@ -76390,7 +76452,7 @@ var import_shuttle2 = __toESM(require_dist8());
76390
76452
 
76391
76453
  // src/brand.ts
76392
76454
  var import_shuttle = __toESM(require_dist8());
76393
- var buildVersion = true ? "0.1.9" : pkg.version;
76455
+ var buildVersion = true ? "0.1.10" : pkg.version;
76394
76456
  var EXECUTORS = ["claude-code"];
76395
76457
  var trivetBrand = {
76396
76458
  product: {