@capxul/cli 4.20.0-beta.7 → 4.20.0-beta.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/main.mjs CHANGED
@@ -2,7 +2,7 @@
2
2
  import { createRequire } from "node:module";
3
3
  import process$1, { arch, exit, platform, stderr, versions } from "node:process";
4
4
  import { fileURLToPath } from "node:url";
5
- import { Cause, Clock, Config, Console, Context, Crypto, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, FileSystem, Layer, LogLevel, Logger, Metric, Option, Path, Queue, Redacted, Ref, References, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stdio, Stream, Tracer } from "effect";
5
+ import { Cause, Clock, Config, Console, Context, Crypto, Data, Deferred, Duration, Effect, Exit, Fiber, FiberSet, FileSystem, Layer, LogLevel, Logger, Metric, Option, Path, Queue, Redacted, Ref, References, Result, Schedule, Schema, SchemaGetter, SchemaIssue, SchemaParser, Scope, Stdio, Stream, Terminal, Tracer } from "effect";
6
6
  import { NodeRuntime, NodeServices } from "@effect/platform-node";
7
7
  import { CliConfig, CliError, Command, Flag, GlobalFlag, Primitive, Prompt } from "effect/unstable/cli";
8
8
  import * as nc from "node:crypto";
@@ -4174,11 +4174,11 @@ function encodePacked(types, values) {
4174
4174
  for (let i = 0; i < types.length; i++) {
4175
4175
  const type = types[i];
4176
4176
  const value = values[i];
4177
- data.push(encode$2(type, value));
4177
+ data.push(encode$3(type, value));
4178
4178
  }
4179
4179
  return concatHex(data);
4180
4180
  }
4181
- function encode$2(type, value, isArray = false) {
4181
+ function encode$3(type, value, isArray = false) {
4182
4182
  if (type === "address") {
4183
4183
  const address = value;
4184
4184
  if (!isAddress(address)) throw new InvalidAddressError({ address });
@@ -4212,7 +4212,7 @@ function encode$2(type, value, isArray = false) {
4212
4212
  if (arrayMatch && Array.isArray(value)) {
4213
4213
  const [_type, childType] = arrayMatch;
4214
4214
  const data = [];
4215
- for (let i = 0; i < value.length; i++) data.push(encode$2(childType, value[i], true));
4215
+ for (let i = 0; i < value.length; i++) data.push(encode$3(childType, value[i], true));
4216
4216
  if (data.length === 0) return "0x";
4217
4217
  return concatHex(data);
4218
4218
  }
@@ -11001,6 +11001,18 @@ function withInvocationParentSpan(effect, controls) {
11001
11001
  const parent = controls?.[INVOCATION_PARENT_SPAN];
11002
11002
  return parent === void 0 ? effect : effect.pipe(Effect.withParentSpan(parent));
11003
11003
  }
11004
+ /**
11005
+ * The input field a refused request named.
11006
+ *
11007
+ * The surfaces that call the port directly already carry `details.field` for an
11008
+ * `INVALID_INPUT` refusal. The actor keeps that one allowlisted detail instead of
11009
+ * dropping it, so a caller sees the same refusal either way.
11010
+ */
11011
+ const refusedInputField = (failure) => {
11012
+ if (failure.code !== "INVALID_INPUT") return void 0;
11013
+ const field = failure.error?.details?.field;
11014
+ return typeof field === "string" && field.length > 0 ? { field } : void 0;
11015
+ };
11004
11016
  var ActorFailure = class extends Error {
11005
11017
  reason;
11006
11018
  machine;
@@ -11166,7 +11178,7 @@ const boot = (spec, options = {}) => Effect.gen(function* () {
11166
11178
  const event = { _tag: env.origin.event };
11167
11179
  const failedState = exit.failure === timeoutFailure || exit.failure === cancelledFailure ? yield* recoverFailure(state, env.origin, exit.failure) : state;
11168
11180
  yield* emit(nonApplied(failedState, event, exit.failure.code, outcome, env, exit.failure));
11169
- const details = exit.failure === timeoutFailure ? { reason: "timeout" } : void 0;
11181
+ const details = exit.failure === timeoutFailure ? { reason: "timeout" } : refusedInputField(exit.failure);
11170
11182
  yield* failReply(held.reply, makeFailure(failedState, event, exit.failure.code, details, exit.failure.error ?? exit.failure));
11171
11183
  return;
11172
11184
  }
@@ -13225,7 +13237,7 @@ function subscribeActivity(deps, actor, controls, onChange) {
13225
13237
  }
13226
13238
  //#endregion
13227
13239
  //#region ../../packages/sdk/package.json
13228
- var version$1 = "4.20.0-beta.7";
13240
+ var version$1 = "4.20.0-beta.9";
13229
13241
  //#endregion
13230
13242
  //#region ../../packages/sdk/src/telemetry/exception-projection.ts
13231
13243
  /** Fixed fallback for failures that have no safe message. */
@@ -25087,7 +25099,7 @@ function toFormData$1(obj, formData, options) {
25087
25099
  *
25088
25100
  * @returns {string} The encoded string.
25089
25101
  */
25090
- function encode$1(str) {
25102
+ function encode$2(str) {
25091
25103
  const charMap = {
25092
25104
  "!": "%21",
25093
25105
  "'": "%27",
@@ -25118,8 +25130,8 @@ prototype.append = function append(name, value) {
25118
25130
  };
25119
25131
  prototype.toString = function toString(encoder) {
25120
25132
  const _encode = encoder ? function(value) {
25121
- return encoder.call(this, value, encode$1);
25122
- } : encode$1;
25133
+ return encoder.call(this, value, encode$2);
25134
+ } : encode$2;
25123
25135
  return this._pairs.map(function each(pair) {
25124
25136
  return _encode(pair[0]) + "=" + _encode(pair[1]);
25125
25137
  }, "").join("&");
@@ -25134,7 +25146,7 @@ prototype.toString = function toString(encoder) {
25134
25146
  *
25135
25147
  * @returns {string} The encoded value.
25136
25148
  */
25137
- function encode(val) {
25149
+ function encode$1(val) {
25138
25150
  return encodeURIComponent(val).replace(/%3A/gi, ":").replace(/%24/g, "$").replace(/%2C/gi, ",").replace(/%20/g, "+");
25139
25151
  }
25140
25152
  /**
@@ -25148,7 +25160,7 @@ function encode(val) {
25148
25160
  */
25149
25161
  function buildURL(url, params, options) {
25150
25162
  if (!params) return url;
25151
- const _encode = options && options.encode || encode;
25163
+ const _encode = options && options.encode || encode$1;
25152
25164
  const _options = utils_default.isFunction(options) ? { serialize: options } : options;
25153
25165
  const serializeFn = _options && _options.serialize;
25154
25166
  let serializedParams;
@@ -41029,6 +41041,7 @@ function mapBetterAuthError(operation, body) {
41029
41041
  const code = typeof errBody.code === "string" ? errBody.code : "";
41030
41042
  if (code === "OTP_EXPIRED") return Errors.otpExpired();
41031
41043
  if (code === "INVALID_OTP") return Errors.invalidInput("otp", errBody.message ?? "invalid OTP");
41044
+ if (code === "TOO_MANY_ATTEMPTS") return Errors.rateLimited({ resource: "better-auth/verifyOtp" });
41032
41045
  if (code === "VALIDATION_ERROR" || code === "INVALID_EMAIL") return Errors.invalidInput("email", errBody.message ?? "invalid email");
41033
41046
  }
41034
41047
  return Errors.providerError("better-auth", operation, new Error(String(body)));
@@ -41646,6 +41659,10 @@ var BetterAuthNodeAdapter = class {
41646
41659
  ok: false,
41647
41660
  error: Errors.invalidInput("otp", errBody.message ?? "invalid OTP")
41648
41661
  };
41662
+ if (errBody.code === "TOO_MANY_ATTEMPTS") return {
41663
+ ok: false,
41664
+ error: Errors.rateLimited({ resource: "better-auth/verifyOtp" })
41665
+ };
41649
41666
  }
41650
41667
  }
41651
41668
  return {
@@ -45158,6 +45175,12 @@ const stderrConvexLogger = {
45158
45175
  error: writeConvexDiagnostic,
45159
45176
  logVerbose: () => void 0
45160
45177
  };
45178
+ const silentConvexLogger = {
45179
+ log: () => void 0,
45180
+ warn: () => void 0,
45181
+ error: () => void 0,
45182
+ logVerbose: () => void 0
45183
+ };
45161
45184
  /** Exact floor-first allowlist; every additional handler must migrate its validator first. */
45162
45185
  const OBSERVED_CONVEX_ACTIONS = /* @__PURE__ */ new Set([
45163
45186
  "system:healthObserved",
@@ -45246,7 +45269,7 @@ var ConvexCallAdapter = class {
45246
45269
  const webSocketConstructor = this.#connectionMonitor.observedWebSocketConstructor();
45247
45270
  this.#client = deps.client ?? new ConvexClient(deps.convexUrl, {
45248
45271
  ...webSocketConstructor === void 0 ? {} : { webSocketConstructor },
45249
- ...deps.logToStderr === true ? { logger: stderrConvexLogger } : {}
45272
+ logger: deps.localDiagnostics === true ? stderrConvexLogger : silentConvexLogger
45250
45273
  });
45251
45274
  this.#tokenProvider = deps.tokenProvider;
45252
45275
  this.#applicationId = deps.applicationId;
@@ -51040,7 +51063,7 @@ function productionAuthCacheLayer(input) {
51040
51063
  function productionConvexCallLayer(input, refreshConvexAuthRef) {
51041
51064
  return Layer.unwrap(Effect.map(AuthClientPortTag, (authClient) => ConvexCallLayer({
51042
51065
  convexUrl: input.runtimeUrls.convexUrl,
51043
- logToStderr: input.runtime === "node",
51066
+ localDiagnostics: input.localDiagnostics,
51044
51067
  ...input.applicationId === void 0 ? {} : { applicationId: input.applicationId },
51045
51068
  ...input.observation === void 0 ? {} : { observation: input.observation },
51046
51069
  ...input.convexClient === void 0 ? {} : { client: input.convexClient },
@@ -51079,6 +51102,7 @@ function resolveProductionObservability(input) {
51079
51102
  };
51080
51103
  }
51081
51104
  async function createProductionAdapters(input) {
51105
+ const localDiagnostics = productionLocalDiagnostics(input);
51082
51106
  const { observation, telemetry, invocationObservation, invocationTelemetry } = resolveProductionObservability(input);
51083
51107
  const resolvedInput = resolveInput(input);
51084
51108
  if (!resolvedInput.ok) return resolvedInput;
@@ -51149,6 +51173,7 @@ async function createProductionAdapters(input) {
51149
51173
  const portsLayer = mergeProductionAdapterLayers(makeProductionAdapterLayerEntries({
51150
51174
  bootstrap,
51151
51175
  runtime: resolvedInput.value.runtime,
51176
+ localDiagnostics,
51152
51177
  ...resolvedInput.value.origin === void 0 ? {} : { origin: resolvedInput.value.origin },
51153
51178
  runtimeUrls: runtimeUrls.value,
51154
51179
  chainId: bootstrapResult.value.chainId,
@@ -51163,7 +51188,7 @@ async function createProductionAdapters(input) {
51163
51188
  ...input.signal === void 0 ? {} : { signal: input.signal },
51164
51189
  ...input.resetSignerSession === void 0 ? {} : { resetSignerSession: input.resetSignerSession }
51165
51190
  }));
51166
- const applicationLayer = Layer.merge(portsLayer, optionalEngineeringTelemetryLayer(bootstrapResult.value.engineeringTelemetry, resolvedInput.value.runtime, hostEngineeringPolicy(input.observability))).pipe(Layer.provideMerge(resolvedInput.value.runtime === "node" ? Layer.succeed(Logger.LogToStderr, true) : Layer.empty));
51191
+ const applicationLayer = Layer.merge(portsLayer, optionalEngineeringTelemetryLayer(bootstrapResult.value.engineeringTelemetry, resolvedInput.value.runtime, hostEngineeringPolicy(input.observability))).pipe(Layer.provideMerge(localDiagnosticsLayer(localDiagnostics, resolvedInput.value.runtime)));
51167
51192
  const context = await Effect.runPromise(Layer.buildWithScope(applicationLayer, scope));
51168
51193
  const collected = await Effect.runPromise(collectProductionFlowPorts.pipe(Effect.provide(context)));
51169
51194
  const indexerUrl = input.indexerUrl ?? bootstrapResult.value.indexerUrl;
@@ -51227,6 +51252,13 @@ function optionalEngineeringTelemetryLayer(policy, runtime, hostPolicy) {
51227
51252
  return Layer.empty;
51228
51253
  }
51229
51254
  }
51255
+ function localDiagnosticsLayer(enabled, runtime) {
51256
+ if (!enabled) return Logger.layer([]);
51257
+ return runtime === "node" ? Layer.succeed(Logger.LogToStderr, true) : Layer.empty;
51258
+ }
51259
+ function productionLocalDiagnostics(input) {
51260
+ return input.localDiagnostics !== false;
51261
+ }
51230
51262
  function refreshConvexAuthOnSession(authClient, refresh) {
51231
51263
  return {
51232
51264
  ...authClient,
@@ -51291,7 +51323,7 @@ async function createCapxulClientWithSignerControls(input, controls) {
51291
51323
  runtime,
51292
51324
  authBaseUrl: resolvedAuthBaseUrl,
51293
51325
  createBrowserSigner: createOpenfortBrowserSignerFromBootstrap,
51294
- diagnostic: new EffectLogDiagnosticAdapter(adapters.value.context, new ConsoleDiagnosticAdapter()),
51326
+ diagnostic: new EffectLogDiagnosticAdapter(adapters.value.context, productionLocalDiagnostics(input) ? new ConsoleDiagnosticAdapter() : void 0),
51295
51327
  signer: input.signer,
51296
51328
  factory: input.openfortSignerFactory,
51297
51329
  finalSign: controls.signUserOpHash,
@@ -51663,8 +51695,8 @@ function captureException(telemetry, error, context) {
51663
51695
  }
51664
51696
  //#endregion
51665
51697
  //#region src/version.ts
51666
- const cliVersion = "4.20.0-beta.7";
51667
- const sdkVersion = "4.20.0-beta.7";
51698
+ const cliVersion = "4.20.0-beta.9";
51699
+ const sdkVersion = "4.20.0-beta.9";
51668
51700
  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"));
51669
51701
  const parts = (version) => {
51670
51702
  const [core = "", ...prerelease] = version.split("+")[0].split("-");
@@ -52193,7 +52225,7 @@ const finishUpdateCheck = Effect.fn(function* (directory, attempt, target) {
52193
52225
  });
52194
52226
  //#endregion
52195
52227
  //#region src/platform/collection-policy.ts
52196
- const decode$2 = Schema.decodeUnknownOption(Schema.fromJsonString(Settings), { onExcessProperty: "error" });
52228
+ const decode$3 = Schema.decodeUnknownOption(Schema.fromJsonString(Settings), { onExcessProperty: "error" });
52197
52229
  const missing = (error) => error instanceof Error && "code" in error && error.code === "ENOENT";
52198
52230
  const resolveProtectedDirectory = (directory, uid) => {
52199
52231
  const stateDirectory = lstatSync(directory, { throwIfNoEntry: false });
@@ -52237,7 +52269,7 @@ const collectionPolicy = (settingsPath, forcedDisabled) => () => {
52237
52269
  length += count;
52238
52270
  }
52239
52271
  if (length > 65536) return false;
52240
- const settings = decode$2(bytes.toString("utf8", 0, length));
52272
+ const settings = decode$3(bytes.toString("utf8", 0, length));
52241
52273
  return Option.isSome(settings) && settings.value.collection.enabled;
52242
52274
  } catch {
52243
52275
  return false;
@@ -52258,9 +52290,31 @@ var CliFailure = class extends Data.TaggedError("CliFailure") {};
52258
52290
  */
52259
52291
  const WRONG_STATE_MESSAGE = "The command cannot run from the current state.";
52260
52292
  const ORGANIZATION_WRONG_STATE_MESSAGE = "The Organization cannot run this command from its current state. Resume that exact Organization with capxul org retry --org instead of creating a replacement.";
52293
+ const PERSONAL_ACCOUNT_WRONG_STATE_MESSAGE = "The personal Account cannot run this command from its current state. Resume the same Account and Profile with capxul auth signup instead of creating a replacement.";
52261
52294
  const INPUT_MESSAGE = "The input was refused. Check the email and verification code, or request a new code.";
52295
+ const PROFILE_INPUT_MESSAGE = "The Profile input was refused. Correct the display name, country, or handle, then run capxul auth signup again.";
52296
+ const HANDLE_INPUT_MESSAGE = "That handle is not available. Choose another handle, then run capxul auth signup again.";
52262
52297
  const organizationCommand = (command) => command === "org" || command?.startsWith("org.") === true;
52263
52298
  /**
52299
+ * The authentication journey family. A Profile or personal Account refusal is
52300
+ * not an email or verification-code problem, so it never reuses OTP copy.
52301
+ */
52302
+ const authCommand = (command) => command === "auth" || command?.startsWith("auth.") === true;
52303
+ /** The Profile fields a personal signup states. */
52304
+ const PROFILE_FIELDS = /* @__PURE__ */ new Set([
52305
+ "profile",
52306
+ "displayName",
52307
+ "country",
52308
+ "countryCode",
52309
+ "handle"
52310
+ ]);
52311
+ const authInputMessage = (error) => {
52312
+ const field = typeof error.details?.field === "string" ? error.details.field : "";
52313
+ if (field === "handle") return HANDLE_INPUT_MESSAGE;
52314
+ if (PROFILE_FIELDS.has(field)) return PROFILE_INPUT_MESSAGE;
52315
+ return INPUT_MESSAGE;
52316
+ };
52317
+ /**
52264
52318
  * The one allowlist. Only a WRONG_STATE whose SDK details carry an Organization
52265
52319
  * ID the public constructor accepts emits that ID, and it never enters message
52266
52320
  * text or valid states. No other error detail reaches terminal output, and a
@@ -52282,13 +52336,13 @@ const configurationFailure = (message) => new CliFailure({
52282
52336
  outcome: "refused"
52283
52337
  });
52284
52338
  function classifyFailure(cause, command) {
52285
- if (Cause.hasInterruptsOnly(cause)) return {
52339
+ const error = Cause.squash(cause);
52340
+ if (Cause.hasInterruptsOnly(cause) || Terminal.isQuitError(error)) return {
52286
52341
  code: "CANCELLED",
52287
52342
  message: "The command was interrupted.",
52288
52343
  exitCode: 130,
52289
52344
  outcome: "cancelled"
52290
52345
  };
52291
- const error = Cause.squash(cause);
52292
52346
  if (error instanceof CliFailure) return error;
52293
52347
  if (error instanceof StateFileError) return {
52294
52348
  code: "CLI_STORAGE",
@@ -52310,7 +52364,7 @@ function classifyFailure(cause, command) {
52310
52364
  const details = allowlistedDetails(error);
52311
52365
  return {
52312
52366
  code: error.code,
52313
- message: auth ? "Authentication is required." : denied ? "This action is not permitted." : wrongState ? organizationCommand(command) ? ORGANIZATION_WRONG_STATE_MESSAGE : WRONG_STATE_MESSAGE : input ? INPUT_MESSAGE : "The Capxul request failed.",
52367
+ message: auth ? "Authentication is required." : denied ? "This action is not permitted." : wrongState ? organizationCommand(command) ? ORGANIZATION_WRONG_STATE_MESSAGE : authCommand(command) ? PERSONAL_ACCOUNT_WRONG_STATE_MESSAGE : WRONG_STATE_MESSAGE : input ? authCommand(command) ? authInputMessage(error) : INPUT_MESSAGE : "The Capxul request failed.",
52314
52368
  exitCode: auth ? 3 : denied ? 4 : input || wrongState ? 2 : 5,
52315
52369
  outcome: auth || denied || input || wrongState ? "refused" : "failed",
52316
52370
  ...details === void 0 ? {} : { details }
@@ -52330,7 +52384,7 @@ function parsedCommand(cause) {
52330
52384
  //#endregion
52331
52385
  //#region src/runtime/config.ts
52332
52386
  const bundledKey = Redacted.make("cap_pk_test_DXF9C0HPKB9GPQX5C6RDP6NXKZG1B0D7");
52333
- const bundledOrigin = "https://site.preview.abuusama.dev";
52387
+ const bundledOrigin = "https://api.staging.capxul.com";
52334
52388
  const defaultHost = "https://us.i.posthog.com";
52335
52389
  const defaultProjectToken = Redacted.make("phc_BZuAVofrkNXzwpJkuDG7Rdfurir45fj2dudQvJZk847e");
52336
52390
  const optionalText$1 = (name) => Config.option(Config.string(name));
@@ -52728,10 +52782,10 @@ function observeInvocation(input, work) {
52728
52782
  });
52729
52783
  yield* Effect.annotateCurrentSpan(completion);
52730
52784
  yield* logEngineeringInfo("cli.command.completed", completion);
52731
- for (const diagnostic of sdkDiagnostics) yield* Effect.logInfo("cli.sdk.operation.failed").pipe(Effect.annotateLogs({
52785
+ for (const diagnostic of sdkDiagnostics) yield* logEngineeringInfo("cli.sdk.operation.failed", {
52732
52786
  ...engineeringEnvelope,
52733
52787
  ...diagnostic
52734
- }));
52788
+ });
52735
52789
  const metricAttributes = {
52736
52790
  command: input.command,
52737
52791
  capability: input.capability,
@@ -52921,6 +52975,39 @@ function metadataAtPath(root, path) {
52921
52975
  };
52922
52976
  }
52923
52977
  //#endregion
52978
+ //#region src/runtime/format.ts
52979
+ const jsonFlag = Flag.boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Print a versioned JSON result"));
52980
+ const JsonOutput = GlobalFlag.setting("json")({ flag: jsonFlag });
52981
+ /** Local diagnostics require an explicit debug level. */
52982
+ const localDiagnosticsEnabled = (args) => {
52983
+ const terminator = args.indexOf("--");
52984
+ const flags = terminator < 0 ? args : args.slice(0, terminator);
52985
+ let level;
52986
+ for (const [index, arg] of flags.entries()) if (arg.startsWith("--log-level=")) level = arg.slice(12);
52987
+ else if (arg.startsWith("--logLevel=")) level = arg.slice(11);
52988
+ else if (arg === "--log-level" || arg === "--logLevel") level = flags[index + 1];
52989
+ return level === "debug";
52990
+ };
52991
+ /** Negotiate only the global format before native CLI error rendering.
52992
+ * Native Flag decoding remains the value authority; the command runner parses everything else.
52993
+ */
52994
+ const outputMode = (args) => Effect.gen(function* () {
52995
+ const terminator = args.indexOf("--");
52996
+ const flags = terminator < 0 ? args : args.slice(0, terminator);
52997
+ const values = [];
52998
+ for (const [index, arg] of flags.entries()) if (arg === "--no-json") values.push("false");
52999
+ else if (arg.startsWith("--json=")) values.push(arg.slice(7));
53000
+ else if (arg === "--json") {
53001
+ const next = flags[index + 1];
53002
+ const value = next === void 0 ? "true" : yield* Primitive.boolean.parse(next).pipe(Effect.as(next), Effect.catch(() => Effect.succeed("true")));
53003
+ values.push(value);
53004
+ }
53005
+ return yield* jsonFlag.parse({
53006
+ flags: { json: values },
53007
+ arguments: []
53008
+ }).pipe(Effect.map(([, json]) => json ? "json" : "human"), Effect.catch(() => Effect.succeed(values.includes("true") ? "json" : "human")));
53009
+ });
53010
+ //#endregion
52924
53011
  //#region src/runtime/run-command.ts
52925
53012
  var CliRuntime = class extends Context.Service()("capxul-cli/Runtime") {};
52926
53013
  const acquireInvocationIdentity = (config, eligible) => Effect.gen(function* () {
@@ -52942,9 +53029,10 @@ const runInvocation = (runtime, metadata, kind, work, network = false) => Effect
52942
53029
  yield* Ref.set(runtime.current, invocation);
52943
53030
  const config = runtime.observation;
52944
53031
  const identity = yield* acquireInvocationIdentity(config, !silent);
53032
+ const debug = localDiagnosticsEnabled(runtime.args ?? []);
52945
53033
  let dropped = 0;
52946
53034
  let engineeringDropped = 0;
52947
- return yield* observeInvocation({
53035
+ const observed = observeInvocation({
52948
53036
  command: metadata.name,
52949
53037
  capability: metadata.capability,
52950
53038
  kind: invocationKind,
@@ -52984,7 +53072,9 @@ const runInvocation = (runtime, metadata, kind, work, network = false) => Effect
52984
53072
  onFinalize
52985
53073
  });
52986
53074
  return yield* work(host, onFinalize);
52987
- })).pipe(Effect.onExit(() => {
53075
+ }));
53076
+ return yield* (debug ? observed : observed.pipe(Effect.provideService(References.MinimumLogLevel, "None"))).pipe(Effect.onExit(() => {
53077
+ if (!debug) return Effect.void;
52988
53078
  const notice = (dropped > 0 ? `Telemetry dropped ${dropped} records.\n` : "") + (engineeringDropped > 0 ? `Engineering telemetry dropped ${engineeringDropped} items.\n` : "");
52989
53079
  return notice === "" ? Effect.void : writeStderr(notice).pipe(Effect.catchCause(() => Effect.void));
52990
53080
  }));
@@ -53006,8 +53096,12 @@ const runCommand = (options, work) => Effect.gen(function* () {
53006
53096
  //#region src/platform/capxul.ts
53007
53097
  const resultValue = (result) => result.ok ? Effect.succeed(result.value) : Effect.fail(result.error);
53008
53098
  const acquireClient = (input, onFinalize) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
53099
+ const runtime = yield* CliRuntime;
53009
53100
  const created = yield* restore(Effect.promise(async (signal) => {
53010
- const result = await createCapxulClient(input);
53101
+ const result = await createCapxulClient({
53102
+ ...input,
53103
+ localDiagnostics: localDiagnosticsEnabled(runtime.args ?? [])
53104
+ });
53011
53105
  if (signal.aborted && result.ok) await result.value.close();
53012
53106
  return result;
53013
53107
  }));
@@ -53166,11 +53260,11 @@ const CurrentSession = Schema.Struct({
53166
53260
  authUserId: Schema.NonEmptyString
53167
53261
  }))
53168
53262
  });
53169
- const decode$1 = (value) => Schema.decodeUnknownEffect(Schema.fromJsonString(CurrentSession), { onExcessProperty: "error" })(value).pipe(Effect.mapError(() => new StateFileError({ reason: "Corrupt" })));
53263
+ const decode$2 = (value) => Schema.decodeUnknownEffect(Schema.fromJsonString(CurrentSession), { onExcessProperty: "error" })(value).pipe(Effect.mapError(() => new StateFileError({ reason: "Corrupt" })));
53170
53264
  const readCurrentSession = (config) => Effect.gen(function* () {
53171
53265
  const { directory, scope } = yield* privateRecordScope(config, "current-session", "current");
53172
53266
  const value = yield* readPrivateRecord(directory, scope);
53173
- return value === null ? null : (yield* decode$1(value)).current;
53267
+ return value === null ? null : (yield* decode$2(value)).current;
53174
53268
  });
53175
53269
  const rememberCurrentSession = (config, session, stillAuthenticated) => Effect.gen(function* () {
53176
53270
  const { directory, scope } = yield* privateRecordScope(config, "current-session", "current");
@@ -53183,7 +53277,7 @@ const rememberCurrentSession = (config, session, stillAuthenticated) => Effect.g
53183
53277
  };
53184
53278
  for (let attempt = 0; attempt < 2; attempt += 1) {
53185
53279
  const expected = yield* readPrivateRecord(directory, scope);
53186
- if (expected !== null) yield* decode$1(expected);
53280
+ if (expected !== null) yield* decode$2(expected);
53187
53281
  if (yield* compareAndSetPrivateRecord(directory, scope, expected, JSON.stringify(next), stillAuthenticated)) return;
53188
53282
  if (!(yield* stillAuthenticated)) break;
53189
53283
  }
@@ -53193,7 +53287,7 @@ const forgetCurrentSession = (config, email, stillCleared) => Effect.gen(functio
53193
53287
  const { directory, scope } = yield* privateRecordScope(config, "current-session", "current");
53194
53288
  const expected = yield* readPrivateRecordSnapshot(directory, scope);
53195
53289
  if (expected === null) return false;
53196
- if ((yield* decode$1(expected)).current?.email !== email.toLowerCase()) return false;
53290
+ if ((yield* decode$2(expected)).current?.email !== email.toLowerCase()) return false;
53197
53291
  return yield* compareAndSetPrivateRecord(directory, scope, expected, JSON.stringify({
53198
53292
  version: 1,
53199
53293
  current: null
@@ -53210,48 +53304,118 @@ const inputRequired = (message) => new CliFailure({
53210
53304
  const readEmail = (email) => Effect.gen(function* () {
53211
53305
  const runtime = yield* CliRuntime;
53212
53306
  if (email === void 0) {
53213
- if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* Effect.fail(inputRequired("Provide --email. Use auth send, then auth verify --otp-stdin for non-interactive login."));
53307
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* Effect.fail(inputRequired("Provide --email."));
53214
53308
  email = yield* Prompt.text({ message: "Email" });
53215
53309
  }
53216
53310
  const normalized = email.trim().toLowerCase();
53217
53311
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized)) return yield* Effect.fail(inputRequired("Provide a valid email."));
53218
53312
  return normalized;
53219
53313
  });
53314
+ const OTP_INPUT_ERROR = "OTP input must contain six digits only.";
53315
+ const OTP_BOUNDARY_MARKS = /^[\s\p{Cf}]+|[\s\p{Cf}]+$/gu;
53316
+ const normalizeOtpInput = (input) => {
53317
+ if (new TextEncoder().encode(input).byteLength > 64) return null;
53318
+ const normalized = input.replace(OTP_BOUNDARY_MARKS, "");
53319
+ return /^[0-9]{6}$/.test(normalized) ? normalized : null;
53320
+ };
53220
53321
  const readOtp = (stdin) => Effect.gen(function* () {
53221
53322
  const runtime = yield* CliRuntime;
53222
53323
  if (!stdin) {
53223
53324
  if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* Effect.fail(inputRequired("Provide the OTP on stdin with --otp-stdin; OTP arguments are not accepted."));
53224
- return Redacted.value(yield* Prompt.hidden({ message: "Email verification code" }));
53325
+ return Redacted.value(yield* Prompt.password({
53326
+ message: "Email OTP",
53327
+ validate: (input) => {
53328
+ const normalized = normalizeOtpInput(input);
53329
+ return normalized === null ? Effect.fail(OTP_INPUT_ERROR) : Effect.succeed(normalized);
53330
+ }
53331
+ }));
53225
53332
  }
53226
53333
  const stdio = yield* Stdio.Stdio;
53227
53334
  let length = 0;
53228
53335
  const chunks = yield* stdio.stdin.pipe(Stream.mapEffect((chunk) => {
53229
53336
  length += chunk.byteLength;
53230
- return length > 64 ? Effect.fail(inputRequired("OTP input must contain six digits only.")) : Effect.succeed(chunk);
53337
+ return length > 64 ? Effect.fail(inputRequired(OTP_INPUT_ERROR)) : Effect.succeed(chunk);
53231
53338
  }), Stream.decodeText(), Stream.runCollect);
53232
- const code = Array.from(chunks).join("").trim();
53233
- if (!/^\d{6}$/.test(code)) return yield* Effect.fail(inputRequired("OTP input must contain six digits only."));
53339
+ const code = normalizeOtpInput(Array.from(chunks).join(""));
53340
+ if (code === null) return yield* Effect.fail(inputRequired(OTP_INPUT_ERROR));
53234
53341
  return code;
53235
53342
  });
53236
- const readRequired = (label, flag, supplied) => Effect.gen(function* () {
53237
- const runtime = yield* CliRuntime;
53238
- let value = supplied;
53239
- if (value === void 0) {
53240
- if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* Effect.fail(inputRequired(`Provide --${flag}.`));
53241
- value = yield* Prompt.text({ message: label });
53343
+ const PROFILE_FLAGS = {
53344
+ displayName: "--display-name",
53345
+ country: "--country",
53346
+ handle: "--handle"
53347
+ };
53348
+ const PROFILE_REFUSALS = {
53349
+ displayName: "Provide --display-name with at least one visible character.",
53350
+ country: "Provide --country as a two-letter ISO country code, for example GH.",
53351
+ handle: "Provide --handle as 3 to 30 characters of a-z, 0-9, underscore, or hyphen."
53352
+ };
53353
+ /**
53354
+ * Accept one Profile value, or return the refusal that names its field.
53355
+ *
53356
+ * Country and handle use the public SDK constructors, so the CLI accepts
53357
+ * exactly the values the backend stores. A handle loses its leading `@` and
53358
+ * its case before the check, which is the canon spelling the owner persists.
53359
+ */
53360
+ const acceptPersonField = (field, raw) => {
53361
+ const trimmed = raw.trim();
53362
+ if (trimmed.length === 0) return { refusal: inputRequired(PROFILE_REFUSALS[field]) };
53363
+ try {
53364
+ if (field === "country") return { value: toCountryCode(trimmed) };
53365
+ if (field === "handle") return { value: toHandle(trimmed.replace(/^@/u, "").toLowerCase()) };
53366
+ return { value: trimmed };
53367
+ } catch {
53368
+ return { refusal: inputRequired(PROFILE_REFUSALS[field]) };
53242
53369
  }
53243
- const trimmed = value.trim();
53244
- if (trimmed.length === 0) return yield* Effect.fail(inputRequired(`Provide --${flag}.`));
53245
- return trimmed;
53370
+ };
53371
+ /**
53372
+ * Validate the Profile values a caller supplied, before any client work.
53373
+ *
53374
+ * `auth signup` runs this first, so a malformed value refuses locally and
53375
+ * consumes no one-time code. An absent field stays with `readPersonInput`,
53376
+ * which prompts on a terminal and refuses headlessly.
53377
+ */
53378
+ const checkPersonFlags = (flags) => Effect.gen(function* () {
53379
+ for (const field of [
53380
+ "displayName",
53381
+ "country",
53382
+ "handle"
53383
+ ]) {
53384
+ const supplied = flags[field];
53385
+ if (supplied === void 0) continue;
53386
+ const accepted = acceptPersonField(field, supplied);
53387
+ if ("refusal" in accepted) return yield* Effect.fail(accepted.refusal);
53388
+ }
53389
+ });
53390
+ /**
53391
+ * One required Profile field.
53392
+ *
53393
+ * A non-interactive run accepts only the explicit flag, so headless signup is
53394
+ * deterministic and never prompts. An interactive run prompts, and offers the
53395
+ * existing Profile value as the default so a continuation keeps it.
53396
+ */
53397
+ const readRequired = (label, field, supplied, existing) => Effect.gen(function* () {
53398
+ const runtime = yield* CliRuntime;
53399
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") {
53400
+ if (supplied === void 0) return yield* Effect.fail(inputRequired(`Provide ${PROFILE_FLAGS[field]}.`));
53401
+ const accepted = acceptPersonField(field, supplied);
53402
+ if ("refusal" in accepted) return yield* Effect.fail(accepted.refusal);
53403
+ return accepted.value;
53404
+ }
53405
+ const fallback = supplied ?? existing;
53406
+ const value = yield* Prompt.text({
53407
+ message: label,
53408
+ ...fallback === void 0 || fallback.trim().length === 0 ? {} : { default: fallback }
53409
+ });
53410
+ const accepted = acceptPersonField(field, value);
53411
+ if ("refusal" in accepted) return yield* Effect.fail(accepted.refusal);
53412
+ return accepted.value;
53246
53413
  });
53247
53414
  const readPersonInput = (flags, profile) => Effect.gen(function* () {
53248
- const displayName = yield* readRequired("Display name", "display-name", flags.displayName ?? profile?.displayName ?? void 0);
53249
- const country = yield* readRequired("Country code", "country", flags.country ?? profile?.country ?? void 0);
53250
- const handle = yield* readRequired("Handle", "handle", flags.handle ?? profile?.handle ?? void 0);
53251
53415
  return {
53252
- displayName,
53253
- country: country.toUpperCase(),
53254
- handle
53416
+ displayName: yield* readRequired("Display name", "displayName", flags.displayName, profile?.displayName ?? void 0),
53417
+ country: yield* readRequired("Country code", "country", flags.country, profile?.country ?? void 0),
53418
+ handle: yield* readRequired("Handle", "handle", flags.handle, profile?.handle ?? void 0)
53255
53419
  };
53256
53420
  });
53257
53421
  //#endregion
@@ -53928,6 +54092,17 @@ const verifiedSession = (client, email) => Effect.gen(function* () {
53928
54092
  if (session === null || session.email.toLowerCase() !== email) return yield* Effect.fail(Errors.notAuthenticated());
53929
54093
  return session;
53930
54094
  });
54095
+ const submitOtp = (otpStdin, verify) => Effect.gen(function* () {
54096
+ const submissionLimit = otpStdin ? 1 : 3;
54097
+ for (let submission = 1; submission <= submissionLimit; submission += 1) {
54098
+ const code = yield* readOtp(otpStdin);
54099
+ const result = yield* Effect.result(verify(code));
54100
+ if (Result.isSuccess(result)) return;
54101
+ const error = result.failure;
54102
+ if (!(error.code === "INVALID_INPUT" && error.details?.field === "otp") || submission === submissionLimit) return yield* Effect.fail(error);
54103
+ yield* writeStderr("Invalid OTP. Try again.\n");
54104
+ }
54105
+ });
53931
54106
  /**
53932
54107
  * Local authentication checks only: the current-session pointer, the explicit
53933
54108
  * email, and a stored credential. No bootstrap, client, or network work. A
@@ -54005,6 +54180,60 @@ const flushWalletDiagnostics = (bridge, invocationId) => Effect.suspend(() => br
54005
54180
  invocation_id: invocationId,
54006
54181
  journey_id: invocationId
54007
54182
  }), { discard: true }));
54183
+ const SIGNUP_OTP_INPUT_REQUIRED = "Use --otp-stdin for non-interactive signup. OTP arguments are not accepted.";
54184
+ /**
54185
+ * One account-setup continuation: profile input, then wallet and Account work.
54186
+ *
54187
+ * `signup` calls this after authentication. An interactive `login` calls it
54188
+ * only after the person accepts the setup offer. A headless `login` never calls
54189
+ * it, so sign-in cannot become signup.
54190
+ *
54191
+ * The step returns a ready report only. When the personal Account is not ready,
54192
+ * it returns a typed recoverable report: `setupState` names the state, the
54193
+ * lifecycle carries the failure code, and the copy names the exact resume
54194
+ * action. The step never starts wallet work for a ready Account, so a retry
54195
+ * reuses the one Profile, wallet, Smart Account, and Account.
54196
+ */
54197
+ const completeAccountSetup = (config, store, client, session, suppliedProfile) => Effect.gen(function* () {
54198
+ const current = yield* call((signal) => client.identity.loadCurrent({ signal }));
54199
+ const before = yield* profileReport(client, session);
54200
+ if (before.data.setupState === "ready") return before;
54201
+ const profile = yield* readPersonInput(suppliedProfile, current);
54202
+ const runtime = yield* CliRuntime;
54203
+ const run = Effect.runPromiseWith(yield* Effect.context());
54204
+ let bridge;
54205
+ const invocation = yield* Ref.get(runtime.current);
54206
+ const openfortSignerFactory = (bootstrap) => {
54207
+ const created = createBrowserSignerBridge({
54208
+ bootstrap,
54209
+ environment: config.environment,
54210
+ handle: () => profile.handle,
54211
+ trustedAuthOrigin: new URL(config.bootstrapBaseUrl).origin,
54212
+ sessionToken: () => run(store.tokenIfCurrent())
54213
+ });
54214
+ bridge = created;
54215
+ invocation?.onFinalize?.(Effect.promise(() => created.close()).pipe(Effect.asVoid, Effect.catchCause(() => Effect.void)));
54216
+ return created.signer;
54217
+ };
54218
+ const walletClient = yield* clientFor(config, store, {
54219
+ restore: true,
54220
+ requirement: "deployed",
54221
+ openfortSignerFactory
54222
+ });
54223
+ const verified = yield* verifiedSession(walletClient, session.email);
54224
+ yield* writeStderr("Preparing your wallet in the browser. Keep that tab open.\n");
54225
+ const report = yield* Effect.gen(function* () {
54226
+ yield* call((signal) => walletClient.onboarding.completePerson(profile, { signal }));
54227
+ return yield* profileReport(walletClient, verified);
54228
+ }).pipe(Effect.ensuring(Effect.suspend(() => flushWalletDiagnostics(bridge, runtime.invocationId))));
54229
+ return {
54230
+ data: {
54231
+ ...report.data,
54232
+ signerStatus: walletClient.signer.status()
54233
+ },
54234
+ lines: report.lines
54235
+ };
54236
+ });
54008
54237
  const sendOtp = (config, suppliedEmail) => Effect.gen(function* () {
54009
54238
  const email = yield* readEmail(suppliedEmail);
54010
54239
  const store = yield* openSessionRecord(config, email);
@@ -54029,7 +54258,6 @@ const verifyOtp = (config, suppliedEmail, otpStdin) => Effect.gen(function* () {
54029
54258
  const store = yield* openSessionRecord(config, email);
54030
54259
  const flow = store.current().flow;
54031
54260
  if (flow === null || flow.email !== email) return yield* Effect.fail(inputRequired("No pending OTP. Run auth send --email <email> first."));
54032
- const code = yield* readOtp(otpStdin);
54033
54261
  const client = yield* clientFor(config, store, { restore: false });
54034
54262
  yield* call((signal) => client.auth.getSession({ signal }));
54035
54263
  yield* call((signal) => client.auth.resumeOtp({
@@ -54039,13 +54267,13 @@ const verifyOtp = (config, suppliedEmail, otpStdin) => Effect.gen(function* () {
54039
54267
  signal,
54040
54268
  journey_id: flow.journeyId
54041
54269
  }));
54042
- yield* call((signal) => client.auth.verifyOtp({
54270
+ yield* submitOtp(otpStdin, (code) => call((signal) => client.auth.verifyOtp({
54043
54271
  email,
54044
54272
  code
54045
54273
  }, {
54046
54274
  signal,
54047
54275
  journey_id: flow.journeyId
54048
- }));
54276
+ })));
54049
54277
  const session = yield* verifiedSession(client, email);
54050
54278
  yield* bindPerson(config, store, session);
54051
54279
  const report = yield* profileReport(client, session);
@@ -54060,95 +54288,64 @@ const verifyOtp = (config, suppliedEmail, otpStdin) => Effect.gen(function* () {
54060
54288
  const login = (config, suppliedEmail, otpStdin) => Effect.gen(function* () {
54061
54289
  const email = yield* readEmail(suppliedEmail);
54062
54290
  const runtime = yield* CliRuntime;
54063
- if (!otpStdin && (!runtime.tty || runtime.ci || runtime.mode === "json")) return yield* Effect.fail(inputRequired("Use auth send, then auth verify --otp-stdin. Interactive login reads the OTP without echo."));
54291
+ if (!otpStdin && (!runtime.tty || runtime.ci || runtime.mode === "json")) return yield* Effect.fail(inputRequired("Provide the OTP on stdin with --otp-stdin. Use capxul auth send, then capxul auth verify, to split sign-in across two commands."));
54064
54292
  const store = yield* openSessionRecord(config, email);
54065
54293
  const client = yield* clientFor(config, store, { restore: false });
54066
54294
  yield* call((signal) => client.auth.getSession({ signal }));
54067
54295
  yield* store.begin(yield* Clock.currentTimeMillis, runtime.invocationId);
54068
54296
  yield* call((signal) => client.auth.signIn({ email }, { signal }));
54069
54297
  yield* writeStderr("Verification email sent. Enter the code to continue.\n");
54070
- const code = yield* readOtp(otpStdin);
54071
- yield* call((signal) => client.auth.verifyOtp({
54298
+ yield* submitOtp(otpStdin, (code) => call((signal) => client.auth.verifyOtp({
54072
54299
  email,
54073
54300
  code
54074
54301
  }, {
54075
54302
  signal,
54076
54303
  journey_id: runtime.invocationId
54077
- }));
54304
+ })));
54078
54305
  const session = yield* verifiedSession(client, email);
54079
54306
  yield* bindPerson(config, store, session);
54080
- return yield* profileReport(client, session);
54307
+ const report = yield* profileReport(client, session);
54308
+ if (report.data.setupState === "ready") return report;
54309
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") return report;
54310
+ if (!(yield* Prompt.confirm({
54311
+ message: "Your Capxul account is not set up yet. Continue setup now?",
54312
+ initial: false
54313
+ }))) return report;
54314
+ return yield* completeAccountSetup(config, store, client, session, {});
54081
54315
  });
54082
54316
  const signup = (config, suppliedEmail, otpStdin, suppliedProfile) => Effect.gen(function* () {
54083
54317
  const email = yield* readEmail(suppliedEmail);
54084
54318
  const runtime = yield* CliRuntime;
54319
+ yield* checkPersonFlags(suppliedProfile);
54085
54320
  const store = yield* openSessionRecord(config, email);
54086
- const run = Effect.runPromiseWith(yield* Effect.context());
54087
54321
  const restore = store.current().token !== null;
54088
- let handle = suppliedProfile.handle ?? "personal";
54089
- let bridge;
54322
+ if (!restore && !otpStdin && (!runtime.tty || runtime.ci || runtime.mode === "json")) return yield* Effect.fail(inputRequired(SIGNUP_OTP_INPUT_REQUIRED));
54090
54323
  let client = yield* clientFor(config, store, { restore });
54091
54324
  let session = restore ? yield* verifiedSession(client, email).pipe(Effect.catch((error) => error.code === "NOT_AUTHENTICATED" ? store.clear.pipe(Effect.as(null)) : Effect.fail(error))) : null;
54092
54325
  if (session === null) {
54093
54326
  if (restore) client = yield* clientFor(config, store, { restore: false });
54094
- if (!otpStdin && (!runtime.tty || runtime.ci || runtime.mode === "json")) return yield* Effect.fail(inputRequired("Use --otp-stdin for non-interactive signup. OTP arguments are not accepted."));
54327
+ if (!otpStdin && (!runtime.tty || runtime.ci || runtime.mode === "json")) return yield* Effect.fail(inputRequired(SIGNUP_OTP_INPUT_REQUIRED));
54095
54328
  yield* call((signal) => client.auth.getSession({ signal }));
54096
54329
  yield* store.begin(yield* Clock.currentTimeMillis, runtime.invocationId);
54097
54330
  yield* call((signal) => client.auth.signIn({ email }, { signal }));
54098
54331
  yield* writeStderr("Verification email sent. Enter the code to continue.\n");
54099
- const code = yield* readOtp(otpStdin);
54100
- yield* call((signal) => client.auth.verifyOtp({
54332
+ yield* submitOtp(otpStdin, (code) => call((signal) => client.auth.verifyOtp({
54101
54333
  email,
54102
54334
  code
54103
54335
  }, {
54104
54336
  signal,
54105
54337
  journey_id: runtime.invocationId
54106
- }));
54338
+ })));
54107
54339
  session = yield* verifiedSession(client, email);
54108
54340
  }
54109
54341
  yield* bindPerson(config, store, session);
54110
- const current = yield* call((signal) => client.identity.loadCurrent({ signal }));
54111
- const before = yield* profileReport(client, session);
54112
- if (before.data.setupState === "ready") return before;
54113
- const profile = yield* readPersonInput(suppliedProfile, current);
54114
- handle = profile.handle;
54115
- const invocation = yield* Ref.get(runtime.current);
54116
- const openfortSignerFactory = (bootstrap) => {
54117
- const created = createBrowserSignerBridge({
54118
- bootstrap,
54119
- environment: config.environment,
54120
- handle: () => handle,
54121
- trustedAuthOrigin: new URL(config.bootstrapBaseUrl).origin,
54122
- sessionToken: () => run(store.tokenIfCurrent())
54123
- });
54124
- bridge = created;
54125
- invocation?.onFinalize?.(Effect.promise(() => created.close()).pipe(Effect.asVoid, Effect.catchCause(() => Effect.void)));
54126
- return created.signer;
54127
- };
54128
- const walletClient = yield* clientFor(config, store, {
54129
- restore: true,
54130
- requirement: "deployed",
54131
- openfortSignerFactory
54132
- });
54133
- session = yield* verifiedSession(walletClient, email);
54134
- yield* writeStderr("Preparing your wallet in the browser. Keep that tab open.\n");
54135
- const report = yield* Effect.gen(function* () {
54136
- yield* call((signal) => walletClient.onboarding.completePerson(profile, { signal }));
54137
- return yield* profileReport(walletClient, session);
54138
- }).pipe(Effect.ensuring(Effect.suspend(() => flushWalletDiagnostics(bridge, runtime.invocationId))));
54139
- return {
54140
- data: {
54141
- ...report.data,
54142
- signerStatus: walletClient.signer.status()
54143
- },
54144
- lines: report.lines
54145
- };
54342
+ return yield* completeAccountSetup(config, store, client, session, suppliedProfile);
54146
54343
  });
54147
54344
  const readProfile = (config, suppliedEmail) => Effect.gen(function* () {
54148
54345
  const email = yield* readEmail(suppliedEmail);
54149
54346
  const store = yield* openSessionRecord(config, email);
54150
54347
  if (store.current().token === null) {
54151
- if (usableGrant(yield* loadGrant(config, email))) return yield* Effect.fail(inputRequired("A legacy account:read grant is stored. Run auth login or auth send/verify to explicitly establish a broader first-party session."));
54348
+ if (usableGrant(yield* loadGrant(config, email))) return yield* Effect.fail(inputRequired("Sign in again with capxul auth login to use this command."));
54152
54349
  return yield* Effect.fail(Errors.notAuthenticated());
54153
54350
  }
54154
54351
  const client = yield* clientFor(config, store, { restore: true });
@@ -54207,11 +54404,11 @@ const logout = (config, suppliedEmail) => Effect.gen(function* () {
54207
54404
  });
54208
54405
  //#endregion
54209
54406
  //#region src/commands/auth.ts
54210
- const emailFlag$3 = Flag.string("email").pipe(Flag.withDescription("Account email"), Flag.optional);
54211
- const otpStdinFlag = Flag.boolean("otp-stdin").pipe(Flag.withDefault(false), Flag.withDescription("Read the six-digit OTP from stdin; never pass it as an argument"));
54212
- const displayNameFlag = Flag.string("display-name").pipe(Flag.withDescription("Profile display name"), Flag.optional);
54213
- const countryFlag = Flag.string("country").pipe(Flag.withDescription("Two-letter ISO country code"), Flag.optional);
54214
- const handleFlag = Flag.string("handle").pipe(Flag.withDescription("Globally unique Capxul handle"), Flag.optional);
54407
+ const emailFlag$3 = Flag.string("email").pipe(Flag.withDescription("Email address"), Flag.optional);
54408
+ const otpStdinFlag = Flag.boolean("otp-stdin").pipe(Flag.withDefault(false), Flag.withDescription("Read the six-digit OTP from standard input; never pass it as an argument"));
54409
+ const displayNameFlag = Flag.string("display-name").pipe(Flag.withDescription("Your display name"), Flag.optional);
54410
+ const countryFlag = Flag.string("country").pipe(Flag.withDescription("Two-letter country code"), Flag.optional);
54411
+ const handleFlag = Flag.string("handle").pipe(Flag.withDescription("Your unique Capxul handle"), Flag.optional);
54215
54412
  const metadata$4 = (name) => ({
54216
54413
  name,
54217
54414
  capability: "authentication",
@@ -54223,7 +54420,7 @@ const loginCommand = Command.make("login", {
54223
54420
  }, ({ email, otpStdin }) => runCommand({
54224
54421
  ...metadata$4("auth.login"),
54225
54422
  network: true
54226
- }, (config) => login(config, Option.getOrUndefined(email), otpStdin))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.login")), Command.withDescription("Establish a first-party session with terminal-owned email OTP"));
54423
+ }, (config) => login(config, Option.getOrUndefined(email), otpStdin))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.login")), Command.withDescription("Sign in with your email and a one-time code"));
54227
54424
  const signupCommand = Command.make("signup", {
54228
54425
  email: emailFlag$3,
54229
54426
  otpStdin: otpStdinFlag,
@@ -54237,31 +54434,31 @@ const signupCommand = Command.make("signup", {
54237
54434
  displayName: Option.getOrUndefined(displayName),
54238
54435
  country: Option.getOrUndefined(country),
54239
54436
  handle: Option.getOrUndefined(handle)
54240
- }))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.signup")), Command.withDescription("Verify email and complete Profile, Openfort wallet, and Account setup"));
54437
+ }))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.signup")), Command.withDescription("Create your Capxul account and wallet"));
54241
54438
  const sendCommand = Command.make("send", { email: emailFlag$3 }, ({ email }) => runCommand({
54242
54439
  ...metadata$4("auth.send"),
54243
54440
  network: true
54244
- }, (config) => sendOtp(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.send")), Command.withDescription("Send an OTP and save non-secret continuation context"));
54441
+ }, (config) => sendOtp(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.send")), Command.withDescription("Send a sign-in code to your email"));
54245
54442
  const verifyCommand = Command.make("verify", {
54246
54443
  email: emailFlag$3,
54247
54444
  otpStdin: otpStdinFlag
54248
54445
  }, ({ email, otpStdin }) => runCommand({
54249
54446
  ...metadata$4("auth.verify"),
54250
54447
  network: true
54251
- }, (config) => verifyOtp(config, Option.getOrUndefined(email), otpStdin))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.verify")), Command.withDescription("Verify a delivered OTP and persist the first-party session"));
54448
+ }, (config) => verifyOtp(config, Option.getOrUndefined(email), otpStdin))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.verify")), Command.withDescription("Finish signing in with the code from your email"));
54252
54449
  const profileCommand = Command.make("profile", { email: emailFlag$3 }, ({ email }) => runCommand({
54253
54450
  ...metadata$4("auth.profile"),
54254
54451
  network: true
54255
- }, (config) => readProfile(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.profile")), Command.withDescription("Restore the session and read the backend profile and account lifecycle"));
54452
+ }, (config) => readProfile(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.profile")), Command.withDescription("Show your profile and account readiness"));
54256
54453
  const statusCommand = Command.make("status", { email: emailFlag$3 }, ({ email }) => runCommand({
54257
54454
  ...metadata$4("auth.status"),
54258
54455
  network: true
54259
- }, (config) => status$2(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.status")), Command.withDescription("Validate a first-party session or show a tagged legacy grant"));
54456
+ }, (config) => status$2(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.status")), Command.withDescription("Show whether you are signed in"));
54260
54457
  const logoutCommand = Command.make("logout", { email: emailFlag$3 }, ({ email }) => runCommand({
54261
54458
  ...metadata$4("auth.logout"),
54262
54459
  network: true
54263
- }, (config) => logout(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.logout")), Command.withDescription("Forget local credentials and revoke their remote session or grant"));
54264
- const auth = Command.make("auth").pipe(Command.annotate(CliCommandMetadata, metadata$4("auth")), Command.withDescription("First-party email sessions (broader authority than legacy account:read grants)"), Command.withSubcommands([
54460
+ }, (config) => logout(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.logout")), Command.withDescription("Sign out on this device"));
54461
+ const auth = Command.make("auth").pipe(Command.annotate(CliCommandMetadata, metadata$4("auth")), Command.withDescription("Sign in, create your account, and manage your session"), Command.withSubcommands([
54265
54462
  loginCommand,
54266
54463
  signupCommand,
54267
54464
  sendCommand,
@@ -54334,6 +54531,73 @@ const telemetry = Command.make("telemetry").pipe(Command.annotate(CliCommandMeta
54334
54531
  disable
54335
54532
  ]));
54336
54533
  //#endregion
54534
+ //#region src/org-recovery.ts
54535
+ /**
54536
+ * The one safe recovery handle for a person's Organization journey.
54537
+ *
54538
+ * `handle` is the exact business handle the create prepared. It is the only
54539
+ * identity the CLI knows while the first durable Organization write is in
54540
+ * flight, and the backend resumes the same Organization for that handle.
54541
+ * `organizationId` is added from the first committed Organization. After that
54542
+ * the ID is the recovery target.
54543
+ *
54544
+ * A record with no handle releases the journey: the backend refused that exact
54545
+ * handle before it wrote any Organization row, so no Organization waits to be
54546
+ * resumed. The record stores no draft, submission, credential, or token.
54547
+ */
54548
+ const Recovery = Schema.Struct({
54549
+ version: Schema.Literal(1),
54550
+ handle: Schema.optional(Schema.NonEmptyString),
54551
+ organizationId: Schema.optional(Schema.NonEmptyString)
54552
+ });
54553
+ const decode$1 = (value) => Schema.decodeUnknownEffect(Schema.fromJsonString(Recovery), { onExcessProperty: "error" })(value).pipe(Effect.flatMap((record) => {
54554
+ const organizationId = record.organizationId ?? null;
54555
+ if (record.handle === void 0) return organizationId === null ? Effect.succeed(null) : Effect.fail(new StateFileError({ reason: "Corrupt" }));
54556
+ const handle = record.handle;
54557
+ return Effect.try(() => ({
54558
+ handle: String(toHandle(handle)),
54559
+ organizationId: organizationId === null ? null : toOrgId(organizationId)
54560
+ }));
54561
+ }), Effect.mapError(() => new StateFileError({ reason: "Corrupt" })));
54562
+ const encode = (recovery) => JSON.stringify({
54563
+ version: 1,
54564
+ handle: recovery.handle,
54565
+ ...recovery.organizationId === null ? {} : { organizationId: String(recovery.organizationId) }
54566
+ });
54567
+ /** The released shape: no handle waits to be resumed. */
54568
+ const RELEASED = JSON.stringify({ version: 1 });
54569
+ /** Read the safe recovery handle without writing. A corrupt record refuses the command. */
54570
+ const readOrganizationRecovery = (config, authUserId) => Effect.gen(function* () {
54571
+ const { directory, scope } = yield* privateRecordScope(config, "organization-recovery", authUserId);
54572
+ const value = yield* readPrivateRecord(directory, scope);
54573
+ return value === null ? null : yield* decode$1(value);
54574
+ });
54575
+ /**
54576
+ * Open the recovery handle for one command.
54577
+ *
54578
+ * `save` and `release` replace only the exact record this command read, so two
54579
+ * commands cannot overwrite each other's Organization journey, and a delayed
54580
+ * write cannot follow a newer one. A record this command did not read refuses
54581
+ * the write.
54582
+ */
54583
+ const openOrganizationRecovery = (config, authUserId, stillAuthenticated) => Effect.gen(function* () {
54584
+ const { directory, scope } = yield* privateRecordScope(config, "organization-recovery", authUserId);
54585
+ let encoded = yield* readPrivateRecord(directory, scope);
54586
+ let record = encoded === null ? null : yield* decode$1(encoded);
54587
+ const write = (next) => Effect.gen(function* () {
54588
+ const value = next === null ? RELEASED : encode(next);
54589
+ if (value === encoded) return;
54590
+ if (!(yield* compareAndSetPrivateRecord(directory, scope, encoded, value, stillAuthenticated))) return yield* Effect.fail(Errors.notAuthenticated("The local Organization recovery state changed. Retry the command."));
54591
+ encoded = value;
54592
+ record = next;
54593
+ });
54594
+ return {
54595
+ current: () => record,
54596
+ save: (next) => write(next),
54597
+ release: () => write(null)
54598
+ };
54599
+ });
54600
+ //#endregion
54337
54601
  //#region src/org-selection.ts
54338
54602
  const Selection = Schema.Struct({
54339
54603
  version: Schema.Literal(1),
@@ -54356,6 +54620,37 @@ const saveOrganizationSelection = (config, authUserId, organizationId, stillAuth
54356
54620
  });
54357
54621
  //#endregion
54358
54622
  //#region src/org.ts
54623
+ /**
54624
+ * The exact Organization a read uses when the caller named no ID: the saved read
54625
+ * default first, then the recorded recovery handle for this person's
54626
+ * Organization journey. `recoverable` marks the handle source, whose Organization
54627
+ * may still run its setup lane and be absent from the own-access list.
54628
+ */
54629
+ const readResolvedOrganization = (config, authUserId, organizationId) => Effect.gen(function* () {
54630
+ if (organizationId !== null) return {
54631
+ organizationId,
54632
+ recoverable: false
54633
+ };
54634
+ const selected = yield* readOrganizationSelection(config, authUserId);
54635
+ if (selected !== null) return {
54636
+ organizationId: selected,
54637
+ recoverable: false
54638
+ };
54639
+ const recovery = yield* readOrganizationRecovery(config, authUserId);
54640
+ return {
54641
+ organizationId: recovery?.organizationId ?? null,
54642
+ recoverable: recovery?.organizationId !== null && recovery?.organizationId !== void 0
54643
+ };
54644
+ });
54645
+ /**
54646
+ * A ready Organization this command reports must satisfy current access.
54647
+ *
54648
+ * The own-access list carries a founder's unfinished Organizations through the
54649
+ * backend's founder fallback, and the backend lifecycle read admits the founder
54650
+ * for the durable lane. A confirmed result therefore reads the list again: an
54651
+ * Organization the person cannot reach refuses instead of reporting its state.
54652
+ */
54653
+ const requireCurrentAccess = (client, organizationId, lifecycle) => lifecycle.status !== "ready" ? Effect.void : Effect.promise((signal) => client.orgs({ signal })).pipe(Effect.flatMap(resultValue), Effect.flatMap((organizations) => organizations.some((organization) => organization.id === organizationId) ? Effect.void : Effect.fail(Errors.invalidInput("org", "The Organization is not accessible."))));
54359
54654
  const readOrganization = (config, command, input) => Effect.gen(function* () {
54360
54655
  const timeout = yield* Schema.decodeUnknownEffect(Schema.Int.check(Schema.isBetween({
54361
54656
  minimum: 1,
@@ -54382,7 +54677,8 @@ const readOrganization = (config, command, input) => Effect.gen(function* () {
54382
54677
  address: org.account.address
54383
54678
  }))
54384
54679
  };
54385
- organizationId ??= yield* readOrganizationSelection(config, session.authUserId);
54680
+ const resolved = yield* readResolvedOrganization(config, session.authUserId, organizationId);
54681
+ organizationId = resolved.organizationId;
54386
54682
  if (organizationId === null) {
54387
54683
  const runtime = yield* CliRuntime;
54388
54684
  if (!runtime.tty || runtime.ci || runtime.mode === "json" || organizations.length === 0) return yield* Effect.fail(inputRequired("Provide --org or select a read default with org use."));
@@ -54397,7 +54693,7 @@ const readOrganization = (config, command, input) => Effect.gen(function* () {
54397
54693
  }))
54398
54694
  });
54399
54695
  }
54400
- if (!organizations.some((org) => org.id === organizationId)) return yield* Effect.fail(Errors.invalidInput("org", "The Organization is not accessible."));
54696
+ if (!organizations.some((org) => org.id === organizationId) && !resolved.recoverable) return yield* Effect.fail(Errors.invalidInput("org", "The Organization is not accessible."));
54401
54697
  if (command === "use") {
54402
54698
  yield* saveOrganizationSelection(config, session.authUserId, organizationId, stillAuthenticated);
54403
54699
  return {
@@ -54450,11 +54746,13 @@ const readOrganization = (config, command, input) => Effect.gen(function* () {
54450
54746
  const read = Effect.promise((signal) => scoped.getLifecycle({ signal })).pipe(Effect.flatMap(resultValue));
54451
54747
  const observed = yield* Effect.gen(function* () {
54452
54748
  let lifecycle = yield* read;
54749
+ if (resolved.recoverable) yield* requireCurrentAccess(client, organizationId, lifecycle);
54453
54750
  if (command !== "wait") return lifecycle;
54454
54751
  while (lifecycle.status !== "ready" && lifecycle.status !== "failed") {
54455
54752
  yield* Effect.sleep(1e3);
54456
54753
  lifecycle = yield* read;
54457
54754
  }
54755
+ if (resolved.recoverable) yield* requireCurrentAccess(client, organizationId, lifecycle);
54458
54756
  return lifecycle;
54459
54757
  }).pipe(Effect.timeoutOption(timeout * 1e3));
54460
54758
  if (Option.isNone(observed)) return yield* Effect.fail(new CliFailure({
@@ -54538,45 +54836,89 @@ const requireWriteConfirmation = (supplied) => Effect.gen(function* () {
54538
54836
  *
54539
54837
  * The committed Organization ID is retained from the first state that carries
54540
54838
  * one and never replaced, so a later failed lane keeps reporting the ID it was
54541
- * committed to. `flush` never resolves a signature on an unwritten line: the
54542
- * command-scoped signer awaits it before forwarding a digest, and a failed or
54543
- * cancelled write throws here instead.
54839
+ * committed to. `flush` writes the queued lines first, then makes the ID durable
54840
+ * as this person's safe recovery handle, and only then resolves: the
54841
+ * command-scoped signer awaits it before forwarding a digest, so a failed or
54842
+ * cancelled write saves no handle and forwards no signature.
54544
54843
  */
54545
- const makeOrganizationProgress = (mode, run) => {
54844
+ const makeOrganizationProgress = (mode, run, persist) => {
54546
54845
  const queued = [];
54846
+ const held = [];
54847
+ const heldSet = /* @__PURE__ */ new Set();
54547
54848
  const written = /* @__PURE__ */ new Set();
54548
54849
  let committed = null;
54850
+ let persisted = null;
54851
+ let readyAllowed = false;
54549
54852
  let failure = null;
54853
+ /** One command owns one committed Organization. A second ID is a defect. */
54854
+ const retain = (observed) => {
54855
+ if (committed === null) {
54856
+ committed = observed;
54857
+ return true;
54858
+ }
54859
+ if (committed === observed) return true;
54860
+ failure ??= new CliFailure({
54861
+ code: "CLI_DEFECT",
54862
+ message: "The onboarding stream reported a different Organization.",
54863
+ exitCode: 1,
54864
+ outcome: "failed"
54865
+ });
54866
+ return false;
54867
+ };
54868
+ /**
54869
+ * A ready line waits until the command confirms current access. Every other
54870
+ * line is progress evidence and goes out with the committed ID.
54871
+ */
54872
+ const enqueue = (text, ready) => {
54873
+ if (written.has(text) || heldSet.has(text)) return;
54874
+ if (ready && !readyAllowed) {
54875
+ heldSet.add(text);
54876
+ held.push(text);
54877
+ return;
54878
+ }
54879
+ written.add(text);
54880
+ queued.push(text);
54881
+ };
54550
54882
  const render = (state) => {
54551
54883
  const organizationId = state.organizationId;
54552
54884
  if (organizationId === null) return null;
54553
- const observed = String(organizationId);
54554
- if (committed === null) committed = observed;
54555
- else if (committed !== observed) {
54556
- failure ??= new CliFailure({
54557
- code: "CLI_DEFECT",
54558
- message: "The onboarding stream reported a different Organization.",
54559
- exitCode: 1,
54560
- outcome: "failed"
54561
- });
54562
- return null;
54563
- }
54885
+ if (!retain(String(organizationId))) return null;
54564
54886
  const status = state.lifecycle?.status;
54565
54887
  if (status === void 0) return null;
54566
- return `${mode === "json" ? JSON.stringify({
54567
- type: "organization.lifecycle",
54568
- organizationId: committed,
54569
- status
54570
- }) : `Organization ${JSON.stringify(committed)}: ${status}`}\n`;
54888
+ return {
54889
+ text: `${mode === "json" ? JSON.stringify({
54890
+ type: "organization.lifecycle",
54891
+ organizationId: committed,
54892
+ status
54893
+ }) : `Organization ${JSON.stringify(committed)}: ${status}`}\n`,
54894
+ ready: status === "ready"
54895
+ };
54571
54896
  };
54572
54897
  return {
54573
54898
  /** Synchronous by contract: the SDK publishes state from its own fiber. */
54574
54899
  observe: (state) => {
54575
- const text = render(state);
54576
- if (text === null || written.has(text)) return;
54577
- written.add(text);
54578
- queued.push(text);
54579
- },
54900
+ const line = render(state);
54901
+ if (line !== null) enqueue(line.text, line.ready);
54902
+ },
54903
+ /** One safe recovery line the command writes before it resumes a lane. */
54904
+ notice: (text) => enqueue(text, false),
54905
+ /** The final result ID joins the same one-Organization rule. */
54906
+ commit: (organizationId) => {
54907
+ retain(organizationId);
54908
+ },
54909
+ /** Release the ready lines once current access is confirmed. */
54910
+ allowReady: () => {
54911
+ readyAllowed = true;
54912
+ while (held.length > 0) {
54913
+ const text = held.shift();
54914
+ heldSet.delete(text);
54915
+ if (written.has(text)) continue;
54916
+ written.add(text);
54917
+ queued.push(text);
54918
+ }
54919
+ },
54920
+ /** The committed ID this command observed, or null when it observed none. */
54921
+ committed: () => committed,
54580
54922
  failure: () => failure,
54581
54923
  flush: async () => {
54582
54924
  while (queued.length > 0) {
@@ -54589,6 +54931,13 @@ const makeOrganizationProgress = (mode, run) => {
54589
54931
  }
54590
54932
  queued.shift();
54591
54933
  }
54934
+ if (committed !== null && persisted !== committed) try {
54935
+ await persist(committed);
54936
+ persisted = committed;
54937
+ } catch (cause) {
54938
+ failure = cause;
54939
+ throw cause;
54940
+ }
54592
54941
  if (failure !== null) throw failure;
54593
54942
  }
54594
54943
  };
@@ -54603,15 +54952,63 @@ const confirmWrite = (preview) => Effect.gen(function* () {
54603
54952
  yield* writeStderr(`${preview.lines.join("\n")}\n`);
54604
54953
  if (!(yield* Prompt.confirm({ message: preview.question }).pipe(Effect.catch(() => Effect.succeed(false))))) return yield* Effect.fail(inputRequired("The write was not confirmed."));
54605
54954
  });
54606
- /** The Organization reads a terminal preview shows before it asks. */
54607
- const organizationPreviewLines = (client, organizationId) => organizationId === null ? Effect.gen(function* () {
54955
+ /**
54956
+ * The Organization reads a terminal preview shows before it asks.
54957
+ *
54958
+ * A create preview also names the recovery handle this machine already holds, so
54959
+ * a person who confirms knows whether the command creates one more Organization
54960
+ * or continues the exact one their interrupted journey reached. The preview
54961
+ * reads that recorded Organization's lifecycle only when the requested handle
54962
+ * continues it; a different handle is a new creation, and the recorded
54963
+ * Organization must not block it.
54964
+ */
54965
+ const organizationPreview = (client, organizationId, recovery, requestedHandle) => organizationId === null ? Effect.gen(function* () {
54608
54966
  const organizations = yield* Effect.promise((signal) => client.orgs({ signal })).pipe(Effect.flatMap(resultValue));
54609
- if (organizations.length === 0) return ["Existing Organizations: none"];
54610
- return [`Existing Organizations: ${organizations.length}`, ...organizations.map((organization) => ` ${JSON.stringify(String(organization.id))} ${JSON.stringify(organization.name)}`)];
54967
+ const lines = organizations.length === 0 ? ["Existing Organizations: none"] : [`Existing Organizations: ${organizations.length}`, ...organizations.map((organization) => ` ${JSON.stringify(String(organization.id))} ${JSON.stringify(organization.name)}`)];
54968
+ if (recovery === null) return {
54969
+ lines,
54970
+ recovery,
54971
+ recoveryLifecycle: null
54972
+ };
54973
+ const exact = recovery.organizationId;
54974
+ if (exact === null) return {
54975
+ lines: [...lines, `Recovery: a create for handle @${recovery.handle} is prepared on this machine.`],
54976
+ recovery,
54977
+ recoveryLifecycle: null
54978
+ };
54979
+ if (requestedHandle !== recovery.handle) return {
54980
+ lines: [...lines, `Recovery: Organization ${JSON.stringify(String(exact))} (@${recovery.handle}) is recorded on this machine.`],
54981
+ recovery,
54982
+ recoveryLifecycle: null
54983
+ };
54984
+ const lifecycle = yield* Effect.promise((signal) => client.org(exact).getLifecycle({ signal })).pipe(Effect.flatMap(resultValue));
54985
+ yield* requireCurrentAccess(client, exact, lifecycle);
54986
+ return {
54987
+ lines: [
54988
+ ...lines,
54989
+ `Recovery: Organization ${JSON.stringify(String(exact))} (@${recovery.handle})`,
54990
+ `Current lifecycle: ${lifecycle.status}`
54991
+ ],
54992
+ recovery,
54993
+ recoveryLifecycle: lifecycle
54994
+ };
54611
54995
  }) : Effect.gen(function* () {
54612
- return [`Current lifecycle: ${(yield* Effect.promise((signal) => client.org(organizationId).getLifecycle({ signal })).pipe(Effect.flatMap(resultValue))).status}`];
54996
+ return {
54997
+ lines: [`Current lifecycle: ${(yield* Effect.promise((signal) => client.org(organizationId).getLifecycle({ signal })).pipe(Effect.flatMap(resultValue))).status}`],
54998
+ recovery,
54999
+ recoveryLifecycle: null
55000
+ };
54613
55001
  });
54614
55002
  /**
55003
+ * The confirmation question for a create. A matching recovery handle means the
55004
+ * command continues one exact Organization instead of creating another one.
55005
+ */
55006
+ const createOrganizationQuestion = (handle, preview) => {
55007
+ if (preview.recovery === null || preview.recovery.organizationId === null) return "Create this Organization?";
55008
+ if (preview.recovery.handle !== handle) return "Create this Organization?";
55009
+ return preview.recoveryLifecycle?.status === "ready" ? "Return this ready Organization?" : "Resume this Organization's setup?";
55010
+ };
55011
+ /**
54615
55012
  * Settle one exact Organization lifecycle read: poll every second with no
54616
55013
  * overlapping read until the lane is ready or failed. The command's own deadline
54617
55014
  * bounds this loop together with the write that preceded it.
@@ -54625,6 +55022,81 @@ const settleOrganizationLifecycle = (read) => Effect.gen(function* () {
54625
55022
  return lifecycle;
54626
55023
  });
54627
55024
  /**
55025
+ * Resume the exact durable Organization lane through the SDK recovery owner, then
55026
+ * settle that exact lifecycle. It never creates an Organization and never
55027
+ * resubmits a recorded bootstrap submission.
55028
+ */
55029
+ const resumeOrganizationSetup = (operation, scoped, deadline) => Effect.gen(function* () {
55030
+ yield* Effect.promise(() => scoped.retrySetup({ signal: deadline.signal })).pipe(Effect.flatMap(resultValue));
55031
+ const lifecycle = yield* settleOrganizationLifecycle(() => scoped.getLifecycle({ signal: deadline.signal }));
55032
+ yield* requireReadyLifecycle(operation, lifecycle);
55033
+ return lifecycle;
55034
+ });
55035
+ /**
55036
+ * A prepared handle owns the next durable Organization for that handle, so a
55037
+ * different handle would create a second Organization and leave the prepared lane
55038
+ * abandoned. The refusal names the exact handle to continue.
55039
+ */
55040
+ const preparedJourneyRefusal = (handle) => new CliFailure({
55041
+ code: "WRONG_STATE",
55042
+ message: `An Organization creation is already prepared for handle @${handle}. Run capxul org create with the same handle to continue that exact Organization.`,
55043
+ exitCode: 2,
55044
+ outcome: "refused"
55045
+ });
55046
+ /**
55047
+ * Read how this create continues the person's Organization journey.
55048
+ *
55049
+ * The recovery handle is the only source of the exact identity, so a matching
55050
+ * handle resumes the Organization its lane reached and never creates another
55051
+ * one. A different handle creates one Organization only after the recorded
55052
+ * Organization reaches ready.
55053
+ */
55054
+ const readCreateRoute = (client, recovery, handle, signal) => Effect.gen(function* () {
55055
+ if (recovery === null) return {
55056
+ kind: "create",
55057
+ continuesRecorded: false
55058
+ };
55059
+ if (recovery.organizationId === null) {
55060
+ if (recovery.handle === handle) return {
55061
+ kind: "create",
55062
+ continuesRecorded: true
55063
+ };
55064
+ return yield* Effect.fail(preparedJourneyRefusal(recovery.handle));
55065
+ }
55066
+ const lifecycle = yield* Effect.promise(() => client.org(recovery.organizationId).getLifecycle({ signal })).pipe(Effect.flatMap(resultValue));
55067
+ if (recovery.handle === handle) return {
55068
+ kind: "recover",
55069
+ organizationId: recovery.organizationId,
55070
+ handle: recovery.handle,
55071
+ lifecycle
55072
+ };
55073
+ if (lifecycle.status === "ready") return {
55074
+ kind: "create",
55075
+ continuesRecorded: false
55076
+ };
55077
+ return yield* Effect.fail(Errors.wrongState({
55078
+ method: CAPXUL_OPERATIONS.onboarding.completeOrganization,
55079
+ currentState: lifecycle.status,
55080
+ validStates: ["ready"],
55081
+ organizationId: String(recovery.organizationId)
55082
+ }));
55083
+ });
55084
+ /** One safe recovery line, in the same newline-delimited shape as the lifecycle. */
55085
+ const recoveryNotice = (mode, recovery) => mode === "json" ? `${JSON.stringify({
55086
+ type: "organization.recovery",
55087
+ organizationId: String(recovery.organizationId),
55088
+ handle: recovery.handle
55089
+ })}\n` : `Recovering Organization ${JSON.stringify(String(recovery.organizationId))} (@${recovery.handle}).\n`;
55090
+ /** A taken handle is refused before the backend writes any Organization row. */
55091
+ const refusedOrganizationHandle = (error) => isCapxulError(error) && error.code === "INVALID_INPUT" && error.details?.field === "handle";
55092
+ /** A write forwards no signature before its recovery handle exists; absence is a defect. */
55093
+ const organizationWriteDefect = () => new CliFailure({
55094
+ code: "CLI_DEFECT",
55095
+ message: "The Organization write lost its recovery handle.",
55096
+ exitCode: 1,
55097
+ outcome: "failed"
55098
+ });
55099
+ /**
54628
55100
  * The one deadline for a write: it aborts the active SDK signal, so the waiting
54629
55101
  * command stops and the signer forwards nothing further, while the durable
54630
55102
  * backend lane keeps running and stays resumable by `org retry`.
@@ -54734,10 +55206,15 @@ const requireReadyLifecycle = (operation, lifecycle) => lifecycle.status === "re
54734
55206
  * explicit create intent, then settle the exact Organization lifecycle. This
54735
55207
  * never saves a read default and never returns an unrelated ready Organization.
54736
55208
  *
55209
+ * A create that meets this person's prepared Organization journey continues that
55210
+ * exact Organization: the same handle resumes the lane its ID reached, and a
55211
+ * different handle refuses while that lane is unsettled. Neither path creates a
55212
+ * replacement Organization or treasury.
55213
+ *
54737
55214
  * One deadline covers the write and its settlement. At the deadline the active
54738
55215
  * SDK signal is aborted: the command stops waiting, forwards no further
54739
55216
  * signature, and returns the typed wait failure, while the durable backend lane
54740
- * keeps running and stays resumable by `org retry`.
55217
+ * keeps running and stays resumable by the same create or by `org retry`.
54741
55218
  */
54742
55219
  const createOrganization = (config, input) => Effect.gen(function* () {
54743
55220
  const operation = CAPXUL_OPERATIONS.org.create;
@@ -54747,50 +55224,99 @@ const createOrganization = (config, input) => Effect.gen(function* () {
54747
55224
  const opened = yield* openAuthenticatedSession(config, input.email);
54748
55225
  if (interactivelyConfirm) {
54749
55226
  const preview = yield* restoreVerifiedClient(config, opened, { requirement: "none" });
54750
- const resolved = yield* organizationPreviewLines(preview.client, null);
55227
+ const recovery = yield* readOrganizationRecovery(config, preview.session.authUserId);
55228
+ const resolved = yield* organizationPreview(preview.client, null, recovery, draft.handle);
54751
55229
  yield* confirmWrite({
54752
55230
  lines: [
54753
55231
  `Create Organization ${JSON.stringify(draft.name)} (@${draft.handle}, ${draft.country})`,
54754
55232
  ...draft.bio === void 0 ? [] : [`Bio: ${JSON.stringify(draft.bio)}`],
54755
55233
  ...draft.size === void 0 ? [] : [`Size: ${JSON.stringify(draft.size)}`],
54756
- ...resolved
55234
+ ...resolved.lines
54757
55235
  ],
54758
- question: "Create this Organization?"
55236
+ question: createOrganizationQuestion(draft.handle, resolved)
54759
55237
  });
54760
55238
  }
54761
55239
  const context = yield* Effect.context();
54762
55240
  const run = Effect.runPromiseWith(context);
54763
55241
  const runtime = yield* CliRuntime;
54764
- const progress = makeOrganizationProgress(runtime.mode, run);
55242
+ const progressRef = yield* Ref.make(null);
54765
55243
  const deadline = new AbortController();
54766
55244
  const openfortSignerFactory = yield* organizationWriteSigner(config, opened, run, async () => {
54767
55245
  const cancelled = cancelledWrite(operation, deadline.signal);
54768
55246
  if (cancelled !== null) throw cancelled;
54769
- await progress.flush();
55247
+ const live = await run(Ref.get(progressRef));
55248
+ if (live === null) throw organizationWriteDefect();
55249
+ await live.flush();
54770
55250
  const stopped = cancelledWrite(operation, deadline.signal);
54771
55251
  if (stopped !== null) throw stopped;
54772
55252
  });
54773
- const { client } = yield* restoreVerifiedClient(config, opened, {
55253
+ const { client, session, stillAuthenticated } = yield* restoreVerifiedClient(config, opened, {
54774
55254
  requirement: "deployed",
54775
55255
  openfortSignerFactory
54776
55256
  });
54777
- const unsubscribe = client.onboarding.subscribeOrganization(progress.observe);
54778
- const settled = yield* Effect.gen(function* () {
54779
- const completed = yield* Effect.promise(() => client.onboarding.completeOrganization(draft, {
54780
- intent: "create",
54781
- signal: deadline.signal
54782
- })).pipe(Effect.ensuring(Effect.sync(unsubscribe)));
55257
+ const recovery = yield* openOrganizationRecovery(config, session.authUserId, stillAuthenticated);
55258
+ const persistRecovery = (organizationId) => Effect.gen(function* () {
55259
+ const committed = yield* onboardingOrganizationId(organizationId);
55260
+ yield* recovery.save({
55261
+ handle: draft.handle,
55262
+ organizationId: committed
55263
+ });
55264
+ });
55265
+ const progress = makeOrganizationProgress(runtime.mode, run, (organizationId) => run(persistRecovery(organizationId)));
55266
+ yield* Ref.set(progressRef, progress);
55267
+ const flushProgress = Effect.gen(function* () {
54783
55268
  yield* Effect.tryPromise({
54784
55269
  try: progress.flush,
54785
55270
  catch: (cause) => cause
54786
55271
  }).pipe(Effect.catch(() => Effect.void));
54787
55272
  const localFailure = progress.failure();
54788
55273
  if (localFailure !== null && localFailure !== void 0) return yield* Effect.fail(localFailure);
54789
- const onboarding = yield* resultValue(completed);
55274
+ });
55275
+ const settled = yield* Effect.gen(function* () {
55276
+ const route = yield* readCreateRoute(client, recovery.current(), draft.handle, deadline.signal);
55277
+ if (route.kind === "recover") {
55278
+ if (route.lifecycle.status === "ready") {
55279
+ yield* requireCurrentAccess(client, route.organizationId, route.lifecycle);
55280
+ progress.allowReady();
55281
+ return {
55282
+ organizationId: route.organizationId,
55283
+ lifecycle: route.lifecycle
55284
+ };
55285
+ }
55286
+ progress.notice(recoveryNotice(runtime.mode, route));
55287
+ yield* flushProgress;
55288
+ const lifecycle = yield* resumeOrganizationSetup(operation, client.org(route.organizationId), deadline);
55289
+ yield* requireCurrentAccess(client, route.organizationId, lifecycle);
55290
+ progress.allowReady();
55291
+ return {
55292
+ organizationId: route.organizationId,
55293
+ lifecycle
55294
+ };
55295
+ }
55296
+ yield* recovery.save({
55297
+ handle: draft.handle,
55298
+ organizationId: null
55299
+ });
55300
+ const unsubscribe = client.onboarding.subscribeOrganization(progress.observe);
55301
+ const completed = yield* Effect.promise(() => client.onboarding.completeOrganization(draft, {
55302
+ intent: "create",
55303
+ signal: deadline.signal
55304
+ })).pipe(Effect.ensuring(Effect.sync(unsubscribe)));
55305
+ if (!completed.ok) {
55306
+ yield* flushProgress;
55307
+ if (refusedOrganizationHandle(completed.error) && progress.committed() === null) yield* recovery.release();
55308
+ return yield* Effect.fail(completed.error);
55309
+ }
55310
+ const onboarding = completed.value;
54790
55311
  const organizationId = yield* onboardingOrganizationId(onboarding.organizationId);
55312
+ progress.commit(String(organizationId));
55313
+ yield* flushProgress;
54791
55314
  const scoped = client.org(organizationId);
54792
55315
  const lifecycle = yield* settleOrganizationLifecycle(() => scoped.getLifecycle({ signal: deadline.signal }));
54793
55316
  yield* requireReadyLifecycle(operation, lifecycle);
55317
+ if (route.continuesRecorded) yield* requireCurrentAccess(client, organizationId, lifecycle);
55318
+ progress.allowReady();
55319
+ yield* flushProgress;
54794
55320
  return {
54795
55321
  organizationId,
54796
55322
  lifecycle
@@ -54835,9 +55361,9 @@ const retryOrganization = (config, input) => Effect.gen(function* () {
54835
55361
  const opened = yield* openAuthenticatedSession(config, input.email);
54836
55362
  if (interactivelyConfirm) {
54837
55363
  const preview = yield* restoreVerifiedClient(config, opened, { requirement: "none" });
54838
- const resolved = yield* organizationPreviewLines(preview.client, organizationId);
55364
+ const resolved = yield* organizationPreview(preview.client, organizationId, null);
54839
55365
  yield* confirmWrite({
54840
- lines: [`Resume setup for Organization ${JSON.stringify(String(organizationId))}`, ...resolved],
55366
+ lines: [`Resume setup for Organization ${JSON.stringify(String(organizationId))}`, ...resolved.lines],
54841
55367
  question: "Resume this Organization's setup?"
54842
55368
  });
54843
55369
  }
@@ -54853,12 +55379,7 @@ const retryOrganization = (config, input) => Effect.gen(function* () {
54853
55379
  openfortSignerFactory
54854
55380
  });
54855
55381
  const scoped = client.org(organizationId);
54856
- const settled = yield* Effect.gen(function* () {
54857
- yield* Effect.promise(() => scoped.retrySetup({ signal: deadline.signal })).pipe(Effect.flatMap(resultValue));
54858
- const lifecycle = yield* settleOrganizationLifecycle(() => scoped.getLifecycle({ signal: deadline.signal }));
54859
- yield* requireReadyLifecycle(operation, lifecycle);
54860
- return lifecycle;
54861
- }).pipe(Effect.onInterrupt(() => Effect.sync(() => deadline.abort()))).pipe(Effect.timeoutOption(timeout * 1e3));
55382
+ const settled = yield* resumeOrganizationSetup(operation, scoped, deadline).pipe(Effect.onInterrupt(() => Effect.sync(() => deadline.abort()))).pipe(Effect.timeoutOption(timeout * 1e3));
54862
55383
  if (Option.isNone(settled)) return yield* Effect.fail(organizationWaitTimeout());
54863
55384
  return {
54864
55385
  data: {
@@ -55926,7 +56447,7 @@ const create = Command.make("create", {
55926
56447
  size: Option.getOrUndefined(size),
55927
56448
  confirm,
55928
56449
  timeoutSeconds
55929
- }))).pipe(Command.annotate(CliCommandMetadata, metadata(CAPXUL_OPERATIONS.org.create)), Command.withDescription("Create one Organization; never saves a read default"));
56450
+ }))).pipe(Command.annotate(CliCommandMetadata, metadata(CAPXUL_OPERATIONS.org.create)), Command.withDescription("Create one Organization, or resume the exact one this machine recorded"));
55930
56451
  const retry = Command.make("retry", {
55931
56452
  ...orgScope,
55932
56453
  email: emailFlag,
@@ -55941,7 +56462,7 @@ const retry = Command.make("retry", {
55941
56462
  confirm,
55942
56463
  timeoutSeconds
55943
56464
  }))).pipe(Command.annotate(CliCommandMetadata, metadata(CAPXUL_OPERATIONS.org.retry)), Command.withDescription("Resume this exact Organization setup lane; never creates one"));
55944
- const org = Command.make("org").pipe(Command.annotate(CliCommandMetadata, metadata("org")), Command.withDescription("Organization reads, writes, and local read defaults"), Command.withSubcommands([
56465
+ const org = Command.make("org").pipe(Command.annotate(CliCommandMetadata, metadata("org")), Command.withDescription("Organization reads, writes, recovery, and local read defaults"), Command.withSubcommands([
55945
56466
  list,
55946
56467
  use,
55947
56468
  status,
@@ -55955,29 +56476,6 @@ const org = Command.make("org").pipe(Command.annotate(CliCommandMetadata, metada
55955
56476
  retry
55956
56477
  ]));
55957
56478
  //#endregion
55958
- //#region src/runtime/format.ts
55959
- const jsonFlag = Flag.boolean("json").pipe(Flag.withDefault(false), Flag.withDescription("Print a versioned JSON result"));
55960
- const JsonOutput = GlobalFlag.setting("json")({ flag: jsonFlag });
55961
- /** Negotiate only the global format before native CLI error rendering.
55962
- * Native Flag decoding remains the value authority; the command runner parses everything else.
55963
- */
55964
- const outputMode = (args) => Effect.gen(function* () {
55965
- const terminator = args.indexOf("--");
55966
- const flags = terminator < 0 ? args : args.slice(0, terminator);
55967
- const values = [];
55968
- for (const [index, arg] of flags.entries()) if (arg === "--no-json") values.push("false");
55969
- else if (arg.startsWith("--json=")) values.push(arg.slice(7));
55970
- else if (arg === "--json") {
55971
- const next = flags[index + 1];
55972
- const value = next === void 0 ? "true" : yield* Primitive.boolean.parse(next).pipe(Effect.as(next), Effect.catch(() => Effect.succeed("true")));
55973
- values.push(value);
55974
- }
55975
- return yield* jsonFlag.parse({
55976
- flags: { json: values },
55977
- arguments: []
55978
- }).pipe(Effect.map(([, json]) => json ? "json" : "human"), Effect.catch(() => Effect.succeed(values.includes("true") ? "json" : "human")));
55979
- });
55980
- //#endregion
55981
56479
  //#region src/command.ts
55982
56480
  const command = Command.make("capxul").pipe(Command.annotate(CliCommandMetadata, {
55983
56481
  name: "capxul",
@@ -56103,6 +56601,7 @@ const runCli = (platform) => Effect.uninterruptibleMask((restore) => Effect.gen(
56103
56601
  const ci = yield* Config.string("CI").pipe(Config.withDefault("false"), Effect.map((value) => value !== "" && value !== "false" && value !== "0"), Effect.catch(() => Effect.succeed(false)));
56104
56602
  const runtime = {
56105
56603
  mode,
56604
+ args,
56106
56605
  current,
56107
56606
  invocationId,
56108
56607
  observation,