@countersign-hub/cli 0.1.0 → 0.1.2

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/bin.js CHANGED
@@ -20231,6 +20231,7 @@ var init__esm = __esm(() => {
20231
20231
  init_createPublicClient();
20232
20232
  init_createWalletClient();
20233
20233
  init_http3();
20234
+ init_abis();
20234
20235
  init_base();
20235
20236
  init_contract();
20236
20237
  init_decodeFunctionResult();
@@ -20247,6 +20248,7 @@ var init__esm = __esm(() => {
20247
20248
  init_parseSignature();
20248
20249
  init_recoverMessageAddress();
20249
20250
  init_recoverTypedDataAddress();
20251
+ init_formatUnits();
20250
20252
  });
20251
20253
 
20252
20254
  // packages/core/src/canonical.ts
@@ -30237,7 +30239,9 @@ var init_known_escrows = __esm(() => {
30237
30239
  KNOWN_ESCROWS = [
30238
30240
  { chainId: 5042002, escrow: "0x19c27d770969291e6a0a305dc1be1a845e7c1b76", name: "Arc testnet, deployed through Arc Studio" },
30239
30241
  { chainId: 5042002, escrow: "0x44d11b8909485e3f575F6628b689383E4420f54a", name: "Arc testnet, forge deployment" },
30240
- { chainId: 1874, escrow: "0x9279D3edeef4c3738a0b200D762e2985Ff3B7eb4", name: "Whitechain Sepolia" }
30242
+ { chainId: 5042002, escrow: "0x10A3c07cc6A519C0106e315D2C42010eCbEFF3CE", name: "Arc testnet, escrow v2" },
30243
+ { chainId: 1874, escrow: "0x9279D3edeef4c3738a0b200D762e2985Ff3B7eb4", name: "Whitechain Sepolia" },
30244
+ { chainId: 1874, escrow: "0x55b3d98C17Ba0878DC93fB1521c76c9a10778391", name: "Whitechain Sepolia, escrow v2" }
30241
30245
  ];
30242
30246
  });
30243
30247
 
@@ -30698,6 +30702,7 @@ var init_privateKeyToAccount = __esm(() => {
30698
30702
  // node_modules/viem/_esm/accounts/index.js
30699
30703
  var init_accounts = __esm(() => {
30700
30704
  init_privateKeyToAccount();
30705
+ init_toAccount();
30701
30706
  });
30702
30707
 
30703
30708
  // node_modules/zod/v3/helpers/util.js
@@ -59072,6 +59077,28 @@ var init_known_rails = __esm(() => {
59072
59077
  tokenName: "Tether USD",
59073
59078
  tokenVersion: "1",
59074
59079
  rpcUrl: "https://rpc.testnet.whitechain.io"
59080
+ },
59081
+ {
59082
+ network: "eip155:5042002",
59083
+ scheme: "countersign",
59084
+ chainId: 5042002,
59085
+ escrow: "0x10A3c07cc6A519C0106e315D2C42010eCbEFF3CE",
59086
+ token: "0x3600000000000000000000000000000000000000",
59087
+ tokenName: "USDC",
59088
+ tokenVersion: "2",
59089
+ rpcUrl: "https://rpc.testnet.arc.network",
59090
+ version: 2
59091
+ },
59092
+ {
59093
+ network: "eip155:1874",
59094
+ scheme: "countersign",
59095
+ chainId: 1874,
59096
+ escrow: "0x55b3d98C17Ba0878DC93fB1521c76c9a10778391",
59097
+ token: "0xcedfB56113143d55b2358b8CFbB0dEB5Ad2f55a7",
59098
+ tokenName: "Tether USD",
59099
+ tokenVersion: "1",
59100
+ rpcUrl: "https://rpc.testnet.whitechain.io",
59101
+ version: 2
59075
59102
  }
59076
59103
  ];
59077
59104
  KNOWN_DIRECTS = [
@@ -59642,7 +59669,7 @@ var init_hub_client = __esm(() => {
59642
59669
  });
59643
59670
 
59644
59671
  // packages/buyer/src/journal.ts
59645
- import { appendFileSync, closeSync, existsSync as existsSync3, mkdirSync as mkdirSync2, openSync, readFileSync as readFileSync4, unlinkSync, writeSync } from "node:fs";
59672
+ import { appendFileSync, closeSync as closeSync2, existsSync as existsSync4, mkdirSync as mkdirSync3, openSync as openSync2, readFileSync as readFileSync5, unlinkSync, writeSync as writeSync2 } from "node:fs";
59646
59673
  import { dirname as dirname2 } from "node:path";
59647
59674
 
59648
59675
  class Journal {
@@ -59654,14 +59681,14 @@ class Journal {
59654
59681
  return `${this.path}.lock`;
59655
59682
  }
59656
59683
  append(entry) {
59657
- mkdirSync2(dirname2(this.path), { recursive: true });
59684
+ mkdirSync3(dirname2(this.path), { recursive: true });
59658
59685
  appendFileSync(this.path, `${JSON.stringify(entry)}
59659
59686
  `);
59660
59687
  }
59661
59688
  entries() {
59662
- if (!existsSync3(this.path))
59689
+ if (!existsSync4(this.path))
59663
59690
  return [];
59664
- return readFileSync4(this.path, "utf8").split(`
59691
+ return readFileSync5(this.path, "utf8").split(`
59665
59692
  `).filter((l) => l.trim() !== "").map((l) => JSON.parse(l));
59666
59693
  }
59667
59694
  reserve(entry, decide) {
@@ -59672,19 +59699,19 @@ class Journal {
59672
59699
  this.append({ ...entry, kind: "countersigned" });
59673
59700
  return decision;
59674
59701
  } finally {
59675
- closeSync(fd);
59702
+ closeSync2(fd);
59676
59703
  try {
59677
59704
  unlinkSync(this.lockPath);
59678
59705
  } catch {}
59679
59706
  }
59680
59707
  }
59681
59708
  acquire() {
59682
- mkdirSync2(dirname2(this.path), { recursive: true });
59709
+ mkdirSync3(dirname2(this.path), { recursive: true });
59683
59710
  const deadline = Date.now() + 5000;
59684
59711
  for (;; ) {
59685
59712
  try {
59686
- const fd = openSync(this.lockPath, "wx");
59687
- writeSync(fd, String(process.pid));
59713
+ const fd = openSync2(this.lockPath, "wx");
59714
+ writeSync2(fd, String(process.pid));
59688
59715
  return fd;
59689
59716
  } catch (e) {
59690
59717
  if (e.code !== "EEXIST")
@@ -59704,7 +59731,7 @@ class Journal {
59704
59731
  ownerIsGone() {
59705
59732
  let pid;
59706
59733
  try {
59707
- pid = Number(readFileSync4(this.lockPath, "utf8").trim());
59734
+ pid = Number(readFileSync5(this.lockPath, "utf8").trim());
59708
59735
  } catch {
59709
59736
  return false;
59710
59737
  }
@@ -61269,6 +61296,175 @@ var init_payer2 = __esm(() => {
61269
61296
  init_known_rails();
61270
61297
  });
61271
61298
 
61299
+ // packages/buyer/src/price-label.ts
61300
+ function railToken(rail2) {
61301
+ return RAIL_TOKENS[rail2] ?? "USDC";
61302
+ }
61303
+ function priceLabel(price, rails) {
61304
+ const tokens = [...new Set(rails.map(railToken))];
61305
+ return `${formatUnits3(BigInt(price), 6)} ${(tokens.length > 0 ? tokens : ["USDC"]).join("/")}`;
61306
+ }
61307
+ var RAIL_TOKENS;
61308
+ var init_price_label = __esm(() => {
61309
+ init__esm();
61310
+ RAIL_TOKENS = { whitechain: "USDT" };
61311
+ });
61312
+
61313
+ // packages/buyer/src/remote-signer.ts
61314
+ function assertHubOrigin(hub) {
61315
+ let url2;
61316
+ try {
61317
+ url2 = new URL(hub);
61318
+ } catch {
61319
+ throw new Error(HTTPS_ONLY);
61320
+ }
61321
+ if (url2.protocol === "https:")
61322
+ return;
61323
+ if (url2.protocol === "http:" && LOCAL_HOSTS.has(url2.hostname))
61324
+ return;
61325
+ throw new Error(HTTPS_ONLY);
61326
+ }
61327
+ function decimalStrings(value) {
61328
+ if (typeof value === "bigint")
61329
+ return value.toString(10);
61330
+ if (Array.isArray(value))
61331
+ return value.map((entry) => decimalStrings(entry));
61332
+ if (value !== null && typeof value === "object") {
61333
+ const copy = {};
61334
+ for (const [key, entry] of Object.entries(value))
61335
+ copy[key] = decimalStrings(entry);
61336
+ return copy;
61337
+ }
61338
+ return value;
61339
+ }
61340
+ function messageOf4(body) {
61341
+ if (body === null || typeof body !== "object")
61342
+ return;
61343
+ const message = body.message;
61344
+ return typeof message === "string" ? message : undefined;
61345
+ }
61346
+ function remoteAccount(session, opts = {}) {
61347
+ assertHubOrigin(session.hub);
61348
+ const fetchImpl = opts.fetch ?? fetch;
61349
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
61350
+ const signTypedData4 = async (parameters) => {
61351
+ const typedData = parameters;
61352
+ const types3 = {};
61353
+ for (const [name, fields] of Object.entries(typedData.types)) {
61354
+ if (name !== "EIP712Domain")
61355
+ types3[name] = fields;
61356
+ }
61357
+ const requestBody = JSON.stringify({
61358
+ kind: "typed-data",
61359
+ typedData: {
61360
+ domain: decimalStrings(typedData.domain),
61361
+ types: decimalStrings(types3),
61362
+ primaryType: typedData.primaryType,
61363
+ message: decimalStrings(typedData.message)
61364
+ }
61365
+ });
61366
+ const controller = new AbortController;
61367
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
61368
+ let status;
61369
+ let body;
61370
+ try {
61371
+ const response = await fetchImpl(`${session.hub.replace(/\/+$/, "")}/cli/sign`, {
61372
+ method: "POST",
61373
+ headers: {
61374
+ authorization: `Bearer ${session.token}`,
61375
+ "content-type": "application/json"
61376
+ },
61377
+ body: requestBody,
61378
+ signal: controller.signal
61379
+ });
61380
+ status = response.status;
61381
+ body = await response.json().catch(() => {
61382
+ return;
61383
+ });
61384
+ } catch {
61385
+ throw new Error(NOT_REACHED);
61386
+ } finally {
61387
+ clearTimeout(timer);
61388
+ }
61389
+ if (status === 200) {
61390
+ const result = body;
61391
+ if (typeof result?.address !== "string" || result.address.toLowerCase() !== session.address.toLowerCase()) {
61392
+ throw new Error("The hub signed with another wallet.");
61393
+ }
61394
+ const signature = result.signature;
61395
+ if (typeof signature !== "string" || !SIGNATURE2.test(signature)) {
61396
+ throw new Error(`The hub could not sign (${status}).`);
61397
+ }
61398
+ const signer = await recoverTypedDataAddress2({
61399
+ ...parameters,
61400
+ signature
61401
+ });
61402
+ if (signer.toLowerCase() !== session.address.toLowerCase()) {
61403
+ throw new Error("The hub signed with another wallet.");
61404
+ }
61405
+ return signature;
61406
+ }
61407
+ if (status === 401)
61408
+ throw new Error(NOT_SIGNED_IN3);
61409
+ if (status === 403) {
61410
+ const message = messageOf4(body) ?? "the hub refused the request.";
61411
+ throw new Error(`Refused by the hub: ${message}`);
61412
+ }
61413
+ if (status === 502)
61414
+ throw new Error("The hub could not sign (privy-failed).");
61415
+ throw new Error(`The hub could not sign (${status}).`);
61416
+ };
61417
+ const unsupported = async () => {
61418
+ throw new Error(TYPED_DATA_ONLY);
61419
+ };
61420
+ const source = {
61421
+ address: session.address,
61422
+ signMessage: unsupported,
61423
+ signTransaction: unsupported,
61424
+ signTypedData: signTypedData4
61425
+ };
61426
+ return toAccount(source);
61427
+ }
61428
+ async function sessionFromEnv(env = process.env, opts = {}) {
61429
+ const token = env.CS_SESSION;
61430
+ if (token === undefined || token === "")
61431
+ return;
61432
+ const hub = env.CS_HUB ?? DEFAULT_HUB;
61433
+ delete env.CS_SESSION;
61434
+ assertHubOrigin(hub);
61435
+ const fetchImpl = opts.fetch ?? fetch;
61436
+ let status;
61437
+ let body;
61438
+ try {
61439
+ const response = await fetchImpl(`${hub.replace(/\/+$/, "")}/cli/session`, {
61440
+ method: "GET",
61441
+ headers: { authorization: `Bearer ${token}` }
61442
+ });
61443
+ status = response.status;
61444
+ body = await response.json().catch(() => {
61445
+ return;
61446
+ });
61447
+ } catch {
61448
+ throw new Error("Could not read the countersign session (no response).");
61449
+ }
61450
+ if (status === 401)
61451
+ return;
61452
+ if (status !== 200)
61453
+ throw new Error(`Could not read the countersign session (${status}).`);
61454
+ const address = body?.address;
61455
+ if (typeof address !== "string" || !isAddress(address)) {
61456
+ throw new Error("The hub returned no valid wallet address.");
61457
+ }
61458
+ return { hub, token, address };
61459
+ }
61460
+ var NOT_SIGNED_IN3 = "Not signed in. Run: npx -y @countersign-hub/cli login", SIGNATURE2, DEFAULT_TIMEOUT_MS = 30000, DEFAULT_HUB = "https://hub-production-e85e.up.railway.app", NOT_REACHED = "The hub could not be reached to sign.", TYPED_DATA_ONLY = "This wallet signs only through the hub's typed-data route.", HTTPS_ONLY = "Refused: the countersign hub must be https.", LOCAL_HOSTS;
61461
+ var init_remote_signer = __esm(() => {
61462
+ init__esm();
61463
+ init_accounts();
61464
+ SIGNATURE2 = /^0x[0-9a-fA-F]{130}$/;
61465
+ LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
61466
+ });
61467
+
61272
61468
  // packages/buyer/src/resume.ts
61273
61469
  async function readJobStatus(rail2, jobId) {
61274
61470
  const client = createPublicClient({ transport: http(rail2.rpcUrl) });
@@ -61406,27 +61602,27 @@ async function acceptJob(a) {
61406
61602
  const slip2 = heldSlip(a, jobId);
61407
61603
  const known = a.known ?? KNOWN_RAILS;
61408
61604
  if (slip2.hash.toLowerCase() !== slipHash(slip2.body).toLowerCase())
61409
- refuse("The slip's hash does not match its body.");
61605
+ refuse2("The slip's hash does not match its body.");
61410
61606
  let seller;
61411
61607
  try {
61412
61608
  seller = await slipSigner(slip2);
61413
61609
  } catch {
61414
- refuse("The slip's seller signature does not parse.");
61610
+ refuse2("The slip's seller signature does not parse.");
61415
61611
  }
61416
61612
  if (getAddress(seller) !== getAddress(slip2.body.seller))
61417
- refuse("The slip is not signed by its seller.");
61613
+ refuse2("The slip is not signed by its seller.");
61418
61614
  if (slip2.body.jobId.toLowerCase() !== jobId)
61419
- refuse("The slip is for another job.");
61615
+ refuse2("The slip is for another job.");
61420
61616
  if (getAddress(slip2.body.buyer) !== getAddress(a.signer.address))
61421
- refuse("The slip names another buyer.");
61617
+ refuse2("The slip names another buyer.");
61422
61618
  if (!slip2.body.escrow || slip2.body.chainId === undefined)
61423
- refuse("The slip is not for an escrow job.");
61619
+ refuse2("The slip is not for an escrow job.");
61424
61620
  const rail2 = pinned(known, slip2.body.chainId, slip2.body.escrow);
61425
61621
  if (!rail2)
61426
- refuse("The slip's escrow is not one this agent pins.");
61622
+ refuse2("The slip's escrow is not one this agent pins.");
61427
61623
  const terms = slip2.payment?.payload?.terms;
61428
61624
  if (!terms || jobIdOf(terms, rail2.chainId, rail2.escrow).toLowerCase() !== jobId) {
61429
- refuse("The slip's countersigned terms do not derive this job on that escrow.");
61625
+ refuse2("The slip's countersigned terms do not derive this job on that escrow.");
61430
61626
  }
61431
61627
  const signature = await a.signer.signTypedData(acceptTypedData(rail2.chainId, rail2.escrow, jobId, slip2.hash));
61432
61628
  await a.hub.acceptJob(jobId, signature);
@@ -61438,7 +61634,7 @@ async function disputeJob(a) {
61438
61634
  const known = a.known ?? KNOWN_RAILS;
61439
61635
  const entries = entriesFor(a.journal, jobId);
61440
61636
  if (entries.length === 0)
61441
- refuse("This agent's journal has no such job.");
61637
+ refuse2("This agent's journal has no such job.");
61442
61638
  const { rail: rail2, status } = await fundedOn(a, jobId, entries, known);
61443
61639
  const signature = await a.signer.signTypedData(disputeTypedData(rail2.chainId, rail2.escrow, jobId, status));
61444
61640
  let transaction;
@@ -61465,7 +61661,7 @@ async function disputeJob(a) {
61465
61661
  async function submitDispute(a, rail2, jobId, status) {
61466
61662
  const account = a.account;
61467
61663
  if (!account)
61468
- refuse("The agent key is not available to send the dispute itself.");
61664
+ refuse2("The agent key is not available to send the dispute itself.");
61469
61665
  return sendDispute(rail2, jobId, status, account, a.transport);
61470
61666
  }
61471
61667
  async function sendDispute(rail2, jobId, status, account, transport) {
@@ -61497,7 +61693,7 @@ async function fundedOn(a, jobId, entries, known) {
61497
61693
  if (recorded) {
61498
61694
  const rail2 = pinned(known, recorded.chainId, recorded.escrow);
61499
61695
  if (!rail2)
61500
- refuse("The job's escrow is not one this agent pins.");
61696
+ refuse2("The job's escrow is not one this agent pins.");
61501
61697
  return { rail: rail2, status: await disputeStatus(a, rail2, jobId, entries) };
61502
61698
  }
61503
61699
  const network = entries.find((e) => e.network)?.network;
@@ -61513,7 +61709,7 @@ async function fundedOn(a, jobId, entries, known) {
61513
61709
  } catch {}
61514
61710
  }
61515
61711
  if (holding.length !== 1)
61516
- refuse("No single pinned escrow holds this job open for this agent.");
61712
+ refuse2("No single pinned escrow holds this job open for this agent.");
61517
61713
  return holding[0];
61518
61714
  }
61519
61715
  async function disputeStatus(a, rail2, jobId, entries) {
@@ -61529,11 +61725,11 @@ async function disputeStatus(a, rail2, jobId, entries) {
61529
61725
  function heldSlip(a, jobId) {
61530
61726
  const entry = [...entriesFor(a.journal, jobId)].reverse().find((e) => e.slipHash);
61531
61727
  if (!entry?.slipHash)
61532
- refuse("This agent holds no slip for this job.");
61728
+ refuse2("This agent holds no slip for this job.");
61533
61729
  try {
61534
61730
  return a.state.loadSlip(entry.slipHash);
61535
61731
  } catch {
61536
- refuse("This agent holds no slip for this job.");
61732
+ refuse2("This agent holds no slip for this job.");
61537
61733
  }
61538
61734
  }
61539
61735
  function entriesFor(journal, jobId) {
@@ -61554,7 +61750,7 @@ function record3(a, jobId, entries) {
61554
61750
  function pinned(known, chainId, escrow2) {
61555
61751
  return known.find((rail2) => rail2.chainId === chainId && rail2.escrow.toLowerCase() === escrow2.toLowerCase());
61556
61752
  }
61557
- function refuse(message) {
61753
+ function refuse2(message) {
61558
61754
  throw new ReviewError(`Refused. ${message}`);
61559
61755
  }
61560
61756
  var ReviewError, DISPUTE_ABI, DISPUTABLE;
@@ -61584,65 +61780,65 @@ var init_review = __esm(() => {
61584
61780
 
61585
61781
  // packages/buyer/src/state.ts
61586
61782
  import {
61587
- chmodSync,
61588
- closeSync as closeSync2,
61589
- existsSync as existsSync4,
61590
- mkdirSync as mkdirSync3,
61591
- openSync as openSync2,
61592
- readFileSync as readFileSync5,
61783
+ chmodSync as chmodSync2,
61784
+ closeSync as closeSync3,
61785
+ existsSync as existsSync5,
61786
+ mkdirSync as mkdirSync4,
61787
+ openSync as openSync3,
61788
+ readFileSync as readFileSync6,
61593
61789
  unlinkSync as unlinkSync2,
61594
61790
  writeFileSync as writeFileSync2,
61595
- writeSync as writeSync2
61791
+ writeSync as writeSync3
61596
61792
  } from "node:fs";
61597
- import { homedir as homedir2 } from "node:os";
61598
- import { join as join3 } from "node:path";
61793
+ import { homedir as homedir3 } from "node:os";
61794
+ import { join as join4 } from "node:path";
61599
61795
 
61600
61796
  class State {
61601
61797
  dir;
61602
61798
  constructor(dir) {
61603
- this.dir = dir ?? join3(homedir2(), ".countersign");
61799
+ this.dir = dir ?? join4(homedir3(), ".countersign");
61604
61800
  }
61605
61801
  get envelopePath() {
61606
- return join3(this.dir, "envelope.json");
61802
+ return join4(this.dir, "envelope.json");
61607
61803
  }
61608
61804
  get journalPath() {
61609
- return join3(this.dir, "journal.jsonl");
61805
+ return join4(this.dir, "journal.jsonl");
61610
61806
  }
61611
61807
  get railsPath() {
61612
- return join3(this.dir, "rails.json");
61808
+ return join4(this.dir, "rails.json");
61613
61809
  }
61614
61810
  get consumedApprovalsPath() {
61615
- return join3(this.dir, "approvals-consumed.json");
61811
+ return join4(this.dir, "approvals-consumed.json");
61616
61812
  }
61617
61813
  slipPath(hash4) {
61618
- return join3(this.dir, "slips", `${hash4}.json`);
61814
+ return join4(this.dir, "slips", `${hash4}.json`);
61619
61815
  }
61620
61816
  loadEnvelope() {
61621
- if (!existsSync4(this.envelopePath)) {
61817
+ if (!existsSync5(this.envelopePath)) {
61622
61818
  throw new Error(`No envelope at ${this.envelopePath}. Create one with: countersign envelope set --cap <usd> --per-merchant <usd>`);
61623
61819
  }
61624
- return JSON.parse(readFileSync5(this.envelopePath, "utf8"));
61820
+ return JSON.parse(readFileSync6(this.envelopePath, "utf8"));
61625
61821
  }
61626
61822
  saveEnvelope(env) {
61627
- mkdirSync3(this.dir, { recursive: true });
61823
+ mkdirSync4(this.dir, { recursive: true });
61628
61824
  writeFileSync2(this.envelopePath, `${JSON.stringify(env, null, 2)}
61629
61825
  `);
61630
61826
  }
61631
61827
  loadRails() {
61632
- if (!existsSync4(this.railsPath))
61828
+ if (!existsSync5(this.railsPath))
61633
61829
  return [];
61634
- const value = JSON.parse(readFileSync5(this.railsPath, "utf8"));
61830
+ const value = JSON.parse(readFileSync6(this.railsPath, "utf8"));
61635
61831
  if (!Array.isArray(value))
61636
61832
  throw new Error(`${this.railsPath} must hold a JSON array of known rails.`);
61637
61833
  return value;
61638
61834
  }
61639
61835
  loadSlip(hash4) {
61640
- return JSON.parse(readFileSync5(this.slipPath(hash4), "utf8"));
61836
+ return JSON.parse(readFileSync6(this.slipPath(hash4), "utf8"));
61641
61837
  }
61642
61838
  loadConsumedApprovals() {
61643
- if (!existsSync4(this.consumedApprovalsPath))
61839
+ if (!existsSync5(this.consumedApprovalsPath))
61644
61840
  return [];
61645
- const value = JSON.parse(readFileSync5(this.consumedApprovalsPath, "utf8"));
61841
+ const value = JSON.parse(readFileSync6(this.consumedApprovalsPath, "utf8"));
61646
61842
  if (!Array.isArray(value)) {
61647
61843
  throw new Error(`${this.consumedApprovalsPath} must hold a JSON array of approval ids.`);
61648
61844
  }
@@ -61660,10 +61856,10 @@ class State {
61660
61856
  ids.push(id);
61661
61857
  writeFileSync2(this.consumedApprovalsPath, `${JSON.stringify(ids, null, 2)}
61662
61858
  `, { mode: 384 });
61663
- chmodSync(this.consumedApprovalsPath, 384);
61859
+ chmodSync2(this.consumedApprovalsPath, 384);
61664
61860
  return true;
61665
61861
  } finally {
61666
- closeSync2(fd);
61862
+ closeSync3(fd);
61667
61863
  try {
61668
61864
  unlinkSync2(this.consumedLockPath);
61669
61865
  } catch {}
@@ -61673,12 +61869,12 @@ class State {
61673
61869
  return `${this.consumedApprovalsPath}.lock`;
61674
61870
  }
61675
61871
  acquireConsumedLock() {
61676
- mkdirSync3(this.dir, { recursive: true });
61872
+ mkdirSync4(this.dir, { recursive: true });
61677
61873
  const deadline = Date.now() + 5000;
61678
61874
  for (;; ) {
61679
61875
  try {
61680
- const fd = openSync2(this.consumedLockPath, "wx");
61681
- writeSync2(fd, String(process.pid));
61876
+ const fd = openSync3(this.consumedLockPath, "wx");
61877
+ writeSync3(fd, String(process.pid));
61682
61878
  return fd;
61683
61879
  } catch (e) {
61684
61880
  if (e.code !== "EEXIST")
@@ -61699,7 +61895,7 @@ class State {
61699
61895
  consumedLockOwnerIsGone() {
61700
61896
  let pid;
61701
61897
  try {
61702
- pid = Number(readFileSync5(this.consumedLockPath, "utf8").trim());
61898
+ pid = Number(readFileSync6(this.consumedLockPath, "utf8").trim());
61703
61899
  } catch {
61704
61900
  return false;
61705
61901
  }
@@ -61713,7 +61909,7 @@ class State {
61713
61909
  }
61714
61910
  }
61715
61911
  saveSlip(slip2) {
61716
- mkdirSync3(join3(this.dir, "slips"), { recursive: true });
61912
+ mkdirSync4(join4(this.dir, "slips"), { recursive: true });
61717
61913
  writeFileSync2(this.slipPath(slip2.hash), `${JSON.stringify(slip2, null, 2)}
61718
61914
  `);
61719
61915
  }
@@ -61725,10 +61921,13 @@ var init_state = __esm(() => {
61725
61921
 
61726
61922
  // packages/buyer/src/cli.ts
61727
61923
  var exports_cli = {};
61924
+ __export(exports_cli, {
61925
+ agentAccount: () => agentAccount
61926
+ });
61728
61927
  import { randomBytes as randomBytes2 } from "node:crypto";
61729
- import { chmodSync as chmodSync2, mkdirSync as mkdirSync4, writeFileSync as writeFileSync3 } from "node:fs";
61730
- import { join as join4 } from "node:path";
61731
- function parse6(argv) {
61928
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "node:fs";
61929
+ import { join as join5 } from "node:path";
61930
+ function parse7(argv) {
61732
61931
  const pos = [];
61733
61932
  const flags = new Map;
61734
61933
  for (let i = 0;i < argv.length; i++) {
@@ -61750,14 +61949,21 @@ function parse6(argv) {
61750
61949
  }
61751
61950
  return { pos, flags };
61752
61951
  }
61753
- function agentAccount() {
61952
+ async function agentAccount() {
61953
+ cachedAccount ??= resolveAccount();
61954
+ return await cachedAccount;
61955
+ }
61956
+ async function resolveAccount() {
61754
61957
  const key = process.env.CS_BUYER_KEY;
61755
- if (!key)
61756
- throw new Error("Set CS_BUYER_KEY to the agent's private key.");
61757
- return privateKeyToAccount(key.startsWith("0x") ? key : `0x${key}`);
61958
+ if (key)
61959
+ return privateKeyToAccount(key.startsWith("0x") ? key : `0x${key}`);
61960
+ const session = await sessionFromEnv(process.env);
61961
+ if (session !== undefined)
61962
+ return remoteAccount(session);
61963
+ throw new Error(NOT_SIGNED_IN3);
61758
61964
  }
61759
61965
  async function main2() {
61760
- const { pos, flags } = parse6(process.argv.slice(2));
61966
+ const { pos, flags } = parse7(process.argv.slice(2));
61761
61967
  const one = (n) => flags.get(n)?.[0];
61762
61968
  let hub = new HubClient(one("hub") ?? "http://127.0.0.1:8787");
61763
61969
  const state = new State(one("state"));
@@ -61789,7 +61995,7 @@ async function main2() {
61789
61995
  console.log(`${page.items.length} of ${page.total} listed, ${payable} payable by this agent.`);
61790
61996
  } else if (cmd === "discover") {
61791
61997
  for (const l of await hub.listOffers()) {
61792
- console.log([l.offerHash.slice(0, 10), l.offer.title, `${formatUnits(BigInt(l.offer.price), 6)} USDC`, l.offer.rails.join(","), l.online ? "online" : "offline"].join(" "));
61998
+ console.log([l.offerHash.slice(0, 10), l.offer.title, priceLabel(l.offer.price, l.offer.rails), l.offer.rails.join(","), l.online ? "online" : "offline"].join(" "));
61793
61999
  }
61794
62000
  } else if (cmd === "buy") {
61795
62001
  const fromUrl = pos[1] !== undefined ? hubCallOf(pos[1]) : undefined;
@@ -61801,7 +62007,7 @@ async function main2() {
61801
62007
  throw new Error("Usage: buy <offerHash|callUrl> --input '<json>' [--approve] [--rail <id>] [--approvals hub]");
61802
62008
  const rail2 = parseRail(one("rail"));
61803
62009
  const approvals = parseApprovals(one("approvals"));
61804
- const account = agentAccount();
62010
+ const account = await agentAccount();
61805
62011
  const result = await buy({
61806
62012
  hub,
61807
62013
  offerHash: hash4,
@@ -61830,9 +62036,9 @@ async function main2() {
61830
62036
  try {
61831
62037
  env = state.loadEnvelope();
61832
62038
  } catch {
61833
- env = { v: 1, agent: agentAccount().address, cap: "0", perMerchant: "0", allowlist: [], revoked: false };
62039
+ env = { v: 1, agent: (await agentAccount()).address, cap: "0", perMerchant: "0", allowlist: [], revoked: false };
61834
62040
  }
61835
- env.agent = agentAccount().address;
62041
+ env.agent = (await agentAccount()).address;
61836
62042
  const cap = one("cap");
61837
62043
  if (cap !== undefined)
61838
62044
  env.cap = parseUnits(cap, 6).toString(10);
@@ -61854,7 +62060,7 @@ async function main2() {
61854
62060
  } else if ((cmd === "accept" || cmd === "dispute") && pos[1] !== undefined) {
61855
62061
  if (!/^0x[0-9a-fA-F]{64}$/.test(pos[1]))
61856
62062
  throw new Error(`Usage: ${cmd} <jobId>`);
61857
- const account = agentAccount();
62063
+ const account = await agentAccount();
61858
62064
  const args = { hub, journal, state, known, signer: account, jobId: pos[1], now: Math.floor(Date.now() / 1000) };
61859
62065
  if (cmd === "accept") {
61860
62066
  const done = await acceptJob(args);
@@ -61870,7 +62076,7 @@ async function main2() {
61870
62076
  for (const e of journal.entries())
61871
62077
  console.log(JSON.stringify(e));
61872
62078
  } else if (cmd === "publish-envelope") {
61873
- const account = agentAccount();
62079
+ const account = await agentAccount();
61874
62080
  const ok2 = await publishEnvelope({
61875
62081
  hub,
61876
62082
  account,
@@ -61880,12 +62086,12 @@ async function main2() {
61880
62086
  });
61881
62087
  console.log(ok2 ? "Envelope published." : "Envelope publish failed.");
61882
62088
  } else if (cmd === "openai") {
61883
- const account = agentAccount();
62089
+ const account = await agentAccount();
61884
62090
  const token = randomBytes2(32).toString("hex");
61885
- mkdirSync4(state.dir, { recursive: true });
61886
- const tokenPath = join4(state.dir, "openai.token");
62091
+ mkdirSync5(state.dir, { recursive: true });
62092
+ const tokenPath = join5(state.dir, "openai.token");
61887
62093
  writeFileSync3(tokenPath, token, { mode: 384 });
61888
- chmodSync2(tokenPath, 384);
62094
+ chmodSync3(tokenPath, 384);
61889
62095
  const server = startOpenAIServer({
61890
62096
  hub,
61891
62097
  account,
@@ -61900,7 +62106,7 @@ async function main2() {
61900
62106
  console.log(`${server.url}/v1`);
61901
62107
  console.log(tokenPath);
61902
62108
  } else if (cmd === "resume") {
61903
- const mirror = flags.has("hub") ? { account: agentAccount(), envelope: state.loadEnvelope() } : undefined;
62109
+ const mirror = flags.has("hub") ? { account: await agentAccount(), envelope: state.loadEnvelope() } : undefined;
61904
62110
  const rows = await resume({
61905
62111
  hub,
61906
62112
  journal,
@@ -61914,7 +62120,7 @@ async function main2() {
61914
62120
  console.log(`${r.jobId} ${r.status}${r.appended ? ` -> ${r.appended}` : ""}`);
61915
62121
  } else {
61916
62122
  console.log("Usage: cli.ts discover [--catalogue <url|circle> [query] [--category <c>] [--limit <n>]] | buy <offerHash|callUrl> --input '<json>' [--approve] [--rail <id>] [--approvals hub] | accept <jobId> | dispute <jobId> [--direct] | envelope show|set | publish-envelope | journal | resume | openai [--port <n>]");
61917
- console.log("Flags: --hub <url> --state <dir>. The agent key comes from CS_BUYER_KEY.");
62123
+ console.log("Flags: --hub <url> --state <dir>. Sign in once with: countersign login.");
61918
62124
  }
61919
62125
  }
61920
62126
  function parseApprovals(raw) {
@@ -61943,7 +62149,7 @@ function parseRail(raw) {
61943
62149
  throw new Error(`Unknown rail ${raw}.`);
61944
62150
  return raw;
61945
62151
  }
61946
- var BOOLEAN, usd2 = (atomic) => formatUnits(BigInt(atomic), 6);
62152
+ var BOOLEAN, cachedAccount, usd2 = (atomic) => formatUnits(BigInt(atomic), 6);
61947
62153
  var init_cli = __esm(() => {
61948
62154
  init_accounts();
61949
62155
  init_src();
@@ -61955,6 +62161,8 @@ var init_cli = __esm(() => {
61955
62161
  init_mirror();
61956
62162
  init_server();
61957
62163
  init_payer2();
62164
+ init_price_label();
62165
+ init_remote_signer();
61958
62166
  init_resume();
61959
62167
  init_review();
61960
62168
  init_state();
@@ -61967,7 +62175,7 @@ var init_cli = __esm(() => {
61967
62175
 
61968
62176
  // packages/verify/src/cli.ts
61969
62177
  var exports_cli2 = {};
61970
- import { readFileSync as readFileSync6 } from "node:fs";
62178
+ import { readFileSync as readFileSync7 } from "node:fs";
61971
62179
  var args, file2, rpcAt, rpcUrl, mirrorAt, mirrorUrl, slip2, verdict, verdictLine;
61972
62180
  var init_cli2 = __esm(async () => {
61973
62181
  init_verify();
@@ -61982,7 +62190,7 @@ var init_cli2 = __esm(async () => {
61982
62190
  process.exit(2);
61983
62191
  }
61984
62192
  try {
61985
- slip2 = JSON.parse(readFileSync6(file2, "utf8"));
62193
+ slip2 = JSON.parse(readFileSync7(file2, "utf8"));
61986
62194
  } catch (e) {
61987
62195
  console.error(`cannot read ${file2}: ${e.message}`);
61988
62196
  process.exit(2);
@@ -62079,6 +62287,7 @@ var init_src4 = __esm(() => {
62079
62287
  init_mirror();
62080
62288
  init_payer2();
62081
62289
  init_resume();
62290
+ init_remote_signer();
62082
62291
  init_review();
62083
62292
  init_state();
62084
62293
  init_terms();
@@ -63381,7 +63590,7 @@ var init_mcp = __esm(() => {
63381
63590
  });
63382
63591
 
63383
63592
  // packages/mcp/src/server.ts
63384
- import { existsSync as existsSync5 } from "node:fs";
63593
+ import { existsSync as existsSync6 } from "node:fs";
63385
63594
  function assertHash(value, what) {
63386
63595
  if (!HASH.test(value))
63387
63596
  throw new Error(`Refused. ${what} must be 0x followed by 64 hex characters.`);
@@ -63408,11 +63617,16 @@ function searchText(offer2) {
63408
63617
  `).toLowerCase();
63409
63618
  }
63410
63619
  function createCountersignMcpServer(deps) {
63411
- const server = new McpServer({ name: "countersign", version: "0.1.0" });
63620
+ const server = new McpServer({ name: "countersign", version: "0.1.1" });
63412
63621
  const clock = deps.now ?? (() => Math.floor(Date.now() / 1000));
63413
63622
  const verify2 = deps.verify ?? verifySlip;
63623
+ const gate = () => {
63624
+ if (deps.account !== undefined)
63625
+ return;
63626
+ return failure3(new Error(deps.problem === undefined ? NOT_SIGNED_IN3 : `${NOT_SIGNED_IN3} (${deps.problem})`));
63627
+ };
63414
63628
  const loadSlip = async (hash4) => {
63415
- if (existsSync5(deps.state.slipPath(hash4)))
63629
+ if (existsSync6(deps.state.slipPath(hash4)))
63416
63630
  return deps.state.loadSlip(hash4);
63417
63631
  const base2 = deps.hubUrl.replace(/\/+$/, "");
63418
63632
  const response = await fetch(`${base2}/slips/${hash4}`);
@@ -63428,6 +63642,9 @@ function createCountersignMcpServer(deps) {
63428
63642
  limit: exports_external.number().int().optional()
63429
63643
  }
63430
63644
  }, async ({ query, limit }) => {
63645
+ const refused = gate();
63646
+ if (refused !== undefined)
63647
+ return refused;
63431
63648
  try {
63432
63649
  const listings = await deps.hub.listOffers();
63433
63650
  const needle = query?.toLowerCase();
@@ -63462,6 +63679,9 @@ function createCountersignMcpServer(deps) {
63462
63679
  offerHash: exports_external.string()
63463
63680
  }
63464
63681
  }, async ({ offerHash: offerHash2 }) => {
63682
+ const refused = gate();
63683
+ if (refused !== undefined)
63684
+ return refused;
63465
63685
  try {
63466
63686
  assertHash(offerHash2, "offerHash");
63467
63687
  const listing = await deps.hub.getOffer(offerHash2);
@@ -63479,16 +63699,17 @@ function createCountersignMcpServer(deps) {
63479
63699
  });
63480
63700
  server.registerTool("buy", {
63481
63701
  title: "Buy an offer",
63482
- description: "Buy an offer under the agent's envelope and return the seller's output and slip. Requires CS_BUYER_KEY.",
63702
+ description: "Buy an offer under the agent's envelope and return the seller's output and slip. Requires sign-in: npx -y @countersign-hub/cli login.",
63483
63703
  inputSchema: {
63484
63704
  offerHash: exports_external.string(),
63485
63705
  input: exports_external.unknown(),
63486
63706
  rail: exports_external.string().optional()
63487
63707
  }
63488
63708
  }, async ({ offerHash: offerHash2, input: input2, rail: rail2 }) => {
63709
+ const refused = gate();
63710
+ if (refused !== undefined)
63711
+ return refused;
63489
63712
  const account = deps.account;
63490
- if (account === undefined)
63491
- return failure3(new Error(NO_KEY));
63492
63713
  if (rail2 !== undefined && !RAIL_IDS.includes(rail2)) {
63493
63714
  return failure3(new Error(`Unknown rail ${rail2}.`));
63494
63715
  }
@@ -63529,6 +63750,9 @@ function createCountersignMcpServer(deps) {
63529
63750
  slipHash: exports_external.string().optional()
63530
63751
  }
63531
63752
  }, async ({ slip: slip3, slipHash: slipHash2 }) => {
63753
+ const refused = gate();
63754
+ if (refused !== undefined)
63755
+ return refused;
63532
63756
  if (slip3 === undefined === (slipHash2 === undefined)) {
63533
63757
  return failure3(new Error("Pass exactly one of slip or slipHash."));
63534
63758
  }
@@ -63546,6 +63770,9 @@ function createCountersignMcpServer(deps) {
63546
63770
  description: "Read the agent's envelope and what the journal says it has spent.",
63547
63771
  inputSchema: {}
63548
63772
  }, async () => {
63773
+ const refused = gate();
63774
+ if (refused !== undefined)
63775
+ return refused;
63549
63776
  try {
63550
63777
  return text({ envelope: deps.state.loadEnvelope(), spent: deps.journal.spend() });
63551
63778
  } catch (error62) {
@@ -63559,6 +63786,9 @@ function createCountersignMcpServer(deps) {
63559
63786
  limit: exports_external.number().int().optional()
63560
63787
  }
63561
63788
  }, async ({ limit }) => {
63789
+ const refused = gate();
63790
+ if (refused !== undefined)
63791
+ return refused;
63562
63792
  try {
63563
63793
  const entries = deps.journal.entries();
63564
63794
  const count = limit ?? 20;
@@ -63569,7 +63799,7 @@ function createCountersignMcpServer(deps) {
63569
63799
  });
63570
63800
  return server;
63571
63801
  }
63572
- var HASH_MISMATCH = "Refused. The hub served an offer that does not match the hash asked for.", NO_KEY = "Set CS_BUYER_KEY to the agent's private key.", HASH;
63802
+ var HASH_MISMATCH = "Refused. The hub served an offer that does not match the hash asked for.", HASH;
63573
63803
  var init_server6 = __esm(() => {
63574
63804
  init_mcp();
63575
63805
  init_zod();
@@ -63582,14 +63812,15 @@ var init_server6 = __esm(() => {
63582
63812
  // packages/mcp/src/main.ts
63583
63813
  var exports_main = {};
63584
63814
  __export(exports_main, {
63815
+ resolveAccount: () => resolveAccount2,
63585
63816
  main: () => main3
63586
63817
  });
63587
63818
  function fail(message) {
63588
63819
  console.error(message);
63589
63820
  process.exit(1);
63590
63821
  }
63591
- function parse7(argv) {
63592
- const config2 = { hub: process.env.CS_HUB ?? DEFAULT_HUB };
63822
+ function parse8(argv) {
63823
+ const config2 = { hub: process.env.CS_HUB ?? DEFAULT_HUB2 };
63593
63824
  if (process.env.CS_RPC_URL !== undefined)
63594
63825
  config2.rpcUrl = process.env.CS_RPC_URL;
63595
63826
  let help = false;
@@ -63615,18 +63846,45 @@ function parse7(argv) {
63615
63846
  }
63616
63847
  return { help, config: config2 };
63617
63848
  }
63618
- function agentAccount2() {
63619
- const key = process.env.CS_BUYER_KEY;
63849
+ function ciAccount(env) {
63850
+ const key = env.CS_BUYER_KEY;
63620
63851
  if (!key)
63621
- return;
63852
+ return {};
63622
63853
  try {
63623
- return privateKeyToAccount(key.startsWith("0x") ? key : `0x${key}`);
63854
+ return { account: privateKeyToAccount(key.startsWith("0x") ? key : `0x${key}`) };
63624
63855
  } catch {
63625
- return fail("CS_BUYER_KEY is not a valid private key.");
63856
+ return { problem: "The configured signer is not valid." };
63626
63857
  }
63627
63858
  }
63628
- async function main3(argv) {
63629
- const { help, config: config2 } = parse7(argv);
63859
+ function sessionProblem(error62) {
63860
+ const message = error62 instanceof Error ? error62.message : String(error62);
63861
+ const firstLine3 = message.split(`
63862
+ `)[0] ?? "";
63863
+ return { problem: `Could not load the countersign session: ${firstLine3}` };
63864
+ }
63865
+ async function resolveAccount2(options, env) {
63866
+ const sessionEnv = { CS_SESSION: env.CS_SESSION, CS_HUB: env.CS_HUB };
63867
+ delete env.CS_SESSION;
63868
+ if (options.loadAccount !== undefined) {
63869
+ try {
63870
+ const account = await options.loadAccount();
63871
+ return account === undefined ? {} : { account };
63872
+ } catch (error62) {
63873
+ return sessionProblem(error62);
63874
+ }
63875
+ }
63876
+ let session;
63877
+ try {
63878
+ session = await sessionFromEnv(sessionEnv);
63879
+ } catch (error62) {
63880
+ return sessionProblem(error62);
63881
+ }
63882
+ if (session !== undefined)
63883
+ return { account: remoteAccount(session) };
63884
+ return ciAccount(env);
63885
+ }
63886
+ async function main3(argv, options = {}) {
63887
+ const { help, config: config2 } = parse8(argv);
63630
63888
  if (help) {
63631
63889
  console.log(USAGE);
63632
63890
  return;
@@ -63639,11 +63897,16 @@ async function main3(argv) {
63639
63897
  const state2 = new State(config2.state);
63640
63898
  const journal2 = new Journal(state2.journalPath);
63641
63899
  const hub = new HubClient(config2.hub);
63642
- const account = agentAccount2();
63643
- console.error(`countersign-mcp: hub ${hub.url}`);
63644
- console.error(`countersign-mcp: state ${state2.dir}`);
63645
- if (account !== undefined)
63646
- console.error(`countersign-mcp: agent ${account.address}`);
63900
+ const { account, problem } = await resolveAccount2(options, process.env);
63901
+ console.error(`countersign mcp: hub ${hub.url}`);
63902
+ console.error(`countersign mcp: state ${state2.dir}`);
63903
+ if (account !== undefined) {
63904
+ console.error(`countersign mcp: agent ${account.address}`);
63905
+ } else {
63906
+ console.error("countersign mcp: not signed in. Run: npx -y @countersign-hub/cli login");
63907
+ if (problem !== undefined)
63908
+ console.error(`countersign mcp: ${problem}`);
63909
+ }
63647
63910
  const server = createCountersignMcpServer({
63648
63911
  hub,
63649
63912
  state: state2,
@@ -63651,11 +63914,12 @@ async function main3(argv) {
63651
63914
  hubUrl: config2.hub,
63652
63915
  now: () => Math.floor(Date.now() / 1000),
63653
63916
  ...account !== undefined ? { account } : {},
63917
+ ...problem !== undefined ? { problem } : {},
63654
63918
  ...config2.rpcUrl !== undefined ? { rpcUrl: config2.rpcUrl } : {}
63655
63919
  });
63656
63920
  await server.connect(new StdioServerTransport);
63657
63921
  }
63658
- var DEFAULT_HUB = "https://hub-production-e85e.up.railway.app", USAGE;
63922
+ var DEFAULT_HUB2 = "https://hub-production-e85e.up.railway.app", USAGE;
63659
63923
  var init_main = __esm(() => {
63660
63924
  init_accounts();
63661
63925
  init_stdio2();
@@ -63666,17 +63930,18 @@ var init_main = __esm(() => {
63666
63930
  Usage: countersign mcp [--hub <url>] [--state <dir>] [--rpc <url>]
63667
63931
 
63668
63932
  Options:
63669
- --hub <url> The countersign hub (env CS_HUB; default ${DEFAULT_HUB})
63933
+ --hub <url> The countersign hub (env CS_HUB; default ${DEFAULT_HUB2})
63670
63934
  --state <dir> Where the envelope, journal and slips live (default ~/.countersign)
63671
63935
  --rpc <url> The RPC verify_slip reads the chain from (env CS_RPC_URL), e.g. https://rpc.testnet.arc.network
63672
63936
  --help Print this usage and exit
63673
63937
 
63674
- Environment:
63675
- CS_BUYER_KEY The agent's private key; without it, buy refuses.
63676
- CS_NETWORK Must be unset or testnet.
63938
+ Sign in once: npx -y @countersign-hub/cli login. Your agent's wallet is a Privy server wallet that the countersign app controls;
63939
+ this server signs through the hub with your session, and no key is ever on this machine or in any config.
63940
+
63941
+ CS_NETWORK must be unset or testnet.
63677
63942
 
63678
- Install it from the countersign release tarball only. The npm packages named @countersign/*,
63679
- countersign and countersign-mcp belong to other projects: never npx or npm install them.`;
63943
+ Install: npm i -g @countersign-hub/cli, then countersign mcp (or npx -y @countersign-hub/cli mcp).
63944
+ The npm packages named @countersign/*, countersign and countersign-mcp belong to other projects: never npx or npm install them.`;
63680
63945
  });
63681
63946
 
63682
63947
  // packages/runner/src/cli.ts
@@ -63957,8 +64222,11 @@ function setTermsArgs(command) {
63957
64222
  }
63958
64223
  async function bondFromEnv(args, log = console.log) {
63959
64224
  const key = process.env.CS_SELLER_KEY;
63960
- if (!key || !/^0x[0-9a-fA-F]{64}$/.test(key)) {
63961
- throw new Error("CS_SELLER_KEY must hold a 0x-prefixed 32-byte hex key.");
64225
+ if (!key) {
64226
+ throw new Error("Bond commands are not available from a signed-in session yet: they send transactions, and the hub only signs.");
64227
+ }
64228
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
64229
+ throw new Error("The CI signing override in this environment is malformed.");
63962
64230
  }
63963
64231
  await runBond(parseBondArgs(args), key, log);
63964
64232
  }
@@ -65153,12 +65421,183 @@ function isRecord3(value) {
65153
65421
  return typeof value === "object" && value !== null && !Array.isArray(value);
65154
65422
  }
65155
65423
 
65424
+ // packages/runner/src/remote-signer.ts
65425
+ init__esm();
65426
+ var NOT_SIGNED_IN = "Not signed in. Run: npx -y @countersign-hub/cli login";
65427
+ var BONDED_REMOTE_REFUSAL = "A bonded offer cannot be served from a signed-in session yet: the hub does not sign Lock for it.";
65428
+ var SIGNATURE = /^0x[0-9a-fA-F]{130}$/;
65429
+ var ADDRESS = /^0x[0-9a-fA-F]{40}$/;
65430
+ function hubFailure(reason) {
65431
+ return new Error(`The hub could not sign right now (${reason}).`);
65432
+ }
65433
+ var WRONG_WALLET = "The hub signed with a different wallet.";
65434
+ function safeHub(hubUrl) {
65435
+ let url2;
65436
+ try {
65437
+ url2 = new URL(hubUrl);
65438
+ } catch {
65439
+ throw new Error("The hub address is not a URL.");
65440
+ }
65441
+ const local = url2.hostname === "localhost" || url2.hostname === "127.0.0.1" || url2.hostname === "[::1]";
65442
+ if (url2.protocol !== "https:" && !(url2.protocol === "http:" && local)) {
65443
+ throw new Error("The session is sent only to an https hub.");
65444
+ }
65445
+ return hubUrl.replace(/\/+$/, "");
65446
+ }
65447
+ function decimalString(_key, value) {
65448
+ return typeof value === "bigint" ? value.toString(10) : value;
65449
+ }
65450
+ function recordOf(value) {
65451
+ return typeof value === "object" && value !== null ? value : null;
65452
+ }
65453
+ function messageOf2(body) {
65454
+ const message = recordOf(body)?.message;
65455
+ return typeof message === "string" && message.length > 0 ? message : null;
65456
+ }
65457
+ function codeOf(body) {
65458
+ const code = recordOf(body)?.code;
65459
+ return typeof code === "string" && code.length > 0 ? code : null;
65460
+ }
65461
+ async function readJson(response) {
65462
+ try {
65463
+ return await response.json();
65464
+ } catch {
65465
+ return;
65466
+ }
65467
+ }
65468
+ function remoteRunnerAccount(options) {
65469
+ const hub = safeHub(options.hubUrl);
65470
+ const fetchImpl = options.fetch ?? fetch;
65471
+ const sign3 = async (payload, recover) => {
65472
+ let response;
65473
+ try {
65474
+ response = await fetchImpl(`${hub}/cli/sign`, {
65475
+ method: "POST",
65476
+ headers: {
65477
+ authorization: `Bearer ${options.session}`,
65478
+ "content-type": "application/json"
65479
+ },
65480
+ body: JSON.stringify(payload, decimalString)
65481
+ });
65482
+ } catch {
65483
+ throw hubFailure("unreachable");
65484
+ }
65485
+ const body = await readJson(response);
65486
+ if (response.status === 200) {
65487
+ const signed2 = recordOf(body);
65488
+ const signature = signed2?.signature;
65489
+ const address = signed2?.address;
65490
+ if (typeof signature !== "string" || !SIGNATURE.test(signature) || typeof address !== "string" || address.toLowerCase() !== options.address.toLowerCase()) {
65491
+ throw new Error(WRONG_WALLET);
65492
+ }
65493
+ let signer;
65494
+ try {
65495
+ signer = await recover(signature);
65496
+ } catch {
65497
+ throw new Error(WRONG_WALLET);
65498
+ }
65499
+ if (signer.toLowerCase() !== options.address.toLowerCase())
65500
+ throw new Error(WRONG_WALLET);
65501
+ return signature;
65502
+ }
65503
+ if (response.status === 401)
65504
+ throw new Error(messageOf2(body) ?? NOT_SIGNED_IN);
65505
+ if (response.status === 403) {
65506
+ throw new Error(`The hub refused to sign this: ${codeOf(body) ?? "not-allowed"}`);
65507
+ }
65508
+ if (response.status === 502) {
65509
+ throw new Error("The hub could not sign right now (privy-failed).");
65510
+ }
65511
+ throw hubFailure(response.status);
65512
+ };
65513
+ return {
65514
+ kind: "remote",
65515
+ address: options.address,
65516
+ signMessage: (args) => sign3({ kind: "message", message: args.message }, (signature) => recoverMessageAddress2({ message: args.message, signature })),
65517
+ signTypedData: (typedData) => sign3({ kind: "typed-data", typedData }, (signature) => recoverTypedDataAddress2({ ...typedData, signature }))
65518
+ };
65519
+ }
65520
+ async function remoteAccountFromSession(options) {
65521
+ const hub = safeHub(options.hubUrl);
65522
+ const fetchImpl = options.fetch ?? fetch;
65523
+ let response;
65524
+ try {
65525
+ response = await fetchImpl(`${hub}/cli/session`, {
65526
+ headers: { authorization: `Bearer ${options.session}` }
65527
+ });
65528
+ } catch {
65529
+ throw hubFailure("unreachable");
65530
+ }
65531
+ const body = await readJson(response);
65532
+ if (response.status === 200) {
65533
+ const address = recordOf(body)?.address;
65534
+ if (typeof address !== "string" || !ADDRESS.test(address)) {
65535
+ throw hubFailure("invalid-address");
65536
+ }
65537
+ return remoteRunnerAccount({ ...options, address });
65538
+ }
65539
+ if (response.status === 401)
65540
+ throw new Error(messageOf2(body) ?? NOT_SIGNED_IN);
65541
+ throw hubFailure(response.status);
65542
+ }
65543
+ function assertRemoteCanServe(account, offer2) {
65544
+ if (account.kind !== "remote")
65545
+ return;
65546
+ let bonded = false;
65547
+ try {
65548
+ bonded = BigInt(offer2.bond) > 0n;
65549
+ } catch {
65550
+ bonded = false;
65551
+ }
65552
+ if (bonded)
65553
+ throw new Error(BONDED_REMOTE_REFUSAL);
65554
+ }
65555
+
65156
65556
  // packages/runner/src/daemon.ts
65157
65557
  var RECONNECT_DELAYS = [1, 2, 4, 8, 16, 30];
65558
+ var ATOMIC = /^(0|[1-9][0-9]*)$/;
65559
+ var ADDRESS2 = /^0x[0-9a-fA-F]{40}$/;
65560
+ function readLockTerms(value) {
65561
+ if (value === null || typeof value !== "object")
65562
+ return null;
65563
+ const raw = value;
65564
+ const buyer = raw.buyer;
65565
+ const payee = raw.payee;
65566
+ const amount2 = raw.amount;
65567
+ const deadline = raw.deadline;
65568
+ const salt = raw.salt;
65569
+ const bond = raw.bond;
65570
+ const reviewWindow = raw.reviewWindow;
65571
+ if (typeof buyer !== "string" || !ADDRESS2.test(buyer))
65572
+ return null;
65573
+ if (typeof payee !== "string" || !ADDRESS2.test(payee))
65574
+ return null;
65575
+ if (typeof amount2 !== "string" || !ATOMIC.test(amount2))
65576
+ return null;
65577
+ if (typeof deadline !== "number" || !Number.isSafeInteger(deadline))
65578
+ return null;
65579
+ if (typeof salt !== "string" || !ATOMIC.test(salt))
65580
+ return null;
65581
+ if (typeof bond !== "string" || !ATOMIC.test(bond))
65582
+ return null;
65583
+ if (typeof reviewWindow !== "number" || !Number.isSafeInteger(reviewWindow))
65584
+ return null;
65585
+ return {
65586
+ buyer,
65587
+ payee,
65588
+ amount: amount2,
65589
+ deadline,
65590
+ salt,
65591
+ bond,
65592
+ reviewWindow
65593
+ };
65594
+ }
65158
65595
  function startRunner(cfg) {
65159
65596
  const clock = cfg.now ?? (() => Math.floor(Date.now() / 1000));
65160
65597
  const timeMs = cfg.limits?.timeMs ?? 30000;
65161
65598
  const maxOutputBytes = cfg.limits?.maxOutputBytes ?? 1e6;
65599
+ for (const entry of cfg.served)
65600
+ assertRemoteCanServe(cfg.account, entry.offer);
65162
65601
  const served = cfg.served.map((entry) => ({ ...entry, hash: offerHash(entry.offer) }));
65163
65602
  const url2 = connectUrl(cfg.hubUrl);
65164
65603
  const funding = cfg.funding === undefined ? null : { ...cfg.funding, read: fundingReader(cfg.funding) };
@@ -65215,9 +65654,51 @@ function startRunner(cfg) {
65215
65654
  });
65216
65655
  return;
65217
65656
  }
65657
+ const refuse = (reason) => {
65658
+ send(target, { t: "lock-refused", jobId: frame.jobId, reason });
65659
+ };
65660
+ const terms = readLockTerms(frame.terms);
65661
+ if (terms === null) {
65662
+ refuse("bad-terms");
65663
+ return;
65664
+ }
65665
+ if (terms.payee.toLowerCase() !== cfg.account.address.toLowerCase()) {
65666
+ refuse("not-payee");
65667
+ return;
65668
+ }
65669
+ let bondAbove = false;
65670
+ try {
65671
+ bondAbove = BigInt(terms.bond) > 0n;
65672
+ } catch {
65673
+ bondAbove = false;
65674
+ }
65675
+ if (terms.amount !== entry.offer.price || terms.bond !== entry.offer.bond || !bondAbove || terms.reviewWindow !== (entry.offer.reviewWindow ?? 0)) {
65676
+ refuse("terms-mismatch");
65677
+ return;
65678
+ }
65679
+ if (terms.deadline - clock() < 60) {
65680
+ refuse("deadline-too-near");
65681
+ return;
65682
+ }
65683
+ const escrow = frame.escrow;
65684
+ const pinned = funding !== null && typeof escrow === "string" && funding.known.some((pin) => pin.chainId === frame.chainId && pin.escrow.toLowerCase() === escrow.toLowerCase());
65685
+ if (!pinned) {
65686
+ refuse("unknown-escrow");
65687
+ return;
65688
+ }
65689
+ let rebuilt;
65690
+ try {
65691
+ rebuilt = jobIdOf({ offerHash: frame.offerHash, ...terms }, frame.chainId, frame.escrow);
65692
+ } catch {
65693
+ refuse("job-mismatch");
65694
+ return;
65695
+ }
65696
+ if (typeof frame.jobId !== "string" || rebuilt.toLowerCase() !== frame.jobId.toLowerCase()) {
65697
+ refuse("job-mismatch");
65698
+ return;
65699
+ }
65218
65700
  try {
65219
- const typed = lockTypedData(frame.chainId, frame.escrow, frame.jobId, frame.funder);
65220
- const sig = await cfg.account.signTypedData(typed);
65701
+ const sig = await cfg.account.signTypedData(lockTypedData(frame.chainId, frame.escrow, frame.jobId, frame.funder));
65221
65702
  send(target, { t: "locked", jobId: frame.jobId, sig });
65222
65703
  } catch {
65223
65704
  send(target, {
@@ -65272,7 +65753,7 @@ function startRunner(cfg) {
65272
65753
  try {
65273
65754
  output2 = await runInChild(substituteBuyer(entry.spec, order.buyer), order.input, { timeMs, maxOutputBytes }, [...order.lineage, order.jobId].join(","));
65274
65755
  } catch (error62) {
65275
- const reason = messageOf2(error62);
65756
+ const reason = messageOf3(error62);
65276
65757
  notify(order, entry.offer.title, false, reason);
65277
65758
  return fail(target, order, reason);
65278
65759
  }
@@ -65308,7 +65789,7 @@ function startRunner(cfg) {
65308
65789
  });
65309
65790
  notify(order, entry.offer.title, true);
65310
65791
  } catch (error62) {
65311
- const reason = messageOf2(error62);
65792
+ const reason = messageOf3(error62);
65312
65793
  notify(order, entry.offer.title, false, reason);
65313
65794
  fail(target, order, reason);
65314
65795
  }
@@ -65394,7 +65875,7 @@ function startRunner(cfg) {
65394
65875
  }
65395
65876
  handleMessage(target, message).catch((error62) => {
65396
65877
  if (message.t === "job")
65397
- fail(target, message.order, messageOf2(error62));
65878
+ fail(target, message.order, messageOf3(error62));
65398
65879
  });
65399
65880
  });
65400
65881
  target.addEventListener("close", () => {
@@ -65487,7 +65968,7 @@ function connectUrl(hubUrl) {
65487
65968
  url2.pathname = `${url2.pathname.replace(/\/+$/, "")}${RUNNER_PATH}`;
65488
65969
  return url2.toString();
65489
65970
  }
65490
- function messageOf2(error62) {
65971
+ function messageOf3(error62) {
65491
65972
  return error62 instanceof Error ? error62.message : String(error62);
65492
65973
  }
65493
65974
 
@@ -66127,6 +66608,7 @@ async function publishCommand(options) {
66127
66608
  console.log(offerHash2);
66128
66609
  }
66129
66610
  function publishWithState(options, account, offer2, skillSha256) {
66611
+ assertRemoteCanServe(account, offer2);
66130
66612
  return publishOffer(options.hub, account, offer2, {
66131
66613
  ...options.state !== undefined ? { stateDir: options.state } : {},
66132
66614
  skillSha256
@@ -66229,11 +66711,22 @@ function makeOffer(seller, skill, options) {
66229
66711
  });
66230
66712
  }
66231
66713
  async function sellerAccount() {
66714
+ const session = process.env.CS_SESSION;
66715
+ delete process.env.CS_SESSION;
66232
66716
  const key = process.env.CS_SELLER_KEY;
66233
- if (!key || !/^0x[0-9a-fA-F]{64}$/.test(key)) {
66234
- throw new Error("CS_SELLER_KEY must hold a 0x-prefixed 32-byte hex key.");
66717
+ if (key) {
66718
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
66719
+ throw new Error("The CI signing override in this environment is malformed.");
66720
+ }
66721
+ return privateKeyToAccount(key);
66235
66722
  }
66236
- return privateKeyToAccount(key);
66723
+ if (session) {
66724
+ const issuer = process.env.CS_HUB;
66725
+ if (!issuer)
66726
+ throw new Error(NOT_SIGNED_IN);
66727
+ return remoteAccountFromSession({ hubUrl: issuer, session });
66728
+ }
66729
+ throw new Error(NOT_SIGNED_IN);
66237
66730
  }
66238
66731
  function parseOptions(args) {
66239
66732
  const skills = [];
@@ -66449,30 +66942,458 @@ function parseRails(raw) {
66449
66942
  }
66450
66943
  if (false) {}
66451
66944
 
66945
+ // packages/cli/src/login.ts
66946
+ init__esm();
66947
+ import { spawn as spawn3 } from "node:child_process";
66948
+
66949
+ // packages/cli/src/session.ts
66950
+ import { execFileSync } from "node:child_process";
66951
+ import {
66952
+ chmodSync,
66953
+ closeSync,
66954
+ existsSync as existsSync3,
66955
+ lstatSync,
66956
+ mkdirSync as mkdirSync2,
66957
+ openSync,
66958
+ readFileSync as readFileSync4,
66959
+ renameSync,
66960
+ rmSync,
66961
+ writeSync
66962
+ } from "node:fs";
66963
+ import { homedir as homedir2 } from "node:os";
66964
+ import { join as join3 } from "node:path";
66965
+ var NOT_SIGNED_IN2 = "Not signed in. Run: npx -y @countersign-hub/cli login";
66966
+ var securityRun = (args, input2) => execFileSync("/usr/bin/security", args, { input: input2, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
66967
+ function parse6(text) {
66968
+ try {
66969
+ const value = JSON.parse(text);
66970
+ if (typeof value.token === "string" && typeof value.hub === "string" && typeof value.address === "string" && typeof value.expiresAt === "number") {
66971
+ return value;
66972
+ }
66973
+ } catch {}
66974
+ return null;
66975
+ }
66976
+ function refuse(path) {
66977
+ process.stderr.write(`countersign: refusing the session at ${path}: symlink, non-regular file or unsafe permissions.
66978
+ `);
66979
+ return null;
66980
+ }
66981
+ function fileSessionBackend(dir = join3(homedir2(), ".config", "countersign"), platform = process.platform) {
66982
+ const path = join3(dir, "session.json");
66983
+ return {
66984
+ kind: "file",
66985
+ where: () => path,
66986
+ read() {
66987
+ try {
66988
+ if (lstatSync(dir).isSymbolicLink())
66989
+ return refuse(dir);
66990
+ } catch {
66991
+ return null;
66992
+ }
66993
+ try {
66994
+ const info = lstatSync(path);
66995
+ const unsafeMode = platform !== "win32" && (info.mode & 63) !== 0;
66996
+ if (info.isSymbolicLink() || !info.isFile() || unsafeMode)
66997
+ return refuse(path);
66998
+ } catch {
66999
+ return null;
67000
+ }
67001
+ try {
67002
+ return parse6(readFileSync4(path, "utf8"));
67003
+ } catch {
67004
+ return null;
67005
+ }
67006
+ },
67007
+ write(session) {
67008
+ mkdirSync2(dir, { recursive: true, mode: 448 });
67009
+ chmodSync(dir, 448);
67010
+ const tmp = `${path}.tmp`;
67011
+ rmSync(tmp, { force: true });
67012
+ const fd = openSync(tmp, "wx", 384);
67013
+ try {
67014
+ writeSync(fd, `${JSON.stringify(session)}
67015
+ `);
67016
+ } finally {
67017
+ closeSync(fd);
67018
+ }
67019
+ renameSync(tmp, path);
67020
+ },
67021
+ clear() {
67022
+ rmSync(path, { force: true });
67023
+ }
67024
+ };
67025
+ }
67026
+ function keychainSessionBackend(run = securityRun) {
67027
+ return {
67028
+ kind: "keychain",
67029
+ where: () => "the macOS login Keychain (service countersign, account session)",
67030
+ read() {
67031
+ try {
67032
+ const hex3 = run(["find-generic-password", "-s", "countersign", "-a", "session", "-w"]).trim();
67033
+ return hex3 === "" ? null : parse6(Buffer.from(hex3, "hex").toString("utf8"));
67034
+ } catch {
67035
+ return null;
67036
+ }
67037
+ },
67038
+ write(session) {
67039
+ const hex3 = Buffer.from(JSON.stringify(session), "utf8").toString("hex");
67040
+ run(["-i"], `add-generic-password -U -s countersign -a session -w ${hex3}
67041
+ `);
67042
+ },
67043
+ clear() {
67044
+ try {
67045
+ run(["delete-generic-password", "-s", "countersign", "-a", "session"]);
67046
+ } catch {}
67047
+ }
67048
+ };
67049
+ }
67050
+ function defaultSessionBackend(env = process.env) {
67051
+ if (env.COUNTERSIGN_KEYSTORE === "file")
67052
+ return fileSessionBackend();
67053
+ if (process.platform === "darwin" && existsSync3("/usr/bin/security"))
67054
+ return keychainSessionBackend();
67055
+ return fileSessionBackend();
67056
+ }
67057
+ function loadSession(backend = defaultSessionBackend(), now = Math.floor(Date.now() / 1000)) {
67058
+ const session = backend.read();
67059
+ return session !== null && session.expiresAt > now ? session : null;
67060
+ }
67061
+
67062
+ // packages/cli/src/login.ts
67063
+ var EXPIRED = `The login expired. Run countersign login again.
67064
+ `;
67065
+ var CONSOLE_ORIGIN = "https://web-production-a58de.up.railway.app";
67066
+ var UNSAFE_URL_CHARACTERS = /[&|^<>"%`;\s]/;
67067
+ var HUB_REFUSED = "Refused: the countersign hub must be https.";
67068
+ function safeConsoleUrl(url2, consoleOrigin = CONSOLE_ORIGIN) {
67069
+ if (UNSAFE_URL_CHARACTERS.test(url2))
67070
+ return null;
67071
+ let parsed;
67072
+ try {
67073
+ parsed = new URL(url2);
67074
+ } catch {
67075
+ return null;
67076
+ }
67077
+ if (parsed.protocol !== "https:")
67078
+ return null;
67079
+ if (parsed.origin !== consoleOrigin)
67080
+ return null;
67081
+ return url2;
67082
+ }
67083
+ function assertHubUrl(hub) {
67084
+ let parsed;
67085
+ try {
67086
+ parsed = new URL(hub);
67087
+ } catch {
67088
+ throw new Error(HUB_REFUSED);
67089
+ }
67090
+ const local = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
67091
+ if (parsed.protocol !== "https:" && !local)
67092
+ throw new Error(HUB_REFUSED);
67093
+ return parsed.origin;
67094
+ }
67095
+ function defaultOpen(url2) {
67096
+ const windows = process.platform === "win32";
67097
+ const command = windows ? "rundll32" : process.platform === "darwin" ? "open" : "xdg-open";
67098
+ const args = windows ? ["url.dll,FileProtocolHandler", url2] : [url2];
67099
+ const child = spawn3(command, args, { detached: true, stdio: "ignore", shell: false });
67100
+ child.on("error", () => {});
67101
+ child.unref();
67102
+ }
67103
+ function hubOrRefuse(hub) {
67104
+ try {
67105
+ return assertHubUrl(hub);
67106
+ } catch (error62) {
67107
+ process.stderr.write(`${error62 instanceof Error ? error62.message : String(error62)}
67108
+ `);
67109
+ process.exitCode = 1;
67110
+ return null;
67111
+ }
67112
+ }
67113
+ function expired() {
67114
+ process.stderr.write(EXPIRED);
67115
+ process.exitCode = 1;
67116
+ }
67117
+ async function runLogin(_args, deps) {
67118
+ const fetchImpl = deps.fetchImpl ?? fetch;
67119
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
67120
+ const now = deps.now ?? (() => Math.floor(Date.now() / 1000));
67121
+ const open2 = deps.open ?? defaultOpen;
67122
+ const hub = hubOrRefuse(deps.hub);
67123
+ if (hub === null)
67124
+ return;
67125
+ let start;
67126
+ try {
67127
+ const response = await fetchImpl(`${hub}/cli/login/start`, { method: "POST" });
67128
+ if (!response.ok)
67129
+ throw new Error("start refused");
67130
+ start = await response.json();
67131
+ } catch {
67132
+ process.stderr.write(`The login could not start. Run countersign login again.
67133
+ `);
67134
+ process.exitCode = 1;
67135
+ return;
67136
+ }
67137
+ deps.write(`Open this link and sign in: ${start.verifyUrl}
67138
+ `);
67139
+ deps.write(`The page will show this code: ${start.code}
67140
+ `);
67141
+ const safeUrl = safeConsoleUrl(start.verifyUrl);
67142
+ if (safeUrl !== null) {
67143
+ try {
67144
+ open2(safeUrl);
67145
+ } catch {}
67146
+ }
67147
+ const interval = Number.isFinite(start.interval) && start.interval > 0 ? start.interval : 5;
67148
+ while (now() < start.expiresAt) {
67149
+ await sleep(interval * 1000);
67150
+ if (now() >= start.expiresAt)
67151
+ break;
67152
+ let poll2;
67153
+ try {
67154
+ const response = await fetchImpl(`${hub}/cli/login/poll`, {
67155
+ method: "POST",
67156
+ headers: { "content-type": "application/json" },
67157
+ body: JSON.stringify({ deviceSecret: start.deviceSecret })
67158
+ });
67159
+ if (response.status === 410)
67160
+ return expired();
67161
+ if (!response.ok)
67162
+ return expired();
67163
+ poll2 = await response.json();
67164
+ } catch {
67165
+ continue;
67166
+ }
67167
+ if (poll2.status === "approved") {
67168
+ const session = {
67169
+ token: poll2.sessionToken,
67170
+ hub,
67171
+ address: poll2.address,
67172
+ expiresAt: poll2.expiresAt
67173
+ };
67174
+ deps.backend.write(session);
67175
+ deps.write(`Signed in. Your agent wallet: ${poll2.address}
67176
+ `);
67177
+ deps.write(`Fund it: send testnet USDC to ${poll2.address} (Circle faucet: https://faucet.circle.com).
67178
+ `);
67179
+ deps.write(`For Whitechain, get test WBT for gas at https://faucet.testnet.whitechain.io.
67180
+ `);
67181
+ deps.write(`The session lasts 7 days; countersign logout ends it.
67182
+ `);
67183
+ return;
67184
+ }
67185
+ if (poll2.status !== "pending")
67186
+ return expired();
67187
+ }
67188
+ expired();
67189
+ }
67190
+ async function runLogout(_args, deps) {
67191
+ const fetchImpl = deps.fetchImpl ?? fetch;
67192
+ const session = loadSession(deps.backend);
67193
+ if (session === null) {
67194
+ deps.backend.clear();
67195
+ deps.write(`Not signed in.
67196
+ `);
67197
+ return;
67198
+ }
67199
+ let confirmed = false;
67200
+ try {
67201
+ assertHubUrl(session.hub);
67202
+ const response = await fetchImpl(`${session.hub}/cli/logout`, {
67203
+ method: "POST",
67204
+ headers: { Authorization: `Bearer ${session.token}` }
67205
+ });
67206
+ confirmed = response.ok;
67207
+ } catch {}
67208
+ deps.backend.clear();
67209
+ if (confirmed) {
67210
+ deps.write(`Signed out.
67211
+ `);
67212
+ } else {
67213
+ const expiry = new Date(session.expiresAt * 1000).toISOString();
67214
+ deps.write(`Signed out here; the hub did not confirm, so the session stays valid until ${expiry}.
67215
+ `);
67216
+ }
67217
+ }
67218
+ async function runWhoami(_args, deps) {
67219
+ const fetchImpl = deps.fetchImpl ?? fetch;
67220
+ const readBalances = deps.balances ?? defaultBalances;
67221
+ const session = loadSession(deps.backend);
67222
+ if (session === null) {
67223
+ process.stderr.write(`${NOT_SIGNED_IN2}
67224
+ `);
67225
+ process.exitCode = 1;
67226
+ return;
67227
+ }
67228
+ try {
67229
+ assertHubUrl(session.hub);
67230
+ } catch {
67231
+ deps.backend.clear();
67232
+ process.stderr.write(`${NOT_SIGNED_IN2}
67233
+ `);
67234
+ process.exitCode = 1;
67235
+ return;
67236
+ }
67237
+ let response;
67238
+ try {
67239
+ response = await fetchImpl(`${session.hub}/cli/session`, {
67240
+ headers: { Authorization: `Bearer ${session.token}` }
67241
+ });
67242
+ } catch {
67243
+ process.stderr.write(`countersign whoami could not reach the hub.
67244
+ `);
67245
+ process.exitCode = 1;
67246
+ return;
67247
+ }
67248
+ if (response.status === 401) {
67249
+ process.stderr.write(`${NOT_SIGNED_IN2}
67250
+ `);
67251
+ process.exitCode = 1;
67252
+ return;
67253
+ }
67254
+ if (!response.ok) {
67255
+ process.stderr.write(`countersign whoami could not read the session.
67256
+ `);
67257
+ process.exitCode = 1;
67258
+ return;
67259
+ }
67260
+ const body = await response.json();
67261
+ deps.write(`Signed in as ${body.address} until ${new Date(body.expiresAt * 1000).toISOString()}
67262
+ `);
67263
+ deps.write(`${await readBalances(body.address)}
67264
+ `);
67265
+ }
67266
+ var BALANCE_CHAINS = [
67267
+ {
67268
+ name: "Arc",
67269
+ symbol: "USDC",
67270
+ token: "0x3600000000000000000000000000000000000000",
67271
+ rpc: "https://rpc.testnet.arc.network"
67272
+ },
67273
+ {
67274
+ name: "Whitechain",
67275
+ symbol: "USDT",
67276
+ token: "0xcedfB56113143d55b2358b8CFbB0dEB5Ad2f55a7",
67277
+ rpc: "https://rpc.ankr.com/whitechain_testnet"
67278
+ }
67279
+ ];
67280
+ async function defaultBalances(address) {
67281
+ const parts = await Promise.all(BALANCE_CHAINS.map(async (chain) => {
67282
+ try {
67283
+ const client = createPublicClient({ transport: http(chain.rpc) });
67284
+ const balance = await client.readContract({
67285
+ address: chain.token,
67286
+ abi: erc20Abi,
67287
+ functionName: "balanceOf",
67288
+ args: [address]
67289
+ });
67290
+ return `${formatUnits3(balance, 6)} ${chain.symbol} on ${chain.name}`;
67291
+ } catch {
67292
+ return `unavailable ${chain.symbol} on ${chain.name}`;
67293
+ }
67294
+ }));
67295
+ return parts.join(", ");
67296
+ }
67297
+
66452
67298
  // packages/cli/src/cli.ts
66453
- var VERSION = "0.1.0";
67299
+ var VERSION = "0.1.2";
67300
+ var DEFAULT_HUB3 = "https://hub-production-e85e.up.railway.app";
66454
67301
  var USAGE2 = `countersign ${VERSION}
66455
67302
 
66456
- Sell. The runner dials out to the hub, so you open no port. The key comes from CS_SELLER_KEY.
67303
+ Sell. The runner dials out to the hub, so you open no port.
66457
67304
  countersign sell skill <SKILL.md> --hub <url> --price <atomic> [runner flags]
66458
67305
  countersign sell openapi --spec <spec.json> --operation <id> --hub <url> --price <atomic> [--base-url <url>] [runner flags]
66459
67306
  Add --publish-only to list the offer without serving it.
66460
67307
 
66461
- Buy. The key comes from CS_BUYER_KEY.
67308
+ Buy.
66462
67309
  countersign discover|buy|envelope|journal [--hub <url>] [--state <dir>] ...
66463
67310
 
67311
+ Sign in, and check the session:
67312
+ countersign login|logout|whoami
67313
+
66464
67314
  Verify a slip offline:
66465
67315
  countersign verify <slip.json> [--rpc <url>]
66466
67316
 
66467
67317
  The buyer MCP server over stdio:
66468
67318
  countersign mcp [--hub <url>] [--state <dir>] [--rpc <url>]
67319
+
67320
+ Start here: countersign login, then fund the printed address, then buy or sell.
67321
+ The default hub is ${DEFAULT_HUB3}.
66469
67322
  `;
67323
+ var COMMAND_USAGE = {
67324
+ login: `Usage: countersign login
67325
+ Opens a sign-in link; your agent wallet is a Privy server wallet, so no key is ever on this machine.
67326
+ `,
67327
+ logout: `Usage: countersign logout
67328
+ Ends the session on the hub and removes the stored token.
67329
+ `,
67330
+ whoami: `Usage: countersign whoami
67331
+ Shows the signed-in address, the session expiry and testnet balances.
67332
+ `,
67333
+ sell: `Usage: countersign sell skill <SKILL.md> --hub <url> --price <atomic> [runner flags]
67334
+ countersign sell openapi --spec <spec.json> --operation <id> --hub <url> --price <atomic> [--base-url <url>] [runner flags]
67335
+ Add --publish-only to list the offer without serving it.
67336
+ `,
67337
+ discover: `Usage: countersign discover [--hub <url>] [--state <dir>] [--catalogue <url|circle> [query] [--category <c>] [--limit <n>]]
67338
+ `,
67339
+ buy: `Usage: countersign buy <offerHash|callUrl> --input '<json>' [--approve] [--rail <id>] [--approvals hub] [--hub <url>] [--state <dir>]
67340
+ `,
67341
+ envelope: `Usage: countersign envelope show|set [--hub <url>] [--state <dir>]
67342
+ `,
67343
+ journal: `Usage: countersign journal [--hub <url>] [--state <dir>]
67344
+ `,
67345
+ verify: `Usage: countersign verify <slip.json> [--rpc <url>] [--mirror <url>]
67346
+ `,
67347
+ mcp: `Usage: countersign mcp [--hub <url>] [--state <dir>] [--rpc <url>]
67348
+ `
67349
+ };
66470
67350
  var BUYER_COMMANDS = new Set(["discover", "buy", "envelope", "journal"]);
67351
+ function withHub(args2, env = process.env) {
67352
+ for (const arg of args2) {
67353
+ if (arg === "--hub" || arg.startsWith("--hub="))
67354
+ return args2;
67355
+ }
67356
+ const hub = env.CS_HUB !== undefined && env.CS_HUB !== "" ? env.CS_HUB : DEFAULT_HUB3;
67357
+ return [...args2, "--hub", hub];
67358
+ }
67359
+ function hubValue(args2, env = process.env) {
67360
+ const settled = withHub(args2, env);
67361
+ const last = settled.at(-1) ?? DEFAULT_HUB3;
67362
+ return last.startsWith("--hub=") ? last.slice("--hub=".length) : last;
67363
+ }
67364
+ function useSession(env = process.env) {
67365
+ if (env.CS_BUYER_KEY !== undefined || env.CS_SELLER_KEY !== undefined)
67366
+ return true;
67367
+ const session = loadSession();
67368
+ if (session === null)
67369
+ return false;
67370
+ let hub;
67371
+ try {
67372
+ hub = assertHubUrl(session.hub);
67373
+ } catch {
67374
+ return false;
67375
+ }
67376
+ env.CS_SESSION = session.token;
67377
+ env.CS_HUB = hub;
67378
+ return true;
67379
+ }
66471
67380
  var DEFAULT_DEPS = {
66472
- runner: (argv) => main(argv),
66473
- loadBuyer: () => Promise.resolve().then(() => (init_cli(), exports_cli)),
67381
+ runner: async (argv) => {
67382
+ if (!useSession())
67383
+ return requireLogin();
67384
+ await main(withHub(argv));
67385
+ },
67386
+ loadBuyer: async () => {
67387
+ if (!useSession() && needsSession())
67388
+ return requireLogin();
67389
+ setArgv(withHub(process.argv.slice(2)));
67390
+ await Promise.resolve().then(() => (init_cli(), exports_cli));
67391
+ },
66474
67392
  loadVerify: () => init_cli2().then(() => exports_cli2),
66475
- loadMcp: () => Promise.resolve().then(() => (init_main(), exports_main))
67393
+ loadMcp: async () => {
67394
+ useSession();
67395
+ return Promise.resolve().then(() => (init_main(), exports_main));
67396
+ }
66476
67397
  };
66477
67398
  function runnerArgs(rest) {
66478
67399
  const publishOnly = rest.includes("--publish-only");
@@ -66514,10 +67435,29 @@ async function run(argv, deps = DEFAULT_DEPS) {
66514
67435
  `);
66515
67436
  return;
66516
67437
  }
67438
+ const commandUsage = COMMAND_USAGE[command];
67439
+ if (commandUsage !== undefined && (rest.includes("--help") || rest.includes("-h"))) {
67440
+ process.stdout.write(commandUsage);
67441
+ return;
67442
+ }
67443
+ if (command === "login" || command === "logout" || command === "whoami") {
67444
+ const loginDeps = {
67445
+ backend: defaultSessionBackend(),
67446
+ hub: hubValue(rest),
67447
+ write: (t) => process.stdout.write(t)
67448
+ };
67449
+ if (command === "login")
67450
+ await runLogin(rest, loginDeps);
67451
+ else if (command === "logout")
67452
+ await runLogout(rest, loginDeps);
67453
+ else
67454
+ await runWhoami(rest, loginDeps);
67455
+ return;
67456
+ }
66517
67457
  if (command === "sell") {
66518
67458
  const mapped = runnerArgs(rest);
66519
67459
  if (!mapped.ok)
66520
- return refuse2(mapped.error);
67460
+ return refuse3(mapped.error);
66521
67461
  await deps.runner(mapped.args);
66522
67462
  return;
66523
67463
  }
@@ -66536,20 +67476,29 @@ async function run(argv, deps = DEFAULT_DEPS) {
66536
67476
  try {
66537
67477
  loaded = await deps.loadMcp();
66538
67478
  } catch {
66539
- return refuse2("countersign mcp could not load its MCP server.");
67479
+ return refuse3("countersign mcp could not load its MCP server.");
66540
67480
  }
66541
67481
  const main4 = isRecord10(loaded) ? loaded.main : undefined;
66542
67482
  if (typeof main4 !== "function")
66543
- return refuse2("The MCP server has no main(argv) export.");
67483
+ return refuse3("The MCP server has no main(argv) export.");
66544
67484
  await main4(rest);
66545
67485
  return;
66546
67486
  }
66547
- return refuse2(`countersign: unknown command '${command}'.`);
67487
+ return refuse3(`countersign: unknown command '${command}'.`);
66548
67488
  }
66549
67489
  function setArgv(args2) {
66550
67490
  process.argv = [process.argv[0] ?? "node", "countersign", ...args2];
66551
67491
  }
66552
- function refuse2(message) {
67492
+ function needsSession() {
67493
+ const [command, verb] = process.argv.slice(2);
67494
+ return command === "buy" || command === "journal" || command === "envelope" && verb === "set";
67495
+ }
67496
+ function requireLogin() {
67497
+ process.stderr.write(`${NOT_SIGNED_IN2}
67498
+ `);
67499
+ process.exitCode = 1;
67500
+ }
67501
+ function refuse3(message) {
66553
67502
  process.stderr.write(`${message}
66554
67503
 
66555
67504
  ${USAGE2}`);
@@ -66566,4 +67515,4 @@ run(process.argv.slice(2)).catch((error62) => {
66566
67515
  process.exitCode = 1;
66567
67516
  });
66568
67517
 
66569
- //# debugId=B268FD45CC002A6364756E2164756E21
67518
+ //# debugId=2C47F561789D7EFC64756E2164756E21