@tinycloud/cli 1.3.5-beta.4 → 1.3.5-beta.5

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.
@@ -4,10 +4,10 @@ var EXT = "https://tinycloud.xyz/ext/courier/v0";
4
4
  var DEFAULT_HOST = "https://tee.node.tinycloud.xyz";
5
5
  var COURIER_SPACE = "agents";
6
6
  var AGENT_GRANT_EXPIRY = "30d";
7
- var kv = (path21, actions) => ({
7
+ var kv = (path22, actions) => ({
8
8
  service: "tinycloud.kv",
9
9
  space: COURIER_SPACE,
10
- path: `${PREFIX}/${path21}`,
10
+ path: `${PREFIX}/${path22}`,
11
11
  actions: actions.map((action) => `tinycloud.kv/${action}`)
12
12
  });
13
13
  var AGENT_GRANT = [
@@ -826,7 +826,7 @@ function authorityTuples(entries) {
826
826
  return tuples;
827
827
  }
828
828
  function renderTuples(tuples) {
829
- const shown = tuples.slice(0, 3).map(({ service, space, path: path21, action }) => `${service} ${space} ${path21 || '""'} ${action}`);
829
+ const shown = tuples.slice(0, 3).map(({ service, space, path: path22, action }) => `${service} ${space} ${path22 || '""'} ${action}`);
830
830
  if (tuples.length > 3)
831
831
  shown.push(`(+${tuples.length - 3} more)`);
832
832
  return shown.join("; ");
@@ -899,9 +899,10 @@ import { TinyCloudNode, DelegatedAccess, NodeWasmBindings, activateValidatedRunt
899
899
  // ../courier/dist/profile.js
900
900
  import fs from "fs";
901
901
  import path from "path";
902
+ import os from "os";
902
903
  import { randomBytes } from "crypto";
903
904
  function profileDir() {
904
- return process.env.COURIER_HOME || path.join(process.env.HOME ?? ".", ".courier");
905
+ return process.env.COURIER_HOME || path.join(os.homedir(), ".courier");
905
906
  }
906
907
  function profilePath(name2, home = profileDir()) {
907
908
  return path.join(home, name2);
@@ -909,11 +910,13 @@ function profilePath(name2, home = profileDir()) {
909
910
  function profileExists() {
910
911
  return fs.existsSync(profilePath("profile.json"));
911
912
  }
912
- function ensureSecureDir() {
913
- const dir = profileDir();
913
+ function mkdirOwnerOnly(dir) {
914
914
  fs.mkdirSync(dir, { recursive: true, mode: 448 });
915
915
  fs.chmodSync(dir, 448);
916
916
  }
917
+ function ensureSecureDir() {
918
+ mkdirOwnerOnly(profileDir());
919
+ }
917
920
  function writeSecure(file, obj) {
918
921
  ensureSecureDir();
919
922
  const temp = `${file}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
@@ -1103,7 +1106,7 @@ async function peerHooks(node, profile, peer) {
1103
1106
  // ../courier/dist/args.js
1104
1107
  function parseArgs(argv) {
1105
1108
  const positional = [];
1106
- const BOOLS = { json: true, help: true, force: true, stdin: true, quiet: true, "rotate-key": true, paste: true, request: true, peek: true, resume: true, once: true, unguarded: true, "install-launchd": true, "install-systemd": true, "dry-run": true };
1109
+ const BOOLS = { json: true, help: true, force: true, stdin: true, "code-stdin": true, quiet: true, "rotate-key": true, paste: true, request: true, peek: true, resume: true, once: true, unguarded: true, "install-launchd": true, "install-systemd": true, "dry-run": true };
1107
1110
  const flags = /* @__PURE__ */ new Map();
1108
1111
  for (let i = 0; i < argv.length; i++) {
1109
1112
  const a = argv[i];
@@ -2255,7 +2258,7 @@ import path3 from "path";
2255
2258
 
2256
2259
  // ../courier/dist/guard/paths.js
2257
2260
  import fs2 from "fs";
2258
- import os from "os";
2261
+ import os2 from "os";
2259
2262
  import path2 from "path";
2260
2263
  var CREDENTIAL_SUBSTRINGS = [
2261
2264
  ".config/gh",
@@ -2404,7 +2407,7 @@ function installRoots(entry) {
2404
2407
  function protectedSet(home, entry, extra = {}) {
2405
2408
  return {
2406
2409
  roots: [path2.resolve(home), ...installRoots(entry)],
2407
- homes: [.../* @__PURE__ */ new Set([os.homedir(), ...extra.agentHome ? [extra.agentHome] : []])]
2410
+ homes: [.../* @__PURE__ */ new Set([os2.homedir(), ...extra.agentHome ? [extra.agentHome] : []])]
2408
2411
  };
2409
2412
  }
2410
2413
  function isProtectedRead(file, cwd, set) {
@@ -4940,7 +4943,7 @@ function analyzeScript(text, parent, inexact) {
4940
4943
 
4941
4944
  // ../courier/dist/guard/install.js
4942
4945
  import fs4 from "fs";
4943
- import os2 from "os";
4946
+ import os3 from "os";
4944
4947
  import path6 from "path";
4945
4948
  import { createHash } from "crypto";
4946
4949
  var CREDENTIAL_FILES = [".config/gh/**", ".npmrc", ".netrc", ".ssh/**", ".aws/**", ".config/gcloud/**", ".docker/config.json", ".config/wrangler/**", ".config/.wrangler/**", ".git-credentials"];
@@ -4982,7 +4985,7 @@ var GATED_DENY = [
4982
4985
  ].map((prefix) => `Bash(${prefix}:*)`);
4983
4986
  var PRIVILEGE_DENY = ["sudo", "su", "doas", "pkexec", "tmux", "screen", "crontab", "systemctl", "ssh", "gh auth", "git credential", "secret-tool"].map((prefix) => `Bash(${prefix}:*)`);
4984
4987
  var shq = (s) => `'${s.replace(/'/g, `'\\''`)}'`;
4985
- function hookCommand(argv, home, realHome = os2.homedir()) {
4988
+ function hookCommand(argv, home, realHome = os3.homedir()) {
4986
4989
  return `COURIER_HOME=${shq(home)} COURIER_REAL_HOME=${shq(realHome)} ${argv.map(shq).join(" ")} || exit 2`;
4987
4990
  }
4988
4991
  var guardDir = (home) => path6.join(home, "guard");
@@ -5005,7 +5008,7 @@ function guardSettings(options) {
5005
5008
  for (const f of ["key.json", "grant.json"])
5006
5009
  deny2.push(`Read(${rulePath(path6.join(home, f))})`);
5007
5010
  for (const rel of CREDENTIAL_FILES)
5008
- deny2.push(`Read(${rulePath(path6.join(os2.homedir(), rel))})`);
5011
+ deny2.push(`Read(${rulePath(path6.join(os3.homedir(), rel))})`);
5009
5012
  for (const prog of programs)
5010
5013
  for (const sub2 of ["pair", "revoke", "init", "pause", "resume", "watch"])
5011
5014
  deny2.push(`Bash(${prog} ${sub2}:*)`);
@@ -5185,7 +5188,7 @@ function courierEnvOverrides(base, home, extra = []) {
5185
5188
  // ../courier/dist/wake.js
5186
5189
  import { spawn } from "child_process";
5187
5190
  import fs7 from "fs";
5188
- import os3 from "os";
5191
+ import os4 from "os";
5189
5192
  import path9 from "path";
5190
5193
 
5191
5194
  // ../courier/dist/guard/cleanhome.js
@@ -5564,7 +5567,7 @@ function createWaker(config, deps) {
5564
5567
  if (cwd === courier || cwd.startsWith(`${courier}${path9.sep}`))
5565
5568
  throw new Error("agent cwd must be outside the Courier tree");
5566
5569
  prepareCleanHome({
5567
- realHome: config.realHome ?? env.HOME ?? os3.homedir(),
5570
+ realHome: config.realHome ?? env.HOME ?? os4.homedir(),
5568
5571
  cleanHome: agentHome(config.home),
5569
5572
  claude: config.adapter !== "exec"
5570
5573
  });
@@ -5656,7 +5659,7 @@ ${UNGUARDED_NOTICE}.`
5656
5659
  }
5657
5660
  const existing = agents.find((a) => typeof a.id === "string" && typeof a.provider === "string" && isClaudeProvider(a.provider) && typeof a.cwd === "string" && sameDir(a.cwd, config.cwd))?.id;
5658
5661
  if (existing) {
5659
- const dir = fs7.mkdtempSync(path9.join(os3.tmpdir(), "courier-wake-"));
5662
+ const dir = fs7.mkdtempSync(path9.join(os4.tmpdir(), "courier-wake-"));
5660
5663
  const file = path9.join(dir, "prompt.txt");
5661
5664
  try {
5662
5665
  fs7.writeFileSync(file, prompt(request), { mode: 384 });
@@ -5800,7 +5803,7 @@ function createNotifier(config, deps) {
5800
5803
 
5801
5804
  // ../courier/dist/commands.js
5802
5805
  import fs9 from "fs";
5803
- import os4 from "os";
5806
+ import os5 from "os";
5804
5807
  import path11 from "path";
5805
5808
  import { NodeWasmBindings as NodeWasmBindings2, activateValidatedRuntimeDelegation as activateValidatedRuntimeDelegation2, deserializeDelegation as deserializeDelegation2, serializeDelegation as serializeDelegation2 } from "@tinycloud/node-sdk";
5806
5809
 
@@ -6270,7 +6273,7 @@ async function cmdInit(args) {
6270
6273
  version: 3,
6271
6274
  host,
6272
6275
  openkeyHost,
6273
- label: labelFlag ?? `courier \xB7 ${os4.hostname()}`,
6276
+ label: labelFlag ?? `courier \xB7 ${os5.hostname()}`,
6274
6277
  ...handleFlag ? { selfHandle: handleFlag } : {},
6275
6278
  courierDid: bareDid(key.did),
6276
6279
  status: "awaiting-grant",
@@ -6844,7 +6847,7 @@ ${DEFAULT_ORDERS}`);
6844
6847
 
6845
6848
  // ../courier/dist/watch-cmd.js
6846
6849
  import fs13 from "fs";
6847
- import os7 from "os";
6850
+ import os8 from "os";
6848
6851
  import path16 from "path";
6849
6852
  import { randomBytes as randomBytes2 } from "crypto";
6850
6853
  import { spawnSync as spawnSync4 } from "child_process";
@@ -7395,8 +7398,8 @@ var Watcher = class {
7395
7398
  const prefix = `${PREFIX}/to/${peer.handle}/threads/`;
7396
7399
  const keys = await listAll(this.deps.selfKv, prefix);
7397
7400
  const known = /* @__PURE__ */ new Set();
7398
- for (const path21 of keys) {
7399
- const match = /^([^/]+)\/(?:meta\.json|(?:msg|task)\/[^/]+\.json)$/.exec(path21.slice(prefix.length));
7401
+ for (const path22 of keys) {
7402
+ const match = /^([^/]+)\/(?:meta\.json|(?:msg|task)\/[^/]+\.json)$/.exec(path22.slice(prefix.length));
7400
7403
  if (match && SAFE_ID.test(match[1]))
7401
7404
  known.add(`${peer.handle}/${match[1]}`);
7402
7405
  }
@@ -8273,19 +8276,19 @@ var Watcher = class {
8273
8276
 
8274
8277
  // ../courier/dist/guard/command.js
8275
8278
  import fs11 from "fs";
8276
- import os6 from "os";
8279
+ import os7 from "os";
8277
8280
  import path14 from "path";
8278
8281
  import { spawnSync as spawnSync3 } from "child_process";
8279
8282
 
8280
8283
  // ../courier/dist/broker-config.js
8281
8284
  import fs10 from "fs";
8282
- import os5 from "os";
8285
+ import os6 from "os";
8283
8286
  import path12 from "path";
8284
8287
  var TEST_TOKEN = `ghp_${"d".repeat(36)}`;
8285
8288
  var ID = /^[a-f0-9]{24}$/;
8286
8289
  var PINNED = ["host", "openkeyHost", "spaceId", "ownerDid", "courierDid"];
8287
8290
  function brokerRoot(testOwnerRoot) {
8288
- const owner = os5.userInfo().homedir;
8291
+ const owner = os6.userInfo().homedir;
8289
8292
  if (testOwnerRoot !== void 0) {
8290
8293
  const root = realpathBest(testOwnerRoot);
8291
8294
  const scratch = realpathBest(path12.join(owner, "scratch"));
@@ -8294,7 +8297,7 @@ function brokerRoot(testOwnerRoot) {
8294
8297
  }
8295
8298
  if (fs10.existsSync(root)) {
8296
8299
  const st = fs10.statSync(root);
8297
- if (st.uid !== os5.userInfo().uid || st.mode & 63)
8300
+ if (st.uid !== os6.userInfo().uid || st.mode & 63)
8298
8301
  throw new CourierError("usage", "--test-owner-root must be owner-private (0700)");
8299
8302
  }
8300
8303
  const broker = realpathBest(path12.join(root, ".config/courier-broker"));
@@ -8350,7 +8353,7 @@ function loadBrokerConfig(id, testOwnerRoot) {
8350
8353
  try {
8351
8354
  for (const f of [path12.dirname(file), file]) {
8352
8355
  const st2 = fs10.lstatSync(f);
8353
- if (st2.isSymbolicLink() || st2.uid !== os5.userInfo().uid || st2.mode & 63)
8356
+ if (st2.isSymbolicLink() || st2.uid !== os6.userInfo().uid || st2.mode & 63)
8354
8357
  throw Error("not owner-private");
8355
8358
  }
8356
8359
  const config = JSON.parse(fs10.readFileSync(file, "utf8"));
@@ -8503,8 +8506,8 @@ function realEnv(cwd, home = profileDir(), entry = process.argv[1] ?? "") {
8503
8506
  binaries: installed,
8504
8507
  resolveProgram: (word, dir) => binaryName(word, dir, searchPath, installed),
8505
8508
  literals: [...installLiterals(home, entry), brokerRoot()],
8506
- protectedRoots: [home, brokerRoot(), path14.join(os6.homedir(), ".courier")],
8507
- realHome: process.env.COURIER_REAL_HOME || os6.userInfo().homedir,
8509
+ protectedRoots: [home, brokerRoot(), path14.join(os7.homedir(), ".courier")],
8510
+ realHome: process.env.COURIER_REAL_HOME || os7.userInfo().homedir,
8508
8511
  courierEntries: installed.filter((b) => b.path === realpathBest(entry) && ["courier", "tc"].includes(b.name)).map((b) => b.path),
8509
8512
  readFile(file) {
8510
8513
  try {
@@ -8575,10 +8578,10 @@ async function cmdGuard(args) {
8575
8578
  const input = JSON.parse(readStdin());
8576
8579
  const home = profileDir();
8577
8580
  const entry = process.argv[1] ?? "";
8578
- const realHome = process.env.COURIER_REAL_HOME || os6.homedir();
8581
+ const realHome = process.env.COURIER_REAL_HOME || os7.homedir();
8579
8582
  const deps = {
8580
8583
  env: realEnv(process.cwd(), home, entry),
8581
- protectedSet: { ...protectedSet(home, entry, { agentHome: os6.homedir() }), roots: [...protectedSet(home, entry).roots, brokerRoot(), path14.join(os6.homedir(), ".courier")], homes: [.../* @__PURE__ */ new Set([realHome, os6.homedir()])], realHome }
8584
+ protectedSet: { ...protectedSet(home, entry, { agentHome: os7.homedir() }), roots: [...protectedSet(home, entry).roots, brokerRoot(), path14.join(os7.homedir(), ".courier")], homes: [.../* @__PURE__ */ new Set([realHome, os7.homedir()])], realHome }
8582
8585
  };
8583
8586
  const timeout = new Promise((_, reject) => setTimeout(() => reject(new Error("timed out")), DEADLINE_MS).unref());
8584
8587
  const decision = await Promise.race([decide(input, deps), timeout]);
@@ -8947,7 +8950,7 @@ async function installService(args, config) {
8947
8950
  logFile: path16.join(profileDir(), `watch${name2 ? `-${name2}` : ""}.log`),
8948
8951
  name: name2
8949
8952
  };
8950
- const plan = launchd ? launchdPlan(spec, os7.homedir(), process.getuid?.() ?? 0) : systemdPlan(spec, os7.homedir());
8953
+ const plan = launchd ? launchdPlan(spec, os8.homedir(), process.getuid?.() ?? 0) : systemdPlan(spec, os8.homedir());
8951
8954
  if (dry) {
8952
8955
  process.stdout.write(renderPlan(plan));
8953
8956
  return;
@@ -9054,36 +9057,36 @@ function parseEntry(value2) {
9054
9057
  return void 0;
9055
9058
  return { tier, ratePerHour, outPerHour };
9056
9059
  }
9057
- function loadPeers(path21, ownerUid2 = process.getuid?.()) {
9060
+ function loadPeers(path22, ownerUid2 = process.getuid?.()) {
9058
9061
  let fd;
9059
9062
  try {
9060
- fd = fs14.openSync(path21, fs14.constants.O_RDONLY | fs14.constants.O_NOFOLLOW);
9063
+ fd = fs14.openSync(path22, fs14.constants.O_RDONLY | fs14.constants.O_NOFOLLOW);
9061
9064
  } catch (error) {
9062
9065
  const code = error.code;
9063
9066
  if (code === "ENOENT")
9064
9067
  return /* @__PURE__ */ Object.create(null);
9065
9068
  if (code === "ELOOP")
9066
- throw new CourierError("insecure-peers", `${path21} is a symbolic link; point the flag at the file itself.`);
9069
+ throw new CourierError("insecure-peers", `${path22} is a symbolic link; point the flag at the file itself.`);
9067
9070
  throw error;
9068
9071
  }
9069
9072
  try {
9070
9073
  const stat = fs14.fstatSync(fd);
9071
9074
  if (!stat.isFile())
9072
- throw new CourierError("insecure-peers", `${path21} is not a regular file.`);
9075
+ throw new CourierError("insecure-peers", `${path22} is not a regular file.`);
9073
9076
  if (ownerUid2 !== void 0 && stat.uid !== ownerUid2) {
9074
- throw new CourierError("insecure-peers", `${path21} is not owned by the peers owner (uid ${ownerUid2}); pass --peers-owner or chown the file.`);
9077
+ throw new CourierError("insecure-peers", `${path22} is not owned by the peers owner (uid ${ownerUid2}); pass --peers-owner or chown the file.`);
9075
9078
  }
9076
9079
  if ((stat.mode & 18) !== 0) {
9077
- throw new CourierError("insecure-peers", `${path21} is writable by group or others; restrict it to the owner (chmod 600).`);
9080
+ throw new CourierError("insecure-peers", `${path22} is writable by group or others; restrict it to the owner (chmod 600).`);
9078
9081
  }
9079
9082
  const parsed = JSON.parse(fs14.readFileSync(fd, "utf8"));
9080
9083
  if (!isRecord(parsed))
9081
- throw new CourierError("bad-peers", `${path21} must be a JSON object keyed by peer id.`);
9084
+ throw new CourierError("bad-peers", `${path22} must be a JSON object keyed by peer id.`);
9082
9085
  const peers = /* @__PURE__ */ Object.create(null);
9083
9086
  for (const [peerId, value2] of Object.entries(parsed)) {
9084
9087
  const policy = parseEntry(value2);
9085
9088
  if (!policy)
9086
- throw new CourierError("bad-peers", `${path21} has an invalid policy for peer "${peerId}"; fix the entry. Nothing was loaded.`);
9089
+ throw new CourierError("bad-peers", `${path22} has an invalid policy for peer "${peerId}"; fix the entry. Nothing was loaded.`);
9087
9090
  peers[peerId] = policy;
9088
9091
  }
9089
9092
  return peers;
@@ -10749,7 +10752,7 @@ ${targetHelp()}`);
10749
10752
  // ../courier/dist/broker.js
10750
10753
  import { execFile } from "child_process";
10751
10754
  import fs18 from "fs";
10752
- import os8 from "os";
10755
+ import os9 from "os";
10753
10756
  import path20 from "path";
10754
10757
  var MAX_OUTPUT = 4e3;
10755
10758
  var EXEC_TIMEOUT_MS = 12e4;
@@ -10758,7 +10761,7 @@ var validToken = (token2) => TOKEN.test(token2);
10758
10761
  async function loadBrokerCredential(config, exec = execFileRunner) {
10759
10762
  if (config.testToken === TEST_TOKEN)
10760
10763
  return { ghBin: config.ghBin, token: TEST_TOKEN };
10761
- const ownerHome = os8.userInfo().homedir;
10764
+ const ownerHome = os9.userInfo().homedir;
10762
10765
  const result = await exec(config.ghBin, ["auth", "token", "--hostname", "github.com"], {
10763
10766
  cwd: ownerHome,
10764
10767
  timeoutMs: 1e4,
@@ -10968,6 +10971,308 @@ async function cmdBroker(args) {
10968
10971
  `);
10969
10972
  }
10970
10973
 
10974
+ // ../courier/dist/credential.js
10975
+ import { createPrivateKey, sign as cryptoSign } from "crypto";
10976
+ import { createInterface as createInterface2 } from "readline";
10977
+ import { completeEmailProof, createEd25519HolderSigner, EmailProofError, emailProofRequirement, requestEmailProofCode } from "@tinycloud/node-sdk/credentials";
10978
+ import { createFileEmailProofPendingStore, PendingStoreError } from "@tinycloud/node-sdk/credential-pending-store";
10979
+ import { credentialRequirementDigest } from "@tinycloud/sdk-core";
10980
+
10981
+ // ../courier/dist/credential-source.js
10982
+ import fs19 from "fs";
10983
+ import path21 from "path";
10984
+ import { createHash as createHash4, randomBytes as randomBytes5 } from "crypto";
10985
+ import { createStoredCredentialRecord, credentialMatchesRequirement } from "@tinycloud/sdk-core";
10986
+ var DIR_NAME = "credentials";
10987
+ function credentialsDir(home) {
10988
+ return path21.join(home, DIR_NAME);
10989
+ }
10990
+ function sha256Hex(text) {
10991
+ return createHash4("sha256").update(text).digest("hex");
10992
+ }
10993
+ function credentialFile(home, holderDid, requirementDigest) {
10994
+ return path21.join(credentialsDir(home), `credential-${sha256Hex(`${holderDid}
10995
+ ${requirementDigest}`)}.json`);
10996
+ }
10997
+ function lstatOrUndefined2(file) {
10998
+ try {
10999
+ return fs19.lstatSync(file);
11000
+ } catch (error) {
11001
+ if (error.code === "ENOENT")
11002
+ return void 0;
11003
+ throw error;
11004
+ }
11005
+ }
11006
+ function assertOwnerOnly(st, label2, expectedMode) {
11007
+ const mode = st.mode & 511;
11008
+ if ((mode & 63) !== 0) {
11009
+ throw new CourierError("credential-permissions", `${label2} must be owner-only (mode ${expectedMode}); found mode ${mode.toString(8)}.`);
11010
+ }
11011
+ assertOwnedByCurrentUser(st, label2);
11012
+ }
11013
+ function assertOwnedByCurrentUser(st, label2) {
11014
+ if (typeof process.getuid === "function" && st.uid !== process.getuid()) {
11015
+ throw new CourierError("credential-permissions", `${label2} is not owned by the current user.`);
11016
+ }
11017
+ }
11018
+ function ensureSecureDir2(home) {
11019
+ const dir = credentialsDir(home);
11020
+ mkdirOwnerOnly(dir);
11021
+ const st = fs19.lstatSync(dir);
11022
+ if (!st.isDirectory())
11023
+ throw new CourierError("credential-permissions", `${dir} must be a real directory, not a symlink or file.`);
11024
+ assertOwnedByCurrentUser(st, dir);
11025
+ fs19.chmodSync(dir, 448);
11026
+ }
11027
+ function checkSecureDir(home) {
11028
+ const dir = credentialsDir(home);
11029
+ const st = lstatOrUndefined2(dir);
11030
+ if (!st)
11031
+ return false;
11032
+ if (!st.isDirectory())
11033
+ throw new CourierError("credential-permissions", `${dir} must be a real directory, not a symlink or file.`);
11034
+ assertOwnerOnly(st, dir, "0700");
11035
+ return true;
11036
+ }
11037
+ function writeOwnerOnly(home, file, obj) {
11038
+ ensureSecureDir2(home);
11039
+ const existing = lstatOrUndefined2(file);
11040
+ if (existing && !existing.isFile()) {
11041
+ throw new CourierError("credential-permissions", `${path21.basename(file)} exists and is not a regular file; refusing to replace it.`);
11042
+ }
11043
+ const temp = `${file}.${process.pid}.${randomBytes5(6).toString("hex")}.tmp`;
11044
+ let fd;
11045
+ try {
11046
+ fd = fs19.openSync(temp, fs19.constants.O_WRONLY | fs19.constants.O_CREAT | fs19.constants.O_EXCL | fs19.constants.O_NOFOLLOW, 384);
11047
+ fs19.writeFileSync(fd, JSON.stringify(obj, null, 2) + "\n");
11048
+ fs19.fchmodSync(fd, 384);
11049
+ fs19.fsyncSync(fd);
11050
+ fs19.closeSync(fd);
11051
+ fd = void 0;
11052
+ fs19.renameSync(temp, file);
11053
+ } finally {
11054
+ if (fd !== void 0)
11055
+ fs19.closeSync(fd);
11056
+ try {
11057
+ fs19.unlinkSync(temp);
11058
+ } catch {
11059
+ }
11060
+ }
11061
+ }
11062
+ function readOwnerOnly(home, file) {
11063
+ if (!checkSecureDir(home))
11064
+ return void 0;
11065
+ const label2 = path21.basename(file);
11066
+ const before = lstatOrUndefined2(file);
11067
+ if (!before)
11068
+ return void 0;
11069
+ if (!before.isFile())
11070
+ throw new CourierError("credential-permissions", `${label2} must be a regular file; symlinks are refused.`);
11071
+ assertOwnerOnly(before, label2, "0600");
11072
+ const fd = fs19.openSync(file, fs19.constants.O_RDONLY | fs19.constants.O_NOFOLLOW);
11073
+ let text;
11074
+ try {
11075
+ const opened = fs19.fstatSync(fd);
11076
+ if (!opened.isFile() || opened.dev !== before.dev || opened.ino !== before.ino) {
11077
+ throw new CourierError("credential-permissions", `${label2} changed while it was being opened.`);
11078
+ }
11079
+ if (opened.nlink !== 1)
11080
+ throw new CourierError("credential-permissions", `${label2} has multiple hard links; refusing to read it.`);
11081
+ assertOwnerOnly(opened, label2, "0600");
11082
+ text = fs19.readFileSync(fd, "utf8");
11083
+ } finally {
11084
+ fs19.closeSync(fd);
11085
+ }
11086
+ try {
11087
+ return JSON.parse(text);
11088
+ } catch {
11089
+ throw new CourierError("credential-store-invalid", `Invalid credential store file ${label2}.`);
11090
+ }
11091
+ }
11092
+ function removeFile(file) {
11093
+ fs19.rmSync(file, { force: true });
11094
+ }
11095
+ var StoredCredentialSource = class {
11096
+ home;
11097
+ constructor(home = profileDir()) {
11098
+ this.home = home;
11099
+ }
11100
+ async find(input) {
11101
+ const file = credentialFile(this.home, input.holderDid, input.requirementDigest);
11102
+ const record = readOwnerOnly(this.home, file);
11103
+ if (!record)
11104
+ return void 0;
11105
+ const now = input.now ?? /* @__PURE__ */ new Date();
11106
+ if (!credentialMatchesRequirement(input.requirement, record, input.holderDid, now)) {
11107
+ removeFile(file);
11108
+ return void 0;
11109
+ }
11110
+ return record;
11111
+ }
11112
+ async store(input) {
11113
+ const record = await createStoredCredentialRecord({
11114
+ verified: input.verified,
11115
+ ownerDid: input.holderDid,
11116
+ requirement: input.requirement,
11117
+ requirementDigest: input.requirementDigest,
11118
+ storedAt: (input.now ?? /* @__PURE__ */ new Date()).toISOString()
11119
+ });
11120
+ writeOwnerOnly(this.home, credentialFile(this.home, input.holderDid, input.requirementDigest), record);
11121
+ return record;
11122
+ }
11123
+ };
11124
+
11125
+ // ../courier/dist/credential.js
11126
+ var acquireUsage = () => `Usage: ${cmd("credential acquire --email <addr> [--code-stdin]")}`;
11127
+ var showUsage = () => `Usage: ${cmd("credential show --email <addr>")}`;
11128
+ var credentialUsage = () => `${acquireUsage()}
11129
+ ${showUsage()}`;
11130
+ function holderFor(key) {
11131
+ let privateKey;
11132
+ try {
11133
+ privateKey = createPrivateKey({ key: key.jwk, format: "jwk" });
11134
+ } catch {
11135
+ throw new CourierError("credential.request.HOLDER_INVALID", "Agent signing key is unusable.");
11136
+ }
11137
+ try {
11138
+ return createEd25519HolderSigner(key.did, async (bytes) => new Uint8Array(cryptoSign(null, bytes, privateKey)));
11139
+ } catch (error) {
11140
+ throw mapEmailProofError(error, "request");
11141
+ }
11142
+ }
11143
+ function isEmailProofError(error) {
11144
+ return error instanceof EmailProofError;
11145
+ }
11146
+ function mapEmailProofError(error, fallbackStep) {
11147
+ if (isEmailProofError(error))
11148
+ return new CourierError(`credential.${error.step}.${error.code}`, error.message);
11149
+ return new CourierError(`credential.${fallbackStep}.FAILED`, "Email verification failed.");
11150
+ }
11151
+ function keepsPendingAfterFailure(error) {
11152
+ return isEmailProofError(error) && error.recoverable;
11153
+ }
11154
+ async function withStoreErrors(run2) {
11155
+ try {
11156
+ return await run2();
11157
+ } catch (error) {
11158
+ if (error instanceof PendingStoreError)
11159
+ throw new CourierError(error.code, error.message);
11160
+ throw error;
11161
+ }
11162
+ }
11163
+ async function readStdinFirstLine() {
11164
+ const rl = createInterface2({ input: process.stdin, crlfDelay: Infinity });
11165
+ for await (const line of rl) {
11166
+ rl.close();
11167
+ return line;
11168
+ }
11169
+ return "";
11170
+ }
11171
+ async function acquire(args, deps) {
11172
+ const address = requireFlag(args, "email", acquireUsage()).trim();
11173
+ if (args.has("code"))
11174
+ throw new CourierError("usage", `--code is refused: it puts the code on argv. Enter the code with --code-stdin. ${acquireUsage()}`);
11175
+ const key = deps.loadKey(deps.home);
11176
+ const holder = holderFor(key);
11177
+ const store = createFileEmailProofPendingStore({ home: deps.home });
11178
+ if (!args.has("code-stdin")) {
11179
+ const older = await withStoreErrors(() => store.findLive(address, deps.now()));
11180
+ let pending2;
11181
+ try {
11182
+ pending2 = await deps.requestCode({ address, holder, fetch: deps.fetch, now: deps.now });
11183
+ } catch (error) {
11184
+ throw mapEmailProofError(error, "request");
11185
+ }
11186
+ await withStoreErrors(async () => {
11187
+ await store.put(pending2);
11188
+ if (older && older.requestId !== pending2.requestId)
11189
+ await store.delete(older.requestId);
11190
+ });
11191
+ deps.out(`Verification code sent to ${address}. Run \`${cmd(`credential acquire --email ${address} --code-stdin`)}\` and enter the code within 15 minutes.
11192
+ `);
11193
+ return;
11194
+ }
11195
+ const code = (await deps.readStdinLine()).trim();
11196
+ if (!/^\d{8}$/.test(code))
11197
+ throw new CourierError("credential.code.CODE_INVALID", "Verification code must be 8 digits.");
11198
+ const pending = await withStoreErrors(() => store.findLive(address, deps.now()));
11199
+ if (!pending) {
11200
+ throw new CourierError("credential-request-missing", `No pending verification for ${address}. Run \`${cmd(`credential acquire --email ${address}`)}\` first.`);
11201
+ }
11202
+ if (pending.holderDid !== holder.did) {
11203
+ await withStoreErrors(() => store.delete(pending.requestId));
11204
+ throw new CourierError("credential.session.SESSION_INVALID", "Pending verification belongs to a different agent key. Request a new code.");
11205
+ }
11206
+ let result;
11207
+ try {
11208
+ result = await deps.completeProof({ pending, code, holder, fetch: deps.fetch, now: deps.now });
11209
+ } catch (error) {
11210
+ if (keepsPendingAfterFailure(error)) {
11211
+ const mapped = mapEmailProofError(error, "credential");
11212
+ throw new CourierError(mapped.code, `${mapped.message} Retry with --code-stdin before ${pending.expiresAt}.`);
11213
+ }
11214
+ await withStoreErrors(() => store.delete(pending.requestId));
11215
+ throw mapEmailProofError(error, "credential");
11216
+ }
11217
+ const requirement = emailProofRequirement(address);
11218
+ const requirementDigest = await credentialRequirementDigest(requirement);
11219
+ await new StoredCredentialSource(deps.home).store({
11220
+ verified: result.verified,
11221
+ requirement,
11222
+ requirementDigest,
11223
+ holderDid: holder.did,
11224
+ now: deps.now()
11225
+ });
11226
+ await withStoreErrors(() => store.delete(pending.requestId));
11227
+ deps.out(`Email credential stored for ${address}. It is valid for up to 1 hour from issuance.
11228
+ `);
11229
+ }
11230
+ async function show(args, deps) {
11231
+ const address = requireFlag(args, "email", showUsage()).trim();
11232
+ const holder = holderFor(deps.loadKey(deps.home));
11233
+ const requirement = emailProofRequirement(address);
11234
+ const requirementDigest = await credentialRequirementDigest(requirement);
11235
+ const record = await new StoredCredentialSource(deps.home).find({
11236
+ requirement,
11237
+ requirementDigest,
11238
+ holderDid: holder.did,
11239
+ now: deps.now()
11240
+ });
11241
+ if (!record) {
11242
+ throw new CourierError("credential.show.NOT_FOUND", `No valid stored email credential for ${address}. Run \`${cmd(`credential acquire --email ${address}`)}\`.`);
11243
+ }
11244
+ deps.out([
11245
+ `email: ${record.claims.email}`,
11246
+ `credentialId: ${record.credentialId}`,
11247
+ `issuedAt: ${record.issuedAt}`,
11248
+ `expiresAt: ${record.expiresAt}`,
11249
+ `status: ${record.status.method}`,
11250
+ ""
11251
+ ].join("\n"));
11252
+ }
11253
+ async function cmdCredential(args, deps = {}) {
11254
+ const resolved = {
11255
+ home: deps.home ?? profileDir(),
11256
+ now: deps.now ?? (() => /* @__PURE__ */ new Date()),
11257
+ fetch: deps.fetch,
11258
+ loadKey: deps.loadKey ?? ((home) => loadKey(home)),
11259
+ requestCode: deps.requestCode ?? requestEmailProofCode,
11260
+ completeProof: deps.completeProof ?? completeEmailProof,
11261
+ readStdinLine: deps.readStdinLine ?? readStdinFirstLine,
11262
+ out: deps.out ?? ((text) => {
11263
+ process.stdout.write(text);
11264
+ })
11265
+ };
11266
+ switch (args.positional[0]) {
11267
+ case "acquire":
11268
+ return acquire(args, resolved);
11269
+ case "show":
11270
+ return show(args, resolved);
11271
+ default:
11272
+ throw new CourierError("usage", credentialUsage());
11273
+ }
11274
+ }
11275
+
10971
11276
  // ../courier/dist/main.js
10972
11277
  function usage() {
10973
11278
  const name2 = programName();
@@ -11066,6 +11371,11 @@ Delivery (one watcher per person per machine; never calls a model):
11066
11371
  Sender: accept one-line item requests over a Unix socket and send the
11067
11372
  named outbox item. Replies are ok or refused:<code>.
11068
11373
 
11374
+ credential acquire --email <addr> [--code-stdin]
11375
+ Email proof: send a verification code to the address, then enter it with
11376
+ --code-stdin (reads one line from stdin; keeps the code off argv).
11377
+ credential show --email <addr> Show the stored email credential for this agent key (no secret printed).
11378
+
11069
11379
  pause <peer> <threadId> [--reason "\u2026"] Humans: stop waking your agent for a thread; tells the peer
11070
11380
  resume <peer> <threadId> Humans: wake it again (the wake budget starts over)
11071
11381
 
@@ -11123,6 +11433,8 @@ async function run(argv) {
11123
11433
  return cmdPause(sub2);
11124
11434
  case "resume":
11125
11435
  return cmdResume(sub2);
11436
+ case "credential":
11437
+ return cmdCredential(sub2);
11126
11438
  case "guard":
11127
11439
  return cmdGuard(sub2);
11128
11440
  case "run-approved":