@countersign-hub/cli 0.1.0 → 0.1.1

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,161 @@ var init_payer2 = __esm(() => {
61269
61296
  init_known_rails();
61270
61297
  });
61271
61298
 
61299
+ // packages/buyer/src/remote-signer.ts
61300
+ function assertHubOrigin(hub) {
61301
+ let url2;
61302
+ try {
61303
+ url2 = new URL(hub);
61304
+ } catch {
61305
+ throw new Error(HTTPS_ONLY);
61306
+ }
61307
+ if (url2.protocol === "https:")
61308
+ return;
61309
+ if (url2.protocol === "http:" && LOCAL_HOSTS.has(url2.hostname))
61310
+ return;
61311
+ throw new Error(HTTPS_ONLY);
61312
+ }
61313
+ function decimalStrings(value) {
61314
+ if (typeof value === "bigint")
61315
+ return value.toString(10);
61316
+ if (Array.isArray(value))
61317
+ return value.map((entry) => decimalStrings(entry));
61318
+ if (value !== null && typeof value === "object") {
61319
+ const copy = {};
61320
+ for (const [key, entry] of Object.entries(value))
61321
+ copy[key] = decimalStrings(entry);
61322
+ return copy;
61323
+ }
61324
+ return value;
61325
+ }
61326
+ function messageOf4(body) {
61327
+ if (body === null || typeof body !== "object")
61328
+ return;
61329
+ const message = body.message;
61330
+ return typeof message === "string" ? message : undefined;
61331
+ }
61332
+ function remoteAccount(session, opts = {}) {
61333
+ assertHubOrigin(session.hub);
61334
+ const fetchImpl = opts.fetch ?? fetch;
61335
+ const timeoutMs = opts.timeoutMs ?? DEFAULT_TIMEOUT_MS;
61336
+ const signTypedData4 = async (parameters) => {
61337
+ const typedData = parameters;
61338
+ const types3 = {};
61339
+ for (const [name, fields] of Object.entries(typedData.types)) {
61340
+ if (name !== "EIP712Domain")
61341
+ types3[name] = fields;
61342
+ }
61343
+ const requestBody = JSON.stringify({
61344
+ kind: "typed-data",
61345
+ typedData: {
61346
+ domain: decimalStrings(typedData.domain),
61347
+ types: decimalStrings(types3),
61348
+ primaryType: typedData.primaryType,
61349
+ message: decimalStrings(typedData.message)
61350
+ }
61351
+ });
61352
+ const controller = new AbortController;
61353
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
61354
+ let status;
61355
+ let body;
61356
+ try {
61357
+ const response = await fetchImpl(`${session.hub.replace(/\/+$/, "")}/cli/sign`, {
61358
+ method: "POST",
61359
+ headers: {
61360
+ authorization: `Bearer ${session.token}`,
61361
+ "content-type": "application/json"
61362
+ },
61363
+ body: requestBody,
61364
+ signal: controller.signal
61365
+ });
61366
+ status = response.status;
61367
+ body = await response.json().catch(() => {
61368
+ return;
61369
+ });
61370
+ } catch {
61371
+ throw new Error(NOT_REACHED);
61372
+ } finally {
61373
+ clearTimeout(timer);
61374
+ }
61375
+ if (status === 200) {
61376
+ const result = body;
61377
+ if (typeof result?.address !== "string" || result.address.toLowerCase() !== session.address.toLowerCase()) {
61378
+ throw new Error("The hub signed with another wallet.");
61379
+ }
61380
+ const signature = result.signature;
61381
+ if (typeof signature !== "string" || !SIGNATURE2.test(signature)) {
61382
+ throw new Error(`The hub could not sign (${status}).`);
61383
+ }
61384
+ const signer = await recoverTypedDataAddress2({
61385
+ ...parameters,
61386
+ signature
61387
+ });
61388
+ if (signer.toLowerCase() !== session.address.toLowerCase()) {
61389
+ throw new Error("The hub signed with another wallet.");
61390
+ }
61391
+ return signature;
61392
+ }
61393
+ if (status === 401)
61394
+ throw new Error(NOT_SIGNED_IN3);
61395
+ if (status === 403) {
61396
+ const message = messageOf4(body) ?? "the hub refused the request.";
61397
+ throw new Error(`Refused by the hub: ${message}`);
61398
+ }
61399
+ if (status === 502)
61400
+ throw new Error("The hub could not sign (privy-failed).");
61401
+ throw new Error(`The hub could not sign (${status}).`);
61402
+ };
61403
+ const unsupported = async () => {
61404
+ throw new Error(TYPED_DATA_ONLY);
61405
+ };
61406
+ const source = {
61407
+ address: session.address,
61408
+ signMessage: unsupported,
61409
+ signTransaction: unsupported,
61410
+ signTypedData: signTypedData4
61411
+ };
61412
+ return toAccount(source);
61413
+ }
61414
+ async function sessionFromEnv(env = process.env, opts = {}) {
61415
+ const token = env.CS_SESSION;
61416
+ if (token === undefined || token === "")
61417
+ return;
61418
+ const hub = env.CS_HUB ?? DEFAULT_HUB;
61419
+ delete env.CS_SESSION;
61420
+ assertHubOrigin(hub);
61421
+ const fetchImpl = opts.fetch ?? fetch;
61422
+ let status;
61423
+ let body;
61424
+ try {
61425
+ const response = await fetchImpl(`${hub.replace(/\/+$/, "")}/cli/session`, {
61426
+ method: "GET",
61427
+ headers: { authorization: `Bearer ${token}` }
61428
+ });
61429
+ status = response.status;
61430
+ body = await response.json().catch(() => {
61431
+ return;
61432
+ });
61433
+ } catch {
61434
+ throw new Error("Could not read the countersign session (no response).");
61435
+ }
61436
+ if (status === 401)
61437
+ return;
61438
+ if (status !== 200)
61439
+ throw new Error(`Could not read the countersign session (${status}).`);
61440
+ const address = body?.address;
61441
+ if (typeof address !== "string" || !isAddress(address)) {
61442
+ throw new Error("The hub returned no valid wallet address.");
61443
+ }
61444
+ return { hub, token, address };
61445
+ }
61446
+ 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;
61447
+ var init_remote_signer = __esm(() => {
61448
+ init__esm();
61449
+ init_accounts();
61450
+ SIGNATURE2 = /^0x[0-9a-fA-F]{130}$/;
61451
+ LOCAL_HOSTS = new Set(["localhost", "127.0.0.1", "[::1]"]);
61452
+ });
61453
+
61272
61454
  // packages/buyer/src/resume.ts
61273
61455
  async function readJobStatus(rail2, jobId) {
61274
61456
  const client = createPublicClient({ transport: http(rail2.rpcUrl) });
@@ -61406,27 +61588,27 @@ async function acceptJob(a) {
61406
61588
  const slip2 = heldSlip(a, jobId);
61407
61589
  const known = a.known ?? KNOWN_RAILS;
61408
61590
  if (slip2.hash.toLowerCase() !== slipHash(slip2.body).toLowerCase())
61409
- refuse("The slip's hash does not match its body.");
61591
+ refuse2("The slip's hash does not match its body.");
61410
61592
  let seller;
61411
61593
  try {
61412
61594
  seller = await slipSigner(slip2);
61413
61595
  } catch {
61414
- refuse("The slip's seller signature does not parse.");
61596
+ refuse2("The slip's seller signature does not parse.");
61415
61597
  }
61416
61598
  if (getAddress(seller) !== getAddress(slip2.body.seller))
61417
- refuse("The slip is not signed by its seller.");
61599
+ refuse2("The slip is not signed by its seller.");
61418
61600
  if (slip2.body.jobId.toLowerCase() !== jobId)
61419
- refuse("The slip is for another job.");
61601
+ refuse2("The slip is for another job.");
61420
61602
  if (getAddress(slip2.body.buyer) !== getAddress(a.signer.address))
61421
- refuse("The slip names another buyer.");
61603
+ refuse2("The slip names another buyer.");
61422
61604
  if (!slip2.body.escrow || slip2.body.chainId === undefined)
61423
- refuse("The slip is not for an escrow job.");
61605
+ refuse2("The slip is not for an escrow job.");
61424
61606
  const rail2 = pinned(known, slip2.body.chainId, slip2.body.escrow);
61425
61607
  if (!rail2)
61426
- refuse("The slip's escrow is not one this agent pins.");
61608
+ refuse2("The slip's escrow is not one this agent pins.");
61427
61609
  const terms = slip2.payment?.payload?.terms;
61428
61610
  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.");
61611
+ refuse2("The slip's countersigned terms do not derive this job on that escrow.");
61430
61612
  }
61431
61613
  const signature = await a.signer.signTypedData(acceptTypedData(rail2.chainId, rail2.escrow, jobId, slip2.hash));
61432
61614
  await a.hub.acceptJob(jobId, signature);
@@ -61438,7 +61620,7 @@ async function disputeJob(a) {
61438
61620
  const known = a.known ?? KNOWN_RAILS;
61439
61621
  const entries = entriesFor(a.journal, jobId);
61440
61622
  if (entries.length === 0)
61441
- refuse("This agent's journal has no such job.");
61623
+ refuse2("This agent's journal has no such job.");
61442
61624
  const { rail: rail2, status } = await fundedOn(a, jobId, entries, known);
61443
61625
  const signature = await a.signer.signTypedData(disputeTypedData(rail2.chainId, rail2.escrow, jobId, status));
61444
61626
  let transaction;
@@ -61465,7 +61647,7 @@ async function disputeJob(a) {
61465
61647
  async function submitDispute(a, rail2, jobId, status) {
61466
61648
  const account = a.account;
61467
61649
  if (!account)
61468
- refuse("The agent key is not available to send the dispute itself.");
61650
+ refuse2("The agent key is not available to send the dispute itself.");
61469
61651
  return sendDispute(rail2, jobId, status, account, a.transport);
61470
61652
  }
61471
61653
  async function sendDispute(rail2, jobId, status, account, transport) {
@@ -61497,7 +61679,7 @@ async function fundedOn(a, jobId, entries, known) {
61497
61679
  if (recorded) {
61498
61680
  const rail2 = pinned(known, recorded.chainId, recorded.escrow);
61499
61681
  if (!rail2)
61500
- refuse("The job's escrow is not one this agent pins.");
61682
+ refuse2("The job's escrow is not one this agent pins.");
61501
61683
  return { rail: rail2, status: await disputeStatus(a, rail2, jobId, entries) };
61502
61684
  }
61503
61685
  const network = entries.find((e) => e.network)?.network;
@@ -61513,7 +61695,7 @@ async function fundedOn(a, jobId, entries, known) {
61513
61695
  } catch {}
61514
61696
  }
61515
61697
  if (holding.length !== 1)
61516
- refuse("No single pinned escrow holds this job open for this agent.");
61698
+ refuse2("No single pinned escrow holds this job open for this agent.");
61517
61699
  return holding[0];
61518
61700
  }
61519
61701
  async function disputeStatus(a, rail2, jobId, entries) {
@@ -61529,11 +61711,11 @@ async function disputeStatus(a, rail2, jobId, entries) {
61529
61711
  function heldSlip(a, jobId) {
61530
61712
  const entry = [...entriesFor(a.journal, jobId)].reverse().find((e) => e.slipHash);
61531
61713
  if (!entry?.slipHash)
61532
- refuse("This agent holds no slip for this job.");
61714
+ refuse2("This agent holds no slip for this job.");
61533
61715
  try {
61534
61716
  return a.state.loadSlip(entry.slipHash);
61535
61717
  } catch {
61536
- refuse("This agent holds no slip for this job.");
61718
+ refuse2("This agent holds no slip for this job.");
61537
61719
  }
61538
61720
  }
61539
61721
  function entriesFor(journal, jobId) {
@@ -61554,7 +61736,7 @@ function record3(a, jobId, entries) {
61554
61736
  function pinned(known, chainId, escrow2) {
61555
61737
  return known.find((rail2) => rail2.chainId === chainId && rail2.escrow.toLowerCase() === escrow2.toLowerCase());
61556
61738
  }
61557
- function refuse(message) {
61739
+ function refuse2(message) {
61558
61740
  throw new ReviewError(`Refused. ${message}`);
61559
61741
  }
61560
61742
  var ReviewError, DISPUTE_ABI, DISPUTABLE;
@@ -61584,65 +61766,65 @@ var init_review = __esm(() => {
61584
61766
 
61585
61767
  // packages/buyer/src/state.ts
61586
61768
  import {
61587
- chmodSync,
61588
- closeSync as closeSync2,
61589
- existsSync as existsSync4,
61590
- mkdirSync as mkdirSync3,
61591
- openSync as openSync2,
61592
- readFileSync as readFileSync5,
61769
+ chmodSync as chmodSync2,
61770
+ closeSync as closeSync3,
61771
+ existsSync as existsSync5,
61772
+ mkdirSync as mkdirSync4,
61773
+ openSync as openSync3,
61774
+ readFileSync as readFileSync6,
61593
61775
  unlinkSync as unlinkSync2,
61594
61776
  writeFileSync as writeFileSync2,
61595
- writeSync as writeSync2
61777
+ writeSync as writeSync3
61596
61778
  } from "node:fs";
61597
- import { homedir as homedir2 } from "node:os";
61598
- import { join as join3 } from "node:path";
61779
+ import { homedir as homedir3 } from "node:os";
61780
+ import { join as join4 } from "node:path";
61599
61781
 
61600
61782
  class State {
61601
61783
  dir;
61602
61784
  constructor(dir) {
61603
- this.dir = dir ?? join3(homedir2(), ".countersign");
61785
+ this.dir = dir ?? join4(homedir3(), ".countersign");
61604
61786
  }
61605
61787
  get envelopePath() {
61606
- return join3(this.dir, "envelope.json");
61788
+ return join4(this.dir, "envelope.json");
61607
61789
  }
61608
61790
  get journalPath() {
61609
- return join3(this.dir, "journal.jsonl");
61791
+ return join4(this.dir, "journal.jsonl");
61610
61792
  }
61611
61793
  get railsPath() {
61612
- return join3(this.dir, "rails.json");
61794
+ return join4(this.dir, "rails.json");
61613
61795
  }
61614
61796
  get consumedApprovalsPath() {
61615
- return join3(this.dir, "approvals-consumed.json");
61797
+ return join4(this.dir, "approvals-consumed.json");
61616
61798
  }
61617
61799
  slipPath(hash4) {
61618
- return join3(this.dir, "slips", `${hash4}.json`);
61800
+ return join4(this.dir, "slips", `${hash4}.json`);
61619
61801
  }
61620
61802
  loadEnvelope() {
61621
- if (!existsSync4(this.envelopePath)) {
61803
+ if (!existsSync5(this.envelopePath)) {
61622
61804
  throw new Error(`No envelope at ${this.envelopePath}. Create one with: countersign envelope set --cap <usd> --per-merchant <usd>`);
61623
61805
  }
61624
- return JSON.parse(readFileSync5(this.envelopePath, "utf8"));
61806
+ return JSON.parse(readFileSync6(this.envelopePath, "utf8"));
61625
61807
  }
61626
61808
  saveEnvelope(env) {
61627
- mkdirSync3(this.dir, { recursive: true });
61809
+ mkdirSync4(this.dir, { recursive: true });
61628
61810
  writeFileSync2(this.envelopePath, `${JSON.stringify(env, null, 2)}
61629
61811
  `);
61630
61812
  }
61631
61813
  loadRails() {
61632
- if (!existsSync4(this.railsPath))
61814
+ if (!existsSync5(this.railsPath))
61633
61815
  return [];
61634
- const value = JSON.parse(readFileSync5(this.railsPath, "utf8"));
61816
+ const value = JSON.parse(readFileSync6(this.railsPath, "utf8"));
61635
61817
  if (!Array.isArray(value))
61636
61818
  throw new Error(`${this.railsPath} must hold a JSON array of known rails.`);
61637
61819
  return value;
61638
61820
  }
61639
61821
  loadSlip(hash4) {
61640
- return JSON.parse(readFileSync5(this.slipPath(hash4), "utf8"));
61822
+ return JSON.parse(readFileSync6(this.slipPath(hash4), "utf8"));
61641
61823
  }
61642
61824
  loadConsumedApprovals() {
61643
- if (!existsSync4(this.consumedApprovalsPath))
61825
+ if (!existsSync5(this.consumedApprovalsPath))
61644
61826
  return [];
61645
- const value = JSON.parse(readFileSync5(this.consumedApprovalsPath, "utf8"));
61827
+ const value = JSON.parse(readFileSync6(this.consumedApprovalsPath, "utf8"));
61646
61828
  if (!Array.isArray(value)) {
61647
61829
  throw new Error(`${this.consumedApprovalsPath} must hold a JSON array of approval ids.`);
61648
61830
  }
@@ -61660,10 +61842,10 @@ class State {
61660
61842
  ids.push(id);
61661
61843
  writeFileSync2(this.consumedApprovalsPath, `${JSON.stringify(ids, null, 2)}
61662
61844
  `, { mode: 384 });
61663
- chmodSync(this.consumedApprovalsPath, 384);
61845
+ chmodSync2(this.consumedApprovalsPath, 384);
61664
61846
  return true;
61665
61847
  } finally {
61666
- closeSync2(fd);
61848
+ closeSync3(fd);
61667
61849
  try {
61668
61850
  unlinkSync2(this.consumedLockPath);
61669
61851
  } catch {}
@@ -61673,12 +61855,12 @@ class State {
61673
61855
  return `${this.consumedApprovalsPath}.lock`;
61674
61856
  }
61675
61857
  acquireConsumedLock() {
61676
- mkdirSync3(this.dir, { recursive: true });
61858
+ mkdirSync4(this.dir, { recursive: true });
61677
61859
  const deadline = Date.now() + 5000;
61678
61860
  for (;; ) {
61679
61861
  try {
61680
- const fd = openSync2(this.consumedLockPath, "wx");
61681
- writeSync2(fd, String(process.pid));
61862
+ const fd = openSync3(this.consumedLockPath, "wx");
61863
+ writeSync3(fd, String(process.pid));
61682
61864
  return fd;
61683
61865
  } catch (e) {
61684
61866
  if (e.code !== "EEXIST")
@@ -61699,7 +61881,7 @@ class State {
61699
61881
  consumedLockOwnerIsGone() {
61700
61882
  let pid;
61701
61883
  try {
61702
- pid = Number(readFileSync5(this.consumedLockPath, "utf8").trim());
61884
+ pid = Number(readFileSync6(this.consumedLockPath, "utf8").trim());
61703
61885
  } catch {
61704
61886
  return false;
61705
61887
  }
@@ -61713,7 +61895,7 @@ class State {
61713
61895
  }
61714
61896
  }
61715
61897
  saveSlip(slip2) {
61716
- mkdirSync3(join3(this.dir, "slips"), { recursive: true });
61898
+ mkdirSync4(join4(this.dir, "slips"), { recursive: true });
61717
61899
  writeFileSync2(this.slipPath(slip2.hash), `${JSON.stringify(slip2, null, 2)}
61718
61900
  `);
61719
61901
  }
@@ -61725,10 +61907,13 @@ var init_state = __esm(() => {
61725
61907
 
61726
61908
  // packages/buyer/src/cli.ts
61727
61909
  var exports_cli = {};
61910
+ __export(exports_cli, {
61911
+ agentAccount: () => agentAccount
61912
+ });
61728
61913
  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) {
61914
+ import { chmodSync as chmodSync3, mkdirSync as mkdirSync5, writeFileSync as writeFileSync3 } from "node:fs";
61915
+ import { join as join5 } from "node:path";
61916
+ function parse7(argv) {
61732
61917
  const pos = [];
61733
61918
  const flags = new Map;
61734
61919
  for (let i = 0;i < argv.length; i++) {
@@ -61750,14 +61935,21 @@ function parse6(argv) {
61750
61935
  }
61751
61936
  return { pos, flags };
61752
61937
  }
61753
- function agentAccount() {
61938
+ async function agentAccount() {
61939
+ cachedAccount ??= resolveAccount();
61940
+ return await cachedAccount;
61941
+ }
61942
+ async function resolveAccount() {
61754
61943
  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}`);
61944
+ if (key)
61945
+ return privateKeyToAccount(key.startsWith("0x") ? key : `0x${key}`);
61946
+ const session = await sessionFromEnv(process.env);
61947
+ if (session !== undefined)
61948
+ return remoteAccount(session);
61949
+ throw new Error(NOT_SIGNED_IN3);
61758
61950
  }
61759
61951
  async function main2() {
61760
- const { pos, flags } = parse6(process.argv.slice(2));
61952
+ const { pos, flags } = parse7(process.argv.slice(2));
61761
61953
  const one = (n) => flags.get(n)?.[0];
61762
61954
  let hub = new HubClient(one("hub") ?? "http://127.0.0.1:8787");
61763
61955
  const state = new State(one("state"));
@@ -61801,7 +61993,7 @@ async function main2() {
61801
61993
  throw new Error("Usage: buy <offerHash|callUrl> --input '<json>' [--approve] [--rail <id>] [--approvals hub]");
61802
61994
  const rail2 = parseRail(one("rail"));
61803
61995
  const approvals = parseApprovals(one("approvals"));
61804
- const account = agentAccount();
61996
+ const account = await agentAccount();
61805
61997
  const result = await buy({
61806
61998
  hub,
61807
61999
  offerHash: hash4,
@@ -61830,9 +62022,9 @@ async function main2() {
61830
62022
  try {
61831
62023
  env = state.loadEnvelope();
61832
62024
  } catch {
61833
- env = { v: 1, agent: agentAccount().address, cap: "0", perMerchant: "0", allowlist: [], revoked: false };
62025
+ env = { v: 1, agent: (await agentAccount()).address, cap: "0", perMerchant: "0", allowlist: [], revoked: false };
61834
62026
  }
61835
- env.agent = agentAccount().address;
62027
+ env.agent = (await agentAccount()).address;
61836
62028
  const cap = one("cap");
61837
62029
  if (cap !== undefined)
61838
62030
  env.cap = parseUnits(cap, 6).toString(10);
@@ -61854,7 +62046,7 @@ async function main2() {
61854
62046
  } else if ((cmd === "accept" || cmd === "dispute") && pos[1] !== undefined) {
61855
62047
  if (!/^0x[0-9a-fA-F]{64}$/.test(pos[1]))
61856
62048
  throw new Error(`Usage: ${cmd} <jobId>`);
61857
- const account = agentAccount();
62049
+ const account = await agentAccount();
61858
62050
  const args = { hub, journal, state, known, signer: account, jobId: pos[1], now: Math.floor(Date.now() / 1000) };
61859
62051
  if (cmd === "accept") {
61860
62052
  const done = await acceptJob(args);
@@ -61870,7 +62062,7 @@ async function main2() {
61870
62062
  for (const e of journal.entries())
61871
62063
  console.log(JSON.stringify(e));
61872
62064
  } else if (cmd === "publish-envelope") {
61873
- const account = agentAccount();
62065
+ const account = await agentAccount();
61874
62066
  const ok2 = await publishEnvelope({
61875
62067
  hub,
61876
62068
  account,
@@ -61880,12 +62072,12 @@ async function main2() {
61880
62072
  });
61881
62073
  console.log(ok2 ? "Envelope published." : "Envelope publish failed.");
61882
62074
  } else if (cmd === "openai") {
61883
- const account = agentAccount();
62075
+ const account = await agentAccount();
61884
62076
  const token = randomBytes2(32).toString("hex");
61885
- mkdirSync4(state.dir, { recursive: true });
61886
- const tokenPath = join4(state.dir, "openai.token");
62077
+ mkdirSync5(state.dir, { recursive: true });
62078
+ const tokenPath = join5(state.dir, "openai.token");
61887
62079
  writeFileSync3(tokenPath, token, { mode: 384 });
61888
- chmodSync2(tokenPath, 384);
62080
+ chmodSync3(tokenPath, 384);
61889
62081
  const server = startOpenAIServer({
61890
62082
  hub,
61891
62083
  account,
@@ -61900,7 +62092,7 @@ async function main2() {
61900
62092
  console.log(`${server.url}/v1`);
61901
62093
  console.log(tokenPath);
61902
62094
  } else if (cmd === "resume") {
61903
- const mirror = flags.has("hub") ? { account: agentAccount(), envelope: state.loadEnvelope() } : undefined;
62095
+ const mirror = flags.has("hub") ? { account: await agentAccount(), envelope: state.loadEnvelope() } : undefined;
61904
62096
  const rows = await resume({
61905
62097
  hub,
61906
62098
  journal,
@@ -61914,7 +62106,7 @@ async function main2() {
61914
62106
  console.log(`${r.jobId} ${r.status}${r.appended ? ` -> ${r.appended}` : ""}`);
61915
62107
  } else {
61916
62108
  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.");
62109
+ console.log("Flags: --hub <url> --state <dir>. Sign in once with: countersign login.");
61918
62110
  }
61919
62111
  }
61920
62112
  function parseApprovals(raw) {
@@ -61943,7 +62135,7 @@ function parseRail(raw) {
61943
62135
  throw new Error(`Unknown rail ${raw}.`);
61944
62136
  return raw;
61945
62137
  }
61946
- var BOOLEAN, usd2 = (atomic) => formatUnits(BigInt(atomic), 6);
62138
+ var BOOLEAN, cachedAccount, usd2 = (atomic) => formatUnits(BigInt(atomic), 6);
61947
62139
  var init_cli = __esm(() => {
61948
62140
  init_accounts();
61949
62141
  init_src();
@@ -61955,6 +62147,7 @@ var init_cli = __esm(() => {
61955
62147
  init_mirror();
61956
62148
  init_server();
61957
62149
  init_payer2();
62150
+ init_remote_signer();
61958
62151
  init_resume();
61959
62152
  init_review();
61960
62153
  init_state();
@@ -61967,7 +62160,7 @@ var init_cli = __esm(() => {
61967
62160
 
61968
62161
  // packages/verify/src/cli.ts
61969
62162
  var exports_cli2 = {};
61970
- import { readFileSync as readFileSync6 } from "node:fs";
62163
+ import { readFileSync as readFileSync7 } from "node:fs";
61971
62164
  var args, file2, rpcAt, rpcUrl, mirrorAt, mirrorUrl, slip2, verdict, verdictLine;
61972
62165
  var init_cli2 = __esm(async () => {
61973
62166
  init_verify();
@@ -61982,7 +62175,7 @@ var init_cli2 = __esm(async () => {
61982
62175
  process.exit(2);
61983
62176
  }
61984
62177
  try {
61985
- slip2 = JSON.parse(readFileSync6(file2, "utf8"));
62178
+ slip2 = JSON.parse(readFileSync7(file2, "utf8"));
61986
62179
  } catch (e) {
61987
62180
  console.error(`cannot read ${file2}: ${e.message}`);
61988
62181
  process.exit(2);
@@ -62079,6 +62272,7 @@ var init_src4 = __esm(() => {
62079
62272
  init_mirror();
62080
62273
  init_payer2();
62081
62274
  init_resume();
62275
+ init_remote_signer();
62082
62276
  init_review();
62083
62277
  init_state();
62084
62278
  init_terms();
@@ -63381,7 +63575,7 @@ var init_mcp = __esm(() => {
63381
63575
  });
63382
63576
 
63383
63577
  // packages/mcp/src/server.ts
63384
- import { existsSync as existsSync5 } from "node:fs";
63578
+ import { existsSync as existsSync6 } from "node:fs";
63385
63579
  function assertHash(value, what) {
63386
63580
  if (!HASH.test(value))
63387
63581
  throw new Error(`Refused. ${what} must be 0x followed by 64 hex characters.`);
@@ -63408,11 +63602,16 @@ function searchText(offer2) {
63408
63602
  `).toLowerCase();
63409
63603
  }
63410
63604
  function createCountersignMcpServer(deps) {
63411
- const server = new McpServer({ name: "countersign", version: "0.1.0" });
63605
+ const server = new McpServer({ name: "countersign", version: "0.1.1" });
63412
63606
  const clock = deps.now ?? (() => Math.floor(Date.now() / 1000));
63413
63607
  const verify2 = deps.verify ?? verifySlip;
63608
+ const gate = () => {
63609
+ if (deps.account !== undefined)
63610
+ return;
63611
+ return failure3(new Error(deps.problem === undefined ? NOT_SIGNED_IN3 : `${NOT_SIGNED_IN3} (${deps.problem})`));
63612
+ };
63414
63613
  const loadSlip = async (hash4) => {
63415
- if (existsSync5(deps.state.slipPath(hash4)))
63614
+ if (existsSync6(deps.state.slipPath(hash4)))
63416
63615
  return deps.state.loadSlip(hash4);
63417
63616
  const base2 = deps.hubUrl.replace(/\/+$/, "");
63418
63617
  const response = await fetch(`${base2}/slips/${hash4}`);
@@ -63428,6 +63627,9 @@ function createCountersignMcpServer(deps) {
63428
63627
  limit: exports_external.number().int().optional()
63429
63628
  }
63430
63629
  }, async ({ query, limit }) => {
63630
+ const refused = gate();
63631
+ if (refused !== undefined)
63632
+ return refused;
63431
63633
  try {
63432
63634
  const listings = await deps.hub.listOffers();
63433
63635
  const needle = query?.toLowerCase();
@@ -63462,6 +63664,9 @@ function createCountersignMcpServer(deps) {
63462
63664
  offerHash: exports_external.string()
63463
63665
  }
63464
63666
  }, async ({ offerHash: offerHash2 }) => {
63667
+ const refused = gate();
63668
+ if (refused !== undefined)
63669
+ return refused;
63465
63670
  try {
63466
63671
  assertHash(offerHash2, "offerHash");
63467
63672
  const listing = await deps.hub.getOffer(offerHash2);
@@ -63479,16 +63684,17 @@ function createCountersignMcpServer(deps) {
63479
63684
  });
63480
63685
  server.registerTool("buy", {
63481
63686
  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.",
63687
+ 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
63688
  inputSchema: {
63484
63689
  offerHash: exports_external.string(),
63485
63690
  input: exports_external.unknown(),
63486
63691
  rail: exports_external.string().optional()
63487
63692
  }
63488
63693
  }, async ({ offerHash: offerHash2, input: input2, rail: rail2 }) => {
63694
+ const refused = gate();
63695
+ if (refused !== undefined)
63696
+ return refused;
63489
63697
  const account = deps.account;
63490
- if (account === undefined)
63491
- return failure3(new Error(NO_KEY));
63492
63698
  if (rail2 !== undefined && !RAIL_IDS.includes(rail2)) {
63493
63699
  return failure3(new Error(`Unknown rail ${rail2}.`));
63494
63700
  }
@@ -63529,6 +63735,9 @@ function createCountersignMcpServer(deps) {
63529
63735
  slipHash: exports_external.string().optional()
63530
63736
  }
63531
63737
  }, async ({ slip: slip3, slipHash: slipHash2 }) => {
63738
+ const refused = gate();
63739
+ if (refused !== undefined)
63740
+ return refused;
63532
63741
  if (slip3 === undefined === (slipHash2 === undefined)) {
63533
63742
  return failure3(new Error("Pass exactly one of slip or slipHash."));
63534
63743
  }
@@ -63546,6 +63755,9 @@ function createCountersignMcpServer(deps) {
63546
63755
  description: "Read the agent's envelope and what the journal says it has spent.",
63547
63756
  inputSchema: {}
63548
63757
  }, async () => {
63758
+ const refused = gate();
63759
+ if (refused !== undefined)
63760
+ return refused;
63549
63761
  try {
63550
63762
  return text({ envelope: deps.state.loadEnvelope(), spent: deps.journal.spend() });
63551
63763
  } catch (error62) {
@@ -63559,6 +63771,9 @@ function createCountersignMcpServer(deps) {
63559
63771
  limit: exports_external.number().int().optional()
63560
63772
  }
63561
63773
  }, async ({ limit }) => {
63774
+ const refused = gate();
63775
+ if (refused !== undefined)
63776
+ return refused;
63562
63777
  try {
63563
63778
  const entries = deps.journal.entries();
63564
63779
  const count = limit ?? 20;
@@ -63569,7 +63784,7 @@ function createCountersignMcpServer(deps) {
63569
63784
  });
63570
63785
  return server;
63571
63786
  }
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;
63787
+ var HASH_MISMATCH = "Refused. The hub served an offer that does not match the hash asked for.", HASH;
63573
63788
  var init_server6 = __esm(() => {
63574
63789
  init_mcp();
63575
63790
  init_zod();
@@ -63582,14 +63797,15 @@ var init_server6 = __esm(() => {
63582
63797
  // packages/mcp/src/main.ts
63583
63798
  var exports_main = {};
63584
63799
  __export(exports_main, {
63800
+ resolveAccount: () => resolveAccount2,
63585
63801
  main: () => main3
63586
63802
  });
63587
63803
  function fail(message) {
63588
63804
  console.error(message);
63589
63805
  process.exit(1);
63590
63806
  }
63591
- function parse7(argv) {
63592
- const config2 = { hub: process.env.CS_HUB ?? DEFAULT_HUB };
63807
+ function parse8(argv) {
63808
+ const config2 = { hub: process.env.CS_HUB ?? DEFAULT_HUB2 };
63593
63809
  if (process.env.CS_RPC_URL !== undefined)
63594
63810
  config2.rpcUrl = process.env.CS_RPC_URL;
63595
63811
  let help = false;
@@ -63615,18 +63831,45 @@ function parse7(argv) {
63615
63831
  }
63616
63832
  return { help, config: config2 };
63617
63833
  }
63618
- function agentAccount2() {
63619
- const key = process.env.CS_BUYER_KEY;
63834
+ function ciAccount(env) {
63835
+ const key = env.CS_BUYER_KEY;
63620
63836
  if (!key)
63621
- return;
63837
+ return {};
63622
63838
  try {
63623
- return privateKeyToAccount(key.startsWith("0x") ? key : `0x${key}`);
63839
+ return { account: privateKeyToAccount(key.startsWith("0x") ? key : `0x${key}`) };
63624
63840
  } catch {
63625
- return fail("CS_BUYER_KEY is not a valid private key.");
63841
+ return { problem: "The configured signer is not valid." };
63626
63842
  }
63627
63843
  }
63628
- async function main3(argv) {
63629
- const { help, config: config2 } = parse7(argv);
63844
+ function sessionProblem(error62) {
63845
+ const message = error62 instanceof Error ? error62.message : String(error62);
63846
+ const firstLine3 = message.split(`
63847
+ `)[0] ?? "";
63848
+ return { problem: `Could not load the countersign session: ${firstLine3}` };
63849
+ }
63850
+ async function resolveAccount2(options, env) {
63851
+ const sessionEnv = { CS_SESSION: env.CS_SESSION, CS_HUB: env.CS_HUB };
63852
+ delete env.CS_SESSION;
63853
+ if (options.loadAccount !== undefined) {
63854
+ try {
63855
+ const account = await options.loadAccount();
63856
+ return account === undefined ? {} : { account };
63857
+ } catch (error62) {
63858
+ return sessionProblem(error62);
63859
+ }
63860
+ }
63861
+ let session;
63862
+ try {
63863
+ session = await sessionFromEnv(sessionEnv);
63864
+ } catch (error62) {
63865
+ return sessionProblem(error62);
63866
+ }
63867
+ if (session !== undefined)
63868
+ return { account: remoteAccount(session) };
63869
+ return ciAccount(env);
63870
+ }
63871
+ async function main3(argv, options = {}) {
63872
+ const { help, config: config2 } = parse8(argv);
63630
63873
  if (help) {
63631
63874
  console.log(USAGE);
63632
63875
  return;
@@ -63639,11 +63882,16 @@ async function main3(argv) {
63639
63882
  const state2 = new State(config2.state);
63640
63883
  const journal2 = new Journal(state2.journalPath);
63641
63884
  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}`);
63885
+ const { account, problem } = await resolveAccount2(options, process.env);
63886
+ console.error(`countersign mcp: hub ${hub.url}`);
63887
+ console.error(`countersign mcp: state ${state2.dir}`);
63888
+ if (account !== undefined) {
63889
+ console.error(`countersign mcp: agent ${account.address}`);
63890
+ } else {
63891
+ console.error("countersign mcp: not signed in. Run: npx -y @countersign-hub/cli login");
63892
+ if (problem !== undefined)
63893
+ console.error(`countersign mcp: ${problem}`);
63894
+ }
63647
63895
  const server = createCountersignMcpServer({
63648
63896
  hub,
63649
63897
  state: state2,
@@ -63651,11 +63899,12 @@ async function main3(argv) {
63651
63899
  hubUrl: config2.hub,
63652
63900
  now: () => Math.floor(Date.now() / 1000),
63653
63901
  ...account !== undefined ? { account } : {},
63902
+ ...problem !== undefined ? { problem } : {},
63654
63903
  ...config2.rpcUrl !== undefined ? { rpcUrl: config2.rpcUrl } : {}
63655
63904
  });
63656
63905
  await server.connect(new StdioServerTransport);
63657
63906
  }
63658
- var DEFAULT_HUB = "https://hub-production-e85e.up.railway.app", USAGE;
63907
+ var DEFAULT_HUB2 = "https://hub-production-e85e.up.railway.app", USAGE;
63659
63908
  var init_main = __esm(() => {
63660
63909
  init_accounts();
63661
63910
  init_stdio2();
@@ -63666,17 +63915,18 @@ var init_main = __esm(() => {
63666
63915
  Usage: countersign mcp [--hub <url>] [--state <dir>] [--rpc <url>]
63667
63916
 
63668
63917
  Options:
63669
- --hub <url> The countersign hub (env CS_HUB; default ${DEFAULT_HUB})
63918
+ --hub <url> The countersign hub (env CS_HUB; default ${DEFAULT_HUB2})
63670
63919
  --state <dir> Where the envelope, journal and slips live (default ~/.countersign)
63671
63920
  --rpc <url> The RPC verify_slip reads the chain from (env CS_RPC_URL), e.g. https://rpc.testnet.arc.network
63672
63921
  --help Print this usage and exit
63673
63922
 
63674
- Environment:
63675
- CS_BUYER_KEY The agent's private key; without it, buy refuses.
63676
- CS_NETWORK Must be unset or testnet.
63923
+ Sign in once: npx -y @countersign-hub/cli login. Your agent's wallet is a Privy server wallet that the countersign app controls;
63924
+ this server signs through the hub with your session, and no key is ever on this machine or in any config.
63925
+
63926
+ CS_NETWORK must be unset or testnet.
63677
63927
 
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.`;
63928
+ Install: npm i -g @countersign-hub/cli, then countersign mcp (or npx -y @countersign-hub/cli mcp).
63929
+ The npm packages named @countersign/*, countersign and countersign-mcp belong to other projects: never npx or npm install them.`;
63680
63930
  });
63681
63931
 
63682
63932
  // packages/runner/src/cli.ts
@@ -63957,8 +64207,11 @@ function setTermsArgs(command) {
63957
64207
  }
63958
64208
  async function bondFromEnv(args, log = console.log) {
63959
64209
  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.");
64210
+ if (!key) {
64211
+ throw new Error("Bond commands are not available from a signed-in session yet: they send transactions, and the hub only signs.");
64212
+ }
64213
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
64214
+ throw new Error("The CI signing override in this environment is malformed.");
63962
64215
  }
63963
64216
  await runBond(parseBondArgs(args), key, log);
63964
64217
  }
@@ -65153,12 +65406,183 @@ function isRecord3(value) {
65153
65406
  return typeof value === "object" && value !== null && !Array.isArray(value);
65154
65407
  }
65155
65408
 
65409
+ // packages/runner/src/remote-signer.ts
65410
+ init__esm();
65411
+ var NOT_SIGNED_IN = "Not signed in. Run: npx -y @countersign-hub/cli login";
65412
+ var BONDED_REMOTE_REFUSAL = "A bonded offer cannot be served from a signed-in session yet: the hub does not sign Lock for it.";
65413
+ var SIGNATURE = /^0x[0-9a-fA-F]{130}$/;
65414
+ var ADDRESS = /^0x[0-9a-fA-F]{40}$/;
65415
+ function hubFailure(reason) {
65416
+ return new Error(`The hub could not sign right now (${reason}).`);
65417
+ }
65418
+ var WRONG_WALLET = "The hub signed with a different wallet.";
65419
+ function safeHub(hubUrl) {
65420
+ let url2;
65421
+ try {
65422
+ url2 = new URL(hubUrl);
65423
+ } catch {
65424
+ throw new Error("The hub address is not a URL.");
65425
+ }
65426
+ const local = url2.hostname === "localhost" || url2.hostname === "127.0.0.1" || url2.hostname === "[::1]";
65427
+ if (url2.protocol !== "https:" && !(url2.protocol === "http:" && local)) {
65428
+ throw new Error("The session is sent only to an https hub.");
65429
+ }
65430
+ return hubUrl.replace(/\/+$/, "");
65431
+ }
65432
+ function decimalString(_key, value) {
65433
+ return typeof value === "bigint" ? value.toString(10) : value;
65434
+ }
65435
+ function recordOf(value) {
65436
+ return typeof value === "object" && value !== null ? value : null;
65437
+ }
65438
+ function messageOf2(body) {
65439
+ const message = recordOf(body)?.message;
65440
+ return typeof message === "string" && message.length > 0 ? message : null;
65441
+ }
65442
+ function codeOf(body) {
65443
+ const code = recordOf(body)?.code;
65444
+ return typeof code === "string" && code.length > 0 ? code : null;
65445
+ }
65446
+ async function readJson(response) {
65447
+ try {
65448
+ return await response.json();
65449
+ } catch {
65450
+ return;
65451
+ }
65452
+ }
65453
+ function remoteRunnerAccount(options) {
65454
+ const hub = safeHub(options.hubUrl);
65455
+ const fetchImpl = options.fetch ?? fetch;
65456
+ const sign3 = async (payload, recover) => {
65457
+ let response;
65458
+ try {
65459
+ response = await fetchImpl(`${hub}/cli/sign`, {
65460
+ method: "POST",
65461
+ headers: {
65462
+ authorization: `Bearer ${options.session}`,
65463
+ "content-type": "application/json"
65464
+ },
65465
+ body: JSON.stringify(payload, decimalString)
65466
+ });
65467
+ } catch {
65468
+ throw hubFailure("unreachable");
65469
+ }
65470
+ const body = await readJson(response);
65471
+ if (response.status === 200) {
65472
+ const signed2 = recordOf(body);
65473
+ const signature = signed2?.signature;
65474
+ const address = signed2?.address;
65475
+ if (typeof signature !== "string" || !SIGNATURE.test(signature) || typeof address !== "string" || address.toLowerCase() !== options.address.toLowerCase()) {
65476
+ throw new Error(WRONG_WALLET);
65477
+ }
65478
+ let signer;
65479
+ try {
65480
+ signer = await recover(signature);
65481
+ } catch {
65482
+ throw new Error(WRONG_WALLET);
65483
+ }
65484
+ if (signer.toLowerCase() !== options.address.toLowerCase())
65485
+ throw new Error(WRONG_WALLET);
65486
+ return signature;
65487
+ }
65488
+ if (response.status === 401)
65489
+ throw new Error(messageOf2(body) ?? NOT_SIGNED_IN);
65490
+ if (response.status === 403) {
65491
+ throw new Error(`The hub refused to sign this: ${codeOf(body) ?? "not-allowed"}`);
65492
+ }
65493
+ if (response.status === 502) {
65494
+ throw new Error("The hub could not sign right now (privy-failed).");
65495
+ }
65496
+ throw hubFailure(response.status);
65497
+ };
65498
+ return {
65499
+ kind: "remote",
65500
+ address: options.address,
65501
+ signMessage: (args) => sign3({ kind: "message", message: args.message }, (signature) => recoverMessageAddress2({ message: args.message, signature })),
65502
+ signTypedData: (typedData) => sign3({ kind: "typed-data", typedData }, (signature) => recoverTypedDataAddress2({ ...typedData, signature }))
65503
+ };
65504
+ }
65505
+ async function remoteAccountFromSession(options) {
65506
+ const hub = safeHub(options.hubUrl);
65507
+ const fetchImpl = options.fetch ?? fetch;
65508
+ let response;
65509
+ try {
65510
+ response = await fetchImpl(`${hub}/cli/session`, {
65511
+ headers: { authorization: `Bearer ${options.session}` }
65512
+ });
65513
+ } catch {
65514
+ throw hubFailure("unreachable");
65515
+ }
65516
+ const body = await readJson(response);
65517
+ if (response.status === 200) {
65518
+ const address = recordOf(body)?.address;
65519
+ if (typeof address !== "string" || !ADDRESS.test(address)) {
65520
+ throw hubFailure("invalid-address");
65521
+ }
65522
+ return remoteRunnerAccount({ ...options, address });
65523
+ }
65524
+ if (response.status === 401)
65525
+ throw new Error(messageOf2(body) ?? NOT_SIGNED_IN);
65526
+ throw hubFailure(response.status);
65527
+ }
65528
+ function assertRemoteCanServe(account, offer2) {
65529
+ if (account.kind !== "remote")
65530
+ return;
65531
+ let bonded = false;
65532
+ try {
65533
+ bonded = BigInt(offer2.bond) > 0n;
65534
+ } catch {
65535
+ bonded = false;
65536
+ }
65537
+ if (bonded)
65538
+ throw new Error(BONDED_REMOTE_REFUSAL);
65539
+ }
65540
+
65156
65541
  // packages/runner/src/daemon.ts
65157
65542
  var RECONNECT_DELAYS = [1, 2, 4, 8, 16, 30];
65543
+ var ATOMIC = /^(0|[1-9][0-9]*)$/;
65544
+ var ADDRESS2 = /^0x[0-9a-fA-F]{40}$/;
65545
+ function readLockTerms(value) {
65546
+ if (value === null || typeof value !== "object")
65547
+ return null;
65548
+ const raw = value;
65549
+ const buyer = raw.buyer;
65550
+ const payee = raw.payee;
65551
+ const amount2 = raw.amount;
65552
+ const deadline = raw.deadline;
65553
+ const salt = raw.salt;
65554
+ const bond = raw.bond;
65555
+ const reviewWindow = raw.reviewWindow;
65556
+ if (typeof buyer !== "string" || !ADDRESS2.test(buyer))
65557
+ return null;
65558
+ if (typeof payee !== "string" || !ADDRESS2.test(payee))
65559
+ return null;
65560
+ if (typeof amount2 !== "string" || !ATOMIC.test(amount2))
65561
+ return null;
65562
+ if (typeof deadline !== "number" || !Number.isSafeInteger(deadline))
65563
+ return null;
65564
+ if (typeof salt !== "string" || !ATOMIC.test(salt))
65565
+ return null;
65566
+ if (typeof bond !== "string" || !ATOMIC.test(bond))
65567
+ return null;
65568
+ if (typeof reviewWindow !== "number" || !Number.isSafeInteger(reviewWindow))
65569
+ return null;
65570
+ return {
65571
+ buyer,
65572
+ payee,
65573
+ amount: amount2,
65574
+ deadline,
65575
+ salt,
65576
+ bond,
65577
+ reviewWindow
65578
+ };
65579
+ }
65158
65580
  function startRunner(cfg) {
65159
65581
  const clock = cfg.now ?? (() => Math.floor(Date.now() / 1000));
65160
65582
  const timeMs = cfg.limits?.timeMs ?? 30000;
65161
65583
  const maxOutputBytes = cfg.limits?.maxOutputBytes ?? 1e6;
65584
+ for (const entry of cfg.served)
65585
+ assertRemoteCanServe(cfg.account, entry.offer);
65162
65586
  const served = cfg.served.map((entry) => ({ ...entry, hash: offerHash(entry.offer) }));
65163
65587
  const url2 = connectUrl(cfg.hubUrl);
65164
65588
  const funding = cfg.funding === undefined ? null : { ...cfg.funding, read: fundingReader(cfg.funding) };
@@ -65215,9 +65639,51 @@ function startRunner(cfg) {
65215
65639
  });
65216
65640
  return;
65217
65641
  }
65642
+ const refuse = (reason) => {
65643
+ send(target, { t: "lock-refused", jobId: frame.jobId, reason });
65644
+ };
65645
+ const terms = readLockTerms(frame.terms);
65646
+ if (terms === null) {
65647
+ refuse("bad-terms");
65648
+ return;
65649
+ }
65650
+ if (terms.payee.toLowerCase() !== cfg.account.address.toLowerCase()) {
65651
+ refuse("not-payee");
65652
+ return;
65653
+ }
65654
+ let bondAbove = false;
65218
65655
  try {
65219
- const typed = lockTypedData(frame.chainId, frame.escrow, frame.jobId, frame.funder);
65220
- const sig = await cfg.account.signTypedData(typed);
65656
+ bondAbove = BigInt(terms.bond) > 0n;
65657
+ } catch {
65658
+ bondAbove = false;
65659
+ }
65660
+ if (terms.amount !== entry.offer.price || terms.bond !== entry.offer.bond || !bondAbove || terms.reviewWindow !== (entry.offer.reviewWindow ?? 0)) {
65661
+ refuse("terms-mismatch");
65662
+ return;
65663
+ }
65664
+ if (terms.deadline - clock() < 60) {
65665
+ refuse("deadline-too-near");
65666
+ return;
65667
+ }
65668
+ const escrow = frame.escrow;
65669
+ const pinned = funding !== null && typeof escrow === "string" && funding.known.some((pin) => pin.chainId === frame.chainId && pin.escrow.toLowerCase() === escrow.toLowerCase());
65670
+ if (!pinned) {
65671
+ refuse("unknown-escrow");
65672
+ return;
65673
+ }
65674
+ let rebuilt;
65675
+ try {
65676
+ rebuilt = jobIdOf({ offerHash: frame.offerHash, ...terms }, frame.chainId, frame.escrow);
65677
+ } catch {
65678
+ refuse("job-mismatch");
65679
+ return;
65680
+ }
65681
+ if (typeof frame.jobId !== "string" || rebuilt.toLowerCase() !== frame.jobId.toLowerCase()) {
65682
+ refuse("job-mismatch");
65683
+ return;
65684
+ }
65685
+ try {
65686
+ const sig = await cfg.account.signTypedData(lockTypedData(frame.chainId, frame.escrow, frame.jobId, frame.funder));
65221
65687
  send(target, { t: "locked", jobId: frame.jobId, sig });
65222
65688
  } catch {
65223
65689
  send(target, {
@@ -65272,7 +65738,7 @@ function startRunner(cfg) {
65272
65738
  try {
65273
65739
  output2 = await runInChild(substituteBuyer(entry.spec, order.buyer), order.input, { timeMs, maxOutputBytes }, [...order.lineage, order.jobId].join(","));
65274
65740
  } catch (error62) {
65275
- const reason = messageOf2(error62);
65741
+ const reason = messageOf3(error62);
65276
65742
  notify(order, entry.offer.title, false, reason);
65277
65743
  return fail(target, order, reason);
65278
65744
  }
@@ -65308,7 +65774,7 @@ function startRunner(cfg) {
65308
65774
  });
65309
65775
  notify(order, entry.offer.title, true);
65310
65776
  } catch (error62) {
65311
- const reason = messageOf2(error62);
65777
+ const reason = messageOf3(error62);
65312
65778
  notify(order, entry.offer.title, false, reason);
65313
65779
  fail(target, order, reason);
65314
65780
  }
@@ -65394,7 +65860,7 @@ function startRunner(cfg) {
65394
65860
  }
65395
65861
  handleMessage(target, message).catch((error62) => {
65396
65862
  if (message.t === "job")
65397
- fail(target, message.order, messageOf2(error62));
65863
+ fail(target, message.order, messageOf3(error62));
65398
65864
  });
65399
65865
  });
65400
65866
  target.addEventListener("close", () => {
@@ -65487,7 +65953,7 @@ function connectUrl(hubUrl) {
65487
65953
  url2.pathname = `${url2.pathname.replace(/\/+$/, "")}${RUNNER_PATH}`;
65488
65954
  return url2.toString();
65489
65955
  }
65490
- function messageOf2(error62) {
65956
+ function messageOf3(error62) {
65491
65957
  return error62 instanceof Error ? error62.message : String(error62);
65492
65958
  }
65493
65959
 
@@ -66127,6 +66593,7 @@ async function publishCommand(options) {
66127
66593
  console.log(offerHash2);
66128
66594
  }
66129
66595
  function publishWithState(options, account, offer2, skillSha256) {
66596
+ assertRemoteCanServe(account, offer2);
66130
66597
  return publishOffer(options.hub, account, offer2, {
66131
66598
  ...options.state !== undefined ? { stateDir: options.state } : {},
66132
66599
  skillSha256
@@ -66229,11 +66696,22 @@ function makeOffer(seller, skill, options) {
66229
66696
  });
66230
66697
  }
66231
66698
  async function sellerAccount() {
66699
+ const session = process.env.CS_SESSION;
66700
+ delete process.env.CS_SESSION;
66232
66701
  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.");
66702
+ if (key) {
66703
+ if (!/^0x[0-9a-fA-F]{64}$/.test(key)) {
66704
+ throw new Error("The CI signing override in this environment is malformed.");
66705
+ }
66706
+ return privateKeyToAccount(key);
66235
66707
  }
66236
- return privateKeyToAccount(key);
66708
+ if (session) {
66709
+ const issuer = process.env.CS_HUB;
66710
+ if (!issuer)
66711
+ throw new Error(NOT_SIGNED_IN);
66712
+ return remoteAccountFromSession({ hubUrl: issuer, session });
66713
+ }
66714
+ throw new Error(NOT_SIGNED_IN);
66237
66715
  }
66238
66716
  function parseOptions(args) {
66239
66717
  const skills = [];
@@ -66449,30 +66927,458 @@ function parseRails(raw) {
66449
66927
  }
66450
66928
  if (false) {}
66451
66929
 
66930
+ // packages/cli/src/login.ts
66931
+ init__esm();
66932
+ import { spawn as spawn3 } from "node:child_process";
66933
+
66934
+ // packages/cli/src/session.ts
66935
+ import { execFileSync } from "node:child_process";
66936
+ import {
66937
+ chmodSync,
66938
+ closeSync,
66939
+ existsSync as existsSync3,
66940
+ lstatSync,
66941
+ mkdirSync as mkdirSync2,
66942
+ openSync,
66943
+ readFileSync as readFileSync4,
66944
+ renameSync,
66945
+ rmSync,
66946
+ writeSync
66947
+ } from "node:fs";
66948
+ import { homedir as homedir2 } from "node:os";
66949
+ import { join as join3 } from "node:path";
66950
+ var NOT_SIGNED_IN2 = "Not signed in. Run: npx -y @countersign-hub/cli login";
66951
+ var securityRun = (args, input2) => execFileSync("/usr/bin/security", args, { input: input2, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"] });
66952
+ function parse6(text) {
66953
+ try {
66954
+ const value = JSON.parse(text);
66955
+ if (typeof value.token === "string" && typeof value.hub === "string" && typeof value.address === "string" && typeof value.expiresAt === "number") {
66956
+ return value;
66957
+ }
66958
+ } catch {}
66959
+ return null;
66960
+ }
66961
+ function refuse(path) {
66962
+ process.stderr.write(`countersign: refusing the session at ${path}: symlink, non-regular file or unsafe permissions.
66963
+ `);
66964
+ return null;
66965
+ }
66966
+ function fileSessionBackend(dir = join3(homedir2(), ".config", "countersign"), platform = process.platform) {
66967
+ const path = join3(dir, "session.json");
66968
+ return {
66969
+ kind: "file",
66970
+ where: () => path,
66971
+ read() {
66972
+ try {
66973
+ if (lstatSync(dir).isSymbolicLink())
66974
+ return refuse(dir);
66975
+ } catch {
66976
+ return null;
66977
+ }
66978
+ try {
66979
+ const info = lstatSync(path);
66980
+ const unsafeMode = platform !== "win32" && (info.mode & 63) !== 0;
66981
+ if (info.isSymbolicLink() || !info.isFile() || unsafeMode)
66982
+ return refuse(path);
66983
+ } catch {
66984
+ return null;
66985
+ }
66986
+ try {
66987
+ return parse6(readFileSync4(path, "utf8"));
66988
+ } catch {
66989
+ return null;
66990
+ }
66991
+ },
66992
+ write(session) {
66993
+ mkdirSync2(dir, { recursive: true, mode: 448 });
66994
+ chmodSync(dir, 448);
66995
+ const tmp = `${path}.tmp`;
66996
+ rmSync(tmp, { force: true });
66997
+ const fd = openSync(tmp, "wx", 384);
66998
+ try {
66999
+ writeSync(fd, `${JSON.stringify(session)}
67000
+ `);
67001
+ } finally {
67002
+ closeSync(fd);
67003
+ }
67004
+ renameSync(tmp, path);
67005
+ },
67006
+ clear() {
67007
+ rmSync(path, { force: true });
67008
+ }
67009
+ };
67010
+ }
67011
+ function keychainSessionBackend(run = securityRun) {
67012
+ return {
67013
+ kind: "keychain",
67014
+ where: () => "the macOS login Keychain (service countersign, account session)",
67015
+ read() {
67016
+ try {
67017
+ const hex3 = run(["find-generic-password", "-s", "countersign", "-a", "session", "-w"]).trim();
67018
+ return hex3 === "" ? null : parse6(Buffer.from(hex3, "hex").toString("utf8"));
67019
+ } catch {
67020
+ return null;
67021
+ }
67022
+ },
67023
+ write(session) {
67024
+ const hex3 = Buffer.from(JSON.stringify(session), "utf8").toString("hex");
67025
+ run(["-i"], `add-generic-password -U -s countersign -a session -w ${hex3}
67026
+ `);
67027
+ },
67028
+ clear() {
67029
+ try {
67030
+ run(["delete-generic-password", "-s", "countersign", "-a", "session"]);
67031
+ } catch {}
67032
+ }
67033
+ };
67034
+ }
67035
+ function defaultSessionBackend(env = process.env) {
67036
+ if (env.COUNTERSIGN_KEYSTORE === "file")
67037
+ return fileSessionBackend();
67038
+ if (process.platform === "darwin" && existsSync3("/usr/bin/security"))
67039
+ return keychainSessionBackend();
67040
+ return fileSessionBackend();
67041
+ }
67042
+ function loadSession(backend = defaultSessionBackend(), now = Math.floor(Date.now() / 1000)) {
67043
+ const session = backend.read();
67044
+ return session !== null && session.expiresAt > now ? session : null;
67045
+ }
67046
+
67047
+ // packages/cli/src/login.ts
67048
+ var EXPIRED = `The login expired. Run countersign login again.
67049
+ `;
67050
+ var CONSOLE_ORIGIN = "https://web-production-a58de.up.railway.app";
67051
+ var UNSAFE_URL_CHARACTERS = /[&|^<>"%`;\s]/;
67052
+ var HUB_REFUSED = "Refused: the countersign hub must be https.";
67053
+ function safeConsoleUrl(url2, consoleOrigin = CONSOLE_ORIGIN) {
67054
+ if (UNSAFE_URL_CHARACTERS.test(url2))
67055
+ return null;
67056
+ let parsed;
67057
+ try {
67058
+ parsed = new URL(url2);
67059
+ } catch {
67060
+ return null;
67061
+ }
67062
+ if (parsed.protocol !== "https:")
67063
+ return null;
67064
+ if (parsed.origin !== consoleOrigin)
67065
+ return null;
67066
+ return url2;
67067
+ }
67068
+ function assertHubUrl(hub) {
67069
+ let parsed;
67070
+ try {
67071
+ parsed = new URL(hub);
67072
+ } catch {
67073
+ throw new Error(HUB_REFUSED);
67074
+ }
67075
+ const local = parsed.protocol === "http:" && (parsed.hostname === "localhost" || parsed.hostname === "127.0.0.1" || parsed.hostname === "[::1]");
67076
+ if (parsed.protocol !== "https:" && !local)
67077
+ throw new Error(HUB_REFUSED);
67078
+ return parsed.origin;
67079
+ }
67080
+ function defaultOpen(url2) {
67081
+ const windows = process.platform === "win32";
67082
+ const command = windows ? "rundll32" : process.platform === "darwin" ? "open" : "xdg-open";
67083
+ const args = windows ? ["url.dll,FileProtocolHandler", url2] : [url2];
67084
+ const child = spawn3(command, args, { detached: true, stdio: "ignore", shell: false });
67085
+ child.on("error", () => {});
67086
+ child.unref();
67087
+ }
67088
+ function hubOrRefuse(hub) {
67089
+ try {
67090
+ return assertHubUrl(hub);
67091
+ } catch (error62) {
67092
+ process.stderr.write(`${error62 instanceof Error ? error62.message : String(error62)}
67093
+ `);
67094
+ process.exitCode = 1;
67095
+ return null;
67096
+ }
67097
+ }
67098
+ function expired() {
67099
+ process.stderr.write(EXPIRED);
67100
+ process.exitCode = 1;
67101
+ }
67102
+ async function runLogin(_args, deps) {
67103
+ const fetchImpl = deps.fetchImpl ?? fetch;
67104
+ const sleep = deps.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
67105
+ const now = deps.now ?? (() => Math.floor(Date.now() / 1000));
67106
+ const open2 = deps.open ?? defaultOpen;
67107
+ const hub = hubOrRefuse(deps.hub);
67108
+ if (hub === null)
67109
+ return;
67110
+ let start;
67111
+ try {
67112
+ const response = await fetchImpl(`${hub}/cli/login/start`, { method: "POST" });
67113
+ if (!response.ok)
67114
+ throw new Error("start refused");
67115
+ start = await response.json();
67116
+ } catch {
67117
+ process.stderr.write(`The login could not start. Run countersign login again.
67118
+ `);
67119
+ process.exitCode = 1;
67120
+ return;
67121
+ }
67122
+ deps.write(`Open this link and sign in: ${start.verifyUrl}
67123
+ `);
67124
+ deps.write(`The page will show this code: ${start.code}
67125
+ `);
67126
+ const safeUrl = safeConsoleUrl(start.verifyUrl);
67127
+ if (safeUrl !== null) {
67128
+ try {
67129
+ open2(safeUrl);
67130
+ } catch {}
67131
+ }
67132
+ const interval = Number.isFinite(start.interval) && start.interval > 0 ? start.interval : 5;
67133
+ while (now() < start.expiresAt) {
67134
+ await sleep(interval * 1000);
67135
+ if (now() >= start.expiresAt)
67136
+ break;
67137
+ let poll2;
67138
+ try {
67139
+ const response = await fetchImpl(`${hub}/cli/login/poll`, {
67140
+ method: "POST",
67141
+ headers: { "content-type": "application/json" },
67142
+ body: JSON.stringify({ deviceSecret: start.deviceSecret })
67143
+ });
67144
+ if (response.status === 410)
67145
+ return expired();
67146
+ if (!response.ok)
67147
+ return expired();
67148
+ poll2 = await response.json();
67149
+ } catch {
67150
+ continue;
67151
+ }
67152
+ if (poll2.status === "approved") {
67153
+ const session = {
67154
+ token: poll2.sessionToken,
67155
+ hub,
67156
+ address: poll2.address,
67157
+ expiresAt: poll2.expiresAt
67158
+ };
67159
+ deps.backend.write(session);
67160
+ deps.write(`Signed in. Your agent wallet: ${poll2.address}
67161
+ `);
67162
+ deps.write(`Fund it: send testnet USDC to ${poll2.address} (Circle faucet: https://faucet.circle.com).
67163
+ `);
67164
+ deps.write(`For Whitechain, get test WBT for gas at https://faucet.testnet.whitechain.io.
67165
+ `);
67166
+ deps.write(`The session lasts 7 days; countersign logout ends it.
67167
+ `);
67168
+ return;
67169
+ }
67170
+ if (poll2.status !== "pending")
67171
+ return expired();
67172
+ }
67173
+ expired();
67174
+ }
67175
+ async function runLogout(_args, deps) {
67176
+ const fetchImpl = deps.fetchImpl ?? fetch;
67177
+ const session = loadSession(deps.backend);
67178
+ if (session === null) {
67179
+ deps.backend.clear();
67180
+ deps.write(`Not signed in.
67181
+ `);
67182
+ return;
67183
+ }
67184
+ let confirmed = false;
67185
+ try {
67186
+ assertHubUrl(session.hub);
67187
+ const response = await fetchImpl(`${session.hub}/cli/logout`, {
67188
+ method: "POST",
67189
+ headers: { Authorization: `Bearer ${session.token}` }
67190
+ });
67191
+ confirmed = response.ok;
67192
+ } catch {}
67193
+ deps.backend.clear();
67194
+ if (confirmed) {
67195
+ deps.write(`Signed out.
67196
+ `);
67197
+ } else {
67198
+ const expiry = new Date(session.expiresAt * 1000).toISOString();
67199
+ deps.write(`Signed out here; the hub did not confirm, so the session stays valid until ${expiry}.
67200
+ `);
67201
+ }
67202
+ }
67203
+ async function runWhoami(_args, deps) {
67204
+ const fetchImpl = deps.fetchImpl ?? fetch;
67205
+ const readBalances = deps.balances ?? defaultBalances;
67206
+ const session = loadSession(deps.backend);
67207
+ if (session === null) {
67208
+ process.stderr.write(`${NOT_SIGNED_IN2}
67209
+ `);
67210
+ process.exitCode = 1;
67211
+ return;
67212
+ }
67213
+ try {
67214
+ assertHubUrl(session.hub);
67215
+ } catch {
67216
+ deps.backend.clear();
67217
+ process.stderr.write(`${NOT_SIGNED_IN2}
67218
+ `);
67219
+ process.exitCode = 1;
67220
+ return;
67221
+ }
67222
+ let response;
67223
+ try {
67224
+ response = await fetchImpl(`${session.hub}/cli/session`, {
67225
+ headers: { Authorization: `Bearer ${session.token}` }
67226
+ });
67227
+ } catch {
67228
+ process.stderr.write(`countersign whoami could not reach the hub.
67229
+ `);
67230
+ process.exitCode = 1;
67231
+ return;
67232
+ }
67233
+ if (response.status === 401) {
67234
+ process.stderr.write(`${NOT_SIGNED_IN2}
67235
+ `);
67236
+ process.exitCode = 1;
67237
+ return;
67238
+ }
67239
+ if (!response.ok) {
67240
+ process.stderr.write(`countersign whoami could not read the session.
67241
+ `);
67242
+ process.exitCode = 1;
67243
+ return;
67244
+ }
67245
+ const body = await response.json();
67246
+ deps.write(`Signed in as ${body.address} until ${new Date(body.expiresAt * 1000).toISOString()}
67247
+ `);
67248
+ deps.write(`${await readBalances(body.address)}
67249
+ `);
67250
+ }
67251
+ var BALANCE_CHAINS = [
67252
+ {
67253
+ name: "Arc",
67254
+ symbol: "USDC",
67255
+ token: "0x3600000000000000000000000000000000000000",
67256
+ rpc: "https://rpc.testnet.arc.network"
67257
+ },
67258
+ {
67259
+ name: "Whitechain",
67260
+ symbol: "USDT",
67261
+ token: "0xcedfB56113143d55b2358b8CFbB0dEB5Ad2f55a7",
67262
+ rpc: "https://rpc.ankr.com/whitechain_testnet"
67263
+ }
67264
+ ];
67265
+ async function defaultBalances(address) {
67266
+ const parts = await Promise.all(BALANCE_CHAINS.map(async (chain) => {
67267
+ try {
67268
+ const client = createPublicClient({ transport: http(chain.rpc) });
67269
+ const balance = await client.readContract({
67270
+ address: chain.token,
67271
+ abi: erc20Abi,
67272
+ functionName: "balanceOf",
67273
+ args: [address]
67274
+ });
67275
+ return `${formatUnits3(balance, 6)} ${chain.symbol} on ${chain.name}`;
67276
+ } catch {
67277
+ return `unavailable ${chain.symbol} on ${chain.name}`;
67278
+ }
67279
+ }));
67280
+ return parts.join(", ");
67281
+ }
67282
+
66452
67283
  // packages/cli/src/cli.ts
66453
- var VERSION = "0.1.0";
67284
+ var VERSION = "0.1.1";
67285
+ var DEFAULT_HUB3 = "https://hub-production-e85e.up.railway.app";
66454
67286
  var USAGE2 = `countersign ${VERSION}
66455
67287
 
66456
- Sell. The runner dials out to the hub, so you open no port. The key comes from CS_SELLER_KEY.
67288
+ Sell. The runner dials out to the hub, so you open no port.
66457
67289
  countersign sell skill <SKILL.md> --hub <url> --price <atomic> [runner flags]
66458
67290
  countersign sell openapi --spec <spec.json> --operation <id> --hub <url> --price <atomic> [--base-url <url>] [runner flags]
66459
67291
  Add --publish-only to list the offer without serving it.
66460
67292
 
66461
- Buy. The key comes from CS_BUYER_KEY.
67293
+ Buy.
66462
67294
  countersign discover|buy|envelope|journal [--hub <url>] [--state <dir>] ...
66463
67295
 
67296
+ Sign in, and check the session:
67297
+ countersign login|logout|whoami
67298
+
66464
67299
  Verify a slip offline:
66465
67300
  countersign verify <slip.json> [--rpc <url>]
66466
67301
 
66467
67302
  The buyer MCP server over stdio:
66468
67303
  countersign mcp [--hub <url>] [--state <dir>] [--rpc <url>]
67304
+
67305
+ Start here: countersign login, then fund the printed address, then buy or sell.
67306
+ The default hub is ${DEFAULT_HUB3}.
66469
67307
  `;
67308
+ var COMMAND_USAGE = {
67309
+ login: `Usage: countersign login
67310
+ Opens a sign-in link; your agent wallet is a Privy server wallet, so no key is ever on this machine.
67311
+ `,
67312
+ logout: `Usage: countersign logout
67313
+ Ends the session on the hub and removes the stored token.
67314
+ `,
67315
+ whoami: `Usage: countersign whoami
67316
+ Shows the signed-in address, the session expiry and testnet balances.
67317
+ `,
67318
+ sell: `Usage: countersign sell skill <SKILL.md> --hub <url> --price <atomic> [runner flags]
67319
+ countersign sell openapi --spec <spec.json> --operation <id> --hub <url> --price <atomic> [--base-url <url>] [runner flags]
67320
+ Add --publish-only to list the offer without serving it.
67321
+ `,
67322
+ discover: `Usage: countersign discover [--hub <url>] [--state <dir>] [--catalogue <url|circle> [query] [--category <c>] [--limit <n>]]
67323
+ `,
67324
+ buy: `Usage: countersign buy <offerHash|callUrl> --input '<json>' [--approve] [--rail <id>] [--approvals hub] [--hub <url>] [--state <dir>]
67325
+ `,
67326
+ envelope: `Usage: countersign envelope show|set [--hub <url>] [--state <dir>]
67327
+ `,
67328
+ journal: `Usage: countersign journal [--hub <url>] [--state <dir>]
67329
+ `,
67330
+ verify: `Usage: countersign verify <slip.json> [--rpc <url>] [--mirror <url>]
67331
+ `,
67332
+ mcp: `Usage: countersign mcp [--hub <url>] [--state <dir>] [--rpc <url>]
67333
+ `
67334
+ };
66470
67335
  var BUYER_COMMANDS = new Set(["discover", "buy", "envelope", "journal"]);
67336
+ function withHub(args2, env = process.env) {
67337
+ for (const arg of args2) {
67338
+ if (arg === "--hub" || arg.startsWith("--hub="))
67339
+ return args2;
67340
+ }
67341
+ const hub = env.CS_HUB !== undefined && env.CS_HUB !== "" ? env.CS_HUB : DEFAULT_HUB3;
67342
+ return [...args2, "--hub", hub];
67343
+ }
67344
+ function hubValue(args2, env = process.env) {
67345
+ const settled = withHub(args2, env);
67346
+ const last = settled.at(-1) ?? DEFAULT_HUB3;
67347
+ return last.startsWith("--hub=") ? last.slice("--hub=".length) : last;
67348
+ }
67349
+ function useSession(env = process.env) {
67350
+ if (env.CS_BUYER_KEY !== undefined || env.CS_SELLER_KEY !== undefined)
67351
+ return true;
67352
+ const session = loadSession();
67353
+ if (session === null)
67354
+ return false;
67355
+ let hub;
67356
+ try {
67357
+ hub = assertHubUrl(session.hub);
67358
+ } catch {
67359
+ return false;
67360
+ }
67361
+ env.CS_SESSION = session.token;
67362
+ env.CS_HUB = hub;
67363
+ return true;
67364
+ }
66471
67365
  var DEFAULT_DEPS = {
66472
- runner: (argv) => main(argv),
66473
- loadBuyer: () => Promise.resolve().then(() => (init_cli(), exports_cli)),
67366
+ runner: async (argv) => {
67367
+ if (!useSession())
67368
+ return requireLogin();
67369
+ await main(withHub(argv));
67370
+ },
67371
+ loadBuyer: async () => {
67372
+ if (!useSession() && needsSession())
67373
+ return requireLogin();
67374
+ setArgv(withHub(process.argv.slice(2)));
67375
+ await Promise.resolve().then(() => (init_cli(), exports_cli));
67376
+ },
66474
67377
  loadVerify: () => init_cli2().then(() => exports_cli2),
66475
- loadMcp: () => Promise.resolve().then(() => (init_main(), exports_main))
67378
+ loadMcp: async () => {
67379
+ useSession();
67380
+ return Promise.resolve().then(() => (init_main(), exports_main));
67381
+ }
66476
67382
  };
66477
67383
  function runnerArgs(rest) {
66478
67384
  const publishOnly = rest.includes("--publish-only");
@@ -66514,10 +67420,29 @@ async function run(argv, deps = DEFAULT_DEPS) {
66514
67420
  `);
66515
67421
  return;
66516
67422
  }
67423
+ const commandUsage = COMMAND_USAGE[command];
67424
+ if (commandUsage !== undefined && (rest.includes("--help") || rest.includes("-h"))) {
67425
+ process.stdout.write(commandUsage);
67426
+ return;
67427
+ }
67428
+ if (command === "login" || command === "logout" || command === "whoami") {
67429
+ const loginDeps = {
67430
+ backend: defaultSessionBackend(),
67431
+ hub: hubValue(rest),
67432
+ write: (t) => process.stdout.write(t)
67433
+ };
67434
+ if (command === "login")
67435
+ await runLogin(rest, loginDeps);
67436
+ else if (command === "logout")
67437
+ await runLogout(rest, loginDeps);
67438
+ else
67439
+ await runWhoami(rest, loginDeps);
67440
+ return;
67441
+ }
66517
67442
  if (command === "sell") {
66518
67443
  const mapped = runnerArgs(rest);
66519
67444
  if (!mapped.ok)
66520
- return refuse2(mapped.error);
67445
+ return refuse3(mapped.error);
66521
67446
  await deps.runner(mapped.args);
66522
67447
  return;
66523
67448
  }
@@ -66536,20 +67461,29 @@ async function run(argv, deps = DEFAULT_DEPS) {
66536
67461
  try {
66537
67462
  loaded = await deps.loadMcp();
66538
67463
  } catch {
66539
- return refuse2("countersign mcp could not load its MCP server.");
67464
+ return refuse3("countersign mcp could not load its MCP server.");
66540
67465
  }
66541
67466
  const main4 = isRecord10(loaded) ? loaded.main : undefined;
66542
67467
  if (typeof main4 !== "function")
66543
- return refuse2("The MCP server has no main(argv) export.");
67468
+ return refuse3("The MCP server has no main(argv) export.");
66544
67469
  await main4(rest);
66545
67470
  return;
66546
67471
  }
66547
- return refuse2(`countersign: unknown command '${command}'.`);
67472
+ return refuse3(`countersign: unknown command '${command}'.`);
66548
67473
  }
66549
67474
  function setArgv(args2) {
66550
67475
  process.argv = [process.argv[0] ?? "node", "countersign", ...args2];
66551
67476
  }
66552
- function refuse2(message) {
67477
+ function needsSession() {
67478
+ const [command, verb] = process.argv.slice(2);
67479
+ return command === "buy" || command === "journal" || command === "envelope" && verb === "set";
67480
+ }
67481
+ function requireLogin() {
67482
+ process.stderr.write(`${NOT_SIGNED_IN2}
67483
+ `);
67484
+ process.exitCode = 1;
67485
+ }
67486
+ function refuse3(message) {
66553
67487
  process.stderr.write(`${message}
66554
67488
 
66555
67489
  ${USAGE2}`);
@@ -66566,4 +67500,4 @@ run(process.argv.slice(2)).catch((error62) => {
66566
67500
  process.exitCode = 1;
66567
67501
  });
66568
67502
 
66569
- //# debugId=B268FD45CC002A6364756E2164756E21
67503
+ //# debugId=086E31B6AC676B0A64756E2164756E21