@capxul/cli 4.20.0-beta.27 → 4.20.0-beta.28

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.
@@ -1,5 +1,5 @@
1
1
  import { i as __exportAll, o as __require, r as __commonJSMin, s as __toESM } from "./lib-DnmstTkB.mjs";
2
- import { A as assertSize, B as pad, C as keccak256$1, D as createCursor, E as toRlp, F as boolToHex, G as IntegerOutOfRangeError, H as trim$1, I as bytesToHex, K as InvalidBytesBooleanError, L as numberToHex, M as hexToNumber, N as hexToBytes, O as concat$1, P as toBytes$1, R as stringToHex, S as getAddress$1, T as format, U as size, V as padHex, W as isHex$1, _ as readField, a as ENVIRONMENT_NAMES, b as isAddress$1, d as explicitOrganization$2, f as flagValue, g as isRecord, h as parseWhere, j as hexToBigInt, k as concatHex, l as describeWhere, m as networkName, n as styleFor, o as STAGING_ORIGIN, p as flagValues, q as BaseError$1, r as whoamiLines, s as chooseEnvironment, t as headerLine, u as environmentOf, v as slice, w as InvalidAddressError, x as checksumAddress, y as sliceBytes, z as toHex } from "./where-view-Csv4lEES.mjs";
2
+ import { A as concatHex, B as toHex, C as getAddress$1, D as toRlp, E as format, F as toBytes$1, G as isHex$1, H as padHex, I as boolToHex, J as BaseError$1, K as IntegerOutOfRangeError, L as bytesToHex, M as hexToBigInt, N as hexToNumber, O as createCursor, P as hexToBytes, R as numberToHex, S as checksumAddress, T as InvalidAddressError, U as trim$1, V as pad, W as size, _ as isRecord, a as ENVIRONMENT_NAMES, b as sliceBytes, d as explicitOrganization$2, f as flagValue, g as scopeArgs, h as parseWhere, j as assertSize, k as concat$1, l as describeWhere, m as networkName, n as styleFor, o as STAGING_ORIGIN, p as flagValues, q as InvalidBytesBooleanError, r as whoamiLines, s as chooseEnvironment, t as headerLine, u as environmentOf, v as readField, w as keccak256$1, x as isAddress$1, y as slice, z as stringToHex } from "./where-view-BJmsAHQb.mjs";
3
3
  import { t as require_lib } from "./lib-CoGPQsbr.mjs";
4
4
  import process$1, { arch, platform } from "node:process";
5
5
  import { fileURLToPath } from "node:url";
@@ -7,8 +7,7 @@ import { closeSync, constants, fstatSync, lstatSync, openSync, readSync, realpat
7
7
  import { basename, dirname, join, resolve } from "node:path";
8
8
  import { randomBytes, randomUUID } from "node:crypto";
9
9
  import { Cause, Clock, Config, Console, Context, Crypto, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, FileSystem, Layer, LogLevel, Logger, Metric, Option, Path, Predicate, Queue, Redacted, Ref, References, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Sink, Stdio, Stream, Terminal, Tracer } from "effect";
10
- import { Argument, CliConfig, CliError, CliOutput, Command, Flag, GlobalFlag, Primitive, Prompt } from "effect/cli";
11
- import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/http";
10
+ import { Argument, CliConfig, CliError, CliOutput, Command, Flag, GlobalFlag, Param, Primitive, Prompt } from "effect/cli";
12
11
  import http from "http";
13
12
  import https from "https";
14
13
  import zlib from "zlib";
@@ -19,6 +18,7 @@ import url from "url";
19
18
  import util from "util";
20
19
  import { resolve as resolve$1 } from "path";
21
20
  import http2 from "http2";
21
+ import { FetchHttpClient, Headers, HttpBody, HttpClient, HttpClientError, HttpClientRequest, HttpClientResponse } from "effect/http";
22
22
  import { OtlpExporter, OtlpLogger, OtlpMetrics, OtlpSerialization, OtlpTracer } from "effect/observability";
23
23
  import { createServer } from "node:http";
24
24
  import { execFile, spawn } from "node:child_process";
@@ -1936,6 +1936,53 @@ function formatUnits(value, decimals) {
1936
1936
  return format(value, decimals);
1937
1937
  }
1938
1938
  //#endregion
1939
+ //#region src/runtime/format.ts
1940
+ const jsonFlag = Flag.Boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Print a versioned JSON result"));
1941
+ const JsonOutput = GlobalFlag.Setting("json")({ flag: jsonFlag });
1942
+ /** `--org <handle>` for one command. Commands resolve it through `#auth/actor`. */
1943
+ const OrganizationFlag = GlobalFlag.Setting("org")({ flag: Flag.String("org").pipe(Flag.withMetavar("<handle>"), Flag.withDescription("act as this organization for one command"), Flag.optional) });
1944
+ /** `--fields <a,b>`: the JSON result keeps only these fields of `data`. */
1945
+ const FieldsFlag = GlobalFlag.Setting("fields")({ flag: Flag.String("fields").pipe(Flag.withMetavar("<a,b>"), Flag.withDescription("choose which JSON fields come back"), Flag.optional) });
1946
+ /** `--env <name>` for one command. The runner reads it with the other environment sources. */
1947
+ const EnvironmentFlag = GlobalFlag.Setting("env")({ flag: Flag.String("env").pipe(Flag.withMetavar("<name>"), Flag.withDescription("use this environment for one command"), Flag.optional) });
1948
+ /** What the flags many commands share say in `--help` and `capxul schema`, in one voice. */
1949
+ const FLAG_HELP = {
1950
+ email: "the signed-in person to use, when this machine has several",
1951
+ confirm: "make the change without asking; scripts must pass it for each write",
1952
+ input: "the JSON input, from a file or - for stdin",
1953
+ requestKey: "a key that makes repeating the same write safe",
1954
+ timeout: "give up after this many seconds, 1 to 3600 (default 120)"
1955
+ };
1956
+ /** Local diagnostics require an explicit debug level. */
1957
+ const localDiagnosticsEnabled = (args) => {
1958
+ const terminator = args.indexOf("--");
1959
+ const flags = terminator < 0 ? args : args.slice(0, terminator);
1960
+ let level;
1961
+ for (const [index, arg] of flags.entries()) if (arg.startsWith("--log-level=")) level = arg.slice(12);
1962
+ else if (arg.startsWith("--logLevel=")) level = arg.slice(11);
1963
+ else if (arg === "--log-level" || arg === "--logLevel") level = flags[index + 1];
1964
+ return level === "debug";
1965
+ };
1966
+ /** Negotiate only the global format before native CLI error rendering.
1967
+ * Native Flag decoding remains the value authority; the command runner parses everything else.
1968
+ */
1969
+ const outputMode = (args) => Effect.gen(function* () {
1970
+ const terminator = args.indexOf("--");
1971
+ const flags = terminator < 0 ? args : args.slice(0, terminator);
1972
+ const values = [];
1973
+ for (const [index, arg] of flags.entries()) if (arg === "--no-json") values.push("false");
1974
+ else if (arg.startsWith("--json=")) values.push(arg.slice(7));
1975
+ else if (arg === "--json") {
1976
+ const next = flags[index + 1];
1977
+ const value = next === void 0 ? "true" : yield* Primitive.Boolean.parse(next).pipe(Effect.as(next), Effect.orElseSucceed(() => "true"));
1978
+ values.push(value);
1979
+ }
1980
+ return yield* jsonFlag.parse({
1981
+ flags: { json: values },
1982
+ arguments: []
1983
+ }).pipe(Effect.map(([, json]) => json ? "json" : "human"), Effect.orElseSucceed(() => values.includes("true") ? "json" : "human"));
1984
+ });
1985
+ //#endregion
1939
1986
  //#region ../../node_modules/.pnpm/convex@1.39.1_react@19.2.7/node_modules/convex/dist/esm/values/base64.js
1940
1987
  var lookup = [];
1941
1988
  var revLookup = [];
@@ -2744,6 +2791,9 @@ function toCurrencyCode(raw) {
2744
2791
  if (!isCurrencyCode(raw)) throw Errors.invalidInput("currencyCode", invalidValueReason("unsupported currency", raw));
2745
2792
  return raw;
2746
2793
  }
2794
+ function currencySymbolFor(code) {
2795
+ return CURRENCY_SYMBOLS[code];
2796
+ }
2747
2797
  const MAJOR_UNITS_RE = /^[0-9]+(?:\.[0-9]+)?$/;
2748
2798
  const MAX_UINT256 = 2n ** 256n;
2749
2799
  /**
@@ -5033,7 +5083,7 @@ faucetMint: Operation.action("accounts.grants.faucetMint", {
5033
5083
  ]
5034
5084
  }) };
5035
5085
  /** Every approval call: the record is not the signed-in person's, or there is none. */
5036
- const APPROVAL$1 = ["ROLE_PERMISSION_DENIED"];
5086
+ const APPROVAL$2 = ["ROLE_PERMISSION_DENIED"];
5037
5087
  /**
5038
5088
  * A prepared money action waits here for the person to sign it with their own
5039
5089
  * key, on the approval page. The backend never signs: it records the person's
@@ -5058,7 +5108,7 @@ const approvals = {
5058
5108
  errors: [
5059
5109
  ...CALL$10,
5060
5110
  ...DEFECTS,
5061
- ...APPROVAL$1
5111
+ ...APPROVAL$2
5062
5112
  ]
5063
5113
  }),
5064
5114
  /** The person opened the approval page: stamp the first review of a pending approval. */
@@ -5068,7 +5118,7 @@ const approvals = {
5068
5118
  errors: [
5069
5119
  ...CALL$10,
5070
5120
  ...DEFECTS,
5071
- ...APPROVAL$1
5121
+ ...APPROVAL$2
5072
5122
  ]
5073
5123
  }),
5074
5124
  /**
@@ -5084,7 +5134,7 @@ const approvals = {
5084
5134
  errors: [
5085
5135
  ...CALL$10,
5086
5136
  ...DEFECTS,
5087
- ...APPROVAL$1,
5137
+ ...APPROVAL$2,
5088
5138
  "WRONG_STATE"
5089
5139
  ]
5090
5140
  }),
@@ -5095,7 +5145,7 @@ const approvals = {
5095
5145
  errors: [
5096
5146
  ...CALL$10,
5097
5147
  ...DEFECTS,
5098
- ...APPROVAL$1,
5148
+ ...APPROVAL$2,
5099
5149
  "WRONG_STATE"
5100
5150
  ]
5101
5151
  }),
@@ -5106,7 +5156,7 @@ const approvals = {
5106
5156
  errors: [
5107
5157
  ...CALL$10,
5108
5158
  ...DEFECTS,
5109
- ...APPROVAL$1,
5159
+ ...APPROVAL$2,
5110
5160
  "WRONG_STATE"
5111
5161
  ]
5112
5162
  })
@@ -5576,6 +5626,28 @@ const probes = {
5576
5626
  })
5577
5627
  };
5578
5628
  //#endregion
5629
+ //#region ../../packages/protocol/src/evm/role-dsl.ts
5630
+ const ORG_BUDGET_ACTIONS = [
5631
+ "send",
5632
+ "sendBatch",
5633
+ "createCommitment"
5634
+ ];
5635
+ /**
5636
+ * How a capped Budget's allowance refills (#2298). `monthly` refills the
5637
+ * allowance to its cap every 30 days. Roles v2 counts fixed periods from the
5638
+ * moment the allowance is set; it has no calendar month. Absent means `none`:
5639
+ * the cap is one pot that spending drains for good.
5640
+ */
5641
+ const ORG_BUDGET_REFILLS = ["monthly", "none"];
5642
+ /** The fixed Roles v2 refill period of a `monthly` Budget: 30 days, in seconds. */
5643
+ const MONTHLY_REFILL_PERIOD_SECONDS = 30n * 24n * 60n * 60n;
5644
+ /** Derive the Zodiac substrate key for one durable Permission identity. */
5645
+ function roleKeyForPermission(permissionId) {
5646
+ const normalized = permissionId.trim();
5647
+ if (normalized.length === 0 || normalized !== permissionId) throw Errors.invalidInput("permissionId", "must be a non-empty canonical Permission ID");
5648
+ return keccak256$1(toBytes$1(`permission ${permissionId}`));
5649
+ }
5650
+ //#endregion
5579
5651
  //#region ../../packages/protocol/src/permissions/permission-schema.ts
5580
5652
  /**
5581
5653
  * One stored `setAllowance` call inside a command's expected authority set.
@@ -5614,12 +5686,19 @@ const BudgetScopeSchema = Schema.Struct({
5614
5686
  })]),
5615
5687
  actions: Schema.NonEmptyArray(Schema.Literals(["pay", "commitments"]))
5616
5688
  }).pipe(Schema.refine((scope) => new Set(scope.actions).size === scope.actions.length && (scope.recipients.type === "anyone" || scope.recipients.accounts.length >= 1 && scope.recipients.accounts.length <= 32 && new Set(scope.recipients.accounts).size === scope.recipients.accounts.length), { message: "Budget scope requires unique actions and one to 32 unique allowed Accounts" }));
5689
+ /**
5690
+ * How a capped Budget refills (#2298): `monthly` tops the cap back up every 30
5691
+ * days (a fixed Roles v2 period, not a calendar month); `none` never refills.
5692
+ * Absent means `none`, and writers store only `monthly`.
5693
+ */
5694
+ const BudgetRefillSchema = Schema.Literals(ORG_BUDGET_REFILLS);
5617
5695
  /** The accepted product policy stored with one Permission revision. */
5618
5696
  const CurrentPolicySchema = Schema.Union([Schema.Struct({
5619
5697
  type: Schema.Literal("budget"),
5620
5698
  asset: AssetIdSchema,
5621
5699
  limit: Schema.NullOr(Schema.String),
5622
- scope: BudgetScopeSchema
5700
+ scope: BudgetScopeSchema,
5701
+ refill: Schema.optional(BudgetRefillSchema)
5623
5702
  }), Schema.Struct({ type: Schema.Literal("managePeople") })]);
5624
5703
  const BudgetIntentSchema = CurrentPolicySchema.pipe(Schema.refine((value) => {
5625
5704
  if (value.type !== "budget") return false;
@@ -5684,7 +5763,8 @@ const ResolvedPermissionBudgetPolicySchema = Schema.Struct({
5684
5763
  "send",
5685
5764
  "sendBatch",
5686
5765
  "createCommitment"
5687
- ]))
5766
+ ])),
5767
+ refill: Schema.optional(BudgetRefillSchema)
5688
5768
  });
5689
5769
  const PermissionRecipientBindingSchema = Schema.Struct({
5690
5770
  accountId: AccountIdSchema$1,
@@ -5696,16 +5776,22 @@ const StoredPermissionIntentSchema = Schema.Union([Schema.Struct({
5696
5776
  type: Schema.Literal("budget"),
5697
5777
  asset: AssetIdSchema,
5698
5778
  limit: Schema.NullOr(AssetAmountSchema),
5699
- scope: Schema.optional(BudgetScopeSchema)
5779
+ scope: Schema.optional(BudgetScopeSchema),
5780
+ refill: Schema.optional(BudgetRefillSchema)
5700
5781
  }).pipe(Schema.refine((intent) => intent.limit === null || intent.limit.asset === intent.asset, { message: "Budget limit must use the Budget asset" })), Schema.Struct({ type: Schema.Literal("managePeople") })]);
5701
5782
  /** Writes state every granted right and use a finite positive AssetAmount cap. */
5702
5783
  const PermissionIntentSchema = Schema.Union([Schema.Struct({
5703
5784
  type: Schema.Literal("budget"),
5704
5785
  asset: AssetIdSchema,
5705
5786
  limit: AssetAmountSchema,
5706
- scope: BudgetScopeSchema
5787
+ scope: BudgetScopeSchema,
5788
+ refill: Schema.optional(BudgetRefillSchema)
5707
5789
  }).pipe(Schema.refine((intent) => intent.limit.asset === intent.asset && /[1-9]/u.test(intent.limit.value), { message: "Budget cap must be positive and use the Budget asset" })), Schema.Struct({ type: Schema.Literal("managePeople") })]);
5708
- /** Decode once and give set-like scope fields one stable command representation. */
5790
+ /**
5791
+ * Decode once and give set-like scope fields one stable command representation.
5792
+ * An explicit `refill: "none"` is kept: on a change it turns a refill off,
5793
+ * where a missing `refill` keeps the Budget's current one (`keepRefillOnChange`).
5794
+ */
5709
5795
  function decodePermissionIntent$1(input) {
5710
5796
  const intent = Schema.decodeUnknownSync(PermissionIntentSchema, { onExcessProperty: "error" })(input);
5711
5797
  if (intent.type === "managePeople") return intent;
@@ -5720,6 +5806,19 @@ function decodePermissionIntent$1(input) {
5720
5806
  }
5721
5807
  });
5722
5808
  }
5809
+ /**
5810
+ * A Budget change that leaves `refill` out keeps the Budget's current refill:
5811
+ * a change never turns a refill off by omission. An explicit `"none"` turns it
5812
+ * off. The backend plans, the SDK verifies and the CLI confirms the same
5813
+ * result. A create has no current refill, so a missing one means `none`.
5814
+ */
5815
+ function keepRefillOnChange(intent, before) {
5816
+ if (intent.type !== "budget" || intent.refill !== void 0 || before?.type !== "budget" || before.refill !== "monthly") return intent;
5817
+ return {
5818
+ ...intent,
5819
+ refill: before.refill
5820
+ };
5821
+ }
5723
5822
  //#endregion
5724
5823
  //#region ../../packages/protocol/src/permissions/replacement-schema.ts
5725
5824
  const InvitationPhaseSchema = Schema.Literals([
@@ -5924,7 +6023,7 @@ const InviteGrantsSchema = Schema.Array(Schema.Union([
5924
6023
  Schema.Struct({
5925
6024
  type: Schema.Literal("new_budget"),
5926
6025
  label: Schema.String.pipe(Schema.refine((value) => value.trim().length >= 1 && value.trim().length <= 128)),
5927
- intent: BudgetIntentSchema
6026
+ intent: BudgetIntentSchema.pipe(Schema.refine((intent) => intent.refill !== "monthly", { message: "An invitation's new Budget cannot refill; change it once granted" }))
5928
6027
  })
5929
6028
  ])).pipe(Schema.refine((grants) => {
5930
6029
  if (grants.length < 1 || grants.length > 16) return false;
@@ -9357,7 +9456,9 @@ const PEOPLE_OPERATIONS = {
9357
9456
  emitSafeConfirmed: "accounts.safes.emitSafeConfirmed",
9358
9457
  readSafe: "accounts.safes.readSafe",
9359
9458
  recordDeployment: "accounts.safes.recordDeployment"
9360
- }
9459
+ },
9460
+ /** The home's requests to pay and Payments to retry, read in one query. */
9461
+ summaries: { loadNeedsYou: "accounts.summaries.loadNeedsYou" }
9361
9462
  },
9362
9463
  /** The host observer's names for `client.approvals.*`. */
9363
9464
  approvals: {
@@ -9616,7 +9717,9 @@ emitSaved: "activity.annotations.emitSaved" },
9616
9717
  pay: "organizationPayments.pay",
9617
9718
  payBatch: "organizationPayments.payBatch",
9618
9719
  prepareExecution: "organizationPayments.prepareExecution",
9619
- retry: "organizationPayments.retry"
9720
+ retry: "organizationPayments.retry",
9721
+ cancel: "organizationPayments.cancel",
9722
+ redirect: "organizationPayments.redirect"
9620
9723
  },
9621
9724
  /**
9622
9725
  * Payments' internal functions and the client's own method names; its public
@@ -9638,7 +9741,9 @@ emitSaved: "activity.annotations.emitSaved" },
9638
9741
  loadContext: "payments.commands.loadContext",
9639
9742
  loadExecutionContext: "payments.commands.loadExecutionContext",
9640
9743
  /** The capped Budget a prepared Organization command spends from, before its allowance is read on chain. */
9641
- loadBudgetAllowance: "payments.commands.loadBudgetAllowance"
9744
+ loadBudgetAllowance: "payments.commands.loadBudgetAllowance",
9745
+ /** What a new Payment command would spend and from which Safe, read before it is born. */
9746
+ loadBirthFunding: "payments.commands.loadBirthFunding"
9642
9747
  },
9643
9748
  lifecycles: { prepareIntent: "payments.lifecycles.prepareIntent" },
9644
9749
  /** The stored UserOperation of every money command: Payment, Permission and configuration. */
@@ -9997,6 +10102,13 @@ const OptionalReasonCode = Schema.optional(Schema.Literals(CAPXUL_ERROR_CODES));
9997
10102
  * no catalog schema validates it.
9998
10103
  */
9999
10104
  const EXCEPTION_EVENT_NAME = "$exception";
10105
+ /**
10106
+ * The SDK's twin of `capxul_backend_expected_outcome`: an expected outcome the
10107
+ * SDK's failure projection captures through the host's PostHog client. Like
10108
+ * the backend's, its properties are the error evidence, so no catalog schema
10109
+ * validates it.
10110
+ */
10111
+ const SDK_EXPECTED_OUTCOME_EVENT_NAME = "capxul_sdk_expected_outcome";
10000
10112
  const PII = Schema.String.pipe(Schema.brand("PII"));
10001
10113
  /**
10002
10114
  * The canonical envelope (ADR-0020 A4): snake_case ONLY. `capxul_env` and
@@ -10096,6 +10208,42 @@ const UiStepProps = {
10096
10208
  organization_id: OptionalOrgId,
10097
10209
  reason_code: OptionalReasonCode
10098
10210
  };
10211
+ /**
10212
+ * A journey's variants (`JOURNEYS`): the way a person came into a journey, or
10213
+ * the way they took through it. A variant is a property of the journey's step
10214
+ * events, never a journey of its own, so each journey funnel breaks down by
10215
+ * it. A step event takes only the variants that a journey contract names on
10216
+ * it, picked one by one; the analytics plan test checks both directions.
10217
+ * `pay_path` and `funding_source` have no carrier until their Phase 2 events.
10218
+ * `payment_*` events already carry `recipient_is_capxul_account`, and
10219
+ * `invoice_issued` carries `issuer_scope_kind`; neither is repeated here.
10220
+ */
10221
+ const JourneyVariantProps = {
10222
+ /** How a person came to Get started: by themselves, an invite, money that waited, or an invoice. */
10223
+ entry_path: Schema.optional(Schema.Literals([
10224
+ "signup",
10225
+ "invite",
10226
+ "claim",
10227
+ "invoice"
10228
+ ])),
10229
+ /** Where the person acted. `agent` is an AI agent driving the CLI. */
10230
+ surface: Schema.optional(Schema.Literals([
10231
+ "web",
10232
+ "cli",
10233
+ "agent"
10234
+ ])),
10235
+ /** Who pays: a person from their own account, or an Organization from a Budget. */
10236
+ payer_kind: Schema.optional(Schema.Literals(["person", "organization"])),
10237
+ /** How a request was paid: from the payer's inbox, or by its link at checkout. */
10238
+ pay_path: Schema.optional(Schema.Literals(["inbox", "link"])),
10239
+ /** Where an account's first money came from. `bank` is the planned on-ramp. */
10240
+ funding_source: Schema.optional(Schema.Literals([
10241
+ "deposit",
10242
+ "payment",
10243
+ "claim",
10244
+ "bank"
10245
+ ]))
10246
+ };
10099
10247
  //#endregion
10100
10248
  //#region ../../packages/observability/src/events/money.ts
10101
10249
  /**
@@ -10185,6 +10333,8 @@ const PaymentInitiatedProps = Schema.Struct({
10185
10333
  execution_id: OptionalString,
10186
10334
  payment_lifecycle_id: OptionalString,
10187
10335
  ...TelemetryEnvelopeProps,
10336
+ payer_kind: JourneyVariantProps.payer_kind,
10337
+ surface: JourneyVariantProps.surface,
10188
10338
  ...RecipientTelemetryProps,
10189
10339
  kind: Schema.String,
10190
10340
  payment_id: Schema.String
@@ -10200,6 +10350,8 @@ const PaymentLifecycleProps = {
10200
10350
  const PaymentSettledL2Props = Schema.Struct({
10201
10351
  ...PaymentLifecycleProps,
10202
10352
  ...TelemetryEnvelopeProps,
10353
+ payer_kind: JourneyVariantProps.payer_kind,
10354
+ surface: JourneyVariantProps.surface,
10203
10355
  ...RecipientTelemetryProps,
10204
10356
  kind: Schema.String,
10205
10357
  chain_id: Schema.Finite,
@@ -10407,6 +10559,7 @@ const OrganizationCreationStartedProps = Schema.Struct({
10407
10559
  });
10408
10560
  const OrganizationCreationReadyProps = Schema.Struct({
10409
10561
  ...TelemetryEnvelopeProps,
10562
+ surface: JourneyVariantProps.surface,
10410
10563
  $insert_id: Schema.String,
10411
10564
  organization_id: OrgIdTelemetrySchema,
10412
10565
  attempt_number: AttemptNumberSchema,
@@ -10431,45 +10584,6 @@ const OrganizationCreationFailedProps = Schema.Struct({
10431
10584
  ...ORGANIZATION_SETUP_REASONS
10432
10585
  ]))
10433
10586
  });
10434
- const OrgCreateStartedProps = Schema.Struct({
10435
- ...TelemetryEnvelopeProps,
10436
- org_id: OptionalOrgId,
10437
- email_domain: OptionalString,
10438
- template: OptionalString
10439
- });
10440
- const OrgSafeCreatedProps = Schema.Struct({
10441
- ...TelemetryEnvelopeProps,
10442
- org_id: OptionalOrgId,
10443
- chainId: OptionalChainId,
10444
- safe_address: OptionalAddress
10445
- });
10446
- const OrgSafeConfirmedProps = Schema.Struct({
10447
- ...TelemetryEnvelopeProps,
10448
- org_id: OptionalOrgId,
10449
- chainId: OptionalChainId,
10450
- safe_address: OptionalAddress,
10451
- durationMs: OptionalDurationMs
10452
- });
10453
- const OrgRolesSeededProps = Schema.Struct({
10454
- ...TelemetryEnvelopeProps,
10455
- org_id: OptionalOrgId,
10456
- chainId: OptionalChainId,
10457
- role: OptionalString,
10458
- txHash: OptionalTxHash
10459
- });
10460
- const OrgCreatedProps = Schema.Struct({
10461
- ...TelemetryEnvelopeProps,
10462
- org_id: OptionalOrgId,
10463
- chainId: OptionalChainId,
10464
- safe_address: OptionalAddress,
10465
- durationMs: OptionalDurationMs
10466
- });
10467
- const OrgCreateFailedProps = Schema.Struct({
10468
- ...TelemetryEnvelopeProps,
10469
- ...ChainCauseTelemetryProps,
10470
- org_id: OptionalOrgId,
10471
- reason: OptionalString
10472
- });
10473
10587
  const OrgInviteSentProps = Schema.Struct({
10474
10588
  ...TelemetryEnvelopeProps,
10475
10589
  org_id: OptionalOrgId,
@@ -10514,30 +10628,6 @@ const OrganizationCreationFailedTelemetryEventSchema = Schema.Struct({
10514
10628
  name: Schema.Literal("organization_creation_failed"),
10515
10629
  props: OrganizationCreationFailedProps
10516
10630
  });
10517
- const OrgCreateStartedTelemetryEventSchema = Schema.Struct({
10518
- name: Schema.Literal("org_create_started"),
10519
- props: OrgCreateStartedProps
10520
- });
10521
- const OrgSafeCreatedTelemetryEventSchema = Schema.Struct({
10522
- name: Schema.Literal("org_safe_created"),
10523
- props: OrgSafeCreatedProps
10524
- });
10525
- const OrgSafeConfirmedTelemetryEventSchema = Schema.Struct({
10526
- name: Schema.Literal("org_safe_confirmed"),
10527
- props: OrgSafeConfirmedProps
10528
- });
10529
- const OrgRolesSeededTelemetryEventSchema = Schema.Struct({
10530
- name: Schema.Literal("org_roles_seeded"),
10531
- props: OrgRolesSeededProps
10532
- });
10533
- const OrgCreatedTelemetryEventSchema = Schema.Struct({
10534
- name: Schema.Literal("org_created"),
10535
- props: OrgCreatedProps
10536
- });
10537
- const OrgCreateFailedTelemetryEventSchema = Schema.Struct({
10538
- name: Schema.Literal("org_create_failed"),
10539
- props: OrgCreateFailedProps
10540
- });
10541
10631
  const OrgInviteSentTelemetryEventSchema = Schema.Struct({
10542
10632
  name: Schema.Literal("org_invite_sent"),
10543
10633
  props: OrgInviteSentProps
@@ -11436,10 +11526,14 @@ const BootstrapFailedProps = Schema.Struct({
11436
11526
  });
11437
11527
  const MemberActivationStartedProps = Schema.Struct({
11438
11528
  ...TelemetryEnvelopeProps,
11529
+ entry_path: JourneyVariantProps.entry_path,
11530
+ surface: JourneyVariantProps.surface,
11439
11531
  $insert_id: Schema.String
11440
11532
  });
11441
11533
  const MemberActivationReadyProps = Schema.Struct({
11442
11534
  ...TelemetryEnvelopeProps,
11535
+ entry_path: JourneyVariantProps.entry_path,
11536
+ surface: JourneyVariantProps.surface,
11443
11537
  $insert_id: Schema.String,
11444
11538
  duration_ms: DurationMsSchema
11445
11539
  });
@@ -11564,12 +11658,6 @@ const TELEMETRY_EVENT_SCHEMAS = {
11564
11658
  bundler_error: BundlerErrorTelemetryEventSchema,
11565
11659
  receipt_pending: ReceiptPendingTelemetryEventSchema,
11566
11660
  operation_retried: OperationRetriedTelemetryEventSchema,
11567
- org_create_started: OrgCreateStartedTelemetryEventSchema,
11568
- org_safe_created: OrgSafeCreatedTelemetryEventSchema,
11569
- org_safe_confirmed: OrgSafeConfirmedTelemetryEventSchema,
11570
- org_roles_seeded: OrgRolesSeededTelemetryEventSchema,
11571
- org_created: OrgCreatedTelemetryEventSchema,
11572
- org_create_failed: OrgCreateFailedTelemetryEventSchema,
11573
11661
  org_invite_sent: OrgInviteSentTelemetryEventSchema,
11574
11662
  org_invite_accepted: OrgInviteAcceptedTelemetryEventSchema,
11575
11663
  org_invite_expired: OrgInviteExpiredTelemetryEventSchema,
@@ -11929,56 +12017,6 @@ function stripUrlQueryAndFragment(value) {
11929
12017
  return value.slice(0, Math.min(queryIndex, fragmentIndex));
11930
12018
  }
11931
12019
  //#endregion
11932
- //#region src/runtime/command-metadata.ts
11933
- var CliCommandMetadata = class extends Context.Service()("capxul-cli/CommandMetadata") {};
11934
- const collectionControl = (name) => name === "config" || name === "config.telemetry" || name.startsWith("config.telemetry.");
11935
- /** Resolve only a complete path supplied by the native parser. */
11936
- function commandAtPath(root, path) {
11937
- if (path[0] !== root.name) return void 0;
11938
- let selected = root;
11939
- for (const part of path.slice(1)) {
11940
- const child = selected.subcommands.flatMap((group) => group.commands).find((candidate) => candidate.name === part || candidate.alias === part);
11941
- if (child === void 0) return void 0;
11942
- selected = child;
11943
- }
11944
- return selected;
11945
- }
11946
- const validateNativeScope = (input) => {
11947
- if (typeof input === "object" && input !== null && "orgs" in input && Array.isArray(input.orgs) && input.orgs.every((id) => typeof id === "string") && new Set(input.orgs).size > 1) return Effect.fail(new CliError.InvalidValue({
11948
- option: "org",
11949
- value: "multiple IDs",
11950
- expected: "one exact Organization ID",
11951
- kind: "flag"
11952
- }));
11953
- return Effect.void;
11954
- };
11955
- /** Keep the native grammar and validate scope without business actions. */
11956
- function nativeInputTree(node) {
11957
- const input = Command.withHandler(node, validateNativeScope);
11958
- const children = node.subcommands.flatMap((group) => {
11959
- const commands = group.commands.map(nativeInputTree);
11960
- return group.group === void 0 ? commands : [{
11961
- group: group.group,
11962
- commands
11963
- }];
11964
- });
11965
- return Command.withSubcommands(input, children);
11966
- }
11967
- function metadataAtPath(root, path) {
11968
- let selected = root;
11969
- for (const part of path.slice(1)) {
11970
- const child = selected.subcommands.flatMap((group) => group.commands).find((command) => command.name === part || command.alias === part);
11971
- if (child === void 0) break;
11972
- selected = child;
11973
- }
11974
- const metadata = Context.getOption(selected.annotations, CliCommandMetadata);
11975
- return Option.isSome(metadata) && CLI_COMMAND_NAMES.includes(metadata.value.name) ? metadata.value : {
11976
- name: "unknown",
11977
- capability: "cli",
11978
- observation: "command"
11979
- };
11980
- }
11981
- //#endregion
11982
12020
  //#region ../../packages/sdk/src/access/actor-references.ts
11983
12021
  function actorReferenceToBackend(actor) {
11984
12022
  if (actor === void 0) return void 0;
@@ -12222,7 +12260,7 @@ const FIELD_RULES = {
12222
12260
  },
12223
12261
  anonymousId: {
12224
12262
  maxLength: 128,
12225
- pattern: /^anon_[A-Za-z0-9-]+$/u
12263
+ pattern: /^(?:anon_[A-Za-z0-9-]+|[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/u
12226
12264
  },
12227
12265
  traceparent: {
12228
12266
  maxLength: 55,
@@ -14870,7 +14908,7 @@ function mapAccountLifecycle(input) {
14870
14908
  }
14871
14909
  //#endregion
14872
14910
  //#region ../../packages/sdk/package.json
14873
- var version$2 = "4.20.0-beta.27";
14911
+ var version$2 = "4.20.0-beta.28";
14874
14912
  //#endregion
14875
14913
  //#region ../../packages/sdk/src/observability/telemetry/exception-projection.ts
14876
14914
  /** Fixed fallback for failures that have no safe message. */
@@ -15308,7 +15346,7 @@ const REVERT_SELECTOR_RE = /^0x(?:[0-9a-fA-F]{8})?$/u;
15308
15346
  //#region ../../packages/sdk/src/observability/posthog-failure.ts
15309
15347
  /** The expected-outcome codes, read as plain strings so a free-form kind can be looked up. */
15310
15348
  /** Stable PostHog event used for typed failures that are expected product outcomes. */
15311
- const CAPXUL_SDK_EXPECTED_OUTCOME_EVENT = "capxul_sdk_expected_outcome";
15349
+ const CAPXUL_SDK_EXPECTED_OUTCOME_EVENT = SDK_EXPECTED_OUTCOME_EVENT_NAME;
15312
15350
  /**
15313
15351
  * Adapt the host's already-initialized PostHog-like client. This function does
15314
15352
  * not import, initialize, configure, or own PostHog.
@@ -18160,6 +18198,12 @@ function paymentsV2Signature(name) {
18160
18198
  const SEND_SIGNATURE = paymentsV2Signature("send");
18161
18199
  const SEND_BATCH_SIGNATURE = paymentsV2Signature("sendBatch");
18162
18200
  const CREATE_COMMITMENT_SIGNATURE = paymentsV2Signature("createCommitment");
18201
+ /**
18202
+ * #2299 ruling C: only an Owner cancels or redirects an Organization-funded
18203
+ * commitment, so these two sit in the Owner role's scope, never a Budget's.
18204
+ * The contract admits only the commitment's creator, the Organization Safe.
18205
+ */
18206
+ const OWNER_COMMITMENT_SIGNATURES = [paymentsV2Signature("cancel"), paymentsV2Signature("redirect")];
18163
18207
  //#endregion
18164
18208
  //#region ../../packages/protocol/src/evm/organization-payments.ts
18165
18209
  /** Zodiac ModuleProxyFactory on Base Sepolia. */
@@ -18396,24 +18440,18 @@ function encodePreparedPaymentCallData(command) {
18396
18440
  }));
18397
18441
  }
18398
18442
  const [item] = command.items;
18399
- if (command.executionRoute.kind !== "direct" || command.items.length !== 1 || item === void 0 || isBirth(item)) throw Errors.invalidInput("items", "Commitment lifecycle requires one direct execution");
18400
- return safe4337ExecuteUserOpCallData({
18443
+ const route = command.executionRoute;
18444
+ if (command.items.length !== 1 || item === void 0 || isBirth(item) || route.kind !== "direct" && item.operation === "claim_commitment") throw Errors.invalidInput("items", "Commitment lifecycle requires one lone execution");
18445
+ const call = {
18401
18446
  to: command.contractAddress,
18447
+ value: 0n,
18402
18448
  data: lifecycleData(item)
18403
- });
18404
- }
18405
- //#endregion
18406
- //#region ../../packages/protocol/src/evm/role-dsl.ts
18407
- const ORG_BUDGET_ACTIONS = [
18408
- "send",
18409
- "sendBatch",
18410
- "createCommitment"
18411
- ];
18412
- /** Derive the Zodiac substrate key for one durable Permission identity. */
18413
- function roleKeyForPermission(permissionId) {
18414
- const normalized = permissionId.trim();
18415
- if (normalized.length === 0 || normalized !== permissionId) throw Errors.invalidInput("permissionId", "must be a non-empty canonical Permission ID");
18416
- return keccak256$1(toBytes$1(`permission ${permissionId}`));
18449
+ };
18450
+ return route.kind === "direct" ? safe4337ExecuteUserOpCallData(call) : buildOrgRoleSpendPersonalCallData(buildOrgRoleMultiCall({
18451
+ rolesModuleAddress: route.rolesModule,
18452
+ roleKey: route.roleKey,
18453
+ calls: [call]
18454
+ }));
18417
18455
  }
18418
18456
  //#endregion
18419
18457
  //#region ../../packages/sdk/src/payments/operation-verification.ts
@@ -18442,6 +18480,16 @@ function moneySafeOpDigest(operation, chainId) {
18442
18480
  userOp: safe4337UserOperation(operation)
18443
18481
  });
18444
18482
  }
18483
+ /**
18484
+ * An Organization-funded cancel or redirect runs through the Owner role
18485
+ * (#2299), which no Budget names. Its role key cannot widen what the signer
18486
+ * may do: the Roles module admits only a role the signer's Safe holds, and the
18487
+ * contract admits only the commitment's creator.
18488
+ */
18489
+ function isOwnerLifecycle(command) {
18490
+ const [item] = command.items;
18491
+ return command.items.length === 1 && (item?.operation === "cancel_commitment" || item?.operation === "redirect_commitment");
18492
+ }
18445
18493
  /** Verify every actual Money signing target against configured trust anchors. */
18446
18494
  function verifiesMoneyOperation(evidence, expectedSafe, chainId, permissionId) {
18447
18495
  try {
@@ -18452,7 +18500,7 @@ function verifiesMoneyOperation(evidence, expectedSafe, chainId, permissionId) {
18452
18500
  const route = command.executionRoute;
18453
18501
  if (route.kind === "direct") {
18454
18502
  if (permissionId !== void 0 || command.senderSafe.toLowerCase() !== expectedSafe.toLowerCase()) return false;
18455
- } else if (permissionId === void 0 || route.roleKey.toLowerCase() !== roleKeyForPermission(permissionId).toLowerCase() || command.senderSafe.toLowerCase() !== route.organizationSafe.toLowerCase()) return false;
18503
+ } else if ((permissionId === void 0 ? !isOwnerLifecycle(command) : route.roleKey.toLowerCase() !== roleKeyForPermission(permissionId).toLowerCase()) || command.senderSafe.toLowerCase() !== route.organizationSafe.toLowerCase()) return false;
18456
18504
  if (operation.callData.toLowerCase() !== encodePreparedPaymentCallData(command).toLowerCase()) return false;
18457
18505
  const digest = moneySafeOpDigest(operation, chainId);
18458
18506
  return evidence.digest.toLowerCase() === digest.toLowerCase() && evidence.executionId === `money_execution_${digest.slice(2).toLowerCase()}`;
@@ -19231,9 +19279,11 @@ function subscribeFreshQuery(deps, args, listener, operation, projection, option
19231
19279
  //#region ../../packages/sdk/src/payments/lifecycle-execution.ts
19232
19280
  /**
19233
19281
  * The prepared command is this lifecycle intent; a retry also names the
19234
- * original birth command of the scope it was asked for.
19282
+ * original birth command of the scope it was asked for. A claim, and an
19283
+ * Account's own cancel or redirect, runs directly from the signer's Safe; an
19284
+ * Organization's cancel or redirect runs through its Owner role (#2299).
19235
19285
  */
19236
- const lifecycleCommandCheck = (intent, expectedOrganizationId) => async (request, evidence) => await matchesIntent(intent)(request, evidence) && (intent.kind !== "retry" || (expectedOrganizationId === void 0 ? evidence.sourceCommand?.kind === "birth" && evidence.sourceCommand.actor.kind === "account" : evidence.sourceCommand?.kind === "birth" && evidence.sourceCommand.actor.kind === "organization" && evidence.sourceCommand.actor.orgId === expectedOrganizationId));
19286
+ const lifecycleCommandCheck = (intent, expectedOrganizationId) => async (request, evidence) => await matchesIntent(intent)(request, evidence) && (intent.kind === "retry" ? expectedOrganizationId === void 0 ? evidence.sourceCommand?.kind === "birth" && evidence.sourceCommand.actor.kind === "account" : evidence.sourceCommand?.kind === "birth" && evidence.sourceCommand.actor.kind === "organization" && evidence.sourceCommand.actor.orgId === expectedOrganizationId : evidence.preparedCommand?.executionRoute.kind === (intent.kind === "claim" || expectedOrganizationId === void 0 ? "direct" : "zodiac_role"));
19237
19287
  /** The prepared command pays exactly the request the payer confirmed. */
19238
19288
  const requestPaymentCheck = (confirmation) => async (request) => "kind" in request && request.kind === "payment_request" && canonicalJson(request.confirmation) === canonicalJson(confirmation);
19239
19289
  function executePaymentLifecycle(deps, intent, signal, controls, expectedOrganizationId, onPrepared) {
@@ -20123,17 +20173,21 @@ function paymentExecutionIntent(input) {
20123
20173
  const to = normalizeTargetForBackend(input.to, "to");
20124
20174
  if (!to.ok) return to;
20125
20175
  try {
20126
- return {
20176
+ const decoded = Schema.decodeResult(PaymentExecutionIntentSchema, { onExcessProperty: "error" })({
20177
+ to: to.value,
20178
+ amount: input.amount,
20179
+ ...input.externalAddressAcknowledged === void 0 ? {} : { externalAddressAcknowledged: input.externalAddressAcknowledged },
20180
+ ...input.paymentType === void 0 ? {} : { paymentType: input.paymentType },
20181
+ ...input.document === void 0 ? {} : { document: input.document },
20182
+ ...input.supportingReceipts === void 0 ? {} : { supportingReceipts: input.supportingReceipts },
20183
+ ...input.timing === void 0 ? {} : { timing: input.timing }
20184
+ });
20185
+ return Result.isSuccess(decoded) ? {
20127
20186
  ok: true,
20128
- value: Schema.decodeSync(PaymentExecutionIntentSchema, { onExcessProperty: "error" })({
20129
- to: to.value,
20130
- amount: input.amount,
20131
- ...input.externalAddressAcknowledged === void 0 ? {} : { externalAddressAcknowledged: input.externalAddressAcknowledged },
20132
- ...input.paymentType === void 0 ? {} : { paymentType: input.paymentType },
20133
- ...input.document === void 0 ? {} : { document: input.document },
20134
- ...input.supportingReceipts === void 0 ? {} : { supportingReceipts: input.supportingReceipts },
20135
- ...input.timing === void 0 ? {} : { timing: input.timing }
20136
- })
20187
+ value: decoded.success
20188
+ } : {
20189
+ ok: false,
20190
+ error: invalidPaymentField(decoded.failure.issue)
20137
20191
  };
20138
20192
  } catch {
20139
20193
  return {
@@ -20142,6 +20196,13 @@ function paymentExecutionIntent(input) {
20142
20196
  };
20143
20197
  }
20144
20198
  }
20199
+ const formatIssue = SchemaIssue.makeFormatterStandardSchemaV1();
20200
+ /** The first failing field and why, e.g. `amount.value`, as the backend's row decode names it (#2381). */
20201
+ function invalidPaymentField(issue) {
20202
+ const first = formatIssue(issue).issues[0];
20203
+ const field = first?.path?.map((segment) => String(typeof segment === "object" ? segment.key : segment)).join(".");
20204
+ return Errors.invalidInput(field === void 0 || field.length === 0 ? "payment" : field, first?.message ?? "invalid personal payment input");
20205
+ }
20145
20206
  function normalizePaymentTiming(payment) {
20146
20207
  const { release: _release, ...rest } = payment;
20147
20208
  return {
@@ -20958,6 +21019,27 @@ function makeOrganizationPaymentsMethods(deps, orgId) {
20958
21019
  flights: /* @__PURE__ */ new Map()
20959
21020
  };
20960
21021
  const execute = (input, signal, operation = "organization-pay-batch", controls, onPrepared) => executeOrganizationBatch(scope, input, signal, operation, controls, onPrepared);
21022
+ /** One Owner-signed cancel or redirect, answering the one Payment it acted on (#2299). */
21023
+ const ownerLifecycle = (intent, controls, operation) => runCapxulEffect(Effect.gen(function* () {
21024
+ const captured = carryInvocationParentSpan(controls, yield* Effect.currentSpan.pipe(Effect.orDie));
21025
+ return yield* awaitCapxulResult(async () => {
21026
+ const execution = executionDependencies$1(deps);
21027
+ if (execution === null) return {
21028
+ ok: false,
21029
+ error: Errors.notImplemented("organizationPayments", "executionComposition")
21030
+ };
21031
+ const result = await executePaymentLifecycle(execution, intent, controls?.signal, captured, orgId);
21032
+ if (!result.ok) return result;
21033
+ const [payment, ...rest] = result.value.payments;
21034
+ return payment === void 0 || rest.length !== 0 ? {
21035
+ ok: false,
21036
+ error: Errors.unknown()
21037
+ } : {
21038
+ ok: true,
21039
+ value: normalizePaymentTiming(payment)
21040
+ };
21041
+ });
21042
+ }), observationOnlyControls(controls), operation, deps.runPromise);
20961
21043
  return {
20962
21044
  ...makeOrganizationPaymentReadMethods(deps, orgId),
20963
21045
  retry: (paymentId, options) => {
@@ -21016,6 +21098,19 @@ function makeOrganizationPaymentsMethods(deps, orgId) {
21016
21098
  });
21017
21099
  }), observationOnlyControls(controls), CAPXUL_OPERATIONS.organizationPayments.pay, deps.runPromise);
21018
21100
  },
21101
+ cancel: (paymentId, options) => ownerLifecycle({
21102
+ kind: "cancel",
21103
+ paymentId
21104
+ }, deps.invocationControls?.(options) ?? options, CAPXUL_OPERATIONS.organizationPayments.cancel),
21105
+ redirect: (input, options) => {
21106
+ const controls = deps.invocationControls?.(options) ?? options;
21107
+ const recipient = normalizeTargetForBackend(input.to, "to");
21108
+ return recipient.ok ? ownerLifecycle({
21109
+ kind: "redirect",
21110
+ paymentId: input.paymentId,
21111
+ recipient: recipient.value
21112
+ }, controls, CAPXUL_OPERATIONS.organizationPayments.redirect) : runCapxulEffect(Effect.fail(recipient.error), observationOnlyControls(controls), CAPXUL_OPERATIONS.organizationPayments.redirect, deps.runPromise);
21113
+ },
21019
21114
  payBatch: (input, options) => {
21020
21115
  const controls = deps.invocationControls?.(options) ?? options;
21021
21116
  return runCapxulEffect(Effect.gen(function* () {
@@ -25305,6 +25400,40 @@ function executeIdentityProductObservation(telemetry, record, state) {
25305
25400
  return ignoreTransportFailure(() => invocationTelemetry.emit(mapped), "emit", mapped.name).pipe(Effect.andThen(ignoreTransportFailure(after, mapped.name === "auth_signed_out" ? "reset" : "identify", mapped.name)));
25306
25401
  }
25307
25402
  //#endregion
25403
+ //#region ../../packages/sdk/src/money/format.ts
25404
+ function formatMoney(money, options = {}) {
25405
+ if (!Number.isInteger(money.decimals) || money.decimals < 0) throw Errors.invalidInput("money.decimals", "must be a non-negative integer");
25406
+ const symbol = currencySymbolFor(toCurrencyCode(money.currency));
25407
+ const maximumFractionDigits = options.grammar === "code" ? 4 : 2;
25408
+ const rounded = roundDecimal(money.value, money.decimals, maximumFractionDigits);
25409
+ const grouped = rounded.integer.replace(/\B(?=(\d{3})+(?!\d))/g, ",");
25410
+ const signed = `${rounded.negative ? "-" : ""}${grouped}`;
25411
+ if (options.grammar === "code") {
25412
+ if (!rounded.hadFraction) return `${signed} ${money.currency}`;
25413
+ let displayedFraction = rounded.fraction;
25414
+ while (displayedFraction.length > 2 && displayedFraction.endsWith("0")) displayedFraction = displayedFraction.slice(0, -1);
25415
+ return `${signed}.${displayedFraction} ${money.currency}`;
25416
+ }
25417
+ return `${symbol}${signed}.${rounded.fraction}`;
25418
+ }
25419
+ function roundDecimal(value, decimals, maximumFractionDigits) {
25420
+ const match = /^(-?)(\d+)(?:\.(\d+))?$/.exec(value);
25421
+ if (match === null) throw Errors.invalidInput("money.value", "must be a base-10 decimal string");
25422
+ const integer = match[2] ?? "0";
25423
+ const sourceFraction = match[3] ?? "";
25424
+ if (sourceFraction.length > decimals) throw Errors.invalidInput("money.value", "fraction exceeds money.decimals");
25425
+ const scale = 10n ** BigInt(maximumFractionDigits);
25426
+ const keptFraction = sourceFraction.slice(0, maximumFractionDigits).padEnd(maximumFractionDigits, "0");
25427
+ let scaled = BigInt(integer) * scale + BigInt(keptFraction || "0");
25428
+ if ((sourceFraction[maximumFractionDigits] ?? "0") >= "5") scaled += 1n;
25429
+ return {
25430
+ negative: match[1] === "-",
25431
+ integer: (scaled / scale).toString(),
25432
+ fraction: (scaled % scale).toString().padStart(maximumFractionDigits, "0"),
25433
+ hadFraction: /[1-9]/.test(sourceFraction)
25434
+ };
25435
+ }
25436
+ //#endregion
25308
25437
  //#region ../../packages/sdk/src/permissions/command-decoding.ts
25309
25438
  /** The caller's input as a command: ids branded and the intent decoded, or invalid input thrown. */
25310
25439
  const buildCommand = {
@@ -34966,13 +35095,15 @@ function normalizeOrgBudgetPolicy(policy) {
34966
35095
  if (actionSet.has(action)) throw Errors.invalidInput("policy.actions", `duplicate Budget action: ${action}`);
34967
35096
  actionSet.add(action);
34968
35097
  }
35098
+ if (policy.refill === "monthly" && limit.kind === "unlimited") throw Errors.invalidInput("policy.refill", "only a capped Budget can refill");
34969
35099
  return {
34970
35100
  assetId,
34971
35101
  tokenAddress,
34972
35102
  decimals: policy.asset.decimals,
34973
35103
  limit,
34974
35104
  recipients,
34975
- actions: ORG_BUDGET_ACTIONS.filter((action) => actionSet.has(action))
35105
+ actions: ORG_BUDGET_ACTIONS.filter((action) => actionSet.has(action)),
35106
+ refillPeriod: policy.refill === "monthly" ? MONTHLY_REFILL_PERIOD_SECONDS : 0n
34976
35107
  };
34977
35108
  }
34978
35109
  function resolvedBudgetApprovalPermission(tokenAddress) {
@@ -35047,8 +35178,8 @@ function buildOrgBudgetCallsWithEncoder(input, encodeRoles) {
35047
35178
  allowanceKey,
35048
35179
  BigInt(policy.limit.rawUnits),
35049
35180
  BigInt(policy.limit.rawUnits),
35050
- 0n,
35051
- 0n,
35181
+ policy.refillPeriod === 0n ? 0n : BigInt(policy.limit.rawUnits),
35182
+ policy.refillPeriod,
35052
35183
  0n
35053
35184
  ]
35054
35185
  }),
@@ -35242,13 +35373,20 @@ function buildExplicitBootstrapRoleCalls(input, encodeRoles) {
35242
35373
  roles: [{
35243
35374
  key: managementRoleKey,
35244
35375
  members: [input.ownerAddress],
35245
- permissions: [{
35246
- targetAddress: input.rolesModuleAddress,
35247
- signature: "assignRoles(address,bytes32[],bool[])"
35248
- }, ...ROLE_MANAGEMENT_SIGNATURES.map((signature) => ({
35249
- targetAddress: input.rolesModuleAddress,
35250
- signature
35251
- }))]
35376
+ permissions: [
35377
+ {
35378
+ targetAddress: input.rolesModuleAddress,
35379
+ signature: "assignRoles(address,bytes32[],bool[])"
35380
+ },
35381
+ ...ROLE_MANAGEMENT_SIGNATURES.map((signature) => ({
35382
+ targetAddress: input.rolesModuleAddress,
35383
+ signature
35384
+ })),
35385
+ ...OWNER_COMMITMENT_SIGNATURES.map((signature) => ({
35386
+ targetAddress: CAPXUL_PAYMENTS_V2_ADDRESS,
35387
+ signature
35388
+ }))
35389
+ ]
35252
35390
  }]
35253
35391
  }).map((call) => ({
35254
35392
  to: toLowerHex(call.to, "setUpRoles.call.to"),
@@ -35716,7 +35854,7 @@ function matchesCurrentPolicy(intent, policy) {
35716
35854
  asset: policy.asset,
35717
35855
  value: policy.limit
35718
35856
  }, CONFIGURED_MONEY_ASSETS).minorUnits;
35719
- return policy.asset === intent.asset && raw === recorded && (intent.scope === void 0 || canonicalJson(intent.scope) === canonicalJson(policy.scope));
35857
+ return policy.asset === intent.asset && raw === recorded && intent.refill === "monthly" === (policy.refill === "monthly") && (intent.scope === void 0 || canonicalJson(intent.scope) === canonicalJson(policy.scope));
35720
35858
  }
35721
35859
  function previousPolicyForChange(effect, command) {
35722
35860
  if (effect.previousIntent.type === "budget" && (effect.previousIntent.scope === void 0 || effect.previousRecipientBindings === void 0)) throw new EvidenceMismatch("Prior complete Budget proof is unavailable");
@@ -35734,8 +35872,9 @@ function policyCalls(evidence, context, command) {
35734
35872
  const roleKey = effect.operation === "change" ? effect.roleKey : roleKeyForPermission(permissionId);
35735
35873
  const revision = effect.operation === "change" ? effect.revision : 0;
35736
35874
  if (effect.operation !== command.operation || effect.operation === "change" && effect.permissionId !== permissionId || evidence.expectedRevision !== revision) throw new EvidenceMismatch("Permission policy binding mismatch");
35875
+ const intent = effect.operation === "change" ? keepRefillOnChange(command.intent, effect.previousCurrentPolicy) : command.intent;
35737
35876
  const value = evidence.value;
35738
- if (Array.isArray(value) || !("type" in value) || !matchesCurrentPolicy(command.intent, value.currentPolicy) || !permissionMatches(value, context.orgId, permissionId, command.operation === "create" ? 0 : revision + 1, command.intent, roleKey)) throw new EvidenceMismatch("Permission value mismatch");
35877
+ if (Array.isArray(value) || !("type" in value) || !matchesCurrentPolicy(intent, value.currentPolicy) || !permissionMatches(value, context.orgId, permissionId, command.operation === "create" ? 0 : revision + 1, intent, roleKey)) throw new EvidenceMismatch("Permission value mismatch");
35739
35878
  const planned = compilePermissionPolicyCallsWithEncoder({
35740
35879
  permissionId,
35741
35880
  roleKey,
@@ -35743,7 +35882,7 @@ function policyCalls(evidence, context, command) {
35743
35882
  actorSafe: evidence.userOpSenderSafe,
35744
35883
  rolesModule: context.rolesModule,
35745
35884
  managerRoleKey: context.managerRoleKey,
35746
- nextIntent: encodingIntent(command.intent, effect.recipientBindings),
35885
+ nextIntent: encodingIntent(intent, effect.recipientBindings),
35747
35886
  ...previous === void 0 ? {} : { previousIntent: previous },
35748
35887
  ...effect.operation !== "change" || effect.previousAllowanceKey === null ? {} : { previousAllowanceKey: effect.previousAllowanceKey }
35749
35888
  }, encodeRoles);
@@ -35795,7 +35934,8 @@ function encodingIntent(intent, bindings) {
35795
35934
  "send",
35796
35935
  "sendBatch",
35797
35936
  "createCommitment"
35798
- ].filter((action) => action === "createCommitment" ? intent.scope.actions.includes("commitments") : intent.scope.actions.includes("pay"))
35937
+ ].filter((action) => action === "createCommitment" ? intent.scope.actions.includes("commitments") : intent.scope.actions.includes("pay")),
35938
+ ...intent.refill === "monthly" ? { refill: intent.refill } : {}
35799
35939
  }
35800
35940
  };
35801
35941
  }
@@ -60190,6 +60330,22 @@ function normalizeRuntimeUrl(field, raw) {
60190
60330
  }
60191
60331
  //#endregion
60192
60332
  //#region ../../packages/sdk/src/applications/bootstrap-adapter.ts
60333
+ /** A cached envelope this close to its expiry is fetched again, so it cannot lapse mid-command. */
60334
+ const CACHE_EXPIRY_MARGIN_MS = 3e4;
60335
+ /**
60336
+ * The cached envelope, when it is still inside its own lifetime. It is decoded
60337
+ * as a fresh body is; anything that does not decode, or a store that fails,
60338
+ * is a miss.
60339
+ */
60340
+ async function cachedEnvelope(cache, now) {
60341
+ try {
60342
+ const cached = decodeBootstrapBody(await cache.load());
60343
+ const expiresAt = cached.issuedAt + cached.expiresIn - CACHE_EXPIRY_MARGIN_MS;
60344
+ return cached.issuedAt <= now && now < expiresAt ? cached : null;
60345
+ } catch {
60346
+ return null;
60347
+ }
60348
+ }
60193
60349
  const DEFAULT_RETRY = {
60194
60350
  attempts: 2,
60195
60351
  baseDelayMs: 400
@@ -60230,19 +60386,24 @@ var HttpBootstrapAdapter = class {
60230
60386
  fetchImpl;
60231
60387
  observation;
60232
60388
  retry;
60389
+ cache;
60233
60390
  constructor(deps) {
60234
60391
  this.bootstrapBaseUrl = deps.bootstrapBaseUrl.replace(/\/$/, "");
60235
60392
  this.bootstrapHost = hostOf(this.bootstrapBaseUrl);
60236
60393
  this.observation = deps.observation;
60237
60394
  this.retry = deps.retry ?? DEFAULT_RETRY;
60395
+ this.cache = deps.cache;
60238
60396
  this.fetchImpl = deps.fetch ?? ((input, init) => globalThis.fetch(input, init));
60239
60397
  }
60240
60398
  resolve(input) {
60241
- return this.attempt(input).pipe(Effect.retry({
60399
+ const fetched = this.attempt(input).pipe(Effect.retry({
60242
60400
  times: this.retry.attempts,
60243
60401
  while: isTransientBootstrapFailure,
60244
60402
  schedule: Schedule.exponential(Duration.millis(this.retry.baseDelayMs))
60245
60403
  }));
60404
+ const cache = this.cache;
60405
+ if (cache === void 0) return fetched;
60406
+ return Effect.promise(() => cachedEnvelope(cache, Date.now())).pipe(Effect.filterOrElse((cached) => cached !== null, () => fetched));
60246
60407
  }
60247
60408
  attempt(input) {
60248
60409
  return Effect.tryPromise({
@@ -60267,7 +60428,13 @@ var HttpBootstrapAdapter = class {
60267
60428
  }
60268
60429
  mapResponse(res, input) {
60269
60430
  if (res.ok) return Effect.tryPromise({
60270
- try: async () => decodeBootstrapBody(await res.json()),
60431
+ try: async () => {
60432
+ const body = await res.json();
60433
+ const resolution = decodeBootstrapBody(body);
60434
+ const cache = this.cache;
60435
+ if (cache !== void 0) await Promise.resolve().then(() => cache.save(body)).catch(() => void 0);
60436
+ return resolution;
60437
+ },
60271
60438
  catch: (cause) => {
60272
60439
  if (cause instanceof CapxulError && cause.code === "INVALID_INPUT") return bootstrapErrorFromCapxul("invalidInput", cause);
60273
60440
  return bootstrapErrorFromCapxul("malformedBody", Errors.providerError("convex", "bootstrap", cause instanceof Error ? cause : new Error(String(cause))));
@@ -61934,7 +62101,9 @@ function makeNotImplementedOrganizationPaymentsMethods() {
61934
62101
  subscribe: organizationPaymentExecutionUnavailable,
61935
62102
  pay: organizationPaymentExecutionUnavailable,
61936
62103
  payBatch: organizationPaymentExecutionUnavailable,
61937
- retry: organizationPaymentExecutionUnavailable
62104
+ retry: organizationPaymentExecutionUnavailable,
62105
+ cancel: organizationPaymentExecutionUnavailable,
62106
+ redirect: organizationPaymentExecutionUnavailable
61938
62107
  };
61939
62108
  }
61940
62109
  const orgMeUnavailable = () => Promise.resolve({
@@ -66127,6 +66296,23 @@ function subscribeConvexQuery(client, fn, args, callback, options) {
66127
66296
  * ever needs a different one.
66128
66297
  */
66129
66298
  const DEFAULT_CLOSE_TIMEOUT_MS = 5e3;
66299
+ /**
66300
+ * The fetcher the Convex client's auth handshake gets. Convex first asks for
66301
+ * "a possibly cached token" (`forceRefreshToken: false`) and, as soon as the
66302
+ * server accepts it, for a fresh one (`true`): the token whose `exp` it
66303
+ * schedules the next refresh from. The Better Auth clients keep no token: each
66304
+ * ask mints one over the network, so the cached ask was a second token fetch
66305
+ * and a second server verification (a JWKS read) on every session restore. It
66306
+ * answers "none" instead, and Convex makes its one fetch forced, as a fresh
66307
+ * token. Every forced ask still reaches the auth client: that first fetch, the
66308
+ * refresh before `exp`, and the retry after the server rejects a token.
66309
+ * ponytail: the OAuth bearer client can hold a cached exchange; it now pays one
66310
+ * exchange for the first authentication instead of reusing it (never more
66311
+ * fetches than before). Pass the cache through if that start ever matters.
66312
+ */
66313
+ function freshTokenHandshake(fetchToken) {
66314
+ return async (args) => args.forceRefreshToken ? fetchToken(args) : null;
66315
+ }
66130
66316
  /** Run a Convex call under the current trace parent, when there is one. */
66131
66317
  const withTraceparent = (run) => Effect.flatMap(Effect.serviceOption(Tracer.ParentSpan), (parent) => run(parent._tag === "Some" ? formatTraceparent(parent.value) : void 0));
66132
66318
  var ConvexCallAdapter = class {
@@ -66151,10 +66337,10 @@ var ConvexCallAdapter = class {
66151
66337
  this.#tokenProvider = deps.tokenProvider;
66152
66338
  this.#applicationId = deps.applicationId;
66153
66339
  this.#observation = deps.observation;
66154
- if (this.#tokenProvider) this.#client.setAuth(this.#tokenProvider);
66340
+ this.refreshAuth();
66155
66341
  }
66156
66342
  refreshAuth() {
66157
- if (this.#tokenProvider) this.#client.setAuth(this.#tokenProvider);
66343
+ if (this.#tokenProvider) this.#client.setAuth(freshTokenHandshake(this.#tokenProvider));
66158
66344
  }
66159
66345
  observeConnection(observer) {
66160
66346
  return this.#connectionMonitor.observe(this.#client, observer);
@@ -67821,7 +68007,8 @@ async function createProductionAdapters(input) {
67821
68007
  const bootstrapLayer = HttpBootstrapLayer({
67822
68008
  bootstrapBaseUrl: resolvedInput.value.bootstrapBaseUrl,
67823
68009
  ...input.fetch === void 0 ? {} : { fetch: input.fetch },
67824
- ...invocationObservation === void 0 ? {} : { observation: invocationObservation }
68010
+ ...invocationObservation === void 0 ? {} : { observation: invocationObservation },
68011
+ ...input.bootstrapCache === void 0 ? {} : { cache: input.bootstrapCache }
67825
68012
  });
67826
68013
  try {
67827
68014
  const bootstrapContext = await Effect.runPromise(Layer.buildWithScope(bootstrapLayer, scope));
@@ -68877,253 +69064,6 @@ function checkoutUrl(frontendOrigin, reference) {
68877
69064
  return result.href;
68878
69065
  }
68879
69066
  //#endregion
68880
- //#region src/observability/projection.ts
68881
- const codes = [
68882
- ...CAPXUL_ERROR_CODES,
68883
- "CLI_USAGE",
68884
- "CLI_CONFIG",
68885
- "CLI_STORAGE",
68886
- "CLI_DEFECT",
68887
- "CLI_DEADLINE"
68888
- ];
68889
- /** A known Capxul or CLI error code, never a free-form string from a source. */
68890
- const safeCode = (value) => codes.find((code) => code === value);
68891
- /** Read one own data property without running a getter on a hostile value. */
68892
- const own = (value, key) => {
68893
- if (typeof value !== "object" || value === null) return void 0;
68894
- try {
68895
- return Object.getOwnPropertyDescriptor(value, key)?.value;
68896
- } catch {
68897
- return;
68898
- }
68899
- };
68900
- /** The numeric and enumerated SDK failure facts a diagnostic log may keep. */
68901
- function projectSdkProperties(properties) {
68902
- const output = {};
68903
- for (const key of [
68904
- "chainId",
68905
- "chain_id",
68906
- "durationMs",
68907
- "duration_ms",
68908
- "http_status",
68909
- "retry_count"
68910
- ]) {
68911
- const value = own(properties, key);
68912
- if (typeof value === "number" && Number.isFinite(value) && value >= 0) output[key] = value;
68913
- }
68914
- for (const key of [
68915
- "error_kind",
68916
- "error_code",
68917
- "reason_code"
68918
- ]) {
68919
- const value = safeCode(own(properties, key));
68920
- if (value !== void 0) output[key] = value;
68921
- }
68922
- const operation = own(properties, "operation");
68923
- if (isCapxulOperation(operation)) output.operation = operation;
68924
- const failureMode = FAILURE_MODES$1.find((mode) => mode === own(properties, "failure_mode"));
68925
- if (failureMode !== void 0) output.failure_mode = failureMode;
68926
- return output;
68927
- }
68928
- /** A source-only `$exception`: basenames, function names and lines, never messages or paths. */
68929
- function projectException(properties, command, kind = "CLI_DEFECT") {
68930
- const sanitized = sanitizePostHogBrowserEvent({
68931
- event: "$exception",
68932
- properties: { ...properties }
68933
- }).properties;
68934
- const exceptions = own(sanitized, "$exception_list");
68935
- const stacktrace = own(Array.isArray(exceptions) ? exceptions[0] : void 0, "stacktrace");
68936
- const rawFrames = own(stacktrace, "frames");
68937
- const frames = (Array.isArray(rawFrames) ? rawFrames : []).slice(0, 12).map((frame) => {
68938
- const filename = own(frame, "filename");
68939
- const basename = typeof filename === "string" ? filename.split(/[\\/]/u).at(-1)?.split(/[?#]/u)[0] : void 0;
68940
- const name = own(frame, "function");
68941
- return {
68942
- platform: "node:javascript",
68943
- filename: `capxul-cli:///${basename !== void 0 && /^[A-Za-z0-9._-]{1,96}$/u.test(basename) ? basename : "boundary"}`,
68944
- function: typeof name === "string" && /^[A-Za-z_$<>][A-Za-z0-9_$.<>-]{0,95}$/u.test(name) ? name : "<anonymous>",
68945
- lineno: boundedLine(own(frame, "lineno")),
68946
- colno: boundedLine(own(frame, "colno"))
68947
- };
68948
- });
68949
- return {
68950
- $exception_type: kind,
68951
- $exception_message: kind === "CLI_DEFECT" ? "CLI command failed" : "SDK operation failed",
68952
- $exception_level: "error",
68953
- $exception_fingerprint: `cli.${command}.${kind}:${frames[0]?.filename ?? "boundary"}:${frames[0]?.function ?? "unknown"}`,
68954
- $issue_name: `${kind === "CLI_DEFECT" ? "CLI" : "SDK"} ${command} defect`,
68955
- $exception_list: [{
68956
- type: kind,
68957
- value: kind === "CLI_DEFECT" ? "CLI command failed" : "SDK operation failed",
68958
- mechanism: {
68959
- handled: true,
68960
- type: kind === "CLI_DEFECT" ? "capxul_cli_boundary" : "capxul_cli_sdk_boundary"
68961
- },
68962
- stacktrace: {
68963
- type: "raw",
68964
- frames
68965
- }
68966
- }]
68967
- };
68968
- }
68969
- const boundedLine = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= 1e7 ? value : null;
68970
- //#endregion
68971
- //#region src/observability/capture.ts
68972
- const isTelemetryEventName = (event) => Object.hasOwn(TELEMETRY_EVENT_SCHEMAS, event);
68973
- const CAPTURE_BYTES = 8192;
68974
- const textEncoder = new TextEncoder();
68975
- const IDENTITY_KEYS = /* @__PURE__ */ new Set([
68976
- "actor_id",
68977
- "actor_kind",
68978
- "auth_user_id",
68979
- "anonymous_id",
68980
- "cli_identity_kind",
68981
- "distinct_id",
68982
- "$groups"
68983
- ]);
68984
- /** The bounded PostHog capture queue writers for one invocation. Nothing here sends. */
68985
- const makeCaptures = (options) => {
68986
- const { input, token, envelope, enabled, clock, queue, state } = options;
68987
- const enqueue = (body) => {
68988
- if (textEncoder.encode(body).byteLength > CAPTURE_BYTES || !Queue.offerUnsafe(queue, body)) state.dropped += 1;
68989
- };
68990
- const capture = (event, properties, identity) => {
68991
- if (!enabled()) {
68992
- state.dropped += 1;
68993
- return;
68994
- }
68995
- const occurredAt = identity?.observedAt ?? clock.currentTimeMillisUnsafe();
68996
- const record = sanitizePostHogBrowserEvent({
68997
- event,
68998
- properties: {
68999
- ...properties,
69000
- ...envelope,
69001
- occurred_at: occurredAt,
69002
- ...state.nativeContext
69003
- }
69004
- });
69005
- if (event === "access_recovery_completed") {
69006
- const checked = Schema.decodeUnknownOption(TELEMETRY_EVENT_SCHEMAS.access_recovery_completed)({
69007
- name: event,
69008
- props: properties
69009
- });
69010
- if (Option.isSome(checked) && checked.value.name === "access_recovery_completed") record.properties.recovery = checked.value.props.recovery;
69011
- }
69012
- enqueue(JSON.stringify({
69013
- api_key: token,
69014
- ...record,
69015
- uuid: identity?.eventId ?? crypto.randomUUID(),
69016
- timestamp: new Date(occurredAt).toISOString(),
69017
- properties: {
69018
- ...record.properties,
69019
- distinct_id: input.anonymousIdentity?.id ?? input.invocationId,
69020
- $process_person_profile: false
69021
- }
69022
- }));
69023
- };
69024
- /**
69025
- * Join this epoch's anonymous history to the person AUTH verified.
69026
- *
69027
- * This is a PostHog lifecycle event, not a catalog event: it carries the
69028
- * person as `distinct_id` and the epoch as `$anon_distinct_id`, and
69029
- * deliberately omits `$process_person_profile` so the merge is processed —
69030
- * every catalog event here sets it false, which is what keeps those events off
69031
- * the person until this record exists.
69032
- *
69033
- * The binding's own stored `eventId` is the record UUID, so PostHog
69034
- * deduplicates a replayed merge. The same idiom `cli_first_used` uses.
69035
- *
69036
- * No `$set` traits: identify traits bypass the per-event redactor, and this
69037
- * module has no pre-minimized person fact to send. The merge is the whole
69038
- * payload.
69039
- */
69040
- const captureIdentify = (authUserId, anonymousId, eventId, occurredAt) => {
69041
- if (!enabled()) {
69042
- state.dropped += 1;
69043
- return;
69044
- }
69045
- enqueue(JSON.stringify({
69046
- api_key: token,
69047
- event: "$identify",
69048
- uuid: eventId,
69049
- timestamp: new Date(occurredAt).toISOString(),
69050
- properties: {
69051
- capxul_env: input.environment,
69052
- distinct_id: authUserId,
69053
- $anon_distinct_id: anonymousId
69054
- }
69055
- }));
69056
- };
69057
- const captureSdkDiagnostic = (event, properties) => {
69058
- const code = safeCode(own(properties, "error_kind")) ?? (event === "$exception" ? "UNKNOWN" : void 0);
69059
- const operation = own(properties, "operation");
69060
- if (code === void 0 || state.sdkDiagnostics.length >= 32) {
69061
- state.dropped += 1;
69062
- return;
69063
- }
69064
- state.sdkDiagnostics.push({
69065
- log: {
69066
- ...projectSdkProperties(properties),
69067
- error_code: code,
69068
- ...isCapxulOperation(operation) ? { operation } : {}
69069
- },
69070
- ...event === "$exception" ? { exception: projectException(properties, input.command, "SDK_DEFECT") } : {}
69071
- });
69072
- };
69073
- return {
69074
- capture,
69075
- captureIdentify,
69076
- client: { capture: (event, properties) => {
69077
- if (!enabled()) return;
69078
- if (event === "$exception" || event === "capxul_sdk_expected_outcome") {
69079
- captureSdkDiagnostic(event, properties);
69080
- return;
69081
- }
69082
- const catalog = isTelemetryEventName(event) ? TELEMETRY_EVENT_SCHEMAS[event] : void 0;
69083
- if (catalog === void 0) {
69084
- state.dropped += 1;
69085
- return;
69086
- }
69087
- const props = {};
69088
- const shape = catalog.fields.props;
69089
- const members = "fields" in shape ? [shape] : shape.members;
69090
- for (const member of members) for (const key of Object.keys(member.fields)) {
69091
- if (IDENTITY_KEYS.has(key)) continue;
69092
- const value = own(properties, key);
69093
- if (value !== void 0) props[key] = value;
69094
- }
69095
- const decoded = Schema.decodeUnknownOption(catalog)({
69096
- name: event,
69097
- props: {
69098
- ...props,
69099
- ...envelope
69100
- }
69101
- });
69102
- if (Option.isSome(decoded) && Queue.sizeUnsafe(queue) < 29) capture(event, { ...decoded.value.props });
69103
- else state.dropped += 1;
69104
- } }
69105
- };
69106
- };
69107
- /** The SDK's host observability for this invocation, joined to the command's trace. */
69108
- const sdkObservation = (options) => {
69109
- const { input, client, enabled, deliveryBudget, traceparent } = options;
69110
- return postHogObservability(client, {
69111
- enabled,
69112
- engineeringEnabled: enabled,
69113
- engineeringPrivacy: "minimal",
69114
- engineeringDeliveryBudget: deliveryBudget,
69115
- capxulEnv: input.environment,
69116
- context: {
69117
- application: "capxul-cli",
69118
- ...input.anonymousIdentity === void 0 ? {} : { anonymousId: input.anonymousIdentity.id },
69119
- release: input.cliVersion,
69120
- correlationId: input.invocationId,
69121
- journeyId: input.invocationId,
69122
- traceparent
69123
- }
69124
- });
69125
- };
69126
- //#endregion
69127
69067
  //#region src/platform/state-faults.ts
69128
69068
  var StateFileError = class extends Data.TaggedError("StateFileError") {
69129
69069
  get message() {
@@ -69412,6 +69352,108 @@ function walletDetails(error) {
69412
69352
  };
69413
69353
  }
69414
69354
  //#endregion
69355
+ //#region src/runtime/amount.ts
69356
+ /** Money as people read it, with at least two decimals: `550.00 USDC`. */
69357
+ const shownAmount = (amount) => {
69358
+ const [number = "", symbol = ""] = formatAssetAmount(amount).split(" ");
69359
+ const [integer = "0", fraction = ""] = number.split(".");
69360
+ return `${integer}.${fraction.padEnd(2, "0")} ${symbol}`;
69361
+ };
69362
+ /** The number of `shownAmount`, for a table whose column names the asset: `1,000.00`. */
69363
+ const shownNumber = (amount) => {
69364
+ try {
69365
+ const shown = shownAmount(amount);
69366
+ return shown.slice(0, shown.lastIndexOf(" "));
69367
+ } catch {
69368
+ return amount.value;
69369
+ }
69370
+ };
69371
+ /** `USDC on Base Sepolia`, as `balance` names it. An asset this CLI does not know keeps its id. */
69372
+ const assetName = (asset) => {
69373
+ try {
69374
+ const { symbol, chainId } = requireAsset(toAssetId(asset));
69375
+ return `${symbol} on ${networkName(chainId)}`;
69376
+ } catch {
69377
+ return asset;
69378
+ }
69379
+ };
69380
+ /** A quantity in an asset's smallest units as a decimal string: `998230000` is `998.23`. */
69381
+ const fromBaseUnits = (raw, { decimals }) => {
69382
+ const digits = raw.padStart(decimals + 1, "0");
69383
+ const whole = digits.slice(0, digits.length - decimals);
69384
+ const fraction = digits.slice(digits.length - decimals).replace(/0+$/u, "");
69385
+ return fraction === "" ? whole : `${whole}.${fraction}`;
69386
+ };
69387
+ /** A signed raw-unit quantity as `shownNumber` shows it, or undefined for an asset this CLI does not know. */
69388
+ const shownBaseUnits = (asset, raw) => {
69389
+ try {
69390
+ const id = toAssetId(asset);
69391
+ const sign = raw.startsWith("-") ? "-" : "";
69392
+ const value = fromBaseUnits(raw.slice(sign.length), requireAsset(id));
69393
+ return `${sign}${shownNumber({
69394
+ asset: id,
69395
+ value
69396
+ })}`;
69397
+ } catch {
69398
+ return;
69399
+ }
69400
+ };
69401
+ //#endregion
69402
+ //#region src/runtime/money-refusal.ts
69403
+ /** The asset a refusal names, by symbol or token address, among the ones this CLI knows. */
69404
+ const namedAsset = (details) => Object.values(TOKENS.BASE_SEPOLIA).map((id) => requireAsset(id)).find((asset) => asset.symbol === details.asset || typeof details.token === "string" && asset.tokenAddress.toLowerCase() === details.token.toLowerCase());
69405
+ /** INSUFFICIENT_BALANCE carries base units; a person reads `998.23 USDC`. */
69406
+ const insufficientBalance = (details) => {
69407
+ const asset = details === void 0 ? void 0 : namedAsset(details);
69408
+ const { available, required } = details ?? {};
69409
+ if (asset === void 0 || typeof available !== "string" || typeof required !== "string" || !/^\d+$/u.test(available) || !/^\d+$/u.test(required)) return "The balance is too low for this payment.";
69410
+ const shown = (raw) => shownAmount({
69411
+ asset: asset.assetId,
69412
+ value: fromBaseUnits(raw, asset)
69413
+ });
69414
+ return `You have ${shown(available)}; this needs ${shown(required)}.`;
69415
+ };
69416
+ const invalidRecipient = (reason) => typeof reason === "string" && /^[\w .,'@-]{1,120}$/u.test(reason) ? `The recipient was refused: ${reason.replace(/\.$/u, "")}.` : "The recipient was refused.";
69417
+ const requestKey = (reason) => /already used/u.test(String(reason)) ? "This request key was used for a different payment. Use a new --request-key, or send the same payment again to replay it." : "The request key was refused. Use a different --request-key.";
69418
+ /** An amount in major units, as a person reads it; `undefined` for anything else. */
69419
+ const heldAmount = (value) => {
69420
+ if (!isRecord(value) || typeof value.asset !== "string" || typeof value.value !== "string") return void 0;
69421
+ try {
69422
+ return shownAmount({
69423
+ asset: toAssetId(value.asset),
69424
+ value: value.value
69425
+ });
69426
+ } catch {
69427
+ return;
69428
+ }
69429
+ };
69430
+ /**
69431
+ * Why an Organization's Budget refused a spend. `not_member` and
69432
+ * `condition_violation` are left out: permission commands raise them too, with
69433
+ * no more cause than the label, so a Budget sentence would mislead there.
69434
+ */
69435
+ const BUDGET_REFUSAL = {
69436
+ over_cap: "This payment is over the per-payment cap of your Budget.",
69437
+ daily_cap: "This payment would go over your Budget's daily allowance.",
69438
+ disallowed_recipient: "Your Budget does not allow paying this recipient."
69439
+ };
69440
+ const budgetRefusal = (details) => {
69441
+ if (details.reason === "budget_exhausted") {
69442
+ const left = heldAmount(details.remaining);
69443
+ const needs = heldAmount(details.required);
69444
+ return left === void 0 || needs === void 0 ? "Your Budget has run out for this period." : `Your Budget has ${left} left; this needs ${needs}.`;
69445
+ }
69446
+ return typeof details.reason === "string" ? BUDGET_REFUSAL[details.reason] : void 0;
69447
+ };
69448
+ /** The sentence for a payment refusal the backend explains in `details`, or nothing for any other. */
69449
+ const moneyRefusalMessage = (error) => {
69450
+ if (error.code === "ROLE_PERMISSION_DENIED") return error.details === void 0 ? void 0 : budgetRefusal(error.details);
69451
+ if (error.code === "INSUFFICIENT_BALANCE") return insufficientBalance(error.details);
69452
+ if (error.code === "INVALID_RECIPIENT") return invalidRecipient(error.details?.reason);
69453
+ if (error.code === "SUPERSEDED") return "Another attempt at this payment went first, so this one was not sent.";
69454
+ if (error.code === "INVALID_INPUT" && error.details?.field === "requestKey") return requestKey(error.details.reason);
69455
+ };
69456
+ //#endregion
69415
69457
  //#region src/runtime/usage-failure.ts
69416
69458
  /** A command or flag name as the CLI declares them. */
69417
69459
  const NAME = /^[a-z][a-z0-9-]{0,63}$/u;
@@ -69457,23 +69499,65 @@ function helpFor(failures, command) {
69457
69499
  ] : ["capxul", "--help"];
69458
69500
  }
69459
69501
  /**
69460
- * The command line with a mistyped command corrected: the typed path up to the
69461
- * typo, the closest command, then the command words typed after it. IDs, flags
69462
- * and values are left out, so nothing typed by mistake is echoed.
69502
+ * Commands #2294 removed, by the path a person types, and what replaced them.
69503
+ * The closest name by spelling would suggest a command that does something else
69504
+ * (`org use` is near `org me`), so these are named by hand. `takes` is an
69505
+ * argument the replacement needs that the old command also took.
69506
+ */
69507
+ const REPLACED = {
69508
+ "org use": {
69509
+ to: ["use", "org"],
69510
+ takes: "<handle>"
69511
+ },
69512
+ "org get": { to: ["org", "show"] },
69513
+ "org status": { to: ["org", "show"] },
69514
+ "account status": { to: ["account", "show"] },
69515
+ telemetry: { to: ["config", "telemetry"] }
69516
+ };
69517
+ /**
69518
+ * The command line with a mistyped or removed command corrected: the typed
69519
+ * path up to the typo, the command to run instead, then the command words typed
69520
+ * after it that the tree still has, then the `--org` and `--env` typed (without
69521
+ * them `org payroll run` refuses bare). Other IDs, handles, flags and values are
69522
+ * left out, so nothing typed by mistake is echoed, and the line never carries a
69523
+ * value into a place that refuses it.
69463
69524
  */
69464
- function correction(failures, args) {
69525
+ function correction(failures, args, kind) {
69465
69526
  const unknown = failures.find((failure) => failure instanceof CliError.UnknownSubcommand);
69466
- const suggested = unknown?.suggestions.find((candidate) => NAME.test(candidate));
69467
69527
  const parent = unknown?.parent ?? ["capxul"];
69468
- if (suggested === void 0 || !parent.every((part) => NAME.test(part))) return void 0;
69528
+ if (unknown === void 0 || !parent.every((part) => NAME.test(part))) return void 0;
69529
+ const replaced = REPLACED[[...parent.slice(1), unknown.subcommand].join(" ")];
69530
+ const suggested = unknown.suggestions.find((candidate) => NAME.test(candidate));
69531
+ const base = replaced === void 0 ? [...parent, suggested] : ["capxul", ...replaced.to];
69532
+ if (!base.every((part) => part !== void 0)) return void 0;
69469
69533
  const at = args.indexOf(unknown.subcommand);
69470
- const after = at === -1 ? [] : args.slice(at + 1);
69534
+ const after = at === -1 || replaced?.takes !== void 0 ? [] : args.slice(at + 1);
69471
69535
  const stop = after.findIndex((arg) => !NAME.test(arg));
69472
- return [
69473
- ...parent,
69474
- suggested,
69475
- ...stop === -1 ? after : after.slice(0, stop)
69476
- ];
69536
+ const carried = [];
69537
+ for (const word of stop === -1 ? after : after.slice(0, stop)) {
69538
+ if (kind !== void 0 && kind([
69539
+ ...base.slice(1),
69540
+ ...carried,
69541
+ word
69542
+ ]) === void 0) break;
69543
+ carried.push(word);
69544
+ }
69545
+ const argv = [...base, ...carried];
69546
+ const scope = scopeArgs(args);
69547
+ const hint = `Did you mean: ${[
69548
+ ...argv,
69549
+ ...replaced?.takes === void 0 ? [] : [replaced.takes],
69550
+ ...scope
69551
+ ].join(" ")}`;
69552
+ const bare = replaced?.takes !== void 0 || kind?.(argv.slice(1)) === "group";
69553
+ return {
69554
+ argv: [
69555
+ ...argv,
69556
+ ...scope,
69557
+ ...bare ? ["--help"] : []
69558
+ ],
69559
+ hint
69560
+ };
69477
69561
  }
69478
69562
  /** The flag or argument a refusal names. */
69479
69563
  const fieldOf = (failures) => {
@@ -69483,18 +69567,18 @@ const fieldOf = (failures) => {
69483
69567
  if (failure instanceof CliError.MissingArgument && NAME.test(failure.argument)) return failure.argument;
69484
69568
  }
69485
69569
  };
69486
- function usageFailure(error, command, args = []) {
69570
+ function usageFailure(error, command, args = [], kind) {
69487
69571
  const failures = error instanceof CliError.ShowHelp ? error.errors : [error];
69488
69572
  const missing = failures.flatMap((failure) => failure instanceof CliError.MissingOption && NAME.test(failure.option) ? [`--${failure.option}`] : []);
69489
69573
  const sentences = [...missing.length === 0 ? [] : [`Missing required flag${missing.length === 1 ? "" : "s"}: ${[...new Set(missing)].join(", ")}.`], ...new Set(failures.flatMap((failure) => refusal(failure) ?? []))];
69490
- const corrected = correction(failures, args);
69574
+ const corrected = correction(failures, args, kind);
69491
69575
  const field = fieldOf(failures);
69492
69576
  return {
69493
69577
  code: "CLI_USAGE",
69494
69578
  message: sentences.length === 0 ? "Invalid command or input." : sentences.join(" "),
69495
69579
  ...corrected === void 0 ? { next: helpFor(failures, command) } : {
69496
- hint: `Did you mean: ${corrected.join(" ")}`,
69497
- next: corrected
69580
+ hint: corrected.hint,
69581
+ next: corrected.argv
69498
69582
  },
69499
69583
  ...field === void 0 ? {} : { field },
69500
69584
  exitCode: 2,
@@ -69561,6 +69645,7 @@ const applicationMessage = (error, command, wallet) => {
69561
69645
  if (error.code === "WRONG_STATE") return wrongStateMessage(error.details?.currentState, command);
69562
69646
  if (error.code === "INVALID_INPUT" || error.code === "OTP_EXPIRED") return authCommand(command) ? authInputMessage(error) : "The input was refused. Check the command arguments.";
69563
69647
  if (error.code === "CAPABILITY_UNAVAILABLE" && error.details?.reason === "backend-function-missing") return backendVersionMessage(error.details.path);
69648
+ if (error.code === "UNKNOWN" && error.details?.reason === "backend-error-redacted") return redactedBackendMessage(error.details.requestId);
69564
69649
  if (error.code === "RATE_LIMITED" && typeof error.details?.retryAfterMs === "number") return `The Capxul request was refused: RATE_LIMITED. Try again in ${Math.ceil(error.details.retryAfterMs / 1e3)} seconds.`;
69565
69650
  if (isExpectedOutcome(error.code)) return `The Capxul request was refused: ${error.code}.`;
69566
69651
  return `The Capxul request failed: ${error.code}.`;
@@ -69569,6 +69654,10 @@ const applicationMessage = (error, command, wallet) => {
69569
69654
  const backendVersionMessage = (path) => {
69570
69655
  return `The backend does not have this command yet${typeof path === "string" && /^[a-z][A-Za-z0-9/_]*:[A-Za-z0-9_]+$/u.test(path) ? ` (no function ${path})` : ""}: it runs a different version from this CLI. Update the backend, or point CAPXUL_BOOTSTRAP_URL at a deployment that matches this CLI.`;
69571
69656
  };
69657
+ /** A deployed backend redacts its own errors (#2304): only its request id is left to quote. */
69658
+ const redactedBackendMessage = (requestId) => {
69659
+ return `The backend refused the request and gave no detail.${typeof requestId === "string" && /^[A-Za-z0-9]{1,64}$/u.test(requestId) ? ` Quote request ${requestId} to Capxul support.` : ""} If the backend is newer than this CLI, updating the CLI may fix it.`;
69660
+ };
69572
69661
  /**
69573
69662
  * Exit codes by class: 1 a CLI defect, 2 refused input or state, 3 not signed
69574
69663
  * in, 4 not permitted, 5 failed, 6 any other refusal the operation names as an
@@ -69607,7 +69696,7 @@ const configurationFailure = (message) => new CliFailure({
69607
69696
  exitCode: 2,
69608
69697
  outcome: "refused"
69609
69698
  });
69610
- function classifyFailure(cause, command, args) {
69699
+ function classifyFailure(cause, command, args, kind) {
69611
69700
  const error = Cause.squash(cause);
69612
69701
  if (Cause.hasInterruptsOnly(cause) || Terminal.isQuitError(error)) return {
69613
69702
  code: "CANCELLED",
@@ -69617,7 +69706,7 @@ function classifyFailure(cause, command, args) {
69617
69706
  };
69618
69707
  if (error instanceof CliFailure) return error;
69619
69708
  if (error instanceof StateFileError) return storageFailure(error);
69620
- if (CliError.isCliError(error)) return usageFailure(error, command, args);
69709
+ if (CliError.isCliError(error)) return usageFailure(error, command, args, kind);
69621
69710
  if (isCapxulError(error)) {
69622
69711
  const wallet = walletDetails(error);
69623
69712
  const details = {
@@ -69627,7 +69716,7 @@ function classifyFailure(cause, command, args) {
69627
69716
  const signIn = error.code === "NOT_AUTHENTICATED" && wallet === void 0 && error.details?.field !== "publishableKey";
69628
69717
  return {
69629
69718
  code: error.code,
69630
- message: applicationMessage(error, command, wallet),
69719
+ message: moneyRefusalMessage(error) ?? applicationMessage(error, command, wallet),
69631
69720
  ...signIn ? { next: SIGN_IN$1 } : {},
69632
69721
  ...details.field === void 0 ? {} : { field: details.field },
69633
69722
  ...error.mode === void 0 ? {} : { mode: error.mode },
@@ -69686,6 +69775,358 @@ function parsedCommand(cause) {
69686
69775
  return CliError.isCliError(error) && error._tag === "ShowHelp" ? error.commandPath.slice(1).join(".") || "capxul" : "capxul";
69687
69776
  }
69688
69777
  //#endregion
69778
+ //#region src/runtime/command-metadata.ts
69779
+ var CliCommandMetadata = class extends Context.Service()("capxul-cli/CommandMetadata") {};
69780
+ const collectionControl = (name) => name === "config" || name === "config.telemetry" || name.startsWith("config.telemetry.");
69781
+ /** Resolve only a complete path supplied by the native parser. */
69782
+ function commandAtPath(root, path) {
69783
+ if (path[0] !== root.name) return void 0;
69784
+ let selected = root;
69785
+ for (const part of path.slice(1)) {
69786
+ const child = selected.subcommands.flatMap((group) => group.commands).find((candidate) => candidate.name === part || candidate.alias === part);
69787
+ if (child === void 0) return void 0;
69788
+ selected = child;
69789
+ }
69790
+ return selected;
69791
+ }
69792
+ /** What the tree has at a path of command words (without the root's name). */
69793
+ const pathKind = (root) => (path) => {
69794
+ const found = commandAtPath(root, [root.name, ...path]);
69795
+ return found === void 0 ? void 0 : found.subcommands.length > 0 ? "group" : "command";
69796
+ };
69797
+ const validateNativeScope = (input) => {
69798
+ if (typeof input === "object" && input !== null && "orgs" in input && Array.isArray(input.orgs) && input.orgs.every((id) => typeof id === "string") && new Set(input.orgs).size > 1) return Effect.fail(new CliError.InvalidValue({
69799
+ option: "org",
69800
+ value: "multiple IDs",
69801
+ expected: "one exact Organization ID",
69802
+ kind: "flag"
69803
+ }));
69804
+ return Effect.void;
69805
+ };
69806
+ /** Keep the native grammar and validate scope without business actions. */
69807
+ function nativeInputTree(node) {
69808
+ const input = Command.withHandler(node, validateNativeScope);
69809
+ const children = node.subcommands.flatMap((group) => {
69810
+ const commands = group.commands.map(nativeInputTree);
69811
+ return group.group === void 0 ? commands : [{
69812
+ group: group.group,
69813
+ commands
69814
+ }];
69815
+ });
69816
+ return Command.withSubcommands(input, children);
69817
+ }
69818
+ /** The command whose metadata is named `name`, searched from `root`. */
69819
+ const commandNamed = (root, name) => {
69820
+ const metadata = Context.getOption(root.annotations, CliCommandMetadata);
69821
+ if (Option.isSome(metadata) && metadata.value.name === name && root.subcommands.length === 0) return root;
69822
+ for (const child of root.subcommands.flatMap((group) => group.commands)) {
69823
+ const found = commandNamed(child, name);
69824
+ if (found !== void 0) return found;
69825
+ }
69826
+ };
69827
+ /** The flag inside `param` when it takes one value: nothing in it repeats, and it is no switch. */
69828
+ const oneValue = (param) => {
69829
+ if (Param.isSingle(param)) return param.primitiveType._tag === "Boolean" ? void 0 : param;
69830
+ if (param._tag === "Variadic") return void 0;
69831
+ const inner = readField(param, "param");
69832
+ return Param.isParam(inner) ? oneValue(inner) : void 0;
69833
+ };
69834
+ /**
69835
+ * The names of `node`'s flags that take one value, as typed (`--name`, `-n`).
69836
+ * The parser keeps the first of a repeated one and drops the rest without a
69837
+ * word, so the caller refuses a repeat. `config` is the command's own definition.
69838
+ */
69839
+ const singleValueFlags = (node) => {
69840
+ const flags = readField(readField(node, "config"), "flags");
69841
+ return (Array.isArray(flags) ? flags : []).flatMap((flag) => {
69842
+ const single = Param.isParam(flag) ? oneValue(flag) : void 0;
69843
+ return single === void 0 ? [] : [`--${single.name}`, ...single.aliases.map((alias) => alias.length === 1 ? `-${alias}` : `--${alias}`)];
69844
+ });
69845
+ };
69846
+ /**
69847
+ * A refusal naming the first flag that takes one value and was typed more than
69848
+ * once: the parser keeps the first `--timeout-seconds` and `--fields` keeps the
69849
+ * last, both silently. `--fields` and `--env` are global flags.
69850
+ */
69851
+ const repeatedFlag = (root, name, args) => {
69852
+ const node = root === void 0 ? void 0 : commandNamed(root, name);
69853
+ const end = args.indexOf("--");
69854
+ const typed = end < 0 ? args : args.slice(0, end);
69855
+ const repeated = [
69856
+ ...node === void 0 ? [] : singleValueFlags(node),
69857
+ "--fields",
69858
+ "--env"
69859
+ ].find((flag) => typed.filter((arg) => arg === flag || arg.startsWith(`${flag}=`)).length > 1);
69860
+ return repeated === void 0 ? void 0 : new CliFailure({
69861
+ code: "CLI_USAGE",
69862
+ message: `Provide ${repeated} once.`,
69863
+ field: repeated.replace(/^-+/u, ""),
69864
+ exitCode: 2,
69865
+ outcome: "refused"
69866
+ });
69867
+ };
69868
+ function metadataAtPath(root, path) {
69869
+ let selected = root;
69870
+ for (const part of path.slice(1)) {
69871
+ const child = selected.subcommands.flatMap((group) => group.commands).find((command) => command.name === part || command.alias === part);
69872
+ if (child === void 0) break;
69873
+ selected = child;
69874
+ }
69875
+ const metadata = Context.getOption(selected.annotations, CliCommandMetadata);
69876
+ return Option.isSome(metadata) && CLI_COMMAND_NAMES.includes(metadata.value.name) ? metadata.value : {
69877
+ name: "unknown",
69878
+ capability: "cli",
69879
+ observation: "command"
69880
+ };
69881
+ }
69882
+ //#endregion
69883
+ //#region src/observability/projection.ts
69884
+ const codes = [
69885
+ ...CAPXUL_ERROR_CODES,
69886
+ "CLI_USAGE",
69887
+ "CLI_CONFIG",
69888
+ "CLI_STORAGE",
69889
+ "CLI_DEFECT",
69890
+ "CLI_DEADLINE"
69891
+ ];
69892
+ /** A known Capxul or CLI error code, never a free-form string from a source. */
69893
+ const safeCode = (value) => codes.find((code) => code === value);
69894
+ /** Read one own data property without running a getter on a hostile value. */
69895
+ const own = (value, key) => {
69896
+ if (typeof value !== "object" || value === null) return void 0;
69897
+ try {
69898
+ return Object.getOwnPropertyDescriptor(value, key)?.value;
69899
+ } catch {
69900
+ return;
69901
+ }
69902
+ };
69903
+ /** The numeric and enumerated SDK failure facts a diagnostic log may keep. */
69904
+ function projectSdkProperties(properties) {
69905
+ const output = {};
69906
+ for (const key of [
69907
+ "chainId",
69908
+ "chain_id",
69909
+ "durationMs",
69910
+ "duration_ms",
69911
+ "http_status",
69912
+ "retry_count"
69913
+ ]) {
69914
+ const value = own(properties, key);
69915
+ if (typeof value === "number" && Number.isFinite(value) && value >= 0) output[key] = value;
69916
+ }
69917
+ for (const key of [
69918
+ "error_kind",
69919
+ "error_code",
69920
+ "reason_code"
69921
+ ]) {
69922
+ const value = safeCode(own(properties, key));
69923
+ if (value !== void 0) output[key] = value;
69924
+ }
69925
+ const operation = own(properties, "operation");
69926
+ if (isCapxulOperation(operation)) output.operation = operation;
69927
+ const failureMode = FAILURE_MODES$1.find((mode) => mode === own(properties, "failure_mode"));
69928
+ if (failureMode !== void 0) output.failure_mode = failureMode;
69929
+ return output;
69930
+ }
69931
+ /** A source-only `$exception`: basenames, function names and lines, never messages or paths. */
69932
+ function projectException(properties, command, kind = "CLI_DEFECT") {
69933
+ const sanitized = sanitizePostHogBrowserEvent({
69934
+ event: "$exception",
69935
+ properties: { ...properties }
69936
+ }).properties;
69937
+ const exceptions = own(sanitized, "$exception_list");
69938
+ const stacktrace = own(Array.isArray(exceptions) ? exceptions[0] : void 0, "stacktrace");
69939
+ const rawFrames = own(stacktrace, "frames");
69940
+ const frames = (Array.isArray(rawFrames) ? rawFrames : []).slice(0, 12).map((frame) => {
69941
+ const filename = own(frame, "filename");
69942
+ const basename = typeof filename === "string" ? filename.split(/[\\/]/u).at(-1)?.split(/[?#]/u)[0] : void 0;
69943
+ const name = own(frame, "function");
69944
+ return {
69945
+ platform: "node:javascript",
69946
+ filename: `capxul-cli:///${basename !== void 0 && /^[A-Za-z0-9._-]{1,96}$/u.test(basename) ? basename : "boundary"}`,
69947
+ function: typeof name === "string" && /^[A-Za-z_$<>][A-Za-z0-9_$.<>-]{0,95}$/u.test(name) ? name : "<anonymous>",
69948
+ lineno: boundedLine(own(frame, "lineno")),
69949
+ colno: boundedLine(own(frame, "colno"))
69950
+ };
69951
+ });
69952
+ return {
69953
+ $exception_type: kind,
69954
+ $exception_message: kind === "CLI_DEFECT" ? "CLI command failed" : "SDK operation failed",
69955
+ $exception_level: "error",
69956
+ $exception_fingerprint: `cli.${command}.${kind}:${frames[0]?.filename ?? "boundary"}:${frames[0]?.function ?? "unknown"}`,
69957
+ $issue_name: `${kind === "CLI_DEFECT" ? "CLI" : "SDK"} ${command} defect`,
69958
+ $exception_list: [{
69959
+ type: kind,
69960
+ value: kind === "CLI_DEFECT" ? "CLI command failed" : "SDK operation failed",
69961
+ mechanism: {
69962
+ handled: true,
69963
+ type: kind === "CLI_DEFECT" ? "capxul_cli_boundary" : "capxul_cli_sdk_boundary"
69964
+ },
69965
+ stacktrace: {
69966
+ type: "raw",
69967
+ frames
69968
+ }
69969
+ }]
69970
+ };
69971
+ }
69972
+ const boundedLine = (value) => typeof value === "number" && Number.isSafeInteger(value) && value > 0 && value <= 1e7 ? value : null;
69973
+ //#endregion
69974
+ //#region src/observability/capture.ts
69975
+ const isTelemetryEventName = (event) => Object.hasOwn(TELEMETRY_EVENT_SCHEMAS, event);
69976
+ const CAPTURE_BYTES = 8192;
69977
+ const textEncoder = new TextEncoder();
69978
+ const IDENTITY_KEYS = /* @__PURE__ */ new Set([
69979
+ "actor_id",
69980
+ "actor_kind",
69981
+ "auth_user_id",
69982
+ "anonymous_id",
69983
+ "cli_identity_kind",
69984
+ "distinct_id",
69985
+ "$groups"
69986
+ ]);
69987
+ /** The bounded PostHog capture queue writers for one invocation. Nothing here sends. */
69988
+ const makeCaptures = (options) => {
69989
+ const { input, token, envelope, enabled, clock, queue, state } = options;
69990
+ const enqueue = (body) => {
69991
+ if (textEncoder.encode(body).byteLength > CAPTURE_BYTES || !Queue.offerUnsafe(queue, body)) state.dropped += 1;
69992
+ };
69993
+ const capture = (event, properties, identity) => {
69994
+ if (!enabled()) {
69995
+ state.dropped += 1;
69996
+ return;
69997
+ }
69998
+ const occurredAt = identity?.observedAt ?? clock.currentTimeMillisUnsafe();
69999
+ const record = sanitizePostHogBrowserEvent({
70000
+ event,
70001
+ properties: {
70002
+ ...properties,
70003
+ ...envelope,
70004
+ occurred_at: occurredAt,
70005
+ ...state.nativeContext
70006
+ }
70007
+ });
70008
+ if (event === "access_recovery_completed") {
70009
+ const checked = Schema.decodeUnknownOption(TELEMETRY_EVENT_SCHEMAS.access_recovery_completed)({
70010
+ name: event,
70011
+ props: properties
70012
+ });
70013
+ if (Option.isSome(checked) && checked.value.name === "access_recovery_completed") record.properties.recovery = checked.value.props.recovery;
70014
+ }
70015
+ enqueue(JSON.stringify({
70016
+ api_key: token,
70017
+ ...record,
70018
+ uuid: identity?.eventId ?? crypto.randomUUID(),
70019
+ timestamp: new Date(occurredAt).toISOString(),
70020
+ properties: {
70021
+ ...record.properties,
70022
+ distinct_id: input.anonymousIdentity?.id ?? input.invocationId,
70023
+ $process_person_profile: false
70024
+ }
70025
+ }));
70026
+ };
70027
+ /**
70028
+ * Join this epoch's anonymous history to the person AUTH verified.
70029
+ *
70030
+ * This is a PostHog lifecycle event, not a catalog event: it carries the
70031
+ * person as `distinct_id` and the epoch as `$anon_distinct_id`, and
70032
+ * deliberately omits `$process_person_profile` so the merge is processed —
70033
+ * every catalog event here sets it false, which is what keeps those events off
70034
+ * the person until this record exists.
70035
+ *
70036
+ * The binding's own stored `eventId` is the record UUID, so PostHog
70037
+ * deduplicates a replayed merge. The same idiom `cli_first_used` uses.
70038
+ *
70039
+ * No `$set` traits: identify traits bypass the per-event redactor, and this
70040
+ * module has no pre-minimized person fact to send. The merge is the whole
70041
+ * payload.
70042
+ */
70043
+ const captureIdentify = (authUserId, anonymousId, eventId, occurredAt) => {
70044
+ if (!enabled()) {
70045
+ state.dropped += 1;
70046
+ return;
70047
+ }
70048
+ enqueue(JSON.stringify({
70049
+ api_key: token,
70050
+ event: "$identify",
70051
+ uuid: eventId,
70052
+ timestamp: new Date(occurredAt).toISOString(),
70053
+ properties: {
70054
+ capxul_env: input.environment,
70055
+ distinct_id: authUserId,
70056
+ $anon_distinct_id: anonymousId
70057
+ }
70058
+ }));
70059
+ };
70060
+ const captureSdkDiagnostic = (event, properties) => {
70061
+ const code = safeCode(own(properties, "error_kind")) ?? (event === "$exception" ? "UNKNOWN" : void 0);
70062
+ const operation = own(properties, "operation");
70063
+ if (code === void 0 || state.sdkDiagnostics.length >= 32) {
70064
+ state.dropped += 1;
70065
+ return;
70066
+ }
70067
+ state.sdkDiagnostics.push({
70068
+ log: {
70069
+ ...projectSdkProperties(properties),
70070
+ error_code: code,
70071
+ ...isCapxulOperation(operation) ? { operation } : {}
70072
+ },
70073
+ ...event === "$exception" ? { exception: projectException(properties, input.command, "SDK_DEFECT") } : {}
70074
+ });
70075
+ };
70076
+ return {
70077
+ capture,
70078
+ captureIdentify,
70079
+ client: { capture: (event, properties) => {
70080
+ if (!enabled()) return;
70081
+ if (event === "$exception" || event === CAPXUL_SDK_EXPECTED_OUTCOME_EVENT) {
70082
+ captureSdkDiagnostic(event, properties);
70083
+ return;
70084
+ }
70085
+ const catalog = isTelemetryEventName(event) ? TELEMETRY_EVENT_SCHEMAS[event] : void 0;
70086
+ if (catalog === void 0) {
70087
+ state.dropped += 1;
70088
+ return;
70089
+ }
70090
+ const props = {};
70091
+ const shape = catalog.fields.props;
70092
+ const members = "fields" in shape ? [shape] : shape.members;
70093
+ for (const member of members) for (const key of Object.keys(member.fields)) {
70094
+ if (IDENTITY_KEYS.has(key)) continue;
70095
+ const value = own(properties, key);
70096
+ if (value !== void 0) props[key] = value;
70097
+ }
70098
+ const decoded = Schema.decodeUnknownOption(catalog)({
70099
+ name: event,
70100
+ props: {
70101
+ ...props,
70102
+ ...envelope
70103
+ }
70104
+ });
70105
+ if (Option.isSome(decoded) && Queue.sizeUnsafe(queue) < 29) capture(event, { ...decoded.value.props });
70106
+ else state.dropped += 1;
70107
+ } }
70108
+ };
70109
+ };
70110
+ /** The SDK's host observability for this invocation, joined to the command's trace. */
70111
+ const sdkObservation = (options) => {
70112
+ const { input, client, enabled, deliveryBudget, traceparent } = options;
70113
+ return postHogObservability(client, {
70114
+ enabled,
70115
+ engineeringEnabled: enabled,
70116
+ engineeringPrivacy: "minimal",
70117
+ engineeringDeliveryBudget: deliveryBudget,
70118
+ capxulEnv: input.environment,
70119
+ context: {
70120
+ application: "capxul-cli",
70121
+ ...input.anonymousIdentity === void 0 ? {} : { anonymousId: input.anonymousIdentity.id },
70122
+ release: input.cliVersion,
70123
+ correlationId: input.invocationId,
70124
+ journeyId: input.invocationId,
70125
+ traceparent
70126
+ }
70127
+ });
70128
+ };
70129
+ //#endregion
69689
70130
  //#region src/platform/state-files.ts
69690
70131
  const Settings = Schema.Struct({
69691
70132
  version: Schema.Literal(1),
@@ -70518,17 +70959,24 @@ function observeInvocation(input, work) {
70518
70959
  //#region src/runtime/output.ts
70519
70960
  const writeStdout = (text) => Effect.flatMap(Stdio.Stdio, (stdio) => Stream.run(Stream.make(text), stdio.stdout()));
70520
70961
  const writeStderr = (text) => Effect.flatMap(Stdio.Stdio, (stdio) => Stream.run(Stream.make(text), stdio.stderr()));
70962
+ const showsProgress = (reader) => Effect.gen(function* () {
70963
+ if (reader.mode === "json") return true;
70964
+ return reader.stderrTty === true && (yield* (yield* Stdio.Stdio).stdoutIsTerminal);
70965
+ });
70966
+ /** A state or recovery line while the work has not failed, under `withWaiting`'s rule. */
70967
+ const writeProgress = (reader, text) => Effect.flatMap(showsProgress(reader), (shown) => shown ? writeStderr(text) : Effect.void);
70521
70968
  /** Wrap pending work only. Keep terminal prompts outside this scope. */
70522
70969
  const withWaiting = (work, options) => Effect.scoped(Effect.gen(function* () {
70523
70970
  const stdio = yield* Stdio.Stdio;
70524
- const terminal = options.stderrTty === true && (yield* stdio.stdoutIsTerminal);
70525
- if (options.mode === "human" && !terminal) return yield* work;
70526
- yield* writeStderr(options.mode === "json" ? `${JSON.stringify({
70527
- type: "cli.waiting",
70528
- label: options.label,
70529
- status: "waiting"
70530
- })}\n` : `Waiting: ${terminalText(options.label)}\n`);
70531
- if (!(options.mode === "human" && options.tty && !options.ci)) return yield* work;
70971
+ if (!(yield* showsProgress(options))) return yield* work;
70972
+ if (!(options.mode === "human" && options.tty && !options.ci)) {
70973
+ yield* writeStderr(options.mode === "json" ? `${JSON.stringify({
70974
+ type: "cli.waiting",
70975
+ label: options.label,
70976
+ status: "waiting"
70977
+ })}\n` : `Waiting: ${terminalText(options.label)}\n`);
70978
+ return yield* work;
70979
+ }
70532
70980
  const clear = "\r\x1B[2K";
70533
70981
  const frames = [
70534
70982
  "⠋",
@@ -70554,20 +71002,27 @@ const withWaiting = (work, options) => Effect.scoped(Effect.gen(function* () {
70554
71002
  function renderResult(mode, envelope, lines = [], rawHuman) {
70555
71003
  const pretty = process.stdout.isTTY;
70556
71004
  if (mode === "json") return writeStdout(`${JSON.stringify(envelope, null, pretty ? 2 : void 0)}\n`);
70557
- return envelope.outcome === "succeeded" ? writeStdout(rawHuman ?? `${(pretty ? lines.map(readableLine) : lines).join("\n")}\n`) : Effect.flatMap(Config.String("NO_COLOR").pipe(Config.withDefault(""), Effect.orElseSucceed(() => "")), (noColor) => writeStderr(errorText(envelope.error, process.stderr.isTTY && noColor === "")));
71005
+ return envelope.outcome === "succeeded" ? writeStdout(rawHuman ?? `${(pretty ? lines.map(readableLine) : lines).join("\n")}\n`) : Effect.flatMap(Config.String("NO_COLOR").pipe(Config.withDefault(""), Effect.orElseSucceed(() => "")), (noColor) => writeStderr(errorText(envelope.error, process.stderr.isTTY && noColor === "", errorRef(envelope.error.details, envelope.invocationId))));
70558
71006
  }
70559
71007
  /** A shell word, quoted only when the shell would read it differently. */
70560
71008
  const shellWord = (value) => /^[\w@%+=:,./-]+$/u.test(value) ? value : shellArgument(value);
70561
71009
  /**
71010
+ * The id a person quotes to support: the backend's request id, else its
71011
+ * correlation id, else this run's invocation ID. Each is validated upstream.
71012
+ */
71013
+ const errorRef = (details, invocationId) => details?.requestId ?? details?.correlationId ?? invocationId ?? void 0;
71014
+ /**
70562
71015
  * A refusal for a person: one line (red on a terminal) for what happened, and
70563
- * one for what fixes it. They share a line when both fit in 80 columns.
71016
+ * one for what fixes it. They share a line when both fit in 80 columns. The
71017
+ * last line is the dim `ref <id>`, unless the message already quotes that id.
70564
71018
  */
70565
- const errorText = (error, color) => {
71019
+ const errorText = (error, color, ref) => {
70566
71020
  const what = `✗ ${error.message}`;
70567
71021
  const fix = error.hint ?? (error.next === void 0 ? void 0 : `Try: ${error.next.argv.map(shellWord).join(" ")}`);
70568
71022
  const painted = color ? `\u001b[31m${what}\u001b[0m` : what;
70569
- if (fix === void 0) return `${painted}\n`;
70570
- return !what.includes("\n") && what.length + fix.length < 80 ? `${painted} ${fix}\n` : `${painted}\n${fix}\n`;
71023
+ const body = fix === void 0 ? `${painted}\n` : !what.includes("\n") && what.length + fix.length < 80 ? `${painted} ${fix}\n` : `${painted}\n${fix}\n`;
71024
+ if (ref === void 0 || error.message.includes(ref)) return body;
71025
+ return `${body}${color ? `\u001b[2mref ${ref}\u001b[0m` : `ref ${ref}`}\n`;
70571
71026
  };
70572
71027
  const readableLine = (line) => {
70573
71028
  try {
@@ -70580,8 +71035,8 @@ const terminalText = (value) => value.replace(/[\p{Cc}\p{Cf}]/gu, (character) =>
70580
71035
  const shellArgument = (value) => `'${value.replaceAll("'", "'\"'\"'")}'`;
70581
71036
  //#endregion
70582
71037
  //#region src/runtime/version.ts
70583
- const cliVersion = "4.20.0-beta.27";
70584
- const sdkVersion = "4.20.0-beta.27";
71038
+ const cliVersion = "4.20.0-beta.28";
71039
+ const sdkVersion = "4.20.0-beta.28";
70585
71040
  const ReleaseVersion = Schema.String.check(Schema.isMaxLength(256), Schema.isPattern(/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*)?$/u)).pipe(Schema.brand("CliReleaseVersion"));
70586
71041
  const parts = (version) => {
70587
71042
  const [core = "", ...prerelease] = version.split("+")[0].split("-");
@@ -70788,6 +71243,29 @@ const readFields = (value) => {
70788
71243
  const fields = value.split(",").map((field) => field.trim()).filter((field) => field !== "");
70789
71244
  return fields.length === 0 || fields.some((field) => !/^[\w-]+(?:\.[\w-]+)*$/u.test(field)) ? void 0 : fields;
70790
71245
  };
71246
+ /**
71247
+ * What `--json` returns from a command that moves money: the open approval,
71248
+ * before anyone approves it. A test holds this to `handOver`'s real result.
71249
+ */
71250
+ const APPROVAL$1 = {
71251
+ status: null,
71252
+ approvalId: null,
71253
+ approvalUrl: null,
71254
+ paymentIds: null,
71255
+ expiresAt: null,
71256
+ next: { argv: null }
71257
+ };
71258
+ /**
71259
+ * The commands that move money, each with the result it declares. A typo in
71260
+ * `--fields` must not cost an approval, so these check `--fields` against the
71261
+ * declared result before they run. `capxul schema` marks the same four.
71262
+ */
71263
+ const MONEY_RESULTS = {
71264
+ "payment.send": APPROVAL$1,
71265
+ "payment.retry": APPROVAL$1,
71266
+ "inbox.pay": APPROVAL$1,
71267
+ "org.payroll.run": APPROVAL$1
71268
+ };
70791
71269
  const fieldsRefusal = (message) => new CliFailure({
70792
71270
  code: "CLI_USAGE",
70793
71271
  message,
@@ -70795,6 +71273,64 @@ const fieldsRefusal = (message) => new CliFailure({
70795
71273
  exitCode: 2,
70796
71274
  outcome: "refused"
70797
71275
  });
71276
+ /** A page is the list shape, `{ items, nextCursor }`; any other `items` is an ordinary field. */
71277
+ const isPage = (data) => isRecord(data) && Array.isArray(data.items) && Object.hasOwn(data, "nextCursor");
71278
+ /** The records `--fields` picks from: each item of a page or list, else the data itself. */
71279
+ const recordsOf = (data) => (Array.isArray(data) ? data : isPage(data) ? data.items : [data]).filter(isRecord);
71280
+ const has = (value, [key, ...rest]) => key !== void 0 && isRecord(value) && Object.hasOwn(value, key) && (rest.length === 0 || has(value[key], rest));
71281
+ /** Edit distance, for "did you mean". */
71282
+ const distance = (a, b) => {
71283
+ let row = Array.from({ length: b.length + 1 }, (_, index) => index);
71284
+ for (let i = 0; i < a.length; i++) {
71285
+ const next = [i + 1];
71286
+ for (let j = 0; j < b.length; j++) next.push(Math.min(row[j + 1] + 1, next[j] + 1, row[j] + (a[i] === b[j] ? 0 : 1)));
71287
+ row = next;
71288
+ }
71289
+ return row[b.length];
71290
+ };
71291
+ /** The fields at the deepest level `path` reaches in any record, and those a letter or two off it. */
71292
+ const nearby = (records, path) => {
71293
+ let depth = path.length - 1;
71294
+ while (depth > 0 && !records.some((record) => has(record, path.slice(0, depth)))) depth--;
71295
+ const parent = path.slice(0, depth);
71296
+ const names = [...new Set(records.flatMap((record) => {
71297
+ const inner = parent.reduce((value, key) => isRecord(value) ? value[key] : void 0, record);
71298
+ return isRecord(inner) ? Object.keys(inner) : [];
71299
+ }))].filter((name) => /^[\w-]+$/u.test(name));
71300
+ const typed = path.slice(depth).join(".").toLowerCase();
71301
+ const close = names.filter((name) => {
71302
+ const apart = distance(name.toLowerCase(), typed);
71303
+ return apart <= 2 && apart * 2 < Math.max(name.length, typed.length);
71304
+ });
71305
+ const named = (name) => [...parent, name].join(".");
71306
+ return {
71307
+ names: names.map(named),
71308
+ close: close.map(named)
71309
+ };
71310
+ };
71311
+ /**
71312
+ * The first requested field the result cannot give, as a refusal that names it,
71313
+ * the closest real field and the fields there are. Against a command's declared
71314
+ * result (`declared`), any other name is unknown. Against the result itself, a
71315
+ * name no record has is refused only as a near miss of one it does have
71316
+ * (`stauts`): an optional field this result lacks (`execution` on a Payment
71317
+ * that has none) is left out, as `jq` would. A field some records lack, a
71318
+ * page's own fields and an empty page pass.
71319
+ */
71320
+ const unknownFieldRefusal = (data, fields, declared = false) => {
71321
+ const records = recordsOf(data);
71322
+ if (records.length === 0) return void 0;
71323
+ const pageKeys = isPage(data) ? Object.keys(data) : [];
71324
+ for (const path of fields.map((field) => field.split("."))) {
71325
+ if (records.some((record) => has(record, path))) continue;
71326
+ if (path.length === 1 && pageKeys.includes(path[0])) continue;
71327
+ const { names, close } = nearby(records, path);
71328
+ if (!declared && close.length === 0) continue;
71329
+ const suggestion = close.length === 0 ? "" : ` Did you mean ${close.map((name) => `"${name}"`).join(" or ")}?`;
71330
+ const list = `${names.slice(0, 30).join(", ")}${names.length > 30 ? ", …" : ""}.`;
71331
+ return fieldsRefusal(!declared ? `Field "${path.join(".")}" is not in this result.${suggestion} Fields in this result: ${list}` : `Unknown field "${path.join(".")}".${suggestion} ${names.length === 0 ? "This result has no fields to pick." : `Valid fields: ${list}`}`);
71332
+ }
71333
+ };
70798
71334
  const copyPath = (from, to, [key, ...rest]) => {
70799
71335
  if (key === void 0 || !Object.hasOwn(from, key)) return;
70800
71336
  const value = from[key];
@@ -70808,14 +71344,14 @@ const copyPath = (from, to, [key, ...rest]) => {
70808
71344
  copyPath(value, inner, rest);
70809
71345
  };
70810
71346
  /**
70811
- * Keep the named fields; `amount.value` reaches inside `amount`. A list keeps
70812
- * its page fields (`nextCursor`) and picks from each item. A field the data
71347
+ * Keep the named fields; `amount.value` reaches inside `amount`. A page keeps
71348
+ * its own fields (`nextCursor`) and picks from each item. A field the data
70813
71349
  * lacks is left out, as `jq` would.
70814
71350
  */
70815
71351
  const pickFields = (data, fields) => {
70816
71352
  if (Array.isArray(data)) return data.map((item) => pickFields(item, fields));
70817
71353
  if (!isRecord(data)) return data;
70818
- if (Array.isArray(data.items)) return {
71354
+ if (isPage(data)) return {
70819
71355
  ...data,
70820
71356
  items: data.items.map((item) => pickFields(item, fields))
70821
71357
  };
@@ -70907,44 +71443,14 @@ const withoutOrganization = (saved, origin) => {
70907
71443
  };
70908
71444
  };
70909
71445
  //#endregion
70910
- //#region src/runtime/format.ts
70911
- const jsonFlag = Flag.Boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Print a versioned JSON result"));
70912
- const JsonOutput = GlobalFlag.Setting("json")({ flag: jsonFlag });
70913
- /** `--org <handle>` for one command. Commands resolve it through `#auth/actor`. */
70914
- const OrganizationFlag = GlobalFlag.Setting("org")({ flag: Flag.String("org").pipe(Flag.withMetavar("<handle>"), Flag.withDescription("act as this organization for one command"), Flag.optional) });
70915
- /** `--fields <a,b>`: the JSON result keeps only these fields of `data`. */
70916
- const FieldsFlag = GlobalFlag.Setting("fields")({ flag: Flag.String("fields").pipe(Flag.withMetavar("<a,b>"), Flag.withDescription("choose which JSON fields come back"), Flag.optional) });
70917
- /** `--env <name>` for one command. The runner reads it with the other environment sources. */
70918
- const EnvironmentFlag = GlobalFlag.Setting("env")({ flag: Flag.String("env").pipe(Flag.withMetavar("<name>"), Flag.withDescription("use this environment for one command"), Flag.optional) });
70919
- /** Local diagnostics require an explicit debug level. */
70920
- const localDiagnosticsEnabled = (args) => {
70921
- const terminator = args.indexOf("--");
70922
- const flags = terminator < 0 ? args : args.slice(0, terminator);
70923
- let level;
70924
- for (const [index, arg] of flags.entries()) if (arg.startsWith("--log-level=")) level = arg.slice(12);
70925
- else if (arg.startsWith("--logLevel=")) level = arg.slice(11);
70926
- else if (arg === "--log-level" || arg === "--logLevel") level = flags[index + 1];
70927
- return level === "debug";
70928
- };
70929
- /** Negotiate only the global format before native CLI error rendering.
70930
- * Native Flag decoding remains the value authority; the command runner parses everything else.
71446
+ //#region src/runtime/root-command.ts
71447
+ /** The command tree `capxul` runs, for the commands that describe it (`capxul schema`). */
71448
+ var RootCommand = class extends Context.Service()("capxul-cli/RootCommand") {};
71449
+ /**
71450
+ * The tree, when the entry provided it. Reading it as optional keeps it out of
71451
+ * every command's requirements; only `schema` asks, and the entry always gives it.
70931
71452
  */
70932
- const outputMode = (args) => Effect.gen(function* () {
70933
- const terminator = args.indexOf("--");
70934
- const flags = terminator < 0 ? args : args.slice(0, terminator);
70935
- const values = [];
70936
- for (const [index, arg] of flags.entries()) if (arg === "--no-json") values.push("false");
70937
- else if (arg.startsWith("--json=")) values.push(arg.slice(7));
70938
- else if (arg === "--json") {
70939
- const next = flags[index + 1];
70940
- const value = next === void 0 ? "true" : yield* Primitive.Boolean.parse(next).pipe(Effect.as(next), Effect.orElseSucceed(() => "true"));
70941
- values.push(value);
70942
- }
70943
- return yield* jsonFlag.parse({
70944
- flags: { json: values },
70945
- arguments: []
70946
- }).pipe(Effect.map(([, json]) => json ? "json" : "human"), Effect.orElseSucceed(() => values.includes("true") ? "json" : "human"));
70947
- });
71453
+ const rootCommand = Effect.flatMap(Effect.serviceOption(RootCommand), (root) => Option.isSome(root) ? Effect.succeed(root.value) : Effect.die("RootCommand is not provided"));
70948
71454
  //#endregion
70949
71455
  //#region src/runtime/run-command.ts
70950
71456
  var CliRuntime = class extends Context.Service()("capxul-cli/Runtime") {};
@@ -71017,14 +71523,28 @@ const runInvocation = (runtime, metadata, kind, work, network = false) => Effect
71017
71523
  return notice === "" ? Effect.void : writeStderr(notice).pipe(Effect.ignoreCause);
71018
71524
  }));
71019
71525
  });
71020
- /** `--fields`, checked before the command runs: a JSON choice that names at least one field. */
71021
- const requestedFields = (runtime) => Effect.gen(function* () {
71526
+ /**
71527
+ * The flags, checked before the command runs: a flag that takes one value is
71528
+ * typed once. `--fields` is a JSON choice that names at least one field. A
71529
+ * command that moves money also checks the names against its declared
71530
+ * result, so a typo is refused before anything is prepared.
71531
+ */
71532
+ const requestedFields = (runtime, name) => Effect.gen(function* () {
71533
+ const root = Option.getOrUndefined(yield* Effect.serviceOption(RootCommand));
71534
+ const repeated = repeatedFlag(root, name, runtime.args ?? []);
71535
+ if (repeated !== void 0) return yield* repeated;
71022
71536
  const value = flagValue(runtime.args ?? [], "fields");
71023
71537
  if (value === void 0) return void 0;
71024
71538
  if (runtime.mode !== "json") return yield* fieldsRefusal("--fields chooses JSON fields. Add --json.");
71025
71539
  const fields = readFields(value);
71026
71540
  if (fields === void 0) return yield* fieldsRefusal("Provide --fields as names separated by commas, like id,status.");
71027
- return fields;
71541
+ const declared = MONEY_RESULTS[name];
71542
+ const unknown = declared === void 0 ? void 0 : unknownFieldRefusal(declared, fields, true);
71543
+ if (unknown !== void 0) return yield* unknown;
71544
+ return {
71545
+ fields,
71546
+ declared: declared !== void 0
71547
+ };
71028
71548
  });
71029
71549
  /** Commands that print where you are themselves, or change who you are. */
71030
71550
  const selfLocating = (name) => name === "whoami" || name === "use" || name.startsWith("use.") || name.startsWith("auth.");
@@ -71040,10 +71560,13 @@ const writeHeader = (config, runtime, scope = "context") => Effect.gen(function*
71040
71560
  const runCommand = (options, work) => Effect.gen(function* () {
71041
71561
  const runtime = yield* CliRuntime;
71042
71562
  return yield* runInvocation(runtime, options, "command", (host, onFinalize) => Effect.gen(function* () {
71043
- const fields = yield* requestedFields(runtime);
71563
+ const requested = yield* requestedFields(runtime, options.name);
71564
+ const fields = requested?.fields;
71044
71565
  const config = yield* loadConfigWith(options.environment ?? flagValue(runtime.args ?? [], "env"));
71045
71566
  if (runtime.mode === "human" && !selfLocating(options.name)) yield* writeHeader(config, runtime, actorScope$1(options.name));
71046
71567
  const result = yield* work(config, host, onFinalize);
71568
+ const unknown = fields === void 0 || requested?.declared === true ? void 0 : unknownFieldRefusal(result.data, fields);
71569
+ if (unknown !== void 0) return yield* unknown;
71047
71570
  yield* renderResult(runtime.mode, {
71048
71571
  version: 1,
71049
71572
  command: options.name,
@@ -71056,7 +71579,7 @@ const runCommand = (options, work) => Effect.gen(function* () {
71056
71579
  });
71057
71580
  //#endregion
71058
71581
  //#region src/commands/account/subcommands/shared.ts
71059
- const emailFlag$10 = Flag.String("email").pipe(Flag.optional);
71582
+ const emailFlag$10 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
71060
71583
  const names$8 = {
71061
71584
  root: "account",
71062
71585
  show: "account.show",
@@ -71234,6 +71757,22 @@ const openSessionRecord = (config, email) => Effect.gen(function* () {
71234
71757
  };
71235
71758
  });
71236
71759
  //#endregion
71760
+ //#region src/auth/bootstrap-cache.ts
71761
+ const bootstrapCache = (config, run) => {
71762
+ const record = privateRecordScope(config, "bootstrap", "application");
71763
+ return {
71764
+ load: () => run(Effect.gen(function* () {
71765
+ const { directory, scope } = yield* record;
71766
+ const value = yield* readPrivateRecord(directory, scope);
71767
+ return value === null ? null : JSON.parse(value);
71768
+ })),
71769
+ save: (body) => run(Effect.gen(function* () {
71770
+ const { directory, scope } = yield* record;
71771
+ yield* writePrivateRecord(directory, scope, JSON.stringify(body));
71772
+ }))
71773
+ };
71774
+ };
71775
+ //#endregion
71237
71776
  //#region src/auth/current-session.ts
71238
71777
  const CurrentSession = Schema.Struct({
71239
71778
  version: Schema.Literal(1),
@@ -71277,12 +71816,23 @@ const forgetCurrentSession = (config, email, stillCleared) => Effect.gen(functio
71277
71816
  });
71278
71817
  //#endregion
71279
71818
  //#region src/auth/input.ts
71280
- const inputRequired = (message) => new CliFailure({
71819
+ /** A refusal of the input: `field` is the flag or argument refused, `next` the command that fixes it. */
71820
+ const inputRequired = (message, fix = {}) => new CliFailure({
71281
71821
  code: "CLI_USAGE",
71282
71822
  message,
71823
+ ...fix,
71283
71824
  exitCode: 2,
71284
71825
  outcome: "refused"
71285
71826
  });
71827
+ /** An Organization command with none chosen: `org list` finds the handle to choose. */
71828
+ const organizationRequired = () => inputRequired("Provide --org, or choose one with: capxul use org <handle>", {
71829
+ field: "org",
71830
+ next: [
71831
+ "capxul",
71832
+ "org",
71833
+ "list"
71834
+ ]
71835
+ });
71286
71836
  const readEmail = (email) => Effect.gen(function* () {
71287
71837
  const runtime = yield* CliRuntime;
71288
71838
  if (email === void 0) {
@@ -71418,7 +71968,7 @@ const withAccountSignerProgress = (client, work) => Effect.scoped(Effect.gen(fun
71418
71968
  const observe = (status) => {
71419
71969
  if (closed || status === previous) return;
71420
71970
  previous = status;
71421
- writes = writes.then(() => run(writeStderr(runtime.mode === "json" ? `${JSON.stringify({
71971
+ writes = writes.then(() => run(writeProgress(runtime, runtime.mode === "json" ? `${JSON.stringify({
71422
71972
  type: "account.signer",
71423
71973
  status
71424
71974
  })}\n` : `Account signer: ${terminalText(status)}\n`)));
@@ -71445,6 +71995,7 @@ const clientFor = (config, store, options) => Effect.gen(function* () {
71445
71995
  return yield* acquireClient({
71446
71996
  publishableKey: Redacted.value(config.publishableKey),
71447
71997
  bootstrapBaseUrl: config.bootstrapBaseUrl,
71998
+ bootstrapCache: bootstrapCache(config, run),
71448
71999
  requirement: options.requirement ?? "none",
71449
72000
  accountSetup: "explicit",
71450
72001
  nodeSessionStorage: {
@@ -71724,15 +72275,48 @@ const lifecycleResult = (lifecycle, email) => ({
71724
72275
  ] : []
71725
72276
  ]
71726
72277
  });
72278
+ /**
72279
+ * A valuation as currency with its code and two decimals (`3,998.22 USD`), as
72280
+ * every amount reads; a currency this CLI cannot format keeps its raw value.
72281
+ */
72282
+ const shownValuation = (valuation) => {
72283
+ if (valuation === null) return "unavailable";
72284
+ try {
72285
+ const symbolic = formatMoney(valuation, { grammar: "symbol" });
72286
+ return terminalText(`${symbolic.replace(/^[^\d-]+/u, "")} ${valuation.currency}`);
72287
+ } catch {
72288
+ return `${terminalText(valuation.value)} ${terminalText(valuation.currency)}`;
72289
+ }
72290
+ };
72291
+ /** Each asset on a row, with the amounts formatted as the home screen formats them. */
72292
+ const positionTable = (positions) => {
72293
+ const rows = [[
72294
+ "ASSET",
72295
+ "BALANCE",
72296
+ "AVAILABLE"
72297
+ ], ...positions.map((position) => [
72298
+ terminalText(assetName(position.asset)),
72299
+ shownNumber({
72300
+ asset: position.asset,
72301
+ value: position.balance
72302
+ }),
72303
+ shownNumber({
72304
+ asset: position.asset,
72305
+ value: position.available
72306
+ })
72307
+ ])];
72308
+ const widths = [
72309
+ 0,
72310
+ 1,
72311
+ 2
72312
+ ].map((column) => Math.max(...rows.map((row) => row[column].length)));
72313
+ return rows.map((row) => row.map((cell, column) => column === 0 ? cell.padEnd(widths[0]) : cell.padStart(widths[column])).join(" ").trimEnd());
72314
+ };
71727
72315
  const balanceLines$1 = (account) => [
71728
72316
  `Account ${terminalText(account.id)}`,
71729
- ...account.balances.length === 0 ? ["No asset balances returned."] : account.balances.flatMap((position) => [
71730
- ` Asset: ${terminalText(position.symbol)} on ${networkName(requireAsset(position.asset).chainId)}`,
71731
- ` Balance: ${terminalText(position.balance)}`,
71732
- ` Available: ${terminalText(position.available)}`
71733
- ]),
71734
- `Balance valuation: ${account.balance === null ? "unavailable" : `${terminalText(account.balance.value)} ${terminalText(account.balance.currency)}`}`,
71735
- `Available valuation: ${account.available === null ? "unavailable" : `${terminalText(account.available.value)} ${terminalText(account.available.currency)}`}`
72317
+ ...account.balances.length === 0 ? ["No asset balances returned."] : positionTable(account.balances),
72318
+ `Balance valuation: ${shownValuation(account.balance)}`,
72319
+ `Available valuation: ${shownValuation(account.available)}`
71736
72320
  ];
71737
72321
  const depositLines = (instructions) => [
71738
72322
  `Deposit to ${terminalText(instructions.label)}`,
@@ -71780,7 +72364,14 @@ const organizationId = (config, value) => Effect.gen(function* () {
71780
72364
  });
71781
72365
  const handle = value.replace(/^@/u, "").toLowerCase();
71782
72366
  const found = organizations.find((org) => org.handle.toLowerCase() === handle);
71783
- if (found === void 0) return yield* inputRequired(`You are not a member of an Organization "${terminalText(value)}". See yours with: capxul org list`);
72367
+ if (found === void 0) return yield* inputRequired(`You are not a member of an Organization "${terminalText(value)}". See yours with: capxul org list`, {
72368
+ field: "org",
72369
+ next: [
72370
+ "capxul",
72371
+ "org",
72372
+ "list"
72373
+ ]
72374
+ });
71784
72375
  return found.id;
71785
72376
  });
71786
72377
  /**
@@ -71814,10 +72405,10 @@ const actorScope = (actor) => actor.kind === "personal" ? {
71814
72405
  };
71815
72406
  //#endregion
71816
72407
  //#region src/commands/org/subcommands/shared.ts
71817
- const emailFlag$9 = Flag.String("email").pipe(Flag.optional);
72408
+ const emailFlag$9 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
71818
72409
  const scoped$1 = { email: emailFlag$9 };
71819
- const confirmFlag$6 = Flag.Boolean("confirm").pipe(Flag.withDefault(false));
71820
- const timeoutFlag$4 = Flag.Int("timeout-seconds").pipe(Flag.withDefault(120));
72410
+ const confirmFlag$6 = Flag.Boolean("confirm").pipe(Flag.withDescription(FLAG_HELP.confirm), Flag.withDefault(false));
72411
+ const timeoutFlag$4 = Flag.Int("timeout-seconds").pipe(Flag.withDescription(FLAG_HELP.timeout), Flag.withDefault(120));
71821
72412
  const metadata$18 = (name) => ({
71822
72413
  name,
71823
72414
  capability: "organization",
@@ -72841,8 +73432,9 @@ const policyLines = (label, policy) => {
72841
73432
  if (policy.type === "managePeople") return [`${label}: Manage people`];
72842
73433
  return [
72843
73434
  `${label}: Budget`,
72844
- ` Asset ID: ${terminalText(policy.asset)}`,
73435
+ ` Asset: ${terminalText(assetName(policy.asset))}`,
72845
73436
  ` Per-payment cap: ${policy.limit === null ? "no cap" : terminalText(policy.limit)}`,
73437
+ ` Refill: ${policy.refill === "monthly" ? "Every 30 days" : "None"}`,
72846
73438
  ` Recipients: ${policy.scope.recipients.type === "anyone" ? "Anyone" : policy.scope.recipients.accounts.map(terminalText).join(", ")}`,
72847
73439
  ` Actions: ${policy.scope.actions.map((action) => action === "pay" ? "Payments (single and batch)" : "Commitments").join(", ")}`
72848
73440
  ];
@@ -72909,7 +73501,7 @@ const organizationMeLines = (member) => [
72909
73501
  ` Name: ${terminalText(budget.label)}`,
72910
73502
  ` Budget ID: ${terminalText(budget.id)}`,
72911
73503
  ` Per-payment cap: ${budget.limit === null ? "no cap" : terminalText(budget.limit.value)} ${terminalText(budget.asset.symbol)}`,
72912
- ` Asset ID: ${terminalText(budget.asset.assetId)}`
73504
+ ` Asset: ${terminalText(assetName(budget.asset.assetId))}`
72913
73505
  ])
72914
73506
  ];
72915
73507
  //#endregion
@@ -73090,7 +73682,7 @@ const readOrganization = (config, command, input, handler) => Effect.gen(functio
73090
73682
  }
73091
73683
  organizationId = resolved;
73092
73684
  if (organizationId === null) {
73093
- if (!interactive || organizations.length === 0) return yield* inputRequired("Provide --org, or choose one with: capxul use org <handle>");
73685
+ if (!interactive || organizations.length === 0) return yield* organizationRequired();
73094
73686
  organizationId = yield* Prompt.Select({
73095
73687
  message: "Organization",
73096
73688
  choices: organizations.map((org) => ({
@@ -73507,10 +74099,10 @@ const command$79 = Command.make("retry", {
73507
74099
  const account = Command.make("account").pipe(annotate$8("root"), Command.withDescription("your Capxul account and its setup"), Command.withSubcommands([command$80, command$79]));
73508
74100
  //#endregion
73509
74101
  //#region src/commands/activity/subcommands/shared.ts
73510
- const emailFlag$8 = Flag.String("email").pipe(Flag.optional);
73511
- const fromFlag = Flag.Int("from").pipe(Flag.optional);
73512
- const toFlag = Flag.Int("to").pipe(Flag.optional);
73513
- const confirmFlag$5 = Flag.Boolean("confirm").pipe(Flag.withDefault(false));
74102
+ const emailFlag$8 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
74103
+ const fromFlag = Flag.Int("from").pipe(Flag.withDescription("only activity from this time on, in epoch milliseconds"), Flag.optional);
74104
+ const toFlag = Flag.Int("to").pipe(Flag.withDescription("only activity before this time, in epoch milliseconds"), Flag.optional);
74105
+ const confirmFlag$5 = Flag.Boolean("confirm").pipe(Flag.withDescription(FLAG_HELP.confirm), Flag.withDefault(false));
73514
74106
  const names$7 = {
73515
74107
  root: "activity",
73516
74108
  list: "activity.list",
@@ -73646,19 +74238,23 @@ const readAnnotation = (source) => Effect.gen(function* () {
73646
74238
  }).pipe(Effect.mapError((error) => source === void 0 || error instanceof CliFailure ? error : annotationError()));
73647
74239
  //#endregion
73648
74240
  //#region src/commands/payment/internal/shared.ts
73649
- /** Money as people read it, with at least two decimals: `550.00 USDC`. */
73650
- const shownAmount = (amount) => {
73651
- const [number = "", symbol = ""] = formatAssetAmount(amount).split(" ");
73652
- const [integer = "0", fraction = ""] = number.split(".");
73653
- return `${integer}.${fraction.padEnd(2, "0")} ${symbol}`;
73654
- };
73655
74241
  const paymentIdPattern = /^payment_[0-9A-Za-z]+$/u;
73656
- const readPaymentId = (paymentId) => paymentIdPattern.test(paymentId) ? Effect.succeed(paymentId) : Effect.fail(new CliFailure({
73657
- code: "CLI_USAGE",
73658
- message: "Provide the Payment ID: payment_ followed by letters or digits.",
73659
- exitCode: 2,
73660
- outcome: "refused"
73661
- }));
74242
+ const readPaymentId = (paymentId) => paymentIdPattern.test(paymentId) ? Effect.succeed(paymentId) : Effect.gen(function* () {
74243
+ const runtime = yield* CliRuntime;
74244
+ return yield* new CliFailure({
74245
+ code: "CLI_USAGE",
74246
+ message: "Provide the Payment ID: payment_ followed by letters or digits.",
74247
+ field: "id",
74248
+ next: [
74249
+ "capxul",
74250
+ "payment",
74251
+ "list",
74252
+ ...scopeArgs(runtime.args ?? [])
74253
+ ],
74254
+ exitCode: 2,
74255
+ outcome: "refused"
74256
+ });
74257
+ });
73662
74258
  const paymentLines = (payment) => [
73663
74259
  `Payment ${terminalText(payment.id)}`,
73664
74260
  ` Status: ${payment.status}`,
@@ -73680,8 +74276,8 @@ const incomplete = (warning) => warning === void 0 ? [] : ["Activity is incomple
73680
74276
  const pageLines = (page) => [
73681
74277
  ...incomplete(page.readWarning),
73682
74278
  `Activity: ${page.items.length} rows`,
73683
- ...page.items.map((item) => `${item.kind === "movement" ? "Movement" : "Payment"} ${terminalText(item.id)}: ${terminalText(item.amount.value)} ${terminalText(item.amount.asset)}`),
73684
- page.nextCursor === null ? "End of activity." : `More: the same command with --after ${terminalText(page.nextCursor)}`,
74279
+ ...page.items.map((item) => `${item.kind === "movement" ? "Movement" : "Payment"} ${terminalText(item.id)}: ${terminalText(item.amount.value)} ${terminalText(assetName(item.amount.asset))}`),
74280
+ page.nextCursor === null ? "End of activity." : `More: the same command with --after ${terminalText(shellArgument(page.nextCursor))}`,
73685
74281
  `Checkpoint: ${page.checkpoint}`,
73686
74282
  `Observed at: ${page.observedAt}`
73687
74283
  ];
@@ -73690,7 +74286,11 @@ const summaryLines = (summary, window) => [
73690
74286
  `From: ${window?.from === void 0 ? "unbounded" : window.from}`,
73691
74287
  `To: ${window?.to === void 0 ? "unbounded" : window.to}`,
73692
74288
  `Activity: ${summary.count} rows`,
73693
- ...summary.totals.map((total) => `${terminalText(total.asset)}: in ${terminalText(total.in)}, out ${terminalText(total.out)}, net ${terminalText(total.net)}, pending in ${terminalText(total.pendingIn)} raw units`),
74289
+ ...summary.totals.map((total) => {
74290
+ const known = shownBaseUnits(total.asset, "0") !== void 0;
74291
+ const show = (raw) => terminalText(shownBaseUnits(total.asset, raw) ?? raw);
74292
+ return `${terminalText(assetName(total.asset))}: in ${show(total.in)}, out ${show(total.out)}, net ${show(total.net)}, pending in ${show(total.pendingIn)}${known ? "" : " raw units"}`;
74293
+ }),
73694
74294
  `Observed at: ${summary.observedAt}`
73695
74295
  ];
73696
74296
  const detailLines = (activity, id) => [
@@ -73726,11 +74326,11 @@ const execute$90 = (config, input) => Effect.gen(function* () {
73726
74326
  //#region src/commands/activity/subcommands/list/command.ts
73727
74327
  const command$78 = Command.make("list", {
73728
74328
  email: emailFlag$8,
73729
- limit: Flag.Int("limit").pipe(Flag.withDefault(25)),
74329
+ limit: Flag.Int("limit").pipe(Flag.withDescription("how many rows to return, 1 to 100"), Flag.withDefault(25)),
73730
74330
  cursor: Flag.String("after").pipe(Flag.withDescription("the next page's cursor"), Flag.optional),
73731
- activityKind: Flag.String("kind").pipe(Flag.optional),
73732
- direction: Flag.String("direction").pipe(Flag.optional),
73733
- statuses: Flag.String("status").pipe(Flag.atLeast(0)),
74331
+ activityKind: Flag.String("kind").pipe(Flag.withDescription("only payments or only movements"), Flag.optional),
74332
+ direction: Flag.String("direction").pipe(Flag.withDescription("only in, out or self"), Flag.optional),
74333
+ statuses: Flag.String("status").pipe(Flag.withDescription("only payments in this status; repeat it for several"), Flag.atLeast(0)),
73734
74334
  from: fromFlag,
73735
74335
  to: toFlag
73736
74336
  }, ({ email, limit, cursor, activityKind, direction, statuses, from, to }) => dispatch$7("list", (config, orgId) => execute$90(config, {
@@ -73854,48 +74454,17 @@ const saveDocumentPdf = (config, document, outputDirectory) => Effect.gen(functi
73854
74454
  byteLength: rendered.bytes.byteLength
73855
74455
  };
73856
74456
  });
73857
- /** Document output cannot change a Payment result or start another Payment. */
73858
- const savePaymentDocuments = (config, client, payment, outputDirectory) => Effect.gen(function* () {
73859
- const refs = [...new Map(payment.documents.map((ref) => [`${ref.documentHash}:${ref.contentHash}`, ref])).values()];
73860
- const outputs = yield* Effect.forEach(refs, (ref) => Effect.gen(function* () {
73861
- const document = yield* Effect.tryPromise({
73862
- try: (signal) => client.paymentDocuments.render({
73863
- documentHash: ref.documentHash,
73864
- contentHash: ref.contentHash
73865
- }, { signal }),
73866
- catch: () => failure("CLI_DEFECT", "The payment document could not be read.")
73867
- }).pipe(Effect.flatMap(resultValue));
73868
- if (document.documentHash.toLowerCase() !== ref.documentHash.toLowerCase() || document.contentHash.toLowerCase() !== ref.contentHash.toLowerCase()) return yield* failure("CLI_DEFECT", "The payment document hashes do not match.");
73869
- return {
73870
- state: "saved",
73871
- ...yield* saveDocumentPdf(config, document, outputDirectory)
73872
- };
73873
- }).pipe(Effect.catch((error) => {
73874
- const classified = classifyFailure(Cause.fail(error));
73875
- return Effect.succeed({
73876
- state: "failed",
73877
- documentHash: ref.documentHash,
73878
- contentHash: ref.contentHash,
73879
- error: {
73880
- code: classified.code,
73881
- message: classified.message
73882
- }
73883
- });
73884
- })));
73885
- return {
73886
- state: outputs.some((output) => output.state === "failed") ? "failed" : payment.documentState === "unavailable" ? "unavailable" : payment.documentState === "pending" || refs.length === 0 ? "pending" : "ready",
73887
- outputs
73888
- };
73889
- });
73890
- const documentOutputLines = (output) => [` Document output: ${output.state}`, ...output.outputs.map((document) => document.state === "saved" ? ` PDF: ${terminalText(document.path)}` : ` Document ${terminalText(document.documentHash)}: ${terminalText(document.error.message)}`)];
73891
74457
  /** The command that saves one document as PDF. Reads never write files themselves. */
73892
74458
  const renderCommand = (ref) => `capxul document render --document-hash ${ref.documentHash} --content-hash ${ref.contentHash}`;
74459
+ const uniqueRefs = (payment) => [...new Map(payment.documents.map((ref) => [`${ref.documentHash}:${ref.contentHash}`, ref])).values()];
73893
74460
  /** A read's documents: where they stand, and the command that saves each one as PDF. */
73894
74461
  const documentReferenceLines = (payment) => {
73895
- const refs = [...new Map(payment.documents.map((ref) => [`${ref.documentHash}:${ref.contentHash}`, ref])).values()];
74462
+ const refs = uniqueRefs(payment);
73896
74463
  if (refs.length === 0) return payment.documentState === "pending" ? [" Documents: being prepared"] : [];
73897
74464
  return refs.map((ref) => ` ${ref.kind}: ${terminalText(renderCommand(ref))}`);
73898
74465
  };
74466
+ /** A write saves no file: this names the command that saves each document as PDF. */
74467
+ const saveDocumentLines = (payment) => uniqueRefs(payment).map((ref) => `Save the ${ref.kind}: ${terminalText(renderCommand(ref))}`);
73899
74468
  //#endregion
73900
74469
  //#region src/commands/activity/subcommands/get/execute.ts
73901
74470
  const execute$89 = (config, input) => Effect.gen(function* () {
@@ -73920,8 +74489,8 @@ const execute$89 = (config, input) => Effect.gen(function* () {
73920
74489
  //#region src/commands/activity/subcommands/get/command.ts
73921
74490
  const command$77 = Command.make("get", {
73922
74491
  email: emailFlag$8,
73923
- activityKind: Flag.String("kind"),
73924
- id: Flag.String("id")
74492
+ activityKind: Flag.String("kind").pipe(Flag.withDescription("payment or movement")),
74493
+ id: Flag.String("id").pipe(Flag.withDescription("the Payment or movement ID"))
73925
74494
  }, ({ email, activityKind, id }) => dispatch$7("get", (config, orgId) => execute$89(config, {
73926
74495
  kind: "get",
73927
74496
  email: Option.getOrUndefined(email),
@@ -73984,7 +74553,7 @@ const execute$87 = (config, input) => Effect.gen(function* () {
73984
74553
  //#region src/commands/activity/subcommands/annotate/command.ts
73985
74554
  const command$75 = Command.make("annotate", {
73986
74555
  email: emailFlag$8,
73987
- source: Flag.String("input").pipe(Flag.optional),
74556
+ source: Flag.String("input").pipe(Flag.withDescription("the annotation JSON, from a file or - for stdin"), Flag.optional),
73988
74557
  confirm: confirmFlag$5
73989
74558
  }, ({ email, source, confirm }) => dispatch$7("annotate", (config, orgId) => execute$87(config, {
73990
74559
  kind: "annotate",
@@ -74044,7 +74613,7 @@ const metadata$16 = {
74044
74613
  capability: "account",
74045
74614
  observation: "command"
74046
74615
  };
74047
- const address = Command.make("address", { email: Flag.String("email").pipe(Flag.optional) }, ({ email }) => runCommand({
74616
+ const address = Command.make("address", { email: Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional) }, ({ email }) => runCommand({
74048
74617
  ...metadata$16,
74049
74618
  network: true
74050
74619
  }, (config) => execute$84(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$16), Command.withDescription("where to send crypto to this account"), Command.withExamples([{ command: "capxul address" }]));
@@ -74125,11 +74694,14 @@ const command$74 = Command.make("login", {
74125
74694
  */
74126
74695
  const execute$82 = (config, input) => Effect.gen(function* () {
74127
74696
  const pending = (yield* readWhere(config)).origins?.[config.bootstrapBaseUrl]?.pending;
74128
- if (input.email === void 0 && pending === void 0) return yield* inputRequired("No sign-in is waiting for a code. Start one with: capxul auth login");
74697
+ if (input.email === void 0 && pending === void 0) return yield* inputRequired("No sign-in is waiting for a code. Start one with: capxul auth login", { next: SIGN_IN$1 });
74129
74698
  const email = yield* readEmail(input.email ?? pending);
74130
74699
  const store = yield* openSessionRecord(config, email);
74131
74700
  const flow = store.current().flow;
74132
- if (flow === null || flow.email !== email) return yield* inputRequired("No sign-in is waiting for a code for that email. Start one with: capxul auth login");
74701
+ if (flow === null || flow.email !== email) return yield* inputRequired("No sign-in is waiting for a code for that email. Start one with: capxul auth login", {
74702
+ field: "email",
74703
+ next: SIGN_IN$1
74704
+ });
74133
74705
  const client = yield* clientFor(config, store, { restore: false });
74134
74706
  yield* call((signal) => client.auth.getSession({ signal }));
74135
74707
  yield* call((signal) => client.auth.resumeOtp({
@@ -74349,7 +74921,7 @@ const metadata$14 = {
74349
74921
  capability: "account",
74350
74922
  observation: "command"
74351
74923
  };
74352
- const balance = Command.make("balance", { email: Flag.String("email").pipe(Flag.optional) }, ({ email }) => runCommand({
74924
+ const balance = Command.make("balance", { email: Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional) }, ({ email }) => runCommand({
74353
74925
  ...metadata$14,
74354
74926
  network: true
74355
74927
  }, (config) => execute$77(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$14), Command.withDescription("balances for whoever you're acting as"), Command.withExamples([{ command: "capxul balance" }, {
@@ -74415,12 +74987,12 @@ const config = Command.make("config").pipe(Command.annotate(CliCommandMetadata,
74415
74987
  }), Command.withDescription("this CLI's own settings"), Command.withSubcommands([telemetry]));
74416
74988
  //#endregion
74417
74989
  //#region src/commands/contact/subcommands/shared.ts
74418
- const emailFlag$6 = Flag.String("email").pipe(Flag.optional);
74419
- const includeHiddenFlag = Flag.Boolean("include-hidden").pipe(Flag.withDefault(false));
74990
+ const emailFlag$6 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
74991
+ const includeHiddenFlag = Flag.Boolean("include-hidden").pipe(Flag.withDescription("also list the contacts you hid"), Flag.withDefault(false));
74420
74992
  const entryIdArgument = Argument.String("id").pipe(Argument.withDescription("The contact's PartyId"), Argument.optional);
74421
- const labelFlag = Flag.String("label");
74422
- const inputFlag = Flag.String("input").pipe(Flag.optional);
74423
- const confirmFlag$4 = Flag.Boolean("confirm").pipe(Flag.withDefault(false));
74993
+ const labelFlag = Flag.String("label").pipe(Flag.withDescription("the new label to show for this contact"));
74994
+ const inputFlag = Flag.String("input").pipe(Flag.withDescription("the contact JSON, from a file or - for stdin"), Flag.optional);
74995
+ const confirmFlag$4 = Flag.Boolean("confirm").pipe(Flag.withDescription(FLAG_HELP.confirm), Flag.withDefault(false));
74424
74996
  const names$6 = {
74425
74997
  root: "contact",
74426
74998
  list: "contact.list",
@@ -74635,7 +75207,15 @@ const selectContactParty = (book, supplied, orgs) => Effect.gen(function* () {
74635
75207
  const optionalPartyId = (value) => Effect.gen(function* () {
74636
75208
  if (value !== void 0) return yield* partyId(value);
74637
75209
  const runtime = yield* CliRuntime;
74638
- if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* inputRequired("Provide the contact ID.");
75210
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* inputRequired("Provide the contact ID.", {
75211
+ field: "id",
75212
+ next: [
75213
+ "capxul",
75214
+ "contact",
75215
+ "list",
75216
+ ...scopeArgs(runtime.args ?? [])
75217
+ ]
75218
+ });
74639
75219
  return null;
74640
75220
  });
74641
75221
  const actorLine = (orgs) => orgs[0] === void 0 ? "Actor: personal" : `Actor: Organization ${JSON.stringify(orgs[0])}`;
@@ -74918,26 +75498,41 @@ const readRecipient$1 = (value) => Effect.gen(function* () {
74918
75498
  kind: "email",
74919
75499
  email: yield* readEmail(text)
74920
75500
  } };
74921
- return yield* Effect.try({
75501
+ if (!/^0x[0-9a-f]*$/iu.test(text)) return yield* Effect.try({
74922
75502
  try: () => ({ to: {
74923
75503
  kind: "handle",
74924
75504
  handle: String(toHandle(text.replace(/^@/u, "")))
74925
75505
  } }),
74926
- catch: () => inputRequired(`Send to an email, a @handle, or an 0x address, not "${terminalText(text)}".`)
75506
+ catch: () => notARecipient(text)
74927
75507
  });
75508
+ return yield* notARecipient(text);
74928
75509
  });
75510
+ /**
75511
+ * The backend refuses a handle nobody has with only the field and a reason, so
75512
+ * the sentence that names the recipient is written here, where it is known.
75513
+ */
75514
+ const namingRecipient = (to) => (error) => to.kind === "handle" && error.code === "INVALID_INPUT" && error.details?.field === "handle" ? new CliFailure({
75515
+ ...classifyFailure(Cause.fail(error)),
75516
+ message: `No one has the handle @${terminalText(to.handle)}. Send to an email, an existing @handle or an 0x address.`
75517
+ }) : error;
75518
+ /** What a recipient can be, naming the one that was typed. */
75519
+ const notARecipient = (text) => inputRequired(`"${terminalText(text)}" is not a valid email, @handle or 0x address.`, { field: "to" });
74929
75520
  /** `<amount>` of `--asset` (a symbol the account holds, or an asset ID). */
74930
75521
  const readAmount = (value, asset, account) => Effect.gen(function* () {
74931
75522
  const wanted = asset.trim().toUpperCase();
74932
75523
  const position = account.balances.find((held) => held.symbol.toUpperCase() === wanted || held.asset.toUpperCase() === wanted);
74933
75524
  if (position === void 0) return yield* inputRequired(`Your account holds no ${terminalText(asset)}. Choose one it holds with --asset: ${account.balances.map((held) => held.symbol).join(", ")}.`);
74934
- const amount = parseAssetAmount(value.trim(), requireAsset(position.asset));
74935
- if (isMoneyParseError(amount) || /^0*(?:\.0*)?$/u.test(amount.value)) return yield* inputRequired("Provide a positive amount, like 50 or 12.5.");
75525
+ const metadata = requireAsset(position.asset);
75526
+ const amount = parseAssetAmount(value.trim(), metadata);
75527
+ if (isMoneyParseError(amount)) return yield* inputRequired(amountRefusal(amount.reason, metadata), { field: "amount" });
75528
+ if (/^0*(?:\.0*)?$/u.test(amount.value)) return yield* inputRequired(amountRefusal("non-positive", metadata), { field: "amount" });
74936
75529
  return {
74937
75530
  amount,
74938
75531
  position
74939
75532
  };
74940
75533
  });
75534
+ /** Why an amount was refused, as a sentence that names the cause. */
75535
+ const amountRefusal = (reason, asset) => reason === "too-many-decimals" ? `${asset.symbol} has ${asset.decimals} decimals: use at most ${asset.decimals} decimal places.` : "Provide a positive amount, like 50 or 12.5.";
74941
75536
  /** The recipient and amount, asked on a terminal when the command line has none. */
74942
75537
  const readSendWords = (to, amount) => Effect.gen(function* () {
74943
75538
  if (to !== void 0 && amount !== void 0) return {
@@ -75079,7 +75674,7 @@ const keyCommand = Command.make("key", {
75079
75674
  const fundCommand = Command.make("fund", {
75080
75675
  amount: Argument.String("amount").pipe(Argument.withDescription("how much test money, like 100"), Argument.withDefault("100")),
75081
75676
  asset: Flag.String("asset").pipe(Flag.withDescription("which test asset: a symbol your account holds"), Flag.withDefault("USDC")),
75082
- email: Flag.String("email").pipe(Flag.optional)
75677
+ email: Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional)
75083
75678
  }, ({ amount, asset, email }) => runCommand({
75084
75679
  ...metadata$11("dev.fund"),
75085
75680
  network: true
@@ -75213,16 +75808,13 @@ const balanceLines = (positions) => {
75213
75808
  if (positions === null) return ["Balances", " unavailable right now"];
75214
75809
  const held = positions.filter((position) => !/^0*(?:\.0*)?$/u.test(position.balance));
75215
75810
  if (held.length === 0) return ["Balances", " nothing yet"];
75216
- const amounts = held.map((position) => {
75217
- const shown = shownAmount({
75811
+ const amounts = held.map((position) => ({
75812
+ symbol: position.symbol,
75813
+ value: shownNumber({
75218
75814
  asset: position.asset,
75219
75815
  value: position.balance
75220
- });
75221
- return {
75222
- symbol: position.symbol,
75223
- value: shown.slice(0, shown.lastIndexOf(" "))
75224
- };
75225
- });
75816
+ })
75817
+ }));
75226
75818
  const symbolWidth = Math.max(...amounts.map((entry) => entry.symbol.length));
75227
75819
  const valueWidth = Math.max(...amounts.map((entry) => entry.value.length));
75228
75820
  return ["Balances", ...amounts.map((entry) => ` ${terminalText(entry.symbol).padEnd(symbolWidth)} ${entry.value.padStart(valueWidth)}`)];
@@ -75298,7 +75890,7 @@ const readEach = (client, orgId, scope) => {
75298
75890
  needs: inbox === null || paid === null ? null : needsFrom({
75299
75891
  requestsToPay: inbox.items.filter((item) => item.status === "open").map((item) => item.id),
75300
75892
  payrollToApprove: (payroll?.items ?? []).filter((run) => run.status === "draft").map((run) => run.id),
75301
- paymentsToRetry: paid.items.filter((payment) => payment.execution?.retryRequired === true).map((payment) => payment.id)
75893
+ paymentsToRetry: paid.items.filter((payment) => payment.execution?.retryRequired === true && payment.direction !== "in").map((payment) => payment.id)
75302
75894
  }, scope)
75303
75895
  })));
75304
75896
  };
@@ -75332,7 +75924,10 @@ const execute$64 = (config) => Effect.gen(function* () {
75332
75924
  if (client === null) return notSignedIn;
75333
75925
  const named = flagValue(runtime.args ?? [], "org");
75334
75926
  const scope = named === void 0 ? [] : ["--org", named];
75335
- const { balances, needs } = yield* readHome(client, actor.kind === "personal" ? null : actor.id, scope);
75927
+ const { balances, needs } = yield* withWaiting(readHome(client, actor.kind === "personal" ? null : actor.id, scope), {
75928
+ ...runtime,
75929
+ label: "Loading balances and what needs you"
75930
+ });
75336
75931
  const tryArgv = [
75337
75932
  [
75338
75933
  "capxul",
@@ -75386,10 +75981,10 @@ const home = Command.make("home", {}, () => runCommand({
75386
75981
  }, execute$64)).pipe(Command.annotate(CliCommandMetadata, metadata$9), Command.withDescription("home screen: where you are, what needs you"), Command.unlisted);
75387
75982
  //#endregion
75388
75983
  //#region src/commands/document/subcommands/shared.ts
75389
- const documentHashFlag = Flag.String("document-hash");
75390
- const contentHashFlag = Flag.String("content-hash");
75391
- const emailFlag$5 = Flag.String("email").pipe(Flag.optional);
75392
- const outFlag = Flag.String("out");
75984
+ const documentHashFlag = Flag.String("document-hash").pipe(Flag.withDescription("the document's hash"));
75985
+ const contentHashFlag = Flag.String("content-hash").pipe(Flag.withDescription("the hash of its content"));
75986
+ const emailFlag$5 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
75987
+ const outFlag = Flag.String("out").pipe(Flag.withDescription("the new file to write the original bytes to"));
75393
75988
  const names$5 = {
75394
75989
  root: "document",
75395
75990
  get: "document.get",
@@ -75508,7 +76103,7 @@ const command$59 = Command.make("render", {
75508
76103
  documentHash: documentHashFlag,
75509
76104
  contentHash: contentHashFlag,
75510
76105
  email: emailFlag$5,
75511
- outputDirectory: Flag.String("output-dir").pipe(Flag.optional)
76106
+ outputDirectory: Flag.String("output-dir").pipe(Flag.withDescription("where to save the PDF and HTML (default: the documents folder next to CLI settings)"), Flag.optional)
75512
76107
  }, ({ documentHash, contentHash, email, outputDirectory }) => dispatch$4("render", (config) => execute$61(config, {
75513
76108
  kind: "render",
75514
76109
  documentHash,
@@ -75946,8 +76541,9 @@ const invitationGrantLines = (grant) => {
75946
76541
  return [
75947
76542
  ` ${grant.type === "new_budget" ? "New Budget" : "Budget"}: ${terminalText(grant.label)}`,
75948
76543
  ...grant.type === "budget" ? [` Budget ID: ${terminalText(grant.budgetId)}`] : [],
75949
- ` Asset: ${terminalText(grant.policy.asset)}`,
75950
- ` Per-payment cap: ${grant.policy.limit === null ? "no cap" : terminalText(grant.policy.limit)}`,
76544
+ ` Asset: ${terminalText(assetName(grant.policy.asset))}`,
76545
+ ` Per-payment cap: ${grant.policy.limit === null ? "no cap" : `${terminalText(grant.policy.limit)} ${terminalText(assetName(grant.policy.asset))}`}`,
76546
+ ` Refill: ${grant.policy.refill === "monthly" ? "Every 30 days" : "None"}`,
75951
76547
  ` Recipients: ${grant.policy.scope.recipients.type === "anyone" ? "anyone" : grant.policy.scope.recipients.accounts.map(terminalText).join(", ")}`,
75952
76548
  ` Actions: ${grant.policy.scope.actions.join(", ")}`
75953
76549
  ];
@@ -76089,10 +76685,10 @@ const previewLines$1 = (preview, recipient) => [
76089
76685
  ];
76090
76686
  //#endregion
76091
76687
  //#region src/commands/org-invite/subcommands/shared.ts
76092
- const emailFlag$4 = Flag.String("email").pipe(Flag.optional);
76688
+ const emailFlag$4 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
76093
76689
  const invitationIdArgument = Argument.String("id").pipe(Argument.withDescription("The invitation ID"), Argument.optional);
76094
- const timeoutFlag$3 = Flag.Int("timeout-seconds").pipe(Flag.withDefault(120));
76095
- const confirmFlag$3 = Flag.Boolean("confirm").pipe(Flag.withDefault(false));
76690
+ const timeoutFlag$3 = Flag.Int("timeout-seconds").pipe(Flag.withDescription(FLAG_HELP.timeout), Flag.withDefault(120));
76691
+ const confirmFlag$3 = Flag.Boolean("confirm").pipe(Flag.withDescription(FLAG_HELP.confirm), Flag.withDefault(false));
76096
76692
  const target = {
76097
76693
  invitationId: invitationIdArgument,
76098
76694
  email: emailFlag$4
@@ -76129,8 +76725,8 @@ const command$57 = Command.make("invite", {
76129
76725
  permissionIds: Flag.String("permission-id").pipe(Flag.withDescription("Manage-people Permission ID"), Flag.atLeast(0)),
76130
76726
  ...newBudgetFlags,
76131
76727
  email: emailFlag$4,
76132
- requestKey: Flag.String("request-key").pipe(Flag.optional),
76133
- preview: Flag.Boolean("preview").pipe(Flag.withDefault(false)),
76728
+ requestKey: Flag.String("request-key").pipe(Flag.withDescription("a key that makes repeating the same invitation safe"), Flag.optional),
76729
+ preview: Flag.Boolean("preview").pipe(Flag.withDescription("show what would be authorized and write nothing"), Flag.withDefault(false)),
76134
76730
  confirm: confirmFlag$3,
76135
76731
  timeoutSeconds: timeoutFlag$3
76136
76732
  }, ({ email, toEmail, accountId, budgetIds, permissionIds, newBudgetName, asset, limit, unlimited, recipientAccounts, anyRecipient, actions, requestKey, preview, confirm, timeoutSeconds }) => Effect.gen(function* () {
@@ -76396,8 +76992,8 @@ const execute$58 = (config, input) => Effect.gen(function* () {
76396
76992
  /** --expected-offer is a compatibility spelling; conflicting digests refuse. */
76397
76993
  const command$56 = Command.make("accept", {
76398
76994
  ...target,
76399
- offerDigest: Flag.String("offer-digest").pipe(Flag.optional),
76400
- expectedOffer: Flag.String("expected-offer").pipe(Flag.optional),
76995
+ offerDigest: Flag.String("offer-digest").pipe(Flag.withDescription("the digest of the offer you reviewed; scripts must pass it"), Flag.optional),
76996
+ expectedOffer: Flag.String("expected-offer").pipe(Flag.withDescription("the same as --offer-digest, under its older name"), Flag.optional),
76401
76997
  confirm: confirmFlag$3,
76402
76998
  timeoutSeconds: timeoutFlag$3
76403
76999
  }, ({ email, invitationId, offerDigest, expectedOffer, confirm, timeoutSeconds }) => dispatch$3("invite.accept", "own", (config, orgs) => execute$58(config, {
@@ -76527,9 +77123,9 @@ const make$1 = (side) => {
76527
77123
  const name = side === "own" ? "invite.list" : "org.invite.list";
76528
77124
  return Command.make("list", {
76529
77125
  email: emailFlag$4,
76530
- limit: Flag.Int("limit").pipe(Flag.optional),
77126
+ limit: Flag.Int("limit").pipe(Flag.withDescription("how many invitations to return"), Flag.optional),
76531
77127
  cursor: Flag.String("after").pipe(Flag.withDescription("the next page's cursor"), Flag.optional),
76532
- phases: Flag.String("phase").pipe(Flag.atLeast(0))
77128
+ phases: Flag.String("phase").pipe(Flag.withDescription("only invitations in this phase; repeat it for several"), Flag.atLeast(0))
76533
77129
  }, ({ email, limit, cursor, phases }) => dispatch$3(name, side, (config, orgs) => side === "org" && orgs.length === 0 ? Effect.fail(inputRequired("Provide --org.")) : execute$54(config, {
76534
77130
  email: Option.getOrUndefined(email),
76535
77131
  orgs,
@@ -76623,7 +77219,7 @@ const execute$50 = (config, input) => Effect.gen(function* () {
76623
77219
  const key = JSON.stringify(progress);
76624
77220
  if (key === previous) return;
76625
77221
  previous = key;
76626
- await run(writeStderr(runtime.mode === "json" ? `${key}\n` : `Invitation ${terminalText(invitation.id)}: ${terminalText(invitation.phase)}; delivery: ${terminalText(invitation.delivery)}${invitation.recovery === null ? "" : `; recovery: ${terminalText(invitation.recovery.action)} (${terminalText(invitation.recovery.reason)})`}\n`), { signal });
77222
+ await run(writeProgress(runtime, runtime.mode === "json" ? `${key}\n` : `Invitation ${terminalText(invitation.id)}: ${terminalText(invitation.phase)}; delivery: ${terminalText(invitation.delivery)}${invitation.recovery === null ? "" : `; recovery: ${terminalText(invitation.recovery.action)} (${terminalText(invitation.recovery.reason)})`}\n`), { signal });
76627
77223
  });
76628
77224
  }).pipe(Effect.onInterrupt(() => writeInvitationReadRecovery(runtime.mode, resolvedOrg, invitationId, session.email).pipe(Effect.ignoreCause))), {
76629
77225
  ...runtime,
@@ -76675,7 +77271,7 @@ const organizationInvite = Command.make("invite").pipe(annotate$3(CAPXUL_OPERATI
76675
77271
  //#endregion
76676
77272
  //#region src/commands/offer/subcommands/shared.ts
76677
77273
  const idArgument$1 = Argument.String("id").pipe(Argument.withDescription("The offer ID"));
76678
- const revisionFlag = Flag.Int("expected-revision");
77274
+ const revisionFlag = Flag.Int("expected-revision").pipe(Flag.withDescription("the revision you last read, so a change made since is not overwritten"));
76679
77275
  const names$4 = {
76680
77276
  root: "offer",
76681
77277
  list: "offer.list",
@@ -76699,9 +77295,9 @@ const makeContext$1 = () => ({
76699
77295
  });
76700
77296
  //#endregion
76701
77297
  //#region src/commands/request/subcommands/shared.ts
76702
- const emailFlag$3 = Flag.String("email").pipe(Flag.optional);
77298
+ const emailFlag$3 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
76703
77299
  const idArgument = Argument.String("id").pipe(Argument.withDescription("The request ID"));
76704
- const confirmFlag$2 = Flag.Boolean("confirm").pipe(Flag.withDefault(false));
77300
+ const confirmFlag$2 = Flag.Boolean("confirm").pipe(Flag.withDescription(FLAG_HELP.confirm), Flag.withDefault(false));
76705
77301
  const names$3 = {
76706
77302
  "issued.root": "request",
76707
77303
  "issued.issue": "request.issue",
@@ -76920,8 +77516,8 @@ const execute$47 = (config, input) => Effect.gen(function* () {
76920
77516
  //#region src/commands/offer/subcommands/create/command.ts
76921
77517
  const command$45 = (context) => Command.make("create", {
76922
77518
  email: emailFlag$3,
76923
- source: Flag.String("input"),
76924
- requestKey: Flag.String("request-key"),
77519
+ source: Flag.String("input").pipe(Flag.withDescription("the offer JSON, from a file or - for stdin")),
77520
+ requestKey: Flag.String("request-key").pipe(Flag.withDescription("a key that makes repeating the same create safe")),
76925
77521
  confirm: confirmFlag$2
76926
77522
  }, ({ email, source, requestKey, confirm }) => context.dispatch("create", (scope) => ({
76927
77523
  kind: "create",
@@ -76960,7 +77556,7 @@ const command$44 = (context) => Command.make("revise", {
76960
77556
  email: emailFlag$3,
76961
77557
  id: idArgument$1,
76962
77558
  revision: revisionFlag,
76963
- source: Flag.String("input"),
77559
+ source: Flag.String("input").pipe(Flag.withDescription("the offer JSON, from a file or - for stdin")),
76964
77560
  confirm: confirmFlag$2
76965
77561
  }, ({ email, id, revision, source, confirm }) => context.dispatch("revise", (scope) => ({
76966
77562
  kind: "revise",
@@ -77022,15 +77618,16 @@ const makeCommand = () => {
77022
77618
  const offer = makeCommand();
77023
77619
  //#endregion
77024
77620
  //#region src/commands/org/subcommands/permission/subcommands/shared.ts
77025
- const emailFlag$2 = Flag.String("email").pipe(Flag.optional);
77621
+ const emailFlag$2 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
77026
77622
  const permissionIdArgument = Argument.String("id").pipe(Argument.withDescription("The Permission ID"));
77027
- const sourceFlag$2 = Flag.String("input").pipe(Flag.optional);
77028
- const accountIdFlag = Flag.String("account-id").pipe(Flag.optional);
77623
+ const sourceFlag$2 = Flag.String("input").pipe(Flag.withDescription("the Permission JSON, from a file or - for stdin"), Flag.optional);
77624
+ const accountIdFlag = Flag.String("account-id").pipe(Flag.withDescription("the member's Account ID"), Flag.optional);
77029
77625
  const assignmentIdArgument = Argument.String("id").pipe(Argument.withDescription("The Permission assignment ID"), Argument.optional);
77030
- const expectedMemberEmailFlag = Flag.String("expected-member-email").pipe(Flag.optional);
77031
- const requestKeyFlag$2 = Flag.String("request-key").pipe(Flag.between(0, 1), Flag.map((values) => Option.fromUndefinedOr(values[0])));
77032
- const confirmFlag$1 = Flag.Boolean("confirm").pipe(Flag.withDefault(false));
77033
- const timeoutFlag$2 = Flag.Int("timeout-seconds").pipe(Flag.withDefault(120));
77626
+ const expectedMemberEmailFlag = Flag.String("expected-member-email").pipe(Flag.withDescription("the member's email, checked before anything changes"), Flag.optional);
77627
+ const requestKeyFlag$2 = Flag.String("request-key").pipe(Flag.withDescription(FLAG_HELP.requestKey), Flag.between(0, 1), Flag.map((values) => Option.fromUndefinedOr(values[0])));
77628
+ const confirmFlag$1 = Flag.Boolean("confirm").pipe(Flag.withDescription(FLAG_HELP.confirm), Flag.withDefault(false));
77629
+ const waitFlag = Flag.Boolean("wait").pipe(Flag.withDescription("after submitting, follow the Permission command until it is applied, failed or needs action"), Flag.withDefault(false));
77630
+ const timeoutFlag$2 = Flag.Int("timeout-seconds").pipe(Flag.withDescription(FLAG_HELP.timeout), Flag.withDefault(120));
77034
77631
  const names$2 = {
77035
77632
  root: "org.permission",
77036
77633
  list: "org.permission.list",
@@ -77057,7 +77654,7 @@ const dispatch$2 = (kind, execute) => runCommand({
77057
77654
  //#endregion
77058
77655
  //#region src/commands/org/subcommands/permission/internal/write-input.ts
77059
77656
  const LIMIT$2 = 65536;
77060
- const invalid$5 = () => inputRequired("Provide a complete Permission policy with an exact asset, positive cap, recipient rule, and supported actions.");
77657
+ const invalid$5 = () => inputRequired("Provide a complete Permission policy with an exact asset, positive cap, recipient rule, supported actions, and an optional refill (\"monthly\": every 30 days, or \"none\").");
77061
77658
  /** File, stdin, and guided answers use the SDK's complete-policy decoder. */
77062
77659
  const parsePermissionInput = (input) => Effect.try({
77063
77660
  try: () => decodePermissionIntent(input),
@@ -77101,6 +77698,18 @@ const guidePermissionInput = (before) => Effect.gen(function* () {
77101
77698
  message: existing === null ? "Per-payment cap (major units)" : `Per-payment cap (major units; asset fixed: ${asset})`,
77102
77699
  ...existing?.limit == null ? {} : { default: existing.limit }
77103
77700
  });
77701
+ const refill = yield* Prompt.Select({
77702
+ message: "Refill",
77703
+ choices: [{
77704
+ title: "Refills every 30 days",
77705
+ value: "monthly",
77706
+ selected: existing === null || existing.refill === "monthly"
77707
+ }, {
77708
+ title: "Never refills (the cap is one pot)",
77709
+ value: "none",
77710
+ selected: existing !== null && existing.refill !== "monthly"
77711
+ }]
77712
+ });
77104
77713
  const recipients = yield* Prompt.Select({
77105
77714
  message: "Recipient rule",
77106
77715
  choices: [{
@@ -77128,6 +77737,7 @@ const guidePermissionInput = (before) => Effect.gen(function* () {
77128
77737
  asset,
77129
77738
  value
77130
77739
  },
77740
+ refill,
77131
77741
  scope: {
77132
77742
  recipients: recipients === "anyone" ? { type: "anyone" } : {
77133
77743
  type: "allowlist",
@@ -77312,7 +77922,7 @@ const validateWriteInput = (input) => Effect.gen(function* () {
77312
77922
  const selectPolicy = (input, read, orgId, suppliedId, suppliedIntent, interactive) => Effect.gen(function* () {
77313
77923
  const permission = input.kind === "change" ? yield* selectPermission(read?.items ?? [], orgId, suppliedId, interactive) : null;
77314
77924
  const before = permission?.currentPolicy ?? null;
77315
- const intent = suppliedIntent ?? (yield* guidePermissionInput(before));
77925
+ const intent = keepRefillOnChange(suppliedIntent ?? (yield* guidePermissionInput(before)), before);
77316
77926
  if (before?.type === "budget" && intent.type === "budget" && before.asset !== intent.asset) return yield* inputRequired("A Budget change must retain its exact asset.");
77317
77927
  const after = intentPolicy(intent);
77318
77928
  const reset = allowanceReset(before, after);
@@ -77346,7 +77956,7 @@ const samePolicy = (left, right) => {
77346
77956
  if (left === null || right === null) return left === right;
77347
77957
  if (left.type !== right.type) return false;
77348
77958
  if (left.type === "managePeople" || right.type === "managePeople") return true;
77349
- return left.asset === right.asset && cap$1(left.limit) === cap$1(right.limit) && recipients(left) === recipients(right) && [...left.scope.actions].sort().join(",") === [...right.scope.actions].sort().join(",");
77959
+ return left.asset === right.asset && cap$1(left.limit) === cap$1(right.limit) && left.refill === "monthly" === (right.refill === "monthly") && recipients(left) === recipients(right) && [...left.scope.actions].sort().join(",") === [...right.scope.actions].sort().join(",");
77350
77960
  };
77351
77961
  const allowanceReset = (before, after) => before?.type === "budget" && before.limit !== null || before !== null && after.type === "budget" && after.limit !== null;
77352
77962
  const writeFailureMessage = (error, fallback) => {
@@ -77372,14 +77982,14 @@ const selectionPreview = (selection) => {
77372
77982
  } : { assignmentId: selection.assignmentId }
77373
77983
  };
77374
77984
  };
77985
+ /** The first line after a write is submitted. Submission does not prove application. */
77986
+ const submittedLine = (kind) => kind === "assign" ? "Submitted. Assignment is pending application." : kind === "revoke" ? "Submitted. Assignment removal is pending application." : kind === "replace" ? "Submitted. Module replacement is pending application." : "Submitted. The Permission is pending application.";
77375
77987
  //#endregion
77376
77988
  //#region src/commands/org/subcommands/permission/internal/presentation.ts
77377
77989
  const cap = (permission) => {
77378
77990
  if (permission.limitRaw === void 0) return "no cap";
77379
77991
  if (permission.decimals === void 0) return `${terminalText(permission.limitRaw)} raw units (decimals unavailable)`;
77380
- const decimals = permission.decimals;
77381
- const raw = String(permission.limitRaw).padStart(decimals + 1, "0");
77382
- return decimals === 0 ? raw : `${raw.slice(0, -decimals)}.${raw.slice(-decimals)}`;
77992
+ return fromBaseUnits(String(permission.limitRaw), { decimals: permission.decimals });
77383
77993
  };
77384
77994
  const permissionLines = (permission) => [
77385
77995
  `Permission ${terminalText(permission.permissionId)}`,
@@ -77387,7 +77997,7 @@ const permissionLines = (permission) => [
77387
77997
  ` Type: ${permission.type === "budget" ? "Budget" : "Manage people"}`,
77388
77998
  ` State: ${permission.state}`,
77389
77999
  ` Revision: ${permission.revision}`,
77390
- ...permission.type === "budget" ? [` Asset ID: ${permission.assetId === void 0 ? "unavailable" : terminalText(permission.assetId)}`, ` Per-payment cap: ${cap(permission)}`] : [],
78000
+ ...permission.type === "budget" ? [` Asset: ${permission.assetId === void 0 ? "unavailable" : terminalText(assetName(permission.assetId))}`, ` Per-payment cap: ${cap(permission)}`] : [],
77391
78001
  ...permission.currentPolicy === void 0 ? [] : policyLines(" Current policy", permission.currentPolicy)
77392
78002
  ];
77393
78003
  const permissionListLines = (organizationId, result, name) => [
@@ -77429,7 +78039,7 @@ const resolvePermissionScope = (config, email, explicit) => Effect.gen(function*
77429
78039
  });
77430
78040
  let orgId = explicit ?? (yield* readOrganizationSelection(config, session.authUserId));
77431
78041
  if (orgId === null) {
77432
- if (!runtime.tty || runtime.ci || runtime.mode === "json" || organizations.length === 0) return yield* inputRequired("Provide --org, or choose one with: capxul use org <handle>");
78042
+ if (!runtime.tty || runtime.ci || runtime.mode === "json" || organizations.length === 0) return yield* organizationRequired();
77433
78043
  orgId = yield* Prompt.Select({
77434
78044
  message: "Organization",
77435
78045
  choices: organizations.map((org) => ({
@@ -77525,6 +78135,112 @@ const restoreWriteClient = (config, opened, signer, confirmed, refusal) => Effec
77525
78135
  return write;
77526
78136
  });
77527
78137
  //#endregion
78138
+ //#region src/platform/observe-snapshots.ts
78139
+ /**
78140
+ * Follow an SDK subscription until `onValue` finishes it. An abort, a failed
78141
+ * registration, an error snapshot or a failed print settles it too; every
78142
+ * progress line is written before it resolves, and teardown runs once.
78143
+ */
78144
+ const observeSnapshots = (register, operation, onValue) => Effect.gen(function* () {
78145
+ const runtime = yield* CliRuntime;
78146
+ const run = Effect.runPromiseWith(yield* Effect.context());
78147
+ let output = Promise.resolve();
78148
+ return yield* Effect.promise((signal) => new Promise((resolve) => {
78149
+ let closed = false;
78150
+ let unsubscribe;
78151
+ let previous;
78152
+ const finish = (result) => {
78153
+ if (closed) return;
78154
+ closed = true;
78155
+ signal.removeEventListener("abort", abort);
78156
+ unsubscribe?.();
78157
+ output.then(() => resolve(result), (cause) => resolve({
78158
+ ok: false,
78159
+ error: Errors.unknown(cause)
78160
+ }));
78161
+ };
78162
+ const abort = () => finish({
78163
+ ok: false,
78164
+ error: Errors.cancelled({ operation })
78165
+ });
78166
+ signal.addEventListener("abort", abort, { once: true });
78167
+ const report = (progress, human) => {
78168
+ const semantic = JSON.stringify(progress);
78169
+ if (semantic === previous) return;
78170
+ previous = semantic;
78171
+ const text = runtime.mode === "json" ? semantic : human();
78172
+ output = output.then(() => signal.aborted ? void 0 : run(writeProgress(runtime, `${text}\n`), { signal }));
78173
+ output.catch((cause) => finish({
78174
+ ok: false,
78175
+ error: Errors.unknown(cause)
78176
+ }));
78177
+ };
78178
+ const listener = (snapshot) => {
78179
+ if (closed || snapshot.status === "loading") return;
78180
+ if (snapshot.status === "error") return finish({
78181
+ ok: false,
78182
+ error: snapshot.error
78183
+ });
78184
+ onValue(snapshot.value, {
78185
+ finish,
78186
+ report
78187
+ });
78188
+ };
78189
+ register(listener, signal).then((result) => {
78190
+ if (!result.ok) return finish(result);
78191
+ unsubscribe = result.value;
78192
+ if (closed) unsubscribe();
78193
+ }).catch((cause) => finish({
78194
+ ok: false,
78195
+ error: Errors.unknown(cause)
78196
+ }));
78197
+ if (signal.aborted) abort();
78198
+ })).pipe(Effect.onInterrupt(() => Effect.promise(() => output).pipe(Effect.ignoreCause)));
78199
+ });
78200
+ //#endregion
78201
+ //#region src/commands/org/subcommands/permission/internal/observation-input.ts
78202
+ const missing = () => new CliFailure({
78203
+ code: "INVALID_INPUT",
78204
+ outcome: "refused",
78205
+ exitCode: 2,
78206
+ message: "The owned Permission command was not found. Check --org and --request-key."
78207
+ });
78208
+ const matches$1 = (value, orgId, key) => value.orgId === orgId && value.requestKey === key;
78209
+ const mismatch$1 = () => Errors.invalidInput("command", "Permission command response scope does not match");
78210
+ const validateObservationInput = (input) => Effect.gen(function* () {
78211
+ const timeout = yield* readWriteTimeout(input.timeoutSeconds);
78212
+ const email = input.email === void 0 ? void 0 : yield* readEmail(input.email);
78213
+ let rawKey = input.requestKey;
78214
+ if (rawKey !== void 0) {
78215
+ const key = yield* readRequestKey(rawKey);
78216
+ if (rawKey !== key) return yield* inputRequired("Provide one exact --request-key without surrounding whitespace.");
78217
+ }
78218
+ let rawOrg = input.orgs[0];
78219
+ if (input.orgs.length > 1 || rawOrg !== void 0 && /[\p{Cc}\p{Cf}]/u.test(rawOrg)) return yield* inputRequired("Provide one exact --org ID.");
78220
+ if (rawOrg !== void 0) yield* Effect.try({
78221
+ try: () => toOrgId(rawOrg),
78222
+ catch: () => inputRequired("Provide a valid --org ID.")
78223
+ });
78224
+ if (rawOrg === void 0 || rawKey === void 0) {
78225
+ const runtime = yield* CliRuntime;
78226
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* inputRequired(rawOrg === void 0 ? "Provide one exact --org ID." : "Provide --request-key.");
78227
+ if (rawOrg === void 0) rawOrg = yield* Prompt.String({ message: "Exact Organization ID (--org)" });
78228
+ }
78229
+ const orgId = yield* Effect.try({
78230
+ try: () => toOrgId(rawOrg),
78231
+ catch: () => inputRequired("Provide a valid --org ID.")
78232
+ });
78233
+ if (rawKey === void 0) rawKey = yield* Prompt.String({ message: "Original request key (--request-key)" });
78234
+ const key = yield* readRequestKey(rawKey);
78235
+ if (rawKey !== key) return yield* inputRequired("Provide one exact --request-key without surrounding whitespace.");
78236
+ return {
78237
+ orgId,
78238
+ key,
78239
+ timeout,
78240
+ email
78241
+ };
78242
+ });
78243
+ //#endregion
77528
78244
  //#region src/commands/org/subcommands/permission/internal/command-recovery.ts
77529
78245
  const commandRecovery = (orgId, key, email, commandId) => {
77530
78246
  const args = [
@@ -77573,6 +78289,112 @@ const commandLines = (view) => [
77573
78289
  "Verified receipts",
77574
78290
  ...view.verifiedReceipts.length === 0 ? [" None recorded."] : view.verifiedReceipts.map((receipt) => ` ${terminalText(receipt.transactionHash)}; block ${receipt.blockNumber}; log ${receipt.logIndex}`)
77575
78291
  ];
78292
+ /** Where `--wait` ended: only an applied command changed the Permission. */
78293
+ const endedLine = (view) => view.status === "applied" ? "Applied." : view.status === "failed" ? "Not applied: the command failed." : view.status === "actionable" ? "Not applied: the command needs action." : "Still pending.";
78294
+ //#endregion
78295
+ //#region src/commands/org/subcommands/permission/internal/command-observation.ts
78296
+ /** The command's state as one progress record: its executions and verified receipts. */
78297
+ const commandProgress = (orgId, key, value) => ({
78298
+ type: "permission.command.state",
78299
+ organizationId: orgId,
78300
+ requestKey: key,
78301
+ commandId: value.commandId,
78302
+ status: value.status,
78303
+ state: value.state,
78304
+ executions: value.executions.map((execution) => ({
78305
+ executionId: execution.executionId,
78306
+ state: execution.state,
78307
+ ...execution.userOpHash === void 0 ? {} : { userOpHash: execution.userOpHash }
78308
+ })),
78309
+ verifiedReceipts: value.verifiedReceipts.map((receipt) => ({
78310
+ transactionHash: receipt.transactionHash,
78311
+ blockNumber: receipt.blockNumber,
78312
+ logIndex: receipt.logIndex
78313
+ }))
78314
+ });
78315
+ const commandProgressLines = (value, progress) => [
78316
+ `Permission command ${terminalText(value.commandId)}: ${value.status}; recorded state ${value.state}`,
78317
+ ...progress.executions.map((execution) => ` Execution ${terminalText(execution.executionId)}: ${execution.state}${execution.userOpHash === void 0 ? "" : `; user operation ${terminalText(execution.userOpHash)}`}`),
78318
+ ...progress.verifiedReceipts.map((receipt) => ` Verified receipt: ${terminalText(receipt.transactionHash)}; block ${receipt.blockNumber}; log ${receipt.logIndex}`)
78319
+ ].join("\n");
78320
+ /** The SDK supplies fresh authorized snapshots; one finish path owns teardown. */
78321
+ const observeCommand = (register, orgId, key, seen) => Effect.suspend(() => {
78322
+ let observedId;
78323
+ return observeSnapshots(register, CAPXUL_OPERATIONS.org.permissions.subscribeCommand, (value, { finish, report }) => {
78324
+ if (value === null) return finish({
78325
+ ok: true,
78326
+ value: null
78327
+ });
78328
+ if (!matches$1(value, orgId, key) || observedId !== void 0 && observedId !== value.commandId) return finish({
78329
+ ok: false,
78330
+ error: mismatch$1()
78331
+ });
78332
+ observedId = value.commandId;
78333
+ seen(value);
78334
+ const progress = commandProgress(orgId, key, value);
78335
+ report(progress, () => commandProgressLines(value, progress));
78336
+ if (value.status === "applied" || value.status === "failed" || value.status === "actionable") finish({
78337
+ ok: true,
78338
+ value
78339
+ });
78340
+ });
78341
+ }).pipe(Effect.flatMap(resultValue));
78342
+ /** Follow the command with fresh SDK snapshots until it is applied, failed or actionable. */
78343
+ const followCommand = (methods, orgId, key, seen) => observeCommand((listener, signal) => methods.subscribeCommand(key, listener, { signal }), orgId, key, seen);
78344
+ /** Historical reads never select current Organization access or compose a signer. */
78345
+ const observeValidatedCommand = (config, input, validated, read) => Effect.gen(function* () {
78346
+ const { orgId, key, timeout } = validated;
78347
+ let email = validated.email;
78348
+ let commandId;
78349
+ const runtime = yield* CliRuntime;
78350
+ const guarded = (work) => work.pipe(Effect.mapError((error) => {
78351
+ const failure = classifyFailure(Cause.fail(error), input.kind === "commandGet" ? "org.permission.command.get" : "org.permission.command.wait");
78352
+ const known = commandRecovery(orgId, key, email, commandId);
78353
+ return new CliFailure({
78354
+ ...failure,
78355
+ details: {
78356
+ ...failure.details,
78357
+ ...known.details
78358
+ },
78359
+ message: `${isCapxulError(error) && error.details?.field === "command" ? "The Permission command response could not be verified." : failure.message}\n${known.lines}`
78360
+ });
78361
+ }), Effect.onInterrupt(() => {
78362
+ const known = commandRecovery(orgId, key, email, commandId);
78363
+ return writeStderr(runtime.mode === "json" ? `${JSON.stringify({
78364
+ type: "permission.command.recovery",
78365
+ ...known.details
78366
+ })}\n` : `${known.lines}\n`).pipe(Effect.ignoreCause);
78367
+ }));
78368
+ const restored = yield* guarded(restoreAuthenticatedClient(config, email, { requirement: "none" }));
78369
+ email = restored.session.email;
78370
+ const result = yield* guarded(Effect.gen(function* () {
78371
+ const reading = read(restored.client.org(orgId).permissions, orgId, key, (value) => {
78372
+ commandId = value.commandId;
78373
+ });
78374
+ const view = yield* input.kind === "commandWait" ? withWaiting(reading, {
78375
+ ...runtime,
78376
+ label: "Observe Permission command state"
78377
+ }) : reading;
78378
+ if (view === null) return yield* missing();
78379
+ if (!matches$1(view, orgId, key)) return yield* Effect.fail(mismatch$1());
78380
+ commandId = view.commandId;
78381
+ return view;
78382
+ })).pipe(Effect.timeoutOption(timeout * 1e3));
78383
+ if (Option.isNone(result)) {
78384
+ const known = commandRecovery(orgId, key, email, commandId);
78385
+ return yield* new CliFailure({
78386
+ code: "CLI_DEADLINE",
78387
+ outcome: "failed",
78388
+ exitCode: 5,
78389
+ details: known.details,
78390
+ message: `The CLI stopped waiting. Permission application is not confirmed.\n${known.lines}`
78391
+ });
78392
+ }
78393
+ return {
78394
+ data: { command: result.value },
78395
+ lines: commandLines(result.value)
78396
+ };
78397
+ });
77576
78398
  //#endregion
77577
78399
  //#region src/commands/org/subcommands/permission/internal/write-target.ts
77578
78400
  /** Read what the confirmed write targets: the replacement preview, the assignment, or the policy. */
@@ -77634,7 +78456,7 @@ const writeValidatedPermission = (config, input, validated, submit) => Effect.ge
77634
78456
  lines,
77635
78457
  question: selection.kind === "replace" ? "Replace this module with this complete authority scope?" : selection.kind === "assign" ? "Assign this Permission to this Account?" : selection.kind === "revoke" ? "Revoke only this assignment?" : "Confirm this complete policy?"
77636
78458
  });
77637
- else if (runtime.mode !== "json") yield* writeStderr(`${lines.join("\n")}\n`);
78459
+ else if (runtime.mode !== "json") yield* writeProgress(runtime, `${lines.join("\n")}\n`);
77638
78460
  else yield* writeStderr(`${JSON.stringify({
77639
78461
  type: "permission.preview",
77640
78462
  organizationId: orgId,
@@ -77706,19 +78528,40 @@ const writeValidatedPermission = (config, input, validated, submit) => Effect.ge
77706
78528
  details: recovery(),
77707
78529
  message: `The CLI stopped waiting. Permission application is not confirmed.\n${recoveryText()}`
77708
78530
  });
77709
- return {
78531
+ const execution = result.value;
78532
+ if (input.wait !== true) return {
77710
78533
  data: {
77711
- execution: result.value,
78534
+ execution,
77712
78535
  requestKey: key
77713
78536
  },
77714
78537
  lines: [
77715
- selection.kind === "assign" ? "Submitted. Assignment is pending application." : selection.kind === "revoke" ? "Submitted. Assignment removal is pending application." : selection.kind === "replace" ? "Submitted. Module replacement is pending application." : "Submitted. The Permission is pending application.",
77716
- `Command: ${terminalText(result.value.commandId)}`,
77717
- `Execution: ${terminalText(result.value.executionId)}`,
78538
+ submittedLine(selection.kind),
78539
+ `Command: ${terminalText(execution.commandId)}`,
78540
+ `Execution: ${terminalText(execution.executionId)}`,
77718
78541
  `Request key: ${key}`,
77719
78542
  commandRecovery(orgId, key, preview.session.email).lines
77720
78543
  ]
77721
78544
  };
78545
+ const followed = yield* observeValidatedCommand(config, {
78546
+ kind: "commandWait",
78547
+ orgs: [orgId],
78548
+ email: preview.session.email,
78549
+ requestKey: key,
78550
+ timeoutSeconds: input.timeoutSeconds
78551
+ }, {
78552
+ orgId,
78553
+ key,
78554
+ timeout,
78555
+ email: preview.session.email
78556
+ }, followCommand);
78557
+ return {
78558
+ data: {
78559
+ execution,
78560
+ requestKey: key,
78561
+ command: followed.data.command
78562
+ },
78563
+ lines: [endedLine(followed.data.command), ...commandLines(followed.data.command)]
78564
+ };
77722
78565
  });
77723
78566
  //#endregion
77724
78567
  //#region src/commands/org/subcommands/permission/subcommands/create/execute.ts
@@ -77742,14 +78585,16 @@ const command$40 = Command.make("create", {
77742
78585
  input: sourceFlag$2,
77743
78586
  requestKey: requestKeyFlag$2,
77744
78587
  confirm: confirmFlag$1,
78588
+ wait: waitFlag,
77745
78589
  timeoutSeconds: timeoutFlag$2
77746
- }, ({ email, input, requestKey, confirm, timeoutSeconds }) => dispatch$2("create", (config, orgs) => execute$42(config, {
78590
+ }, ({ email, input, requestKey, confirm, wait, timeoutSeconds }) => dispatch$2("create", (config, orgs) => execute$42(config, {
77747
78591
  kind: "create",
77748
78592
  orgs,
77749
78593
  email: Option.getOrUndefined(email),
77750
78594
  source: Option.getOrUndefined(input),
77751
78595
  requestKey: Option.getOrUndefined(requestKey),
77752
78596
  confirm,
78597
+ wait,
77753
78598
  timeoutSeconds
77754
78599
  }))).pipe(annotate$2("create"), Command.withDescription("Create a Permission from its complete policy"));
77755
78600
  //#endregion
@@ -77776,8 +78621,9 @@ const command$39 = Command.make("change", {
77776
78621
  input: sourceFlag$2,
77777
78622
  requestKey: requestKeyFlag$2,
77778
78623
  confirm: confirmFlag$1,
78624
+ wait: waitFlag,
77779
78625
  timeoutSeconds: timeoutFlag$2
77780
- }, ({ email, permissionId, input, requestKey, confirm, timeoutSeconds }) => dispatch$2("change", (config, orgs) => execute$41(config, {
78626
+ }, ({ email, permissionId, input, requestKey, confirm, wait, timeoutSeconds }) => dispatch$2("change", (config, orgs) => execute$41(config, {
77781
78627
  kind: "change",
77782
78628
  orgs,
77783
78629
  email: Option.getOrUndefined(email),
@@ -77785,6 +78631,7 @@ const command$39 = Command.make("change", {
77785
78631
  source: Option.getOrUndefined(input),
77786
78632
  requestKey: Option.getOrUndefined(requestKey),
77787
78633
  confirm,
78634
+ wait,
77788
78635
  timeoutSeconds
77789
78636
  }))).pipe(annotate$2("change"), Command.withDescription("Replace one active Permission's complete policy"));
77790
78637
  //#endregion
@@ -77813,8 +78660,9 @@ const command$38 = Command.make("assign", {
77813
78660
  expectedMemberEmail: expectedMemberEmailFlag,
77814
78661
  requestKey: requestKeyFlag$2,
77815
78662
  confirm: confirmFlag$1,
78663
+ wait: waitFlag,
77816
78664
  timeoutSeconds: timeoutFlag$2
77817
- }, ({ email, permissionId, accountId, expectedMemberEmail, requestKey, confirm, timeoutSeconds }) => dispatch$2("assign", (config, orgs) => execute$40(config, {
78665
+ }, ({ email, permissionId, accountId, expectedMemberEmail, requestKey, confirm, wait, timeoutSeconds }) => dispatch$2("assign", (config, orgs) => execute$40(config, {
77818
78666
  kind: "assign",
77819
78667
  orgs,
77820
78668
  email: Option.getOrUndefined(email),
@@ -77823,6 +78671,7 @@ const command$38 = Command.make("assign", {
77823
78671
  expectedMemberEmail: Option.getOrUndefined(expectedMemberEmail),
77824
78672
  requestKey: Option.getOrUndefined(requestKey),
77825
78673
  confirm,
78674
+ wait,
77826
78675
  timeoutSeconds
77827
78676
  }))).pipe(annotate$2("assign"), Command.withDescription("Assign one active Permission to one exact Account"));
77828
78677
  //#endregion
@@ -77847,14 +78696,16 @@ const command$37 = Command.make("revoke", {
77847
78696
  assignmentId: assignmentIdArgument,
77848
78697
  requestKey: requestKeyFlag$2,
77849
78698
  confirm: confirmFlag$1,
78699
+ wait: waitFlag,
77850
78700
  timeoutSeconds: timeoutFlag$2
77851
- }, ({ email, assignmentId, requestKey, confirm, timeoutSeconds }) => dispatch$2("revoke", (config, orgs) => execute$39(config, {
78701
+ }, ({ email, assignmentId, requestKey, confirm, wait, timeoutSeconds }) => dispatch$2("revoke", (config, orgs) => execute$39(config, {
77852
78702
  kind: "revoke",
77853
78703
  orgs,
77854
78704
  email: Option.getOrUndefined(email),
77855
78705
  assignmentId: Option.getOrUndefined(assignmentId),
77856
78706
  requestKey: Option.getOrUndefined(requestKey),
77857
78707
  confirm,
78708
+ wait,
77858
78709
  timeoutSeconds
77859
78710
  }))).pipe(annotate$2("revoke"), Command.withDescription("Revoke one exact active Permission assignment"));
77860
78711
  //#endregion
@@ -77878,224 +78729,18 @@ const command$36 = Command.make("replace", {
77878
78729
  email: emailFlag$2,
77879
78730
  requestKey: requestKeyFlag$2,
77880
78731
  confirm: confirmFlag$1,
78732
+ wait: waitFlag,
77881
78733
  timeoutSeconds: timeoutFlag$2
77882
- }, ({ email, requestKey, confirm, timeoutSeconds }) => dispatch$2("replace", (config, orgs) => execute$38(config, {
78734
+ }, ({ email, requestKey, confirm, wait, timeoutSeconds }) => dispatch$2("replace", (config, orgs) => execute$38(config, {
77883
78735
  kind: "replace",
77884
78736
  orgs,
77885
78737
  email: Option.getOrUndefined(email),
77886
78738
  requestKey: Option.getOrUndefined(requestKey),
77887
78739
  confirm,
78740
+ wait,
77888
78741
  timeoutSeconds
77889
78742
  }))).pipe(annotate$2("replace"), Command.withDescription("Replace the current Roles module with its complete authority scope"));
77890
78743
  //#endregion
77891
- //#region src/platform/observe-snapshots.ts
77892
- /**
77893
- * Follow an SDK subscription until `onValue` finishes it. An abort, a failed
77894
- * registration, an error snapshot or a failed print settles it too; every
77895
- * progress line is written before it resolves, and teardown runs once.
77896
- */
77897
- const observeSnapshots = (register, operation, onValue) => Effect.gen(function* () {
77898
- const runtime = yield* CliRuntime;
77899
- const run = Effect.runPromiseWith(yield* Effect.context());
77900
- let output = Promise.resolve();
77901
- return yield* Effect.promise((signal) => new Promise((resolve) => {
77902
- let closed = false;
77903
- let unsubscribe;
77904
- let previous;
77905
- const finish = (result) => {
77906
- if (closed) return;
77907
- closed = true;
77908
- signal.removeEventListener("abort", abort);
77909
- unsubscribe?.();
77910
- output.then(() => resolve(result), (cause) => resolve({
77911
- ok: false,
77912
- error: Errors.unknown(cause)
77913
- }));
77914
- };
77915
- const abort = () => finish({
77916
- ok: false,
77917
- error: Errors.cancelled({ operation })
77918
- });
77919
- signal.addEventListener("abort", abort, { once: true });
77920
- const report = (progress, human) => {
77921
- const semantic = JSON.stringify(progress);
77922
- if (semantic === previous) return;
77923
- previous = semantic;
77924
- const text = runtime.mode === "json" ? semantic : human();
77925
- output = output.then(() => signal.aborted ? void 0 : run(writeStderr(`${text}\n`), { signal }));
77926
- output.catch((cause) => finish({
77927
- ok: false,
77928
- error: Errors.unknown(cause)
77929
- }));
77930
- };
77931
- const listener = (snapshot) => {
77932
- if (closed || snapshot.status === "loading") return;
77933
- if (snapshot.status === "error") return finish({
77934
- ok: false,
77935
- error: snapshot.error
77936
- });
77937
- onValue(snapshot.value, {
77938
- finish,
77939
- report
77940
- });
77941
- };
77942
- register(listener, signal).then((result) => {
77943
- if (!result.ok) return finish(result);
77944
- unsubscribe = result.value;
77945
- if (closed) unsubscribe();
77946
- }).catch((cause) => finish({
77947
- ok: false,
77948
- error: Errors.unknown(cause)
77949
- }));
77950
- if (signal.aborted) abort();
77951
- })).pipe(Effect.onInterrupt(() => Effect.promise(() => output).pipe(Effect.ignoreCause)));
77952
- });
77953
- //#endregion
77954
- //#region src/commands/org/subcommands/permission/internal/observation-input.ts
77955
- const missing = () => new CliFailure({
77956
- code: "INVALID_INPUT",
77957
- outcome: "refused",
77958
- exitCode: 2,
77959
- message: "The owned Permission command was not found. Check --org and --request-key."
77960
- });
77961
- const matches$1 = (value, orgId, key) => value.orgId === orgId && value.requestKey === key;
77962
- const mismatch$1 = () => Errors.invalidInput("command", "Permission command response scope does not match");
77963
- const validateObservationInput = (input) => Effect.gen(function* () {
77964
- const timeout = yield* readWriteTimeout(input.timeoutSeconds);
77965
- const email = input.email === void 0 ? void 0 : yield* readEmail(input.email);
77966
- let rawKey = input.requestKey;
77967
- if (rawKey !== void 0) {
77968
- const key = yield* readRequestKey(rawKey);
77969
- if (rawKey !== key) return yield* inputRequired("Provide one exact --request-key without surrounding whitespace.");
77970
- }
77971
- let rawOrg = input.orgs[0];
77972
- if (input.orgs.length > 1 || rawOrg !== void 0 && /[\p{Cc}\p{Cf}]/u.test(rawOrg)) return yield* inputRequired("Provide one exact --org ID.");
77973
- if (rawOrg !== void 0) yield* Effect.try({
77974
- try: () => toOrgId(rawOrg),
77975
- catch: () => inputRequired("Provide a valid --org ID.")
77976
- });
77977
- if (rawOrg === void 0 || rawKey === void 0) {
77978
- const runtime = yield* CliRuntime;
77979
- if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* inputRequired(rawOrg === void 0 ? "Provide one exact --org ID." : "Provide --request-key.");
77980
- if (rawOrg === void 0) rawOrg = yield* Prompt.String({ message: "Exact Organization ID (--org)" });
77981
- }
77982
- const orgId = yield* Effect.try({
77983
- try: () => toOrgId(rawOrg),
77984
- catch: () => inputRequired("Provide a valid --org ID.")
77985
- });
77986
- if (rawKey === void 0) rawKey = yield* Prompt.String({ message: "Original request key (--request-key)" });
77987
- const key = yield* readRequestKey(rawKey);
77988
- if (rawKey !== key) return yield* inputRequired("Provide one exact --request-key without surrounding whitespace.");
77989
- return {
77990
- orgId,
77991
- key,
77992
- timeout,
77993
- email
77994
- };
77995
- });
77996
- //#endregion
77997
- //#region src/commands/org/subcommands/permission/internal/command-observation.ts
77998
- /** The command's state as one progress record: its executions and verified receipts. */
77999
- const commandProgress = (orgId, key, value) => ({
78000
- type: "permission.command.state",
78001
- organizationId: orgId,
78002
- requestKey: key,
78003
- commandId: value.commandId,
78004
- status: value.status,
78005
- state: value.state,
78006
- executions: value.executions.map((execution) => ({
78007
- executionId: execution.executionId,
78008
- state: execution.state,
78009
- ...execution.userOpHash === void 0 ? {} : { userOpHash: execution.userOpHash }
78010
- })),
78011
- verifiedReceipts: value.verifiedReceipts.map((receipt) => ({
78012
- transactionHash: receipt.transactionHash,
78013
- blockNumber: receipt.blockNumber,
78014
- logIndex: receipt.logIndex
78015
- }))
78016
- });
78017
- const commandProgressLines = (value, progress) => [
78018
- `Permission command ${terminalText(value.commandId)}: ${value.status}; recorded state ${value.state}`,
78019
- ...progress.executions.map((execution) => ` Execution ${terminalText(execution.executionId)}: ${execution.state}${execution.userOpHash === void 0 ? "" : `; user operation ${terminalText(execution.userOpHash)}`}`),
78020
- ...progress.verifiedReceipts.map((receipt) => ` Verified receipt: ${terminalText(receipt.transactionHash)}; block ${receipt.blockNumber}; log ${receipt.logIndex}`)
78021
- ].join("\n");
78022
- /** The SDK supplies fresh authorized snapshots; one finish path owns teardown. */
78023
- const observeCommand = (register, orgId, key, seen) => Effect.suspend(() => {
78024
- let observedId;
78025
- return observeSnapshots(register, CAPXUL_OPERATIONS.org.permissions.subscribeCommand, (value, { finish, report }) => {
78026
- if (value === null) return finish({
78027
- ok: true,
78028
- value: null
78029
- });
78030
- if (!matches$1(value, orgId, key) || observedId !== void 0 && observedId !== value.commandId) return finish({
78031
- ok: false,
78032
- error: mismatch$1()
78033
- });
78034
- observedId = value.commandId;
78035
- seen(value);
78036
- const progress = commandProgress(orgId, key, value);
78037
- report(progress, () => commandProgressLines(value, progress));
78038
- if (value.status === "applied" || value.status === "failed" || value.status === "actionable") finish({
78039
- ok: true,
78040
- value
78041
- });
78042
- });
78043
- }).pipe(Effect.flatMap(resultValue));
78044
- /** Historical reads never select current Organization access or compose a signer. */
78045
- const observeValidatedCommand = (config, input, validated, read) => Effect.gen(function* () {
78046
- const { orgId, key, timeout } = validated;
78047
- let email = validated.email;
78048
- let commandId;
78049
- const runtime = yield* CliRuntime;
78050
- const result = yield* Effect.gen(function* () {
78051
- const restored = yield* restoreAuthenticatedClient(config, email, { requirement: "none" });
78052
- email = restored.session.email;
78053
- const methods = restored.client.org(orgId).permissions;
78054
- const reading = read(methods, orgId, key, (value) => {
78055
- commandId = value.commandId;
78056
- });
78057
- const view = yield* input.kind === "commandWait" ? withWaiting(reading, {
78058
- ...runtime,
78059
- label: "Observe Permission command state"
78060
- }) : reading;
78061
- if (view === null) return yield* missing();
78062
- if (!matches$1(view, orgId, key)) return yield* Effect.fail(mismatch$1());
78063
- commandId = view.commandId;
78064
- return view;
78065
- }).pipe(Effect.mapError((error) => {
78066
- const failure = classifyFailure(Cause.fail(error), input.kind === "commandGet" ? "org.permission.command.get" : "org.permission.command.wait");
78067
- const known = commandRecovery(orgId, key, email, commandId);
78068
- return new CliFailure({
78069
- ...failure,
78070
- details: {
78071
- ...failure.details,
78072
- ...known.details
78073
- },
78074
- message: `${isCapxulError(error) && error.details?.field === "command" ? "The Permission command response could not be verified." : failure.message}\n${known.lines}`
78075
- });
78076
- }), Effect.onInterrupt(() => {
78077
- const known = commandRecovery(orgId, key, email, commandId);
78078
- return writeStderr(runtime.mode === "json" ? `${JSON.stringify({
78079
- type: "permission.command.recovery",
78080
- ...known.details
78081
- })}\n` : `${known.lines}\n`).pipe(Effect.ignoreCause);
78082
- }), Effect.timeoutOption(timeout * 1e3));
78083
- if (Option.isNone(result)) {
78084
- const known = commandRecovery(orgId, key, email, commandId);
78085
- return yield* new CliFailure({
78086
- code: "CLI_DEADLINE",
78087
- outcome: "failed",
78088
- exitCode: 5,
78089
- details: known.details,
78090
- message: `The CLI stopped waiting. Permission application is not confirmed.\n${known.lines}`
78091
- });
78092
- }
78093
- return {
78094
- data: { command: result.value },
78095
- lines: commandLines(result.value)
78096
- };
78097
- });
78098
- //#endregion
78099
78744
  //#region src/commands/org/subcommands/permission/subcommands/command/subcommands/get/execute.ts
78100
78745
  const execute$37 = (config, input) => Effect.gen(function* () {
78101
78746
  const validated = yield* validateObservationInput(input);
@@ -78118,7 +78763,7 @@ const command$35 = Command.make("get", {
78118
78763
  //#region src/commands/org/subcommands/permission/subcommands/command/subcommands/wait/execute.ts
78119
78764
  const execute$36 = (config, input) => Effect.gen(function* () {
78120
78765
  const validated = yield* validateObservationInput(input);
78121
- return yield* observeValidatedCommand(config, input, validated, (methods, orgId, key, seen) => observeCommand((listener, signal) => methods.subscribeCommand(key, listener, { signal }), orgId, key, seen));
78766
+ return yield* observeValidatedCommand(config, input, validated, followCommand);
78122
78767
  });
78123
78768
  //#endregion
78124
78769
  //#region src/commands/org/subcommands/permission/subcommands/command/subcommands/wait/command.ts
@@ -78150,14 +78795,14 @@ const permission = Command.make("permission").pipe(annotate$2("root"), Command.w
78150
78795
  ]));
78151
78796
  //#endregion
78152
78797
  //#region src/commands/org/subcommands/payroll/subcommands/shared.ts
78153
- const emailFlag$1 = Flag.String("email").pipe(Flag.optional);
78154
- const sourceFlag$1 = Flag.String("input").pipe(Flag.atLeast(0));
78798
+ const emailFlag$1 = Flag.String("email").pipe(Flag.withDescription(FLAG_HELP.email), Flag.optional);
78799
+ const sourceFlag$1 = Flag.String("input").pipe(Flag.withDescription(FLAG_HELP.input), Flag.atLeast(0));
78155
78800
  const groupIdArgument = Argument.String("id").pipe(Argument.withDescription("The payroll group ID"), Argument.optional, Argument.map(Option.toArray));
78156
78801
  const runIdArgument = Argument.String("id").pipe(Argument.withDescription("The Payroll run ID"), Argument.optional, Argument.map(Option.toArray));
78157
- const requestKeyFlag$1 = Flag.String("request-key").pipe(Flag.atLeast(0));
78158
- const resumeFlag = Flag.String("resume").pipe(Flag.atLeast(0));
78159
- const confirmFlag = Flag.Boolean("confirm").pipe(Flag.withDefault(false));
78160
- const timeoutFlag$1 = Flag.Int("timeout-seconds").pipe(Flag.withDefault(120));
78802
+ const requestKeyFlag$1 = Flag.String("request-key").pipe(Flag.withDescription("a key that makes repeating the same run safe"), Flag.atLeast(0));
78803
+ const resumeFlag = Flag.String("resume").pipe(Flag.withDescription("prepare a saved run again, by its request key"), Flag.atLeast(0));
78804
+ const confirmFlag = Flag.Boolean("confirm").pipe(Flag.withDescription(FLAG_HELP.confirm), Flag.withDefault(false));
78805
+ const timeoutFlag$1 = Flag.Int("timeout-seconds").pipe(Flag.withDescription(FLAG_HELP.timeout), Flag.withDefault(120));
78161
78806
  const names$1 = {
78162
78807
  root: "org.payroll",
78163
78808
  groups: "org.payroll.groups",
@@ -78196,7 +78841,7 @@ const selectOrganization = (organizations, selected) => Effect.gen(function* ()
78196
78841
  let orgId = selected;
78197
78842
  if (orgId === null) {
78198
78843
  const runtime = yield* CliRuntime;
78199
- if (!runtime.tty || runtime.ci || runtime.mode === "json" || organizations.length === 0) return yield* inputRequired("Provide --org, or choose one with: capxul use org <handle>");
78844
+ if (!runtime.tty || runtime.ci || runtime.mode === "json" || organizations.length === 0) return yield* organizationRequired();
78200
78845
  orgId = yield* Prompt.Select({
78201
78846
  message: "Organization",
78202
78847
  choices: organizations.map((org) => ({
@@ -78382,10 +79027,10 @@ const runLines$1 = (value) => [
78382
79027
  ...value.items.length === 0 ? ["No Payroll runs found."] : value.items.flatMap((run) => [
78383
79028
  `Run: ${terminalText(run.id)} (${terminalText(run.status)})`,
78384
79029
  ` Period (epoch ms): ${run.periodStart} to ${run.periodEnd}; recipients: ${run.recipientCount}`,
78385
- ` Total: ${terminalText(run.total.value)} ${terminalText(run.total.asset)}`
79030
+ ` Total: ${terminalText(shownNumber(run.total))} ${terminalText(assetName(run.total.asset))}`
78386
79031
  ]),
78387
79032
  "Settled this month (backend totals)",
78388
- ...value.settledThisMonth.length === 0 ? [" No settled asset totals."] : value.settledThisMonth.map((total) => ` ${terminalText(total.value)} ${terminalText(total.asset)}`)
79033
+ ...value.settledThisMonth.length === 0 ? [" No settled asset totals."] : value.settledThisMonth.map((total) => ` ${terminalText(shownNumber(total))} ${terminalText(assetName(total.asset))}`)
78389
79034
  ];
78390
79035
  //#endregion
78391
79036
  //#region src/commands/org/subcommands/payroll/subcommands/groups/subcommands/list/execute.ts
@@ -78756,7 +79401,7 @@ const guideRunInput = (budgets, groups, terms, contacts) => Effect.gen(function*
78756
79401
  const asset = yield* Prompt.Select({
78757
79402
  message: "Settlement asset",
78758
79403
  choices: assets.map((value) => ({
78759
- title: `${terminalText(requireAsset(value).symbol)} (${terminalText(value)})`,
79404
+ title: terminalText(assetName(value)),
78760
79405
  value
78761
79406
  }))
78762
79407
  });
@@ -78843,6 +79488,47 @@ const guideRunInput = (budgets, groups, terms, contacts) => Effect.gen(function*
78843
79488
  });
78844
79489
  });
78845
79490
  //#endregion
79491
+ //#region src/commands/org/subcommands/payroll/internal/run-lookup.ts
79492
+ const isPayrollRunId = (value) => /^payroll_run_[0-9a-f]{64}$/u.test(value);
79493
+ const exactRunId = (value) => isPayrollRunId(value) ? Effect.succeed(value) : Effect.fail(inputRequired("Provide the Payroll run ID: payroll_run_ followed by a 64-character digest."));
79494
+ const validateRunInput = (input) => Effect.gen(function* () {
79495
+ const timeout = yield* readWriteTimeout(input.timeoutSeconds);
79496
+ const email = input.email === void 0 ? void 0 : yield* readEmail(input.email);
79497
+ if (input.orgs.length > 1) return yield* inputRequired("Provide one exact --org ID.");
79498
+ const orgId = yield* explicitOrganization(input.orgs);
79499
+ if (input.runIds.length > 1) return yield* inputRequired("Provide one exact Payroll run ID.");
79500
+ let runId = input.runIds[0];
79501
+ if (runId !== void 0) yield* exactRunId(runId);
79502
+ const runtime = yield* CliRuntime;
79503
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") {
79504
+ if (orgId === null) return yield* inputRequired("Provide one exact --org ID.");
79505
+ if (runId === void 0) return yield* inputRequired("Provide the Payroll run ID.");
79506
+ }
79507
+ if (runId === void 0) runId = yield* Prompt.String({ message: "Payroll run ID" });
79508
+ return {
79509
+ orgId,
79510
+ runId: yield* exactRunId(runId),
79511
+ timeout,
79512
+ email
79513
+ };
79514
+ });
79515
+ const missingRun = () => new CliFailure({
79516
+ code: "INVALID_INPUT",
79517
+ outcome: "refused",
79518
+ exitCode: 2,
79519
+ message: "The Payroll run was not found or is unavailable to this session. Check --org and the Payroll run ID."
79520
+ });
79521
+ const matches = (detail, orgId, runId) => detail.orgId === orgId && detail.run.id === runId;
79522
+ const mismatch = () => Errors.invalidInput("runId", "Payroll run response scope does not match");
79523
+ const finishedRun = (detail) => detail.items.length > 0 && detail.items.every((item) => paymentPhase(item.payment.status).outcome === "good") || detail.items.some((item) => {
79524
+ const phase = paymentPhase(item.payment.status);
79525
+ return phase.terminal && phase.outcome !== "good" || phase.needsAction || item.payment.execution?.retryRequired === true;
79526
+ }) || [
79527
+ "failed",
79528
+ "mismatch",
79529
+ "unresolved"
79530
+ ].includes(detail.command.verificationState) || detail.command.completionOutcome === "failed" || detail.command.terminalOutcome !== void 0 && detail.command.terminalOutcome !== "settled";
79531
+ //#endregion
78846
79532
  //#region src/commands/org/subcommands/payroll/internal/run-preview.ts
78847
79533
  /** The recovery a Payroll run prints: resume the saved input, or read and wait for the run. */
78848
79534
  const runCommandRecovery = (input) => {
@@ -78896,6 +79582,19 @@ const runCommandRecovery = (input) => {
78896
79582
  ]
78897
79583
  };
78898
79584
  };
79585
+ /**
79586
+ * The backend's refusal for a request key whose run already settled or failed
79587
+ * (#2383): the run to read. Resuming that key returns the same refusal.
79588
+ */
79589
+ const completedRun = (error, key) => {
79590
+ if (!isCapxulError(error) || error.code !== "WRONG_STATE") return void 0;
79591
+ const runId = error.details?.runId;
79592
+ const resolution = error.details?.payrollResolution;
79593
+ return typeof runId === "string" && isPayrollRunId(runId) && error.details?.requestKey === key && (resolution === "settled" || resolution === "failed") ? {
79594
+ runId,
79595
+ resolution
79596
+ } : void 0;
79597
+ };
78899
79598
  /** Each Payroll recipient resolves to its asserted known Party and Safe, never an external address; each item comes back with its target. */
78900
79599
  const resolveRunTargets = (client, items) => Effect.forEach(items, (item) => Effect.promise((signal) => client.targets.resolve(item.to, { signal })).pipe(Effect.flatMap(resultValue), Effect.flatMap((target) => {
78901
79600
  if (target.kind === "external_address" || target.requiresAcknowledgement === true) return Effect.fail(inputRequired("External-address Payroll requires an explicit acknowledgement before sending."));
@@ -78913,14 +79612,14 @@ const runPreviewLines = (input) => {
78913
79612
  `Acting as: ${terminalText(preview.session.email)} (${terminalText(preview.session.authUserId)})`,
78914
79613
  `Organization: ${terminalText(selectedOrg)}`,
78915
79614
  `Budget: ${terminalText(budget.label)} (${terminalText(budget.id)})`,
78916
- `Per-payment cap: ${budget.limit === null ? "unlimited" : terminalText(budget.limit.value)} ${terminalText(requested.asset)}`,
78917
- `Settlement asset: ${terminalText(requested.asset)}`,
79615
+ `Per-payment cap: ${budget.limit === null ? "unlimited" : terminalText(budget.limit.value)} ${terminalText(assetName(requested.asset))}`,
79616
+ `Settlement asset: ${terminalText(assetName(requested.asset))}`,
78918
79617
  `Period (epoch ms): ${requested.period.start} to ${requested.period.end}`,
78919
79618
  `Request key: ${terminalText(key)}`,
78920
79619
  ...employees.flatMap(({ item, target }, index) => [
78921
79620
  `Employee ${index + 1}: ${terminalText(target.label)} (${terminalText(item.partyId)})`,
78922
79621
  ` Recipient: ${terminalText(JSON.stringify(item.to))}; Safe: ${terminalText(target.safeAddress)}`,
78923
- ` Payout quantity: ${terminalText(item.amount.value)} ${terminalText(item.amount.asset)}`,
79622
+ ` Payout quantity: ${terminalText(item.amount.value)} ${terminalText(assetName(item.amount.asset))}`,
78924
79623
  ` Gross: ${terminalText(item.gross)} raw units; net: ${terminalText(item.net)} raw units`,
78925
79624
  ...item.adjustments.map((row) => ` Adjustment: ${terminalText(row.label)}; ${terminalText(row.amount)} raw units`)
78926
79625
  ])
@@ -79085,21 +79784,34 @@ const followApproval = (client, approvalId, options) => Effect.gen(function* ()
79085
79784
  onNone: () => Effect.fail(stillWaiting(approvalId, options)),
79086
79785
  onSome: Effect.succeed
79087
79786
  })));
79088
- /** `Paid 50.00 USDC to Rex · 0x5b2e…91c4`, and where the receipt is. */
79787
+ /** `Paid 50.00 USDC to Rex · 0x5b2e…91c4`, where the receipt is, and how to save it as PDF. */
79089
79788
  const paidLines = (outcome, scope = {}) => outcome.payments.flatMap((payment) => {
79090
79789
  const hash = payment.receipt?.transactionHash;
79091
- return [`Paid ${shownAmount(payment.amount)} to ${terminalText(payment.counterpartyLabel)}${hash === void 0 ? "" : ` · ${hash.slice(0, 6)}…${hash.slice(-4)}`}`, `receipt: ${terminalText([
79092
- "capxul",
79093
- "payment",
79094
- "get",
79095
- payment.id,
79096
- ...scoped(scope)
79097
- ].join(" "))}`];
79790
+ return [
79791
+ `Paid ${shownAmount(payment.amount)} to ${terminalText(payment.counterpartyLabel)}${hash === void 0 ? "" : ` · ${hash.slice(0, 6)}…${hash.slice(-4)}`}`,
79792
+ `receipt: ${terminalText([
79793
+ "capxul",
79794
+ "payment",
79795
+ "get",
79796
+ payment.id,
79797
+ ...scoped(scope)
79798
+ ].join(" "))}`,
79799
+ ...saveDocumentLines(payment)
79800
+ ];
79098
79801
  });
79099
- /** A progress line for a person, on stderr. */
79100
- const progress = (line) => writeStderr(`${line}\n`);
79802
+ /** A progress line for a person, on stderr at a terminal. */
79803
+ const progress = (reader, line) => writeProgress(reader, `${line}\n`);
79804
+ /** What the person must do: shown even when output is piped, like a prompt. */
79805
+ const instruction = (line) => writeStderr(`${line}\n`);
79101
79806
  /** After the approval lapses, the time a send and its settlement may still take. */
79102
79807
  const SETTLE_MS = 12e4;
79808
+ /** How long to follow an approval that lapses at `expiresAt`: until then, plus the settlement. */
79809
+ const untilLapseMs = (expiresAt, now) => Math.max(expiresAt - now, 0) + SETTLE_MS;
79810
+ /** The default wait for an approval ID: as long as `payment send` follows it. */
79811
+ const followUntilLapseMs = (client, approvalId) => Effect.gen(function* () {
79812
+ const approval = yield* read(client, approvalId);
79813
+ return untilLapseMs(approval.expiresAt, yield* Clock.currentTimeMillis);
79814
+ });
79103
79815
  /**
79104
79816
  * Hand a prepared approval over. An agent gets the link and the wait command
79105
79817
  * back at once. A person sees the preview, the approval page opens, and the
@@ -79112,18 +79824,18 @@ const handOver = (client, approval, options) => Effect.gen(function* () {
79112
79824
  data: awaitingApproval(approval, options),
79113
79825
  lines: []
79114
79826
  };
79115
- for (const line of previewLines(approval, options)) yield* progress(line);
79116
- yield* progress("✓ Prepared");
79827
+ for (const line of previewLines(approval, options)) yield* progress(runtime, line);
79828
+ yield* progress(runtime, "✓ Prepared");
79117
79829
  const opened = runtime.tty && !runtime.ci ? yield* openUrl(approval.url) : false;
79118
- yield* progress(`… Approve in your browser${opened ? " (opened)" : ""}`);
79119
- yield* progress(` ${approval.url}`);
79830
+ yield* instruction(`… Approve in your browser${opened ? " (opened)" : ""}`);
79831
+ yield* instruction(` ${approval.url}`);
79120
79832
  const now = yield* Clock.currentTimeMillis;
79121
79833
  const outcome = yield* followApproval(client, approval.approvalId, {
79122
79834
  organizationId: options.organizationId,
79123
- deadlineMs: options.timeoutSeconds === void 0 ? Math.max(approval.expiresAt - now, 0) + SETTLE_MS : options.timeoutSeconds * 1e3,
79124
- onSigned: progress("✓ Signed")
79125
- }).pipe(Effect.onInterrupt(() => progress(`Nothing is sent until you approve. Follow it with: ${waitArgv(approval.approvalId, options).join(" ")}`).pipe(Effect.ignore)));
79126
- yield* progress("✓ Confirmed");
79835
+ deadlineMs: options.timeoutSeconds === void 0 ? untilLapseMs(approval.expiresAt, now) : options.timeoutSeconds * 1e3,
79836
+ onSigned: progress(runtime, "✓ Signed")
79837
+ }).pipe(Effect.onInterrupt(() => instruction(`Nothing is sent until you approve. Follow it with: ${waitArgv(approval.approvalId, options).join(" ")}`).pipe(Effect.ignore)));
79838
+ yield* progress(runtime, "✓ Confirmed");
79127
79839
  return {
79128
79840
  data: outcome,
79129
79841
  lines: paidLines(outcome, options)
@@ -79146,13 +79858,18 @@ const execute$30 = (config, input) => Effect.gen(function* () {
79146
79858
  runId: void 0,
79147
79859
  canResume: saved
79148
79860
  });
79149
- const notice = (type) => {
79861
+ const noticeText = (type) => {
79150
79862
  const known = recovery();
79151
- return writeStderr(runtime.mode === "json" ? `${JSON.stringify({
79863
+ return runtime.mode === "json" ? `${JSON.stringify({
79152
79864
  type,
79153
79865
  ...known.details
79154
- })}\n` : `${known.lines.join("\n")}\n`);
79866
+ })}\n` : `${known.lines.join("\n")}\n`;
79155
79867
  };
79868
+ const keyNotice = (type) => runtime.mode === "json" ? writeProgress(runtime, `${JSON.stringify({
79869
+ type,
79870
+ organizationId: orgId,
79871
+ requestKey: key
79872
+ })}\n`) : Effect.void;
79156
79873
  const within = (work) => boundedNetwork(work, timeout);
79157
79874
  return yield* Effect.gen(function* () {
79158
79875
  const opened = yield* openAuthenticatedSession(config, email);
@@ -79185,18 +79902,18 @@ const execute$30 = (config, input) => Effect.gen(function* () {
79185
79902
  employees,
79186
79903
  key
79187
79904
  });
79188
- yield* writeStderr(runtime.mode === "json" ? `${JSON.stringify({
79905
+ yield* writeProgress(runtime, runtime.mode === "json" ? `${JSON.stringify({
79189
79906
  type: "org.payroll.preview",
79190
79907
  organizationId: selectedOrg,
79191
79908
  requestKey: key,
79192
79909
  input: requested,
79193
79910
  targets
79194
79911
  })}\n` : `${lines.join("\n")}\n`);
79195
- yield* notice("org.payroll.requestKey");
79912
+ yield* keyNotice("org.payroll.requestKey");
79196
79913
  const draft = stored ?? (yield* saveCommandDraft(config, selector, requested, preview.stillAuthenticated));
79197
79914
  saved = true;
79198
79915
  const admitted = yield* parseRunInput(draft.input);
79199
- yield* notice("org.payroll.saved");
79916
+ yield* keyNotice("org.payroll.saved");
79200
79917
  const approval = yield* within(withWaiting(Effect.promise((signal) => preview.client.approvals.prepare({
79201
79918
  organizationId: selectedOrg,
79202
79919
  payrollRun: {
@@ -79214,16 +79931,24 @@ const execute$30 = (config, input) => Effect.gen(function* () {
79214
79931
  }).pipe(Effect.mapError((error) => {
79215
79932
  const scoped = orgId === null ? error : normalizeOrganizationReadFailure(error, orgId);
79216
79933
  const failure = classifyFailure(Cause.fail(scoped), operation);
79217
- const known = recovery();
79934
+ const done = completedRun(error, key);
79935
+ const known = done === void 0 ? recovery() : runCommandRecovery({
79936
+ orgId,
79937
+ actorEmail,
79938
+ key,
79939
+ runId: done.runId,
79940
+ canResume: false
79941
+ });
79942
+ const message = done === void 0 ? failure.message : `This request key's Payroll run already ${done.resolution}. Read it below; a new run needs a new --request-key.`;
79218
79943
  return new CliFailure({
79219
79944
  ...failure,
79220
79945
  details: {
79221
79946
  ...failure.details,
79222
79947
  ...known.details
79223
79948
  },
79224
- message: `${failure.message}${known.lines.length === 0 ? "" : `\n${known.lines.join("\n")}`}`
79949
+ message: `${message}${known.lines.length === 0 ? "" : `\n${known.lines.join("\n")}`}`
79225
79950
  });
79226
- }), Effect.onInterrupt(() => notice("org.payroll.recovery").pipe(Effect.ignoreCause)));
79951
+ }), Effect.onInterrupt(() => writeStderr(noticeText("org.payroll.recovery")).pipe(Effect.ignoreCause)));
79227
79952
  });
79228
79953
  //#endregion
79229
79954
  //#region src/commands/org/subcommands/payroll/subcommands/run/command.ts
@@ -79243,47 +79968,6 @@ const command$27 = Command.make("run", {
79243
79968
  timeoutSeconds: timeout
79244
79969
  }))).pipe(annotate$1("run"), Command.withDescription("run payroll from a Budget; you approve it in the browser"));
79245
79970
  //#endregion
79246
- //#region src/commands/org/subcommands/payroll/internal/run-lookup.ts
79247
- const isPayrollRunId = (value) => /^payroll_run_[0-9a-f]{64}$/u.test(value);
79248
- const exactRunId = (value) => isPayrollRunId(value) ? Effect.succeed(value) : Effect.fail(inputRequired("Provide the Payroll run ID: payroll_run_ followed by a 64-character digest."));
79249
- const validateRunInput = (input) => Effect.gen(function* () {
79250
- const timeout = yield* readWriteTimeout(input.timeoutSeconds);
79251
- const email = input.email === void 0 ? void 0 : yield* readEmail(input.email);
79252
- if (input.orgs.length > 1) return yield* inputRequired("Provide one exact --org ID.");
79253
- const orgId = yield* explicitOrganization(input.orgs);
79254
- if (input.runIds.length > 1) return yield* inputRequired("Provide one exact Payroll run ID.");
79255
- let runId = input.runIds[0];
79256
- if (runId !== void 0) yield* exactRunId(runId);
79257
- const runtime = yield* CliRuntime;
79258
- if (!runtime.tty || runtime.ci || runtime.mode === "json") {
79259
- if (orgId === null) return yield* inputRequired("Provide one exact --org ID.");
79260
- if (runId === void 0) return yield* inputRequired("Provide the Payroll run ID.");
79261
- }
79262
- if (runId === void 0) runId = yield* Prompt.String({ message: "Payroll run ID" });
79263
- return {
79264
- orgId,
79265
- runId: yield* exactRunId(runId),
79266
- timeout,
79267
- email
79268
- };
79269
- });
79270
- const missingRun = () => new CliFailure({
79271
- code: "INVALID_INPUT",
79272
- outcome: "refused",
79273
- exitCode: 2,
79274
- message: "The Payroll run was not found or is unavailable to this session. Check --org and the Payroll run ID."
79275
- });
79276
- const matches = (detail, orgId, runId) => detail.orgId === orgId && detail.run.id === runId;
79277
- const mismatch = () => Errors.invalidInput("runId", "Payroll run response scope does not match");
79278
- const finishedRun = (detail) => detail.items.length > 0 && detail.items.every((item) => paymentPhase(item.payment.status).outcome === "good") || detail.items.some((item) => {
79279
- const phase = paymentPhase(item.payment.status);
79280
- return phase.terminal && phase.outcome !== "good" || phase.needsAction || item.payment.execution?.retryRequired === true;
79281
- }) || [
79282
- "failed",
79283
- "mismatch",
79284
- "unresolved"
79285
- ].includes(detail.command.verificationState) || detail.command.completionOutcome === "failed" || detail.command.terminalOutcome !== void 0 && detail.command.terminalOutcome !== "settled";
79286
- //#endregion
79287
79971
  //#region src/commands/org/subcommands/payroll/internal/run-lines.ts
79288
79972
  const runRecovery = (orgId, runId, email, command) => {
79289
79973
  const args = [
@@ -79320,7 +80004,7 @@ const runDetailLines = (detail) => [
79320
80004
  `Payroll run ${terminalText(detail.run.id)}: recorded status ${detail.run.status}`,
79321
80005
  ` Organization: ${terminalText(detail.orgId)}`,
79322
80006
  ` Period: ${detail.run.periodStart} to ${detail.run.periodEnd}`,
79323
- ` Total: ${terminalText(formatAssetAmount(detail.run.total))}; recipients: ${detail.run.recipientCount}`,
80007
+ ` Total: ${terminalText(shownNumber(detail.run.total))} ${terminalText(assetName(detail.run.total.asset))}; recipients: ${detail.run.recipientCount}`,
79324
80008
  ` Request key: ${terminalText(detail.command.requestKey)}`,
79325
80009
  ` Command: ${terminalText(detail.command.commandId)}; state ${detail.command.state}; verification ${detail.command.verificationState}`,
79326
80010
  ...detail.command.completionOutcome === void 0 ? [] : [` Recorded completion: ${detail.command.completionOutcome}`],
@@ -79332,7 +80016,7 @@ const runDetailLines = (detail) => [
79332
80016
  ` Item ${item.itemIndex}: employee ${terminalText(item.employeeRef)}; engagement ${terminalText(item.engagementEdgeId)}`,
79333
80017
  ` Gross: ${terminalText(item.gross)} raw units; net: ${terminalText(item.net)} raw units`,
79334
80018
  ...item.adjustments.map((adjustment) => ` Adjustment: ${terminalText(adjustment.label)}; ${terminalText(adjustment.amount)} raw units`),
79335
- ` Quantity: ${terminalText(item.amount.value)}; asset: ${terminalText(item.amount.asset)}`,
80019
+ ` Quantity: ${terminalText(item.amount.value)}; asset: ${terminalText(assetName(item.amount.asset))}`,
79336
80020
  ` Payment ${terminalText(item.payment.id)}: ${item.payment.status}`,
79337
80021
  ` Counterparty: ${terminalText(item.payment.counterpartyLabel)}`,
79338
80022
  ` Receipt: ${item.payment.receipt === null ? "none recorded" : `${terminalText(item.payment.receipt.transactionHash)} (chain ${item.payment.receipt.chainId})`}`,
@@ -79350,28 +80034,7 @@ const readValidatedRun = (config, input, validated, read) => Effect.gen(function
79350
80034
  let command;
79351
80035
  const runtime = yield* CliRuntime;
79352
80036
  const known = () => orgId === null ? null : runRecovery(orgId, validated.runId, email, command);
79353
- const observed = yield* Effect.gen(function* () {
79354
- const restored = yield* restoreAuthenticatedClient(config, email, { requirement: "none" });
79355
- email = restored.session.email;
79356
- const organizations = yield* Effect.promise((signal) => restored.client.orgs({ signal })).pipe(Effect.flatMap(resultItems));
79357
- orgId = (yield* selectOrganization(organizations, orgId)).id;
79358
- if (!(yield* restored.stillAuthenticated)) return yield* Effect.fail(Errors.notAuthenticated());
79359
- const reading = read(restored.client.org(orgId).payroll, orgId, validated.runId, (value) => {
79360
- command = value.command;
79361
- });
79362
- const detail = yield* input.kind === "wait" ? withWaiting(reading, {
79363
- ...runtime,
79364
- label: "Observe Payroll run state"
79365
- }) : reading;
79366
- if (detail === null) return yield* missingRun();
79367
- if (!matches(detail, orgId, validated.runId)) return yield* Effect.fail(mismatch());
79368
- if (!(yield* restored.stillAuthenticated)) return yield* Effect.fail(Errors.notAuthenticated());
79369
- command = detail.command;
79370
- return {
79371
- detail,
79372
- client: restored.client
79373
- };
79374
- }).pipe(Effect.mapError((error) => {
80037
+ const guarded = (work) => work.pipe(Effect.mapError((error) => {
79375
80038
  const failure = classifyFailure(Cause.fail(orgId === null ? error : normalizeOrganizationReadFailure(error, orgId)), `org.payroll.${input.kind}`);
79376
80039
  const recovery = known();
79377
80040
  return new CliFailure({
@@ -79391,7 +80054,33 @@ const readValidatedRun = (config, input, validated, read) => Effect.gen(function
79391
80054
  ...recovery.details,
79392
80055
  message: recovery.lines
79393
80056
  })}\n` : `${recovery.lines}\n`).pipe(Effect.ignoreCause);
79394
- }), Effect.timeoutOption(validated.timeout * 1e3));
80057
+ }));
80058
+ const { restored, selectedOrg } = yield* guarded(Effect.gen(function* () {
80059
+ const session = yield* restoreAuthenticatedClient(config, email, { requirement: "none" });
80060
+ email = session.session.email;
80061
+ const organizations = yield* Effect.promise((signal) => session.client.orgs({ signal })).pipe(Effect.flatMap(resultItems));
80062
+ const organization = yield* selectOrganization(organizations, orgId);
80063
+ orgId = organization.id;
80064
+ if (!(yield* session.stillAuthenticated)) return yield* Effect.fail(Errors.notAuthenticated());
80065
+ return {
80066
+ restored: session,
80067
+ selectedOrg: organization.id
80068
+ };
80069
+ }));
80070
+ const observed = yield* guarded(Effect.gen(function* () {
80071
+ const reading = read(restored.client.org(selectedOrg).payroll, selectedOrg, validated.runId, (value) => {
80072
+ command = value.command;
80073
+ });
80074
+ const detail = yield* input.kind === "wait" ? withWaiting(reading, {
80075
+ ...runtime,
80076
+ label: "Observe Payroll run state"
80077
+ }) : reading;
80078
+ if (detail === null) return yield* missingRun();
80079
+ if (!matches(detail, selectedOrg, validated.runId)) return yield* Effect.fail(mismatch());
80080
+ if (!(yield* restored.stillAuthenticated)) return yield* Effect.fail(Errors.notAuthenticated());
80081
+ command = detail.command;
80082
+ return { detail };
80083
+ })).pipe(Effect.timeoutOption(validated.timeout * 1e3));
79395
80084
  if (Option.isNone(observed)) {
79396
80085
  const recovery = known();
79397
80086
  return yield* new CliFailure({
@@ -79528,6 +80217,11 @@ const continueRecoverySetup = (scoped, signal, onCurrent) => Effect.gen(function
79528
80217
  });
79529
80218
  //#endregion
79530
80219
  //#region src/commands/org/internal/progress.ts
80220
+ /** An epoch-ms time as a person reads it; a value no date can hold keeps its number. */
80221
+ const checkTime = (at) => {
80222
+ const date = new Date(at);
80223
+ return Number.isNaN(date.valueOf()) ? `${at} (Unix ms)` : date.toISOString();
80224
+ };
79531
80225
  /**
79532
80226
  * Organization lifecycle progress owned by one write command.
79533
80227
  *
@@ -79539,7 +80233,7 @@ const continueRecoverySetup = (scoped, signal, onCurrent) => Effect.gen(function
79539
80233
  * the signature. An interruption retains an ID that was already saved.
79540
80234
  */
79541
80235
  const asProgressError = (cause) => cause instanceof Error ? cause : new Error("Organization progress failed.", { cause });
79542
- const makeOrganizationProgress = (mode, run, persist) => {
80236
+ const makeOrganizationProgress = (reader, run, persist) => {
79543
80237
  const queued = [];
79544
80238
  const held = [];
79545
80239
  const heldSet = /* @__PURE__ */ new Set();
@@ -79554,7 +80248,7 @@ const makeOrganizationProgress = (mode, run, persist) => {
79554
80248
  draining = draining.then(async () => {
79555
80249
  while (queued.length > 0) {
79556
80250
  const text = queued[0];
79557
- await run(writeStderr(text));
80251
+ await run(writeProgress(reader, text));
79558
80252
  queued.shift();
79559
80253
  }
79560
80254
  if (committed !== null && persisted !== committed) {
@@ -79603,14 +80297,14 @@ const makeOrganizationProgress = (mode, run, persist) => {
79603
80297
  lastObserved = state;
79604
80298
  const status = state.status;
79605
80299
  return {
79606
- text: `${mode === "json" ? JSON.stringify({
80300
+ text: `${reader.mode === "json" ? JSON.stringify({
79607
80301
  type: "organization.lifecycle",
79608
80302
  organizationId: committed,
79609
80303
  status,
79610
80304
  recovery: state.recovery,
79611
80305
  reason: state.issue?.reason ?? null,
79612
80306
  nextCheckAt: state.nextCheckAt
79613
- }) : `Organization ${JSON.stringify(committed)}: ${terminalText(status)}; recovery: ${terminalText(state.recovery)}${state.issue === null ? "" : `; reason: ${terminalText(state.issue.reason)}`}${state.nextCheckAt === null ? "" : `; next check: ${state.nextCheckAt}`}`}\n`,
80307
+ }) : `Organization ${JSON.stringify(committed)}: ${terminalText(status)}; recovery: ${terminalText(state.recovery)}${state.issue === null ? "" : `; reason: ${terminalText(state.issue.reason)}`}${state.nextCheckAt === null ? "" : `; next check: ${checkTime(state.nextCheckAt)}`}`}\n`,
79614
80308
  ready: status === "ready"
79615
80309
  };
79616
80310
  };
@@ -79709,7 +80403,7 @@ const execute$27 = (config, input) => Effect.gen(function* () {
79709
80403
  handle: draft.handle,
79710
80404
  organizationId: toOrgId(organizationId)
79711
80405
  });
79712
- const progress = makeOrganizationProgress(runtime.mode, run, (organizationId) => run(persistRecovery(organizationId)));
80406
+ const progress = makeOrganizationProgress(runtime, run, (organizationId) => run(persistRecovery(organizationId)));
79713
80407
  yield* Effect.addFinalizer(() => Effect.promise(progress.flush).pipe(Effect.ignoreCause));
79714
80408
  yield* Ref.set(progressRef, progress);
79715
80409
  const flushProgress = Effect.gen(function* () {
@@ -79807,11 +80501,11 @@ const execute$27 = (config, input) => Effect.gen(function* () {
79807
80501
  //#endregion
79808
80502
  //#region src/commands/org/subcommands/create/command.ts
79809
80503
  const command$24 = Command.make("create", {
79810
- name: Flag.String("name"),
79811
- handle: Flag.String("handle"),
79812
- country: Flag.String("country"),
79813
- bio: Flag.String("bio").pipe(Flag.optional),
79814
- size: Flag.String("size").pipe(Flag.optional),
80504
+ name: Flag.String("name").pipe(Flag.withDescription("the Organization's name, as people see it")),
80505
+ handle: Flag.String("handle").pipe(Flag.withDescription("its @handle, written without the @ sign")),
80506
+ country: Flag.String("country").pipe(Flag.withDescription("its country, as a two-letter code like GB")),
80507
+ bio: Flag.String("bio").pipe(Flag.withDescription("a short description of the Organization"), Flag.optional),
80508
+ size: Flag.String("size").pipe(Flag.withDescription("how big the Organization is, in your own words"), Flag.optional),
79815
80509
  email: emailFlag$9,
79816
80510
  confirm: confirmFlag$6,
79817
80511
  timeoutSeconds: timeoutFlag$4
@@ -79989,7 +80683,7 @@ const execute$22 = (config, input) => Effect.gen(function* () {
79989
80683
  return yield* withWaiting(Effect.gen(function* () {
79990
80684
  const context = yield* Effect.context();
79991
80685
  const run = Effect.runPromiseWith(context);
79992
- const progress = makeOrganizationProgress(waitingRuntime.mode, run, async () => void 0);
80686
+ const progress = makeOrganizationProgress(waitingRuntime, run, async () => void 0);
79993
80687
  yield* Effect.addFinalizer(() => Effect.promise(progress.flush).pipe(Effect.ignoreCause));
79994
80688
  const deadline = new AbortController();
79995
80689
  const openfortSignerFactory = yield* organizationWriteSigner(config, opened, run, async () => {
@@ -80094,7 +80788,7 @@ const follow = (client, scoped, organizationId, email, observed) => Effect.gen(f
80094
80788
  const runtime = yield* CliRuntime;
80095
80789
  return yield* withWaiting(Effect.gen(function* () {
80096
80790
  const run = Effect.runPromiseWith(yield* Effect.context());
80097
- const progress = makeOrganizationProgress(runtime.mode, run, async () => void 0);
80791
+ const progress = makeOrganizationProgress(runtime, run, async () => void 0);
80098
80792
  yield* Effect.addFinalizer(() => Effect.promise(progress.flush).pipe(Effect.ignoreCause));
80099
80793
  const lifecycle = yield* observeOrganizationSetup((listener, signal) => scoped.setup.subscribe(listener, { signal }), organizationId, null, async (value) => {
80100
80794
  observed(value);
@@ -80147,8 +80841,10 @@ const org = Command.make("org").pipe(Command.annotate(CliCommandMetadata, metada
80147
80841
  //#endregion
80148
80842
  //#region src/commands/payment/subcommands/shared.ts
80149
80843
  const emailFlag = Flag.String("email").pipe(Flag.withDescription("the signed-in person to use, when this machine has several"), Flag.optional);
80150
- const paymentIdArgument = Argument.String("id").pipe(Argument.withDescription("The Payment ID (wait also takes an approval ID)"));
80151
- const timeoutFlag = Flag.Int("timeout-seconds").pipe(Flag.withDefault(120));
80844
+ const paymentIdArgument = Argument.String("id").pipe(Argument.withDescription("The Payment ID"));
80845
+ /** `payment wait` also follows the approval `payment send --json` returns. */
80846
+ const paymentOrApprovalIdArgument = Argument.String("id").pipe(Argument.withDescription("The Payment ID, or the approval ID from payment send"));
80847
+ const timeoutFlag = Flag.Int("timeout-seconds").pipe(Flag.withDescription("stop following after this many seconds (default: follow until it ends)"), Flag.optional);
80152
80848
  const sourceFlag = Flag.String("input").pipe(Flag.withDescription("the SDK payment JSON, from a file or - for stdin"), Flag.optional);
80153
80849
  const requestKeyFlag = Flag.String("request-key").pipe(Flag.withDescription("a key that makes repeating the same payment safe"), Flag.optional);
80154
80850
  const names = {
@@ -80362,7 +81058,7 @@ const timeoutFailure = (paymentId, email, organizationId) => {
80362
81058
  code: "NETWORK_ERROR",
80363
81059
  outcome: "failed",
80364
81060
  exitCode: 5,
80365
- message: email === void 0 ? `The Payment wait stopped before session selection completed. The Payment state is unknown.\n${known.lines}` : `Payment settlement is not confirmed. The command stopped waiting. This Payment may still settle.\n${known.lines}`,
81061
+ message: `Payment settlement is not confirmed. The command stopped waiting. This Payment may still settle.\n${known.lines}`,
80366
81062
  details: known.details
80367
81063
  });
80368
81064
  };
@@ -80439,31 +81135,29 @@ const observePayment = (register, paymentId, organizationId) => observeSnapshots
80439
81135
  value: payment
80440
81136
  });
80441
81137
  }).pipe(Effect.flatMap(resultValue), Effect.filterOrFail((payment) => payment !== null, paymentNotFound));
81138
+ /** `Option.none()` when `seconds` pass first. With no bound the wait follows the Payment until it ends. */
81139
+ const within = (seconds) => (work) => seconds === void 0 ? Effect.asSome(work) : Effect.timeoutOption(work, seconds * 1e3);
80442
81140
  const waitForPayment = (config, input, register, organizationId) => Effect.gen(function* () {
80443
81141
  const paymentId = yield* readPaymentId(input.paymentId);
80444
- const timeoutSeconds = yield* readWriteTimeout(input.timeoutSeconds);
81142
+ const timeoutSeconds = input.timeoutSeconds === void 0 ? void 0 : yield* readWriteTimeout(input.timeoutSeconds);
80445
81143
  let email;
80446
- const observed = yield* Effect.gen(function* () {
80447
- const restored = yield* restoreAuthenticatedClient(config, input.email);
80448
- email = restored.session.email;
80449
- const runtime = yield* CliRuntime;
80450
- return {
80451
- payment: yield* withWaiting(observePayment((listener, signal) => register(restored.client, paymentId, listener, signal), paymentId, organizationId), {
80452
- ...runtime,
80453
- label: "Observe Payment state"
80454
- }),
80455
- client: restored.client
80456
- };
80457
- }).pipe(Effect.mapError((error) => recoverFailure(error, paymentId, email, organizationId)), Effect.onInterrupt(() => Effect.gen(function* () {
81144
+ const guarded = (work) => work.pipe(Effect.mapError((error) => recoverFailure(error, paymentId, email, organizationId)), Effect.onInterrupt(() => Effect.gen(function* () {
80458
81145
  const runtime = yield* CliRuntime;
80459
81146
  const known = recovery(paymentId, email, organizationId);
80460
81147
  yield* writeStderr(runtime.mode === "json" ? `${JSON.stringify({
80461
81148
  type: "payment.recovery",
80462
81149
  ...known.details
80463
81150
  })}\n` : `${known.lines}\n`);
80464
- }).pipe(Effect.ignoreCause)), Effect.timeoutOption(timeoutSeconds * 1e3));
81151
+ }).pipe(Effect.ignoreCause)));
81152
+ const restored = yield* guarded(restoreAuthenticatedClient(config, input.email));
81153
+ email = restored.session.email;
81154
+ const runtime = yield* CliRuntime;
81155
+ const observed = yield* guarded(withWaiting(observePayment((listener, signal) => register(restored.client, paymentId, listener, signal), paymentId, organizationId), {
81156
+ ...runtime,
81157
+ label: "Observe Payment state"
81158
+ })).pipe(within(timeoutSeconds));
80465
81159
  if (Option.isNone(observed)) return yield* timeoutFailure(paymentId, email, organizationId);
80466
- const { payment } = observed.value;
81160
+ const payment = observed.value;
80467
81161
  return {
80468
81162
  data: payment,
80469
81163
  lines: [...paymentLines(payment), ...documentReferenceLines(payment)]
@@ -80473,7 +81167,7 @@ const waitForPayment = (config, input, register, organizationId) => Effect.gen(f
80473
81167
  //#region src/commands/org/subcommands/payment/subcommands/wait/execute.ts
80474
81168
  const execute$15 = (config, input) => Effect.gen(function* () {
80475
81169
  const paymentId = yield* readPaymentId(input.paymentId);
80476
- const timeoutSeconds = yield* readWriteTimeout(input.timeoutSeconds);
81170
+ const timeoutSeconds = input.timeoutSeconds === void 0 ? void 0 : yield* readWriteTimeout(input.timeoutSeconds);
80477
81171
  const email = input.email === void 0 ? void 0 : yield* readEmail(input.email);
80478
81172
  const organizationId = yield* readOrganizationId(input.orgs);
80479
81173
  return yield* waitForPayment(config, {
@@ -80495,16 +81189,16 @@ const isApprovalId = (id) => id.startsWith("approval_");
80495
81189
  */
80496
81190
  const waitForApproval = (config, input) => Effect.gen(function* () {
80497
81191
  if (!APPROVAL_ID.test(input.approvalId)) return yield* inputRequired("Provide the approval ID: approval_ followed by 26 letters or digits.");
80498
- const timeoutSeconds = yield* readWriteTimeout(input.timeoutSeconds);
81192
+ const timeoutSeconds = input.timeoutSeconds === void 0 ? void 0 : yield* readWriteTimeout(input.timeoutSeconds);
80499
81193
  const runtime = yield* CliRuntime;
80500
81194
  const { client } = yield* restoreAuthenticatedClient(config, input.email);
80501
81195
  const human = runtime.mode === "human";
80502
81196
  const outcome = yield* followApproval(client, input.approvalId, {
80503
81197
  organizationId: input.organizationId,
80504
- deadlineMs: timeoutSeconds * 1e3,
80505
- onSigned: human ? progress("✓ Signed") : Effect.void
81198
+ deadlineMs: timeoutSeconds === void 0 ? yield* followUntilLapseMs(client, input.approvalId) : timeoutSeconds * 1e3,
81199
+ onSigned: human ? progress(runtime, "✓ Signed") : Effect.void
80506
81200
  });
80507
- if (human) yield* progress("✓ Confirmed");
81201
+ if (human) yield* progress(runtime, "✓ Confirmed");
80508
81202
  return {
80509
81203
  data: outcome,
80510
81204
  lines: paidLines(outcome, input)
@@ -80516,33 +81210,38 @@ const execute$14 = (config, input) => waitForPayment(config, input, (client, pay
80516
81210
  //#endregion
80517
81211
  //#region src/commands/payment/subcommands/wait/command.ts
80518
81212
  const command$13 = Command.make("wait", {
80519
- paymentId: paymentIdArgument,
81213
+ paymentId: paymentOrApprovalIdArgument,
80520
81214
  email: emailFlag,
80521
81215
  timeoutSeconds: timeoutFlag
80522
- }, ({ paymentId, email, timeoutSeconds }) => dispatch("wait", {
80523
- personal: (config) => isApprovalId(paymentId) ? waitForApproval(config, {
80524
- approvalId: paymentId,
80525
- email: Option.getOrUndefined(email),
80526
- timeoutSeconds
80527
- }) : execute$14(config, {
80528
- kind: "wait",
80529
- paymentId,
80530
- email: Option.getOrUndefined(email),
80531
- timeoutSeconds
80532
- }),
80533
- organization: (config, orgId) => isApprovalId(paymentId) ? waitForApproval(config, {
80534
- approvalId: paymentId,
80535
- email: Option.getOrUndefined(email),
80536
- timeoutSeconds,
80537
- organizationId: orgId
80538
- }) : execute$15(config, {
80539
- kind: "wait",
80540
- orgs: [orgId],
80541
- paymentId,
80542
- email: Option.getOrUndefined(email),
80543
- timeoutSeconds
80544
- })
80545
- })).pipe(annotate("wait"), Command.withDescription("Follow a Payment, or an approval ID from payment send, until it ends"));
81216
+ }, (flags) => {
81217
+ const { paymentId } = flags;
81218
+ const email = Option.getOrUndefined(flags.email);
81219
+ const timeoutSeconds = Option.getOrUndefined(flags.timeoutSeconds);
81220
+ return dispatch("wait", {
81221
+ personal: (config) => isApprovalId(paymentId) ? waitForApproval(config, {
81222
+ approvalId: paymentId,
81223
+ email,
81224
+ timeoutSeconds
81225
+ }) : execute$14(config, {
81226
+ kind: "wait",
81227
+ paymentId,
81228
+ email,
81229
+ timeoutSeconds
81230
+ }),
81231
+ organization: (config, orgId) => isApprovalId(paymentId) ? waitForApproval(config, {
81232
+ approvalId: paymentId,
81233
+ email,
81234
+ timeoutSeconds,
81235
+ organizationId: orgId
81236
+ }) : execute$15(config, {
81237
+ kind: "wait",
81238
+ orgs: [orgId],
81239
+ paymentId,
81240
+ email,
81241
+ timeoutSeconds
81242
+ })
81243
+ });
81244
+ }).pipe(annotate("wait"), Command.withDescription("Follow a Payment, or an approval ID from payment send, until it ends"));
80546
81245
  //#endregion
80547
81246
  //#region src/commands/payment/internal/send-input.ts
80548
81247
  const LIMIT$1 = 65536;
@@ -80596,8 +81295,9 @@ const parsePaymentInput = (value) => Effect.gen(function* () {
80596
81295
  if (typeof rawValue !== "string") return yield* invalid$2();
80597
81296
  const assetId = Object.values(TOKENS.BASE_SEPOLIA).find((id) => id === amountInput.asset);
80598
81297
  if (assetId === void 0) return yield* inputRequired("Provide an admitted Base Sepolia asset ID.");
80599
- const amount = parseAssetAmount(rawValue, requireAsset(assetId));
80600
- if (isMoneyParseError(amount)) return yield* inputRequired(`Provide a positive exact amount (${amount.reason}).`);
81298
+ const metadata = requireAsset(assetId);
81299
+ const amount = parseAssetAmount(rawValue, metadata);
81300
+ if (isMoneyParseError(amount)) return yield* inputRequired(amountRefusal(amount.reason, metadata), { field: "amount" });
80601
81301
  if (value.paymentType !== void 0 && value.paymentType !== "unspecified" && value.paymentType !== "invoice" && value.paymentType !== "reimbursement") return yield* inputRequired("Provide a supported --payment-type.");
80602
81302
  if (value.timing !== void 0 && (!record$1(value.timing) || !keysOnly(value.timing, ["kind"]) || value.timing.kind !== "instant")) return yield* inputRequired("Only instant Payment timing is supported.");
80603
81303
  return {
@@ -80712,6 +81412,7 @@ const readPaymentInput = (source) => Effect.gen(function* () {
80712
81412
  /** The checks every send makes before a client: one source of the payment, the key and the follow time. */
80713
81413
  const readSendFlags = (input) => Effect.gen(function* () {
80714
81414
  if (input.source !== void 0 && (input.to !== void 0 || input.amount !== void 0)) return yield* inputRequired("Use <to> <amount> or --input, not both.");
81415
+ if (input.to !== void 0) yield* readRecipient$1(input.to);
80715
81416
  return {
80716
81417
  requestKey: input.requestKey === void 0 ? void 0 : yield* readRequestKey(input.requestKey),
80717
81418
  timeoutSeconds: input.timeoutSeconds === void 0 ? void 0 : yield* readWriteTimeout(input.timeoutSeconds)
@@ -80739,7 +81440,7 @@ const execute$13 = (config, input) => Effect.gen(function* () {
80739
81440
  const approval = yield* withWaiting(Effect.promise((signal) => client.approvals.prepare({
80740
81441
  ...payment,
80741
81442
  ...flags.requestKey === void 0 ? {} : { requestKey: flags.requestKey }
80742
- }, { signal })).pipe(Effect.flatMap(resultValue)), {
81443
+ }, { signal })).pipe(Effect.flatMap(resultValue), Effect.mapError(namingRecipient(payment.to))), {
80743
81444
  ...runtime,
80744
81445
  label: "Preparing the payment"
80745
81446
  });
@@ -80868,7 +81569,7 @@ const execute$12 = (config, orgId, input) => Effect.gen(function* () {
80868
81569
  organizationId: orgId,
80869
81570
  permissionId: String(budget.id),
80870
81571
  ...flags.requestKey === void 0 ? {} : { requestKey: flags.requestKey }
80871
- }, { signal })).pipe(Effect.flatMap(resultValue)), {
81572
+ }, { signal })).pipe(Effect.flatMap(resultValue), Effect.mapError(namingRecipient(payment.to))), {
80872
81573
  ...runtime,
80873
81574
  label: "Preparing the payment"
80874
81575
  });
@@ -81231,33 +81932,29 @@ const execute$6 = (config, input) => Effect.gen(function* () {
81231
81932
  const actor = orgId === null ? client.account : client.org(orgId);
81232
81933
  const stored = yield* Effect.promise((signal) => actor.requests.issue(draft, { signal })).pipe(Effect.flatMap(resultValue), Effect.mapError((error) => requestWriteFailure(error, orgId, "issued", null, session.email)));
81233
81934
  const item = requestCheckout(stored, config.checkoutFrontendOrigin);
81234
- const documents = yield* savePaymentDocuments(config, client, item, input.outputDirectory);
81235
81935
  return {
81236
81936
  data: {
81237
81937
  scope: scopeValue(orgId),
81238
81938
  direction: input.direction,
81239
- item,
81240
- documents
81939
+ item
81241
81940
  },
81242
- lines: [...requestLines(item, session.email, orgId), ...documentOutputLines(documents)]
81941
+ lines: requestLines(item, session.email, orgId)
81243
81942
  };
81244
81943
  });
81245
81944
  //#endregion
81246
81945
  //#region src/commands/request/subcommands/issue/command.ts
81247
81946
  const command$6 = (context) => Command.make("issue", {
81248
81947
  email: emailFlag$3,
81249
- source: Flag.String("input"),
81250
- confirm: confirmFlag$2,
81251
- outputDirectory: Flag.String("output-dir").pipe(Flag.optional)
81252
- }, ({ email, source, confirm, outputDirectory }) => context.dispatch("issue", (scope) => ({
81948
+ source: Flag.String("input").pipe(Flag.withDescription("the request JSON, from a file or - for stdin")),
81949
+ confirm: confirmFlag$2
81950
+ }, ({ email, source, confirm }) => context.dispatch("issue", (scope) => ({
81253
81951
  kind: "issue",
81254
81952
  ...scope,
81255
81953
  direction: context.direction,
81256
81954
  source,
81257
81955
  confirm,
81258
- email: Option.getOrUndefined(email),
81259
- outputDirectory: Option.getOrUndefined(outputDirectory)
81260
- }), execute$6)).pipe(context.annotate("issue"), Command.withDescription("Issue a payment request and save its available PDFs"));
81956
+ email: Option.getOrUndefined(email)
81957
+ }), execute$6)).pipe(context.annotate("issue"), Command.withDescription("Issue a payment request"));
81261
81958
  //#endregion
81262
81959
  //#region src/commands/request/subcommands/pay/execute.ts
81263
81960
  const command$5 = (args) => args.map(shellArgument).join(" ");
@@ -81363,12 +82060,7 @@ const inbox = Command.make("inbox").pipe(received.annotate("root"), Command.with
81363
82060
  //#endregion
81364
82061
  //#region src/commands/schema/internal/contract.ts
81365
82062
  /** Commands that move money. Each one hands over to the approval page. */
81366
- const MONEY = /* @__PURE__ */ new Set([
81367
- "payment.send",
81368
- "payment.retry",
81369
- "inbox.pay",
81370
- "org.payroll.run"
81371
- ]);
82063
+ const MONEY = new Set(Object.keys(MONEY_RESULTS));
81372
82064
  /** Commands that answer from this machine alone. */
81373
82065
  const LOCAL = /* @__PURE__ */ new Set([
81374
82066
  "whoami",
@@ -81380,7 +82072,9 @@ const LOCAL = /* @__PURE__ */ new Set([
81380
82072
  "schema"
81381
82073
  ]);
81382
82074
  /** A money command's `data`: the approval link for an agent, the outcome for a person. */
81383
- const APPROVAL = "--json: { status: 'awaiting_approval', approvalId, approvalUrl, paymentIds, expiresAt, next: { argv } }; a person: ApprovalOutcome { approval, payments }";
82075
+ const APPROVAL = "--json: { status: 'awaiting_approval', approvalId, approvalUrl, paymentIds, expiresAt, next: { argv } }; a person: ApprovalOutcome { approval, payments }. It saves no PDF: `capxul document render` does";
82076
+ /** `data` of an Organization Permission write: what was submitted, and with `--wait` where the command ended. */
82077
+ const PERMISSION_WRITE = "{ execution, requestKey }; with --wait also command: PermissionCommandView (status applied, failed or actionable)";
81384
82078
  /** `data` of the commands an agent reaches for most; other lists are pages, others the SDK value. */
81385
82079
  const OUTPUT = {
81386
82080
  home: "{ where, signedIn, balances: Account | null, needsYou: NeedsYou[] | null, try }",
@@ -81394,6 +82088,12 @@ const OUTPUT = {
81394
82088
  "payment.retry": APPROVAL,
81395
82089
  "inbox.pay": APPROVAL,
81396
82090
  "org.payroll.run": APPROVAL,
82091
+ "request.issue": "{ scope, direction, item: ActorRequest & { checkoutUrl } }. It saves no PDF: `capxul document render` does",
82092
+ "org.permission.create": PERMISSION_WRITE,
82093
+ "org.permission.change": PERMISSION_WRITE,
82094
+ "org.permission.assign": PERMISSION_WRITE,
82095
+ "org.permission.revoke": PERMISSION_WRITE,
82096
+ "org.permission.replace": PERMISSION_WRITE,
81397
82097
  [CAPXUL_OPERATIONS.inbox.list]: "Page<InboxItem>",
81398
82098
  "inbox.get": "InboxItem",
81399
82099
  "request.list": "Page<ActorRequest>",
@@ -81456,15 +82156,6 @@ const EXIT_CODES = {
81456
82156
  130: "cancelled: interrupted, or rejected on the approval page"
81457
82157
  };
81458
82158
  //#endregion
81459
- //#region src/runtime/root-command.ts
81460
- /** The command tree `capxul` runs, for the commands that describe it (`capxul schema`). */
81461
- var RootCommand = class extends Context.Service()("capxul-cli/RootCommand") {};
81462
- /**
81463
- * The tree, when the entry provided it. Reading it as optional keeps it out of
81464
- * every command's requirements; only `schema` asks, and the entry always gives it.
81465
- */
81466
- const rootCommand = Effect.flatMap(Effect.serviceOption(RootCommand), (root) => Option.isSome(root) ? Effect.succeed(root.value) : Effect.die("RootCommand is not provided"));
81467
- //#endregion
81468
82159
  //#region src/commands/schema/internal/describe.ts
81469
82160
  const flagOf = (flag) => ({
81470
82161
  name: `--${flag.name}`,
@@ -81644,7 +82335,14 @@ const execute$2 = (config, wanted) => Effect.gen(function* () {
81644
82335
  });
81645
82336
  const handle = wanted.replace(/^@/u, "").toLowerCase();
81646
82337
  const organization = organizations.find((candidate) => candidate.handle.toLowerCase() === handle || candidate.id === wanted);
81647
- if (organization === void 0) return yield* inputRequired(`You are not a member of an Organization "${terminalText(wanted)}". See yours with: capxul org list`);
82338
+ if (organization === void 0) return yield* inputRequired(`You are not a member of an Organization "${terminalText(wanted)}". See yours with: capxul org list`, {
82339
+ field: "handle",
82340
+ next: [
82341
+ "capxul",
82342
+ "org",
82343
+ "list"
82344
+ ]
82345
+ });
81648
82346
  const saved = {
81649
82347
  id: organization.id,
81650
82348
  handle: organization.handle,
@@ -82285,6 +82983,15 @@ const completionFailure = (error) => {
82285
82983
  if (error._tag === "ShowHelp") return error.errors.some(completionFailure);
82286
82984
  return (error._tag === "InvalidValue" || error._tag === "MissingOption" || error._tag === "DuplicateOption") && (error.option === "completions" || error.option === "--completions");
82287
82985
  };
82986
+ const helpFlag = (arg) => arg === "--help" || arg === "-h";
82987
+ const unknownSubcommand = (error) => error._tag === "UnknownSubcommand" || error._tag === "ShowHelp" && error.errors.some((cause) => cause._tag === "UnknownSubcommand");
82988
+ /**
82989
+ * The refusal for an unknown command in a line that asks for help: `capxul
82990
+ * nonsense --help` would print the parent's help and exit 0. The line is
82991
+ * parsed without its help flag, and an unknown command refuses as it does
82992
+ * without one.
82993
+ */
82994
+ const unknownCommandUnderHelp = (args, parse) => args.some(helpFlag) ? Effect.map(parse(args.filter((arg) => !helpFlag(arg))), (parsed) => Result.isFailure(parsed) && unknownSubcommand(parsed.failure) ? parsed.failure : void 0) : Effect.undefined;
82288
82995
  /** Help shown only because required options or arguments were missing. */
82289
82996
  const missingParameters = (error) => CliError.isCliError(error) && error._tag === "ShowHelp" && error.errors.length > 0 && error.errors.every((cause) => cause._tag === "MissingOption" || cause._tag === "MissingArgument");
82290
82997
  /**
@@ -82333,7 +83040,7 @@ const reportNativeFailure = (input) => Effect.gen(function* () {
82333
83040
  }
82334
83041
  }
82335
83042
  const invocation = yield* Ref.get(current);
82336
- const failure = classifyFailure(cause, invocation?.command ?? parsedCommand(cause), runtime.args);
83043
+ const failure = classifyFailure(cause, invocation?.command ?? parsedCommand(cause), runtime.args, pathKind(command));
82337
83044
  yield* renderResult(mode, failureEnvelope(failure, invocation?.command ?? parsedCommand(cause), invocation?.kind === "command" ? invocation.invocationId : null));
82338
83045
  return failure.exitCode;
82339
83046
  });
@@ -82456,7 +83163,8 @@ const runCli = (platform) => Effect.uninterruptibleMask((restore) => Effect.gen(
82456
83163
  })))
82457
83164
  });
82458
83165
  const builtIns = cliConfig.builtIns.filter((flag) => interactive && !ci && mode === "human" || flag !== GlobalFlag.Wizard).map((flag) => flag === GlobalFlag.Help ? decorate(GlobalFlag.Help, "help") : flag === GlobalFlag.Version ? decorate(GlobalFlag.Version, "version") : flag === GlobalFlag.Completions ? decorate(GlobalFlag.Completions, "completion") : flag === GlobalFlag.Wizard ? wizard : flag);
82459
- const nativeExit = yield* restore(runNative(homeInvocation(args)).pipe(Effect.catch((error) => Effect.gen(function* () {
83166
+ const unknownCommand = yield* unknownCommandUnderHelp(args, (rest) => parseProbe(homeInvocation(rest)).pipe(Effect.provideService(Console.Console, captureConsole)));
83167
+ const nativeExit = yield* restore((unknownCommand ? Effect.fail(unknownCommand) : runNative(homeInvocation(args))).pipe(Effect.catch((error) => Effect.gen(function* () {
82460
83168
  if (!interactive || ci || mode !== "human" || !missingParameters(error) || (yield* Ref.get(current)) !== null) return yield* Effect.fail(error);
82461
83169
  const leaf = commandAtPath(command, error.commandPath);
82462
83170
  if (leaf === void 0 || leaf.subcommands.length > 0) return yield* error;
@@ -82488,4 +83196,4 @@ const runCli = (platform) => Effect.uninterruptibleMask((restore) => Effect.gen(
82488
83196
  //#endregion
82489
83197
  export { runCli };
82490
83198
 
82491
- //# sourceMappingURL=entry-GLdITgH4.mjs.map
83199
+ //# sourceMappingURL=entry-DXu0dr0S.mjs.map