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

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
@@ -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.8";
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.8";
51699
+ const sdkVersion = "4.20.0-beta.8";
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
@@ -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));
@@ -53005,9 +53059,14 @@ const runCommand = (options, work) => Effect.gen(function* () {
53005
53059
  //#endregion
53006
53060
  //#region src/platform/capxul.ts
53007
53061
  const resultValue = (result) => result.ok ? Effect.succeed(result.value) : Effect.fail(result.error);
53062
+ const localDiagnosticsEnabled = (args) => args.some((arg, index) => arg === "--log-level=debug" || arg === "--logLevel=debug" || (arg === "--log-level" || arg === "--logLevel") && args[index + 1] === "debug");
53008
53063
  const acquireClient = (input, onFinalize) => Effect.uninterruptibleMask((restore) => Effect.gen(function* () {
53064
+ const runtime = yield* CliRuntime;
53009
53065
  const created = yield* restore(Effect.promise(async (signal) => {
53010
- const result = await createCapxulClient(input);
53066
+ const result = await createCapxulClient({
53067
+ ...input,
53068
+ localDiagnostics: localDiagnosticsEnabled(runtime.args ?? [])
53069
+ });
53011
53070
  if (signal.aborted && result.ok) await result.value.close();
53012
53071
  return result;
53013
53072
  }));
@@ -53166,11 +53225,11 @@ const CurrentSession = Schema.Struct({
53166
53225
  authUserId: Schema.NonEmptyString
53167
53226
  }))
53168
53227
  });
53169
- const decode$1 = (value) => Schema.decodeUnknownEffect(Schema.fromJsonString(CurrentSession), { onExcessProperty: "error" })(value).pipe(Effect.mapError(() => new StateFileError({ reason: "Corrupt" })));
53228
+ const decode$2 = (value) => Schema.decodeUnknownEffect(Schema.fromJsonString(CurrentSession), { onExcessProperty: "error" })(value).pipe(Effect.mapError(() => new StateFileError({ reason: "Corrupt" })));
53170
53229
  const readCurrentSession = (config) => Effect.gen(function* () {
53171
53230
  const { directory, scope } = yield* privateRecordScope(config, "current-session", "current");
53172
53231
  const value = yield* readPrivateRecord(directory, scope);
53173
- return value === null ? null : (yield* decode$1(value)).current;
53232
+ return value === null ? null : (yield* decode$2(value)).current;
53174
53233
  });
53175
53234
  const rememberCurrentSession = (config, session, stillAuthenticated) => Effect.gen(function* () {
53176
53235
  const { directory, scope } = yield* privateRecordScope(config, "current-session", "current");
@@ -53183,7 +53242,7 @@ const rememberCurrentSession = (config, session, stillAuthenticated) => Effect.g
53183
53242
  };
53184
53243
  for (let attempt = 0; attempt < 2; attempt += 1) {
53185
53244
  const expected = yield* readPrivateRecord(directory, scope);
53186
- if (expected !== null) yield* decode$1(expected);
53245
+ if (expected !== null) yield* decode$2(expected);
53187
53246
  if (yield* compareAndSetPrivateRecord(directory, scope, expected, JSON.stringify(next), stillAuthenticated)) return;
53188
53247
  if (!(yield* stillAuthenticated)) break;
53189
53248
  }
@@ -53193,7 +53252,7 @@ const forgetCurrentSession = (config, email, stillCleared) => Effect.gen(functio
53193
53252
  const { directory, scope } = yield* privateRecordScope(config, "current-session", "current");
53194
53253
  const expected = yield* readPrivateRecordSnapshot(directory, scope);
53195
53254
  if (expected === null) return false;
53196
- if ((yield* decode$1(expected)).current?.email !== email.toLowerCase()) return false;
53255
+ if ((yield* decode$2(expected)).current?.email !== email.toLowerCase()) return false;
53197
53256
  return yield* compareAndSetPrivateRecord(directory, scope, expected, JSON.stringify({
53198
53257
  version: 1,
53199
53258
  current: null
@@ -53210,48 +53269,118 @@ const inputRequired = (message) => new CliFailure({
53210
53269
  const readEmail = (email) => Effect.gen(function* () {
53211
53270
  const runtime = yield* CliRuntime;
53212
53271
  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."));
53272
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") return yield* Effect.fail(inputRequired("Provide --email."));
53214
53273
  email = yield* Prompt.text({ message: "Email" });
53215
53274
  }
53216
53275
  const normalized = email.trim().toLowerCase();
53217
53276
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(normalized)) return yield* Effect.fail(inputRequired("Provide a valid email."));
53218
53277
  return normalized;
53219
53278
  });
53279
+ const OTP_INPUT_ERROR = "OTP input must contain six digits only.";
53280
+ const OTP_BOUNDARY_MARKS = /^[\s\p{Cf}]+|[\s\p{Cf}]+$/gu;
53281
+ const normalizeOtpInput = (input) => {
53282
+ if (new TextEncoder().encode(input).byteLength > 64) return null;
53283
+ const normalized = input.replace(OTP_BOUNDARY_MARKS, "");
53284
+ return /^[0-9]{6}$/.test(normalized) ? normalized : null;
53285
+ };
53220
53286
  const readOtp = (stdin) => Effect.gen(function* () {
53221
53287
  const runtime = yield* CliRuntime;
53222
53288
  if (!stdin) {
53223
53289
  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" }));
53290
+ return Redacted.value(yield* Prompt.password({
53291
+ message: "Email OTP",
53292
+ validate: (input) => {
53293
+ const normalized = normalizeOtpInput(input);
53294
+ return normalized === null ? Effect.fail(OTP_INPUT_ERROR) : Effect.succeed(normalized);
53295
+ }
53296
+ }));
53225
53297
  }
53226
53298
  const stdio = yield* Stdio.Stdio;
53227
53299
  let length = 0;
53228
53300
  const chunks = yield* stdio.stdin.pipe(Stream.mapEffect((chunk) => {
53229
53301
  length += chunk.byteLength;
53230
- return length > 64 ? Effect.fail(inputRequired("OTP input must contain six digits only.")) : Effect.succeed(chunk);
53302
+ return length > 64 ? Effect.fail(inputRequired(OTP_INPUT_ERROR)) : Effect.succeed(chunk);
53231
53303
  }), 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."));
53304
+ const code = normalizeOtpInput(Array.from(chunks).join(""));
53305
+ if (code === null) return yield* Effect.fail(inputRequired(OTP_INPUT_ERROR));
53234
53306
  return code;
53235
53307
  });
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 });
53308
+ const PROFILE_FLAGS = {
53309
+ displayName: "--display-name",
53310
+ country: "--country",
53311
+ handle: "--handle"
53312
+ };
53313
+ const PROFILE_REFUSALS = {
53314
+ displayName: "Provide --display-name with at least one visible character.",
53315
+ country: "Provide --country as a two-letter ISO country code, for example GH.",
53316
+ handle: "Provide --handle as 3 to 30 characters of a-z, 0-9, underscore, or hyphen."
53317
+ };
53318
+ /**
53319
+ * Accept one Profile value, or return the refusal that names its field.
53320
+ *
53321
+ * Country and handle use the public SDK constructors, so the CLI accepts
53322
+ * exactly the values the backend stores. A handle loses its leading `@` and
53323
+ * its case before the check, which is the canon spelling the owner persists.
53324
+ */
53325
+ const acceptPersonField = (field, raw) => {
53326
+ const trimmed = raw.trim();
53327
+ if (trimmed.length === 0) return { refusal: inputRequired(PROFILE_REFUSALS[field]) };
53328
+ try {
53329
+ if (field === "country") return { value: toCountryCode(trimmed) };
53330
+ if (field === "handle") return { value: toHandle(trimmed.replace(/^@/u, "").toLowerCase()) };
53331
+ return { value: trimmed };
53332
+ } catch {
53333
+ return { refusal: inputRequired(PROFILE_REFUSALS[field]) };
53334
+ }
53335
+ };
53336
+ /**
53337
+ * Validate the Profile values a caller supplied, before any client work.
53338
+ *
53339
+ * `auth signup` runs this first, so a malformed value refuses locally and
53340
+ * consumes no one-time code. An absent field stays with `readPersonInput`,
53341
+ * which prompts on a terminal and refuses headlessly.
53342
+ */
53343
+ const checkPersonFlags = (flags) => Effect.gen(function* () {
53344
+ for (const field of [
53345
+ "displayName",
53346
+ "country",
53347
+ "handle"
53348
+ ]) {
53349
+ const supplied = flags[field];
53350
+ if (supplied === void 0) continue;
53351
+ const accepted = acceptPersonField(field, supplied);
53352
+ if ("refusal" in accepted) return yield* Effect.fail(accepted.refusal);
53242
53353
  }
53243
- const trimmed = value.trim();
53244
- if (trimmed.length === 0) return yield* Effect.fail(inputRequired(`Provide --${flag}.`));
53245
- return trimmed;
53354
+ });
53355
+ /**
53356
+ * One required Profile field.
53357
+ *
53358
+ * A non-interactive run accepts only the explicit flag, so headless signup is
53359
+ * deterministic and never prompts. An interactive run prompts, and offers the
53360
+ * existing Profile value as the default so a continuation keeps it.
53361
+ */
53362
+ const readRequired = (label, field, supplied, existing) => Effect.gen(function* () {
53363
+ const runtime = yield* CliRuntime;
53364
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") {
53365
+ if (supplied === void 0) return yield* Effect.fail(inputRequired(`Provide ${PROFILE_FLAGS[field]}.`));
53366
+ const accepted = acceptPersonField(field, supplied);
53367
+ if ("refusal" in accepted) return yield* Effect.fail(accepted.refusal);
53368
+ return accepted.value;
53369
+ }
53370
+ const fallback = supplied ?? existing;
53371
+ const value = yield* Prompt.text({
53372
+ message: label,
53373
+ ...fallback === void 0 || fallback.trim().length === 0 ? {} : { default: fallback }
53374
+ });
53375
+ const accepted = acceptPersonField(field, value);
53376
+ if ("refusal" in accepted) return yield* Effect.fail(accepted.refusal);
53377
+ return accepted.value;
53246
53378
  });
53247
53379
  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
53380
  return {
53252
- displayName,
53253
- country: country.toUpperCase(),
53254
- handle
53381
+ displayName: yield* readRequired("Display name", "displayName", flags.displayName, profile?.displayName ?? void 0),
53382
+ country: yield* readRequired("Country code", "country", flags.country, profile?.country ?? void 0),
53383
+ handle: yield* readRequired("Handle", "handle", flags.handle, profile?.handle ?? void 0)
53255
53384
  };
53256
53385
  });
53257
53386
  //#endregion
@@ -53928,6 +54057,17 @@ const verifiedSession = (client, email) => Effect.gen(function* () {
53928
54057
  if (session === null || session.email.toLowerCase() !== email) return yield* Effect.fail(Errors.notAuthenticated());
53929
54058
  return session;
53930
54059
  });
54060
+ const submitOtp = (otpStdin, verify) => Effect.gen(function* () {
54061
+ const submissionLimit = otpStdin ? 1 : 3;
54062
+ for (let submission = 1; submission <= submissionLimit; submission += 1) {
54063
+ const code = yield* readOtp(otpStdin);
54064
+ const result = yield* Effect.result(verify(code));
54065
+ if (Result.isSuccess(result)) return;
54066
+ const error = result.failure;
54067
+ if (!(error.code === "INVALID_INPUT" && error.details?.field === "otp") || submission === submissionLimit) return yield* Effect.fail(error);
54068
+ yield* writeStderr("Invalid OTP. Try again.\n");
54069
+ }
54070
+ });
53931
54071
  /**
53932
54072
  * Local authentication checks only: the current-session pointer, the explicit
53933
54073
  * email, and a stored credential. No bootstrap, client, or network work. A
@@ -54005,6 +54145,60 @@ const flushWalletDiagnostics = (bridge, invocationId) => Effect.suspend(() => br
54005
54145
  invocation_id: invocationId,
54006
54146
  journey_id: invocationId
54007
54147
  }), { discard: true }));
54148
+ const SIGNUP_OTP_INPUT_REQUIRED = "Use --otp-stdin for non-interactive signup. OTP arguments are not accepted.";
54149
+ /**
54150
+ * One account-setup continuation: profile input, then wallet and Account work.
54151
+ *
54152
+ * `signup` calls this after authentication. An interactive `login` calls it
54153
+ * only after the person accepts the setup offer. A headless `login` never calls
54154
+ * it, so sign-in cannot become signup.
54155
+ *
54156
+ * The step returns a ready report only. When the personal Account is not ready,
54157
+ * it returns a typed recoverable report: `setupState` names the state, the
54158
+ * lifecycle carries the failure code, and the copy names the exact resume
54159
+ * action. The step never starts wallet work for a ready Account, so a retry
54160
+ * reuses the one Profile, wallet, Smart Account, and Account.
54161
+ */
54162
+ const completeAccountSetup = (config, store, client, session, suppliedProfile) => Effect.gen(function* () {
54163
+ const current = yield* call((signal) => client.identity.loadCurrent({ signal }));
54164
+ const before = yield* profileReport(client, session);
54165
+ if (before.data.setupState === "ready") return before;
54166
+ const profile = yield* readPersonInput(suppliedProfile, current);
54167
+ const runtime = yield* CliRuntime;
54168
+ const run = Effect.runPromiseWith(yield* Effect.context());
54169
+ let bridge;
54170
+ const invocation = yield* Ref.get(runtime.current);
54171
+ const openfortSignerFactory = (bootstrap) => {
54172
+ const created = createBrowserSignerBridge({
54173
+ bootstrap,
54174
+ environment: config.environment,
54175
+ handle: () => profile.handle,
54176
+ trustedAuthOrigin: new URL(config.bootstrapBaseUrl).origin,
54177
+ sessionToken: () => run(store.tokenIfCurrent())
54178
+ });
54179
+ bridge = created;
54180
+ invocation?.onFinalize?.(Effect.promise(() => created.close()).pipe(Effect.asVoid, Effect.catchCause(() => Effect.void)));
54181
+ return created.signer;
54182
+ };
54183
+ const walletClient = yield* clientFor(config, store, {
54184
+ restore: true,
54185
+ requirement: "deployed",
54186
+ openfortSignerFactory
54187
+ });
54188
+ const verified = yield* verifiedSession(walletClient, session.email);
54189
+ yield* writeStderr("Preparing your wallet in the browser. Keep that tab open.\n");
54190
+ const report = yield* Effect.gen(function* () {
54191
+ yield* call((signal) => walletClient.onboarding.completePerson(profile, { signal }));
54192
+ return yield* profileReport(walletClient, verified);
54193
+ }).pipe(Effect.ensuring(Effect.suspend(() => flushWalletDiagnostics(bridge, runtime.invocationId))));
54194
+ return {
54195
+ data: {
54196
+ ...report.data,
54197
+ signerStatus: walletClient.signer.status()
54198
+ },
54199
+ lines: report.lines
54200
+ };
54201
+ });
54008
54202
  const sendOtp = (config, suppliedEmail) => Effect.gen(function* () {
54009
54203
  const email = yield* readEmail(suppliedEmail);
54010
54204
  const store = yield* openSessionRecord(config, email);
@@ -54029,7 +54223,6 @@ const verifyOtp = (config, suppliedEmail, otpStdin) => Effect.gen(function* () {
54029
54223
  const store = yield* openSessionRecord(config, email);
54030
54224
  const flow = store.current().flow;
54031
54225
  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
54226
  const client = yield* clientFor(config, store, { restore: false });
54034
54227
  yield* call((signal) => client.auth.getSession({ signal }));
54035
54228
  yield* call((signal) => client.auth.resumeOtp({
@@ -54039,13 +54232,13 @@ const verifyOtp = (config, suppliedEmail, otpStdin) => Effect.gen(function* () {
54039
54232
  signal,
54040
54233
  journey_id: flow.journeyId
54041
54234
  }));
54042
- yield* call((signal) => client.auth.verifyOtp({
54235
+ yield* submitOtp(otpStdin, (code) => call((signal) => client.auth.verifyOtp({
54043
54236
  email,
54044
54237
  code
54045
54238
  }, {
54046
54239
  signal,
54047
54240
  journey_id: flow.journeyId
54048
- }));
54241
+ })));
54049
54242
  const session = yield* verifiedSession(client, email);
54050
54243
  yield* bindPerson(config, store, session);
54051
54244
  const report = yield* profileReport(client, session);
@@ -54060,95 +54253,64 @@ const verifyOtp = (config, suppliedEmail, otpStdin) => Effect.gen(function* () {
54060
54253
  const login = (config, suppliedEmail, otpStdin) => Effect.gen(function* () {
54061
54254
  const email = yield* readEmail(suppliedEmail);
54062
54255
  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."));
54256
+ 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
54257
  const store = yield* openSessionRecord(config, email);
54065
54258
  const client = yield* clientFor(config, store, { restore: false });
54066
54259
  yield* call((signal) => client.auth.getSession({ signal }));
54067
54260
  yield* store.begin(yield* Clock.currentTimeMillis, runtime.invocationId);
54068
54261
  yield* call((signal) => client.auth.signIn({ email }, { signal }));
54069
54262
  yield* writeStderr("Verification email sent. Enter the code to continue.\n");
54070
- const code = yield* readOtp(otpStdin);
54071
- yield* call((signal) => client.auth.verifyOtp({
54263
+ yield* submitOtp(otpStdin, (code) => call((signal) => client.auth.verifyOtp({
54072
54264
  email,
54073
54265
  code
54074
54266
  }, {
54075
54267
  signal,
54076
54268
  journey_id: runtime.invocationId
54077
- }));
54269
+ })));
54078
54270
  const session = yield* verifiedSession(client, email);
54079
54271
  yield* bindPerson(config, store, session);
54080
- return yield* profileReport(client, session);
54272
+ const report = yield* profileReport(client, session);
54273
+ if (report.data.setupState === "ready") return report;
54274
+ if (!runtime.tty || runtime.ci || runtime.mode === "json") return report;
54275
+ if (!(yield* Prompt.confirm({
54276
+ message: "Your Capxul account is not set up yet. Continue setup now?",
54277
+ initial: false
54278
+ }))) return report;
54279
+ return yield* completeAccountSetup(config, store, client, session, {});
54081
54280
  });
54082
54281
  const signup = (config, suppliedEmail, otpStdin, suppliedProfile) => Effect.gen(function* () {
54083
54282
  const email = yield* readEmail(suppliedEmail);
54084
54283
  const runtime = yield* CliRuntime;
54284
+ yield* checkPersonFlags(suppliedProfile);
54085
54285
  const store = yield* openSessionRecord(config, email);
54086
- const run = Effect.runPromiseWith(yield* Effect.context());
54087
54286
  const restore = store.current().token !== null;
54088
- let handle = suppliedProfile.handle ?? "personal";
54089
- let bridge;
54287
+ if (!restore && !otpStdin && (!runtime.tty || runtime.ci || runtime.mode === "json")) return yield* Effect.fail(inputRequired(SIGNUP_OTP_INPUT_REQUIRED));
54090
54288
  let client = yield* clientFor(config, store, { restore });
54091
54289
  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
54290
  if (session === null) {
54093
54291
  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."));
54292
+ if (!otpStdin && (!runtime.tty || runtime.ci || runtime.mode === "json")) return yield* Effect.fail(inputRequired(SIGNUP_OTP_INPUT_REQUIRED));
54095
54293
  yield* call((signal) => client.auth.getSession({ signal }));
54096
54294
  yield* store.begin(yield* Clock.currentTimeMillis, runtime.invocationId);
54097
54295
  yield* call((signal) => client.auth.signIn({ email }, { signal }));
54098
54296
  yield* writeStderr("Verification email sent. Enter the code to continue.\n");
54099
- const code = yield* readOtp(otpStdin);
54100
- yield* call((signal) => client.auth.verifyOtp({
54297
+ yield* submitOtp(otpStdin, (code) => call((signal) => client.auth.verifyOtp({
54101
54298
  email,
54102
54299
  code
54103
54300
  }, {
54104
54301
  signal,
54105
54302
  journey_id: runtime.invocationId
54106
- }));
54303
+ })));
54107
54304
  session = yield* verifiedSession(client, email);
54108
54305
  }
54109
54306
  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
- };
54307
+ return yield* completeAccountSetup(config, store, client, session, suppliedProfile);
54146
54308
  });
54147
54309
  const readProfile = (config, suppliedEmail) => Effect.gen(function* () {
54148
54310
  const email = yield* readEmail(suppliedEmail);
54149
54311
  const store = yield* openSessionRecord(config, email);
54150
54312
  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."));
54313
+ if (usableGrant(yield* loadGrant(config, email))) return yield* Effect.fail(inputRequired("Sign in again with capxul auth login to use this command."));
54152
54314
  return yield* Effect.fail(Errors.notAuthenticated());
54153
54315
  }
54154
54316
  const client = yield* clientFor(config, store, { restore: true });
@@ -54207,11 +54369,11 @@ const logout = (config, suppliedEmail) => Effect.gen(function* () {
54207
54369
  });
54208
54370
  //#endregion
54209
54371
  //#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);
54372
+ const emailFlag$3 = Flag.string("email").pipe(Flag.withDescription("Email address"), Flag.optional);
54373
+ 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"));
54374
+ const displayNameFlag = Flag.string("display-name").pipe(Flag.withDescription("Your display name"), Flag.optional);
54375
+ const countryFlag = Flag.string("country").pipe(Flag.withDescription("Two-letter country code"), Flag.optional);
54376
+ const handleFlag = Flag.string("handle").pipe(Flag.withDescription("Your unique Capxul handle"), Flag.optional);
54215
54377
  const metadata$4 = (name) => ({
54216
54378
  name,
54217
54379
  capability: "authentication",
@@ -54223,7 +54385,7 @@ const loginCommand = Command.make("login", {
54223
54385
  }, ({ email, otpStdin }) => runCommand({
54224
54386
  ...metadata$4("auth.login"),
54225
54387
  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"));
54388
+ }, (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
54389
  const signupCommand = Command.make("signup", {
54228
54390
  email: emailFlag$3,
54229
54391
  otpStdin: otpStdinFlag,
@@ -54237,31 +54399,31 @@ const signupCommand = Command.make("signup", {
54237
54399
  displayName: Option.getOrUndefined(displayName),
54238
54400
  country: Option.getOrUndefined(country),
54239
54401
  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"));
54402
+ }))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.signup")), Command.withDescription("Create your Capxul account and wallet"));
54241
54403
  const sendCommand = Command.make("send", { email: emailFlag$3 }, ({ email }) => runCommand({
54242
54404
  ...metadata$4("auth.send"),
54243
54405
  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"));
54406
+ }, (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
54407
  const verifyCommand = Command.make("verify", {
54246
54408
  email: emailFlag$3,
54247
54409
  otpStdin: otpStdinFlag
54248
54410
  }, ({ email, otpStdin }) => runCommand({
54249
54411
  ...metadata$4("auth.verify"),
54250
54412
  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"));
54413
+ }, (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
54414
  const profileCommand = Command.make("profile", { email: emailFlag$3 }, ({ email }) => runCommand({
54253
54415
  ...metadata$4("auth.profile"),
54254
54416
  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"));
54417
+ }, (config) => readProfile(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.profile")), Command.withDescription("Show your profile and account readiness"));
54256
54418
  const statusCommand = Command.make("status", { email: emailFlag$3 }, ({ email }) => runCommand({
54257
54419
  ...metadata$4("auth.status"),
54258
54420
  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"));
54421
+ }, (config) => status$2(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.status")), Command.withDescription("Show whether you are signed in"));
54260
54422
  const logoutCommand = Command.make("logout", { email: emailFlag$3 }, ({ email }) => runCommand({
54261
54423
  ...metadata$4("auth.logout"),
54262
54424
  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([
54425
+ }, (config) => logout(config, Option.getOrUndefined(email)))).pipe(Command.annotate(CliCommandMetadata, metadata$4("auth.logout")), Command.withDescription("Sign out on this device"));
54426
+ 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
54427
  loginCommand,
54266
54428
  signupCommand,
54267
54429
  sendCommand,
@@ -54334,6 +54496,73 @@ const telemetry = Command.make("telemetry").pipe(Command.annotate(CliCommandMeta
54334
54496
  disable
54335
54497
  ]));
54336
54498
  //#endregion
54499
+ //#region src/org-recovery.ts
54500
+ /**
54501
+ * The one safe recovery handle for a person's Organization journey.
54502
+ *
54503
+ * `handle` is the exact business handle the create prepared. It is the only
54504
+ * identity the CLI knows while the first durable Organization write is in
54505
+ * flight, and the backend resumes the same Organization for that handle.
54506
+ * `organizationId` is added from the first committed Organization. After that
54507
+ * the ID is the recovery target.
54508
+ *
54509
+ * A record with no handle releases the journey: the backend refused that exact
54510
+ * handle before it wrote any Organization row, so no Organization waits to be
54511
+ * resumed. The record stores no draft, submission, credential, or token.
54512
+ */
54513
+ const Recovery = Schema.Struct({
54514
+ version: Schema.Literal(1),
54515
+ handle: Schema.optional(Schema.NonEmptyString),
54516
+ organizationId: Schema.optional(Schema.NonEmptyString)
54517
+ });
54518
+ const decode$1 = (value) => Schema.decodeUnknownEffect(Schema.fromJsonString(Recovery), { onExcessProperty: "error" })(value).pipe(Effect.flatMap((record) => {
54519
+ const organizationId = record.organizationId ?? null;
54520
+ if (record.handle === void 0) return organizationId === null ? Effect.succeed(null) : Effect.fail(new StateFileError({ reason: "Corrupt" }));
54521
+ const handle = record.handle;
54522
+ return Effect.try(() => ({
54523
+ handle: String(toHandle(handle)),
54524
+ organizationId: organizationId === null ? null : toOrgId(organizationId)
54525
+ }));
54526
+ }), Effect.mapError(() => new StateFileError({ reason: "Corrupt" })));
54527
+ const encode = (recovery) => JSON.stringify({
54528
+ version: 1,
54529
+ handle: recovery.handle,
54530
+ ...recovery.organizationId === null ? {} : { organizationId: String(recovery.organizationId) }
54531
+ });
54532
+ /** The released shape: no handle waits to be resumed. */
54533
+ const RELEASED = JSON.stringify({ version: 1 });
54534
+ /** Read the safe recovery handle without writing. A corrupt record refuses the command. */
54535
+ const readOrganizationRecovery = (config, authUserId) => Effect.gen(function* () {
54536
+ const { directory, scope } = yield* privateRecordScope(config, "organization-recovery", authUserId);
54537
+ const value = yield* readPrivateRecord(directory, scope);
54538
+ return value === null ? null : yield* decode$1(value);
54539
+ });
54540
+ /**
54541
+ * Open the recovery handle for one command.
54542
+ *
54543
+ * `save` and `release` replace only the exact record this command read, so two
54544
+ * commands cannot overwrite each other's Organization journey, and a delayed
54545
+ * write cannot follow a newer one. A record this command did not read refuses
54546
+ * the write.
54547
+ */
54548
+ const openOrganizationRecovery = (config, authUserId, stillAuthenticated) => Effect.gen(function* () {
54549
+ const { directory, scope } = yield* privateRecordScope(config, "organization-recovery", authUserId);
54550
+ let encoded = yield* readPrivateRecord(directory, scope);
54551
+ let record = encoded === null ? null : yield* decode$1(encoded);
54552
+ const write = (next) => Effect.gen(function* () {
54553
+ const value = next === null ? RELEASED : encode(next);
54554
+ if (value === encoded) return;
54555
+ if (!(yield* compareAndSetPrivateRecord(directory, scope, encoded, value, stillAuthenticated))) return yield* Effect.fail(Errors.notAuthenticated("The local Organization recovery state changed. Retry the command."));
54556
+ encoded = value;
54557
+ record = next;
54558
+ });
54559
+ return {
54560
+ current: () => record,
54561
+ save: (next) => write(next),
54562
+ release: () => write(null)
54563
+ };
54564
+ });
54565
+ //#endregion
54337
54566
  //#region src/org-selection.ts
54338
54567
  const Selection = Schema.Struct({
54339
54568
  version: Schema.Literal(1),
@@ -54356,6 +54585,37 @@ const saveOrganizationSelection = (config, authUserId, organizationId, stillAuth
54356
54585
  });
54357
54586
  //#endregion
54358
54587
  //#region src/org.ts
54588
+ /**
54589
+ * The exact Organization a read uses when the caller named no ID: the saved read
54590
+ * default first, then the recorded recovery handle for this person's
54591
+ * Organization journey. `recoverable` marks the handle source, whose Organization
54592
+ * may still run its setup lane and be absent from the own-access list.
54593
+ */
54594
+ const readResolvedOrganization = (config, authUserId, organizationId) => Effect.gen(function* () {
54595
+ if (organizationId !== null) return {
54596
+ organizationId,
54597
+ recoverable: false
54598
+ };
54599
+ const selected = yield* readOrganizationSelection(config, authUserId);
54600
+ if (selected !== null) return {
54601
+ organizationId: selected,
54602
+ recoverable: false
54603
+ };
54604
+ const recovery = yield* readOrganizationRecovery(config, authUserId);
54605
+ return {
54606
+ organizationId: recovery?.organizationId ?? null,
54607
+ recoverable: recovery?.organizationId !== null && recovery?.organizationId !== void 0
54608
+ };
54609
+ });
54610
+ /**
54611
+ * A ready Organization this command reports must satisfy current access.
54612
+ *
54613
+ * The own-access list carries a founder's unfinished Organizations through the
54614
+ * backend's founder fallback, and the backend lifecycle read admits the founder
54615
+ * for the durable lane. A confirmed result therefore reads the list again: an
54616
+ * Organization the person cannot reach refuses instead of reporting its state.
54617
+ */
54618
+ 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
54619
  const readOrganization = (config, command, input) => Effect.gen(function* () {
54360
54620
  const timeout = yield* Schema.decodeUnknownEffect(Schema.Int.check(Schema.isBetween({
54361
54621
  minimum: 1,
@@ -54382,7 +54642,8 @@ const readOrganization = (config, command, input) => Effect.gen(function* () {
54382
54642
  address: org.account.address
54383
54643
  }))
54384
54644
  };
54385
- organizationId ??= yield* readOrganizationSelection(config, session.authUserId);
54645
+ const resolved = yield* readResolvedOrganization(config, session.authUserId, organizationId);
54646
+ organizationId = resolved.organizationId;
54386
54647
  if (organizationId === null) {
54387
54648
  const runtime = yield* CliRuntime;
54388
54649
  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 +54658,7 @@ const readOrganization = (config, command, input) => Effect.gen(function* () {
54397
54658
  }))
54398
54659
  });
54399
54660
  }
54400
- if (!organizations.some((org) => org.id === organizationId)) return yield* Effect.fail(Errors.invalidInput("org", "The Organization is not accessible."));
54661
+ if (!organizations.some((org) => org.id === organizationId) && !resolved.recoverable) return yield* Effect.fail(Errors.invalidInput("org", "The Organization is not accessible."));
54401
54662
  if (command === "use") {
54402
54663
  yield* saveOrganizationSelection(config, session.authUserId, organizationId, stillAuthenticated);
54403
54664
  return {
@@ -54450,11 +54711,13 @@ const readOrganization = (config, command, input) => Effect.gen(function* () {
54450
54711
  const read = Effect.promise((signal) => scoped.getLifecycle({ signal })).pipe(Effect.flatMap(resultValue));
54451
54712
  const observed = yield* Effect.gen(function* () {
54452
54713
  let lifecycle = yield* read;
54714
+ if (resolved.recoverable) yield* requireCurrentAccess(client, organizationId, lifecycle);
54453
54715
  if (command !== "wait") return lifecycle;
54454
54716
  while (lifecycle.status !== "ready" && lifecycle.status !== "failed") {
54455
54717
  yield* Effect.sleep(1e3);
54456
54718
  lifecycle = yield* read;
54457
54719
  }
54720
+ if (resolved.recoverable) yield* requireCurrentAccess(client, organizationId, lifecycle);
54458
54721
  return lifecycle;
54459
54722
  }).pipe(Effect.timeoutOption(timeout * 1e3));
54460
54723
  if (Option.isNone(observed)) return yield* Effect.fail(new CliFailure({
@@ -54538,45 +54801,89 @@ const requireWriteConfirmation = (supplied) => Effect.gen(function* () {
54538
54801
  *
54539
54802
  * The committed Organization ID is retained from the first state that carries
54540
54803
  * 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.
54804
+ * committed to. `flush` writes the queued lines first, then makes the ID durable
54805
+ * as this person's safe recovery handle, and only then resolves: the
54806
+ * command-scoped signer awaits it before forwarding a digest, so a failed or
54807
+ * cancelled write saves no handle and forwards no signature.
54544
54808
  */
54545
- const makeOrganizationProgress = (mode, run) => {
54809
+ const makeOrganizationProgress = (mode, run, persist) => {
54546
54810
  const queued = [];
54811
+ const held = [];
54812
+ const heldSet = /* @__PURE__ */ new Set();
54547
54813
  const written = /* @__PURE__ */ new Set();
54548
54814
  let committed = null;
54815
+ let persisted = null;
54816
+ let readyAllowed = false;
54549
54817
  let failure = null;
54818
+ /** One command owns one committed Organization. A second ID is a defect. */
54819
+ const retain = (observed) => {
54820
+ if (committed === null) {
54821
+ committed = observed;
54822
+ return true;
54823
+ }
54824
+ if (committed === observed) return true;
54825
+ failure ??= new CliFailure({
54826
+ code: "CLI_DEFECT",
54827
+ message: "The onboarding stream reported a different Organization.",
54828
+ exitCode: 1,
54829
+ outcome: "failed"
54830
+ });
54831
+ return false;
54832
+ };
54833
+ /**
54834
+ * A ready line waits until the command confirms current access. Every other
54835
+ * line is progress evidence and goes out with the committed ID.
54836
+ */
54837
+ const enqueue = (text, ready) => {
54838
+ if (written.has(text) || heldSet.has(text)) return;
54839
+ if (ready && !readyAllowed) {
54840
+ heldSet.add(text);
54841
+ held.push(text);
54842
+ return;
54843
+ }
54844
+ written.add(text);
54845
+ queued.push(text);
54846
+ };
54550
54847
  const render = (state) => {
54551
54848
  const organizationId = state.organizationId;
54552
54849
  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
- }
54850
+ if (!retain(String(organizationId))) return null;
54564
54851
  const status = state.lifecycle?.status;
54565
54852
  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`;
54853
+ return {
54854
+ text: `${mode === "json" ? JSON.stringify({
54855
+ type: "organization.lifecycle",
54856
+ organizationId: committed,
54857
+ status
54858
+ }) : `Organization ${JSON.stringify(committed)}: ${status}`}\n`,
54859
+ ready: status === "ready"
54860
+ };
54571
54861
  };
54572
54862
  return {
54573
54863
  /** Synchronous by contract: the SDK publishes state from its own fiber. */
54574
54864
  observe: (state) => {
54575
- const text = render(state);
54576
- if (text === null || written.has(text)) return;
54577
- written.add(text);
54578
- queued.push(text);
54579
- },
54865
+ const line = render(state);
54866
+ if (line !== null) enqueue(line.text, line.ready);
54867
+ },
54868
+ /** One safe recovery line the command writes before it resumes a lane. */
54869
+ notice: (text) => enqueue(text, false),
54870
+ /** The final result ID joins the same one-Organization rule. */
54871
+ commit: (organizationId) => {
54872
+ retain(organizationId);
54873
+ },
54874
+ /** Release the ready lines once current access is confirmed. */
54875
+ allowReady: () => {
54876
+ readyAllowed = true;
54877
+ while (held.length > 0) {
54878
+ const text = held.shift();
54879
+ heldSet.delete(text);
54880
+ if (written.has(text)) continue;
54881
+ written.add(text);
54882
+ queued.push(text);
54883
+ }
54884
+ },
54885
+ /** The committed ID this command observed, or null when it observed none. */
54886
+ committed: () => committed,
54580
54887
  failure: () => failure,
54581
54888
  flush: async () => {
54582
54889
  while (queued.length > 0) {
@@ -54589,6 +54896,13 @@ const makeOrganizationProgress = (mode, run) => {
54589
54896
  }
54590
54897
  queued.shift();
54591
54898
  }
54899
+ if (committed !== null && persisted !== committed) try {
54900
+ await persist(committed);
54901
+ persisted = committed;
54902
+ } catch (cause) {
54903
+ failure = cause;
54904
+ throw cause;
54905
+ }
54592
54906
  if (failure !== null) throw failure;
54593
54907
  }
54594
54908
  };
@@ -54603,15 +54917,63 @@ const confirmWrite = (preview) => Effect.gen(function* () {
54603
54917
  yield* writeStderr(`${preview.lines.join("\n")}\n`);
54604
54918
  if (!(yield* Prompt.confirm({ message: preview.question }).pipe(Effect.catch(() => Effect.succeed(false))))) return yield* Effect.fail(inputRequired("The write was not confirmed."));
54605
54919
  });
54606
- /** The Organization reads a terminal preview shows before it asks. */
54607
- const organizationPreviewLines = (client, organizationId) => organizationId === null ? Effect.gen(function* () {
54920
+ /**
54921
+ * The Organization reads a terminal preview shows before it asks.
54922
+ *
54923
+ * A create preview also names the recovery handle this machine already holds, so
54924
+ * a person who confirms knows whether the command creates one more Organization
54925
+ * or continues the exact one their interrupted journey reached. The preview
54926
+ * reads that recorded Organization's lifecycle only when the requested handle
54927
+ * continues it; a different handle is a new creation, and the recorded
54928
+ * Organization must not block it.
54929
+ */
54930
+ const organizationPreview = (client, organizationId, recovery, requestedHandle) => organizationId === null ? Effect.gen(function* () {
54608
54931
  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)}`)];
54932
+ const lines = organizations.length === 0 ? ["Existing Organizations: none"] : [`Existing Organizations: ${organizations.length}`, ...organizations.map((organization) => ` ${JSON.stringify(String(organization.id))} ${JSON.stringify(organization.name)}`)];
54933
+ if (recovery === null) return {
54934
+ lines,
54935
+ recovery,
54936
+ recoveryLifecycle: null
54937
+ };
54938
+ const exact = recovery.organizationId;
54939
+ if (exact === null) return {
54940
+ lines: [...lines, `Recovery: a create for handle @${recovery.handle} is prepared on this machine.`],
54941
+ recovery,
54942
+ recoveryLifecycle: null
54943
+ };
54944
+ if (requestedHandle !== recovery.handle) return {
54945
+ lines: [...lines, `Recovery: Organization ${JSON.stringify(String(exact))} (@${recovery.handle}) is recorded on this machine.`],
54946
+ recovery,
54947
+ recoveryLifecycle: null
54948
+ };
54949
+ const lifecycle = yield* Effect.promise((signal) => client.org(exact).getLifecycle({ signal })).pipe(Effect.flatMap(resultValue));
54950
+ yield* requireCurrentAccess(client, exact, lifecycle);
54951
+ return {
54952
+ lines: [
54953
+ ...lines,
54954
+ `Recovery: Organization ${JSON.stringify(String(exact))} (@${recovery.handle})`,
54955
+ `Current lifecycle: ${lifecycle.status}`
54956
+ ],
54957
+ recovery,
54958
+ recoveryLifecycle: lifecycle
54959
+ };
54611
54960
  }) : Effect.gen(function* () {
54612
- return [`Current lifecycle: ${(yield* Effect.promise((signal) => client.org(organizationId).getLifecycle({ signal })).pipe(Effect.flatMap(resultValue))).status}`];
54961
+ return {
54962
+ lines: [`Current lifecycle: ${(yield* Effect.promise((signal) => client.org(organizationId).getLifecycle({ signal })).pipe(Effect.flatMap(resultValue))).status}`],
54963
+ recovery,
54964
+ recoveryLifecycle: null
54965
+ };
54613
54966
  });
54614
54967
  /**
54968
+ * The confirmation question for a create. A matching recovery handle means the
54969
+ * command continues one exact Organization instead of creating another one.
54970
+ */
54971
+ const createOrganizationQuestion = (handle, preview) => {
54972
+ if (preview.recovery === null || preview.recovery.organizationId === null) return "Create this Organization?";
54973
+ if (preview.recovery.handle !== handle) return "Create this Organization?";
54974
+ return preview.recoveryLifecycle?.status === "ready" ? "Return this ready Organization?" : "Resume this Organization's setup?";
54975
+ };
54976
+ /**
54615
54977
  * Settle one exact Organization lifecycle read: poll every second with no
54616
54978
  * overlapping read until the lane is ready or failed. The command's own deadline
54617
54979
  * bounds this loop together with the write that preceded it.
@@ -54625,6 +54987,81 @@ const settleOrganizationLifecycle = (read) => Effect.gen(function* () {
54625
54987
  return lifecycle;
54626
54988
  });
54627
54989
  /**
54990
+ * Resume the exact durable Organization lane through the SDK recovery owner, then
54991
+ * settle that exact lifecycle. It never creates an Organization and never
54992
+ * resubmits a recorded bootstrap submission.
54993
+ */
54994
+ const resumeOrganizationSetup = (operation, scoped, deadline) => Effect.gen(function* () {
54995
+ yield* Effect.promise(() => scoped.retrySetup({ signal: deadline.signal })).pipe(Effect.flatMap(resultValue));
54996
+ const lifecycle = yield* settleOrganizationLifecycle(() => scoped.getLifecycle({ signal: deadline.signal }));
54997
+ yield* requireReadyLifecycle(operation, lifecycle);
54998
+ return lifecycle;
54999
+ });
55000
+ /**
55001
+ * A prepared handle owns the next durable Organization for that handle, so a
55002
+ * different handle would create a second Organization and leave the prepared lane
55003
+ * abandoned. The refusal names the exact handle to continue.
55004
+ */
55005
+ const preparedJourneyRefusal = (handle) => new CliFailure({
55006
+ code: "WRONG_STATE",
55007
+ message: `An Organization creation is already prepared for handle @${handle}. Run capxul org create with the same handle to continue that exact Organization.`,
55008
+ exitCode: 2,
55009
+ outcome: "refused"
55010
+ });
55011
+ /**
55012
+ * Read how this create continues the person's Organization journey.
55013
+ *
55014
+ * The recovery handle is the only source of the exact identity, so a matching
55015
+ * handle resumes the Organization its lane reached and never creates another
55016
+ * one. A different handle creates one Organization only after the recorded
55017
+ * Organization reaches ready.
55018
+ */
55019
+ const readCreateRoute = (client, recovery, handle, signal) => Effect.gen(function* () {
55020
+ if (recovery === null) return {
55021
+ kind: "create",
55022
+ continuesRecorded: false
55023
+ };
55024
+ if (recovery.organizationId === null) {
55025
+ if (recovery.handle === handle) return {
55026
+ kind: "create",
55027
+ continuesRecorded: true
55028
+ };
55029
+ return yield* Effect.fail(preparedJourneyRefusal(recovery.handle));
55030
+ }
55031
+ const lifecycle = yield* Effect.promise(() => client.org(recovery.organizationId).getLifecycle({ signal })).pipe(Effect.flatMap(resultValue));
55032
+ if (recovery.handle === handle) return {
55033
+ kind: "recover",
55034
+ organizationId: recovery.organizationId,
55035
+ handle: recovery.handle,
55036
+ lifecycle
55037
+ };
55038
+ if (lifecycle.status === "ready") return {
55039
+ kind: "create",
55040
+ continuesRecorded: false
55041
+ };
55042
+ return yield* Effect.fail(Errors.wrongState({
55043
+ method: CAPXUL_OPERATIONS.onboarding.completeOrganization,
55044
+ currentState: lifecycle.status,
55045
+ validStates: ["ready"],
55046
+ organizationId: String(recovery.organizationId)
55047
+ }));
55048
+ });
55049
+ /** One safe recovery line, in the same newline-delimited shape as the lifecycle. */
55050
+ const recoveryNotice = (mode, recovery) => mode === "json" ? `${JSON.stringify({
55051
+ type: "organization.recovery",
55052
+ organizationId: String(recovery.organizationId),
55053
+ handle: recovery.handle
55054
+ })}\n` : `Recovering Organization ${JSON.stringify(String(recovery.organizationId))} (@${recovery.handle}).\n`;
55055
+ /** A taken handle is refused before the backend writes any Organization row. */
55056
+ const refusedOrganizationHandle = (error) => isCapxulError(error) && error.code === "INVALID_INPUT" && error.details?.field === "handle";
55057
+ /** A write forwards no signature before its recovery handle exists; absence is a defect. */
55058
+ const organizationWriteDefect = () => new CliFailure({
55059
+ code: "CLI_DEFECT",
55060
+ message: "The Organization write lost its recovery handle.",
55061
+ exitCode: 1,
55062
+ outcome: "failed"
55063
+ });
55064
+ /**
54628
55065
  * The one deadline for a write: it aborts the active SDK signal, so the waiting
54629
55066
  * command stops and the signer forwards nothing further, while the durable
54630
55067
  * backend lane keeps running and stays resumable by `org retry`.
@@ -54734,10 +55171,15 @@ const requireReadyLifecycle = (operation, lifecycle) => lifecycle.status === "re
54734
55171
  * explicit create intent, then settle the exact Organization lifecycle. This
54735
55172
  * never saves a read default and never returns an unrelated ready Organization.
54736
55173
  *
55174
+ * A create that meets this person's prepared Organization journey continues that
55175
+ * exact Organization: the same handle resumes the lane its ID reached, and a
55176
+ * different handle refuses while that lane is unsettled. Neither path creates a
55177
+ * replacement Organization or treasury.
55178
+ *
54737
55179
  * One deadline covers the write and its settlement. At the deadline the active
54738
55180
  * SDK signal is aborted: the command stops waiting, forwards no further
54739
55181
  * signature, and returns the typed wait failure, while the durable backend lane
54740
- * keeps running and stays resumable by `org retry`.
55182
+ * keeps running and stays resumable by the same create or by `org retry`.
54741
55183
  */
54742
55184
  const createOrganization = (config, input) => Effect.gen(function* () {
54743
55185
  const operation = CAPXUL_OPERATIONS.org.create;
@@ -54747,50 +55189,99 @@ const createOrganization = (config, input) => Effect.gen(function* () {
54747
55189
  const opened = yield* openAuthenticatedSession(config, input.email);
54748
55190
  if (interactivelyConfirm) {
54749
55191
  const preview = yield* restoreVerifiedClient(config, opened, { requirement: "none" });
54750
- const resolved = yield* organizationPreviewLines(preview.client, null);
55192
+ const recovery = yield* readOrganizationRecovery(config, preview.session.authUserId);
55193
+ const resolved = yield* organizationPreview(preview.client, null, recovery, draft.handle);
54751
55194
  yield* confirmWrite({
54752
55195
  lines: [
54753
55196
  `Create Organization ${JSON.stringify(draft.name)} (@${draft.handle}, ${draft.country})`,
54754
55197
  ...draft.bio === void 0 ? [] : [`Bio: ${JSON.stringify(draft.bio)}`],
54755
55198
  ...draft.size === void 0 ? [] : [`Size: ${JSON.stringify(draft.size)}`],
54756
- ...resolved
55199
+ ...resolved.lines
54757
55200
  ],
54758
- question: "Create this Organization?"
55201
+ question: createOrganizationQuestion(draft.handle, resolved)
54759
55202
  });
54760
55203
  }
54761
55204
  const context = yield* Effect.context();
54762
55205
  const run = Effect.runPromiseWith(context);
54763
55206
  const runtime = yield* CliRuntime;
54764
- const progress = makeOrganizationProgress(runtime.mode, run);
55207
+ const progressRef = yield* Ref.make(null);
54765
55208
  const deadline = new AbortController();
54766
55209
  const openfortSignerFactory = yield* organizationWriteSigner(config, opened, run, async () => {
54767
55210
  const cancelled = cancelledWrite(operation, deadline.signal);
54768
55211
  if (cancelled !== null) throw cancelled;
54769
- await progress.flush();
55212
+ const live = await run(Ref.get(progressRef));
55213
+ if (live === null) throw organizationWriteDefect();
55214
+ await live.flush();
54770
55215
  const stopped = cancelledWrite(operation, deadline.signal);
54771
55216
  if (stopped !== null) throw stopped;
54772
55217
  });
54773
- const { client } = yield* restoreVerifiedClient(config, opened, {
55218
+ const { client, session, stillAuthenticated } = yield* restoreVerifiedClient(config, opened, {
54774
55219
  requirement: "deployed",
54775
55220
  openfortSignerFactory
54776
55221
  });
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)));
55222
+ const recovery = yield* openOrganizationRecovery(config, session.authUserId, stillAuthenticated);
55223
+ const persistRecovery = (organizationId) => Effect.gen(function* () {
55224
+ const committed = yield* onboardingOrganizationId(organizationId);
55225
+ yield* recovery.save({
55226
+ handle: draft.handle,
55227
+ organizationId: committed
55228
+ });
55229
+ });
55230
+ const progress = makeOrganizationProgress(runtime.mode, run, (organizationId) => run(persistRecovery(organizationId)));
55231
+ yield* Ref.set(progressRef, progress);
55232
+ const flushProgress = Effect.gen(function* () {
54783
55233
  yield* Effect.tryPromise({
54784
55234
  try: progress.flush,
54785
55235
  catch: (cause) => cause
54786
55236
  }).pipe(Effect.catch(() => Effect.void));
54787
55237
  const localFailure = progress.failure();
54788
55238
  if (localFailure !== null && localFailure !== void 0) return yield* Effect.fail(localFailure);
54789
- const onboarding = yield* resultValue(completed);
55239
+ });
55240
+ const settled = yield* Effect.gen(function* () {
55241
+ const route = yield* readCreateRoute(client, recovery.current(), draft.handle, deadline.signal);
55242
+ if (route.kind === "recover") {
55243
+ if (route.lifecycle.status === "ready") {
55244
+ yield* requireCurrentAccess(client, route.organizationId, route.lifecycle);
55245
+ progress.allowReady();
55246
+ return {
55247
+ organizationId: route.organizationId,
55248
+ lifecycle: route.lifecycle
55249
+ };
55250
+ }
55251
+ progress.notice(recoveryNotice(runtime.mode, route));
55252
+ yield* flushProgress;
55253
+ const lifecycle = yield* resumeOrganizationSetup(operation, client.org(route.organizationId), deadline);
55254
+ yield* requireCurrentAccess(client, route.organizationId, lifecycle);
55255
+ progress.allowReady();
55256
+ return {
55257
+ organizationId: route.organizationId,
55258
+ lifecycle
55259
+ };
55260
+ }
55261
+ yield* recovery.save({
55262
+ handle: draft.handle,
55263
+ organizationId: null
55264
+ });
55265
+ const unsubscribe = client.onboarding.subscribeOrganization(progress.observe);
55266
+ const completed = yield* Effect.promise(() => client.onboarding.completeOrganization(draft, {
55267
+ intent: "create",
55268
+ signal: deadline.signal
55269
+ })).pipe(Effect.ensuring(Effect.sync(unsubscribe)));
55270
+ if (!completed.ok) {
55271
+ yield* flushProgress;
55272
+ if (refusedOrganizationHandle(completed.error) && progress.committed() === null) yield* recovery.release();
55273
+ return yield* Effect.fail(completed.error);
55274
+ }
55275
+ const onboarding = completed.value;
54790
55276
  const organizationId = yield* onboardingOrganizationId(onboarding.organizationId);
55277
+ progress.commit(String(organizationId));
55278
+ yield* flushProgress;
54791
55279
  const scoped = client.org(organizationId);
54792
55280
  const lifecycle = yield* settleOrganizationLifecycle(() => scoped.getLifecycle({ signal: deadline.signal }));
54793
55281
  yield* requireReadyLifecycle(operation, lifecycle);
55282
+ if (route.continuesRecorded) yield* requireCurrentAccess(client, organizationId, lifecycle);
55283
+ progress.allowReady();
55284
+ yield* flushProgress;
54794
55285
  return {
54795
55286
  organizationId,
54796
55287
  lifecycle
@@ -54835,9 +55326,9 @@ const retryOrganization = (config, input) => Effect.gen(function* () {
54835
55326
  const opened = yield* openAuthenticatedSession(config, input.email);
54836
55327
  if (interactivelyConfirm) {
54837
55328
  const preview = yield* restoreVerifiedClient(config, opened, { requirement: "none" });
54838
- const resolved = yield* organizationPreviewLines(preview.client, organizationId);
55329
+ const resolved = yield* organizationPreview(preview.client, organizationId, null);
54839
55330
  yield* confirmWrite({
54840
- lines: [`Resume setup for Organization ${JSON.stringify(String(organizationId))}`, ...resolved],
55331
+ lines: [`Resume setup for Organization ${JSON.stringify(String(organizationId))}`, ...resolved.lines],
54841
55332
  question: "Resume this Organization's setup?"
54842
55333
  });
54843
55334
  }
@@ -54853,12 +55344,7 @@ const retryOrganization = (config, input) => Effect.gen(function* () {
54853
55344
  openfortSignerFactory
54854
55345
  });
54855
55346
  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));
55347
+ const settled = yield* resumeOrganizationSetup(operation, scoped, deadline).pipe(Effect.onInterrupt(() => Effect.sync(() => deadline.abort()))).pipe(Effect.timeoutOption(timeout * 1e3));
54862
55348
  if (Option.isNone(settled)) return yield* Effect.fail(organizationWaitTimeout());
54863
55349
  return {
54864
55350
  data: {
@@ -55926,7 +56412,7 @@ const create = Command.make("create", {
55926
56412
  size: Option.getOrUndefined(size),
55927
56413
  confirm,
55928
56414
  timeoutSeconds
55929
- }))).pipe(Command.annotate(CliCommandMetadata, metadata(CAPXUL_OPERATIONS.org.create)), Command.withDescription("Create one Organization; never saves a read default"));
56415
+ }))).pipe(Command.annotate(CliCommandMetadata, metadata(CAPXUL_OPERATIONS.org.create)), Command.withDescription("Create one Organization, or resume the exact one this machine recorded"));
55930
56416
  const retry = Command.make("retry", {
55931
56417
  ...orgScope,
55932
56418
  email: emailFlag,
@@ -55941,7 +56427,7 @@ const retry = Command.make("retry", {
55941
56427
  confirm,
55942
56428
  timeoutSeconds
55943
56429
  }))).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([
56430
+ const org = Command.make("org").pipe(Command.annotate(CliCommandMetadata, metadata("org")), Command.withDescription("Organization reads, writes, recovery, and local read defaults"), Command.withSubcommands([
55945
56431
  list,
55946
56432
  use,
55947
56433
  status,
@@ -56103,6 +56589,7 @@ const runCli = (platform) => Effect.uninterruptibleMask((restore) => Effect.gen(
56103
56589
  const ci = yield* Config.string("CI").pipe(Config.withDefault("false"), Effect.map((value) => value !== "" && value !== "false" && value !== "0"), Effect.catch(() => Effect.succeed(false)));
56104
56590
  const runtime = {
56105
56591
  mode,
56592
+ args,
56106
56593
  current,
56107
56594
  invocationId,
56108
56595
  observation,