@toon-protocol/rig 2.3.0 → 2.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/rig.js CHANGED
@@ -19,9 +19,14 @@ import {
19
19
  writeGitObjects
20
20
  } from "../chunk-W2SDL2PE.js";
21
21
  import {
22
+ DEFAULT_KEYSTORE_PASSWORD,
22
23
  MissingIdentityError,
24
+ clientConfigPath,
25
+ clientKeystorePath,
26
+ linkKeystoreInClientConfig,
27
+ readClientConfigFile,
23
28
  resolveIdentity
24
- } from "../chunk-CW4HJNMU.js";
29
+ } from "../chunk-XGFBDUQX.js";
25
30
  import {
26
31
  COMMENT_KIND,
27
32
  authorizedStatusAuthors,
@@ -45,7 +50,7 @@ import {
45
50
  } from "../chunk-X2CZPPDM.js";
46
51
 
47
52
  // src/cli/rig.ts
48
- import { createInterface } from "readline/promises";
53
+ import { createInterface as createInterface2 } from "readline/promises";
49
54
 
50
55
  // src/cli/dispatch.ts
51
56
  import { createRequire } from "module";
@@ -900,7 +905,7 @@ function loadPaidSession(deps, relayUrl) {
900
905
  });
901
906
  }
902
907
  var defaultLoadStandalone = async (options) => {
903
- const mod = await import("../standalone-mode-JLGAUMRF.js");
908
+ const mod = await import("../standalone-mode-VH2XPOPK.js");
904
909
  return mod.createStandaloneContext(options);
905
910
  };
906
911
  function identityReport(ctx) {
@@ -3434,7 +3439,9 @@ pays). The identity comes from RIG_MNEMONIC (env or a project .env) or the
3434
3439
  ~/.toon-client keystore/config; the faucet from TOON_CLIENT_FAUCET_URL, the
3435
3440
  faucetUrl config field, or the deployed devnet faucet when the network is
3436
3441
  devnet \u2014 including when a configured *.devnet.toonprotocol.dev origin infers
3437
- it. The faucet drips a FIXED amount per chain (there is no --amount). On a
3442
+ it (a devnet relay/proxy/btp endpoint, or the git origin remote from
3443
+ \`rig remote add origin <devnet relay>\`). The faucet drips a FIXED amount per
3444
+ chain (there is no --amount). On a
3438
3445
  network without a faucet, prints the wallet address(es) to fund externally
3439
3446
  instead.
3440
3447
 
@@ -3445,24 +3452,50 @@ Options:
3445
3452
  --json machine-readable envelope
3446
3453
  -h, --help show this help`;
3447
3454
  var SHARED_DEVNET_SUFFIX = ".devnet.toonprotocol.dev";
3448
- function sharedDevnetOrigin(env, file) {
3455
+ function devnetHost(url) {
3456
+ if (!url) return void 0;
3457
+ try {
3458
+ const { hostname } = new URL(url);
3459
+ if (hostname.endsWith(SHARED_DEVNET_SUFFIX) || hostname === SHARED_DEVNET_SUFFIX.slice(1)) {
3460
+ return url;
3461
+ }
3462
+ } catch {
3463
+ }
3464
+ return void 0;
3465
+ }
3466
+ function sharedDevnetOrigin(env, file, originRelays = []) {
3449
3467
  const candidates = [
3450
3468
  env["TOON_CLIENT_RELAY_URL"] ?? file.relayUrl,
3451
3469
  env["TOON_CLIENT_PROXY_URL"] ?? file.proxyUrl,
3452
- env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl
3470
+ env["TOON_CLIENT_BTP_URL"] ?? file.btpUrl,
3471
+ ...originRelays
3453
3472
  ];
3454
3473
  for (const url of candidates) {
3455
- if (!url) continue;
3456
- try {
3457
- const { hostname } = new URL(url);
3458
- if (hostname.endsWith(SHARED_DEVNET_SUFFIX) || hostname === SHARED_DEVNET_SUFFIX.slice(1)) {
3459
- return url;
3460
- }
3461
- } catch {
3462
- }
3474
+ const hit = devnetHost(url);
3475
+ if (hit) return hit;
3463
3476
  }
3464
3477
  return void 0;
3465
3478
  }
3479
+ async function resolveOriginRelays(cwd) {
3480
+ let repoRoot;
3481
+ try {
3482
+ repoRoot = await resolveRepoRoot(cwd);
3483
+ } catch {
3484
+ return [];
3485
+ }
3486
+ try {
3487
+ const toonConfig = await readToonConfig(repoRoot);
3488
+ const resolved = await resolveRelays({
3489
+ relayFlags: [],
3490
+ remoteName: void 0,
3491
+ repoRoot,
3492
+ toonRelays: toonConfig.relays
3493
+ });
3494
+ return resolved.relays;
3495
+ } catch {
3496
+ return [];
3497
+ }
3498
+ }
3466
3499
  function noFaucetGuidance(network, devnetOrigin) {
3467
3500
  const head = `no faucet is configured for network ${JSON.stringify(network ?? "custom")}`;
3468
3501
  const external = "To fund the wallet externally instead, send the settlement token plus native gas to the address below for the chain your channels settle on.";
@@ -3529,7 +3562,9 @@ async function runFund(args, deps) {
3529
3562
  );
3530
3563
  }
3531
3564
  const network = env["TOON_CLIENT_NETWORK"] ?? file.network;
3532
- const devnetOrigin = sharedDevnetOrigin(env, file);
3565
+ const canInfer = network === void 0 || network === "custom";
3566
+ const originRelays = canInfer ? await resolveOriginRelays(deps.cwd) : [];
3567
+ const devnetOrigin = sharedDevnetOrigin(env, file, originRelays);
3533
3568
  const inferredDevnet = devnetOrigin !== void 0 && (network === void 0 || network === "custom");
3534
3569
  const effectiveNetwork = inferredDevnet ? "devnet" : network;
3535
3570
  const faucetUrl = env["TOON_CLIENT_FAUCET_URL"] ?? file.faucetUrl ?? (effectiveNetwork === "devnet" ? DEVNET_FAUCET_URL : void 0);
@@ -3668,9 +3703,396 @@ var runGitPassthrough = (argv2, options = {}) => {
3668
3703
  });
3669
3704
  };
3670
3705
 
3706
+ // src/cli/identity-cmd.ts
3707
+ import { existsSync, mkdirSync } from "fs";
3708
+ import { dirname as dirname2 } from "path";
3709
+ import { createInterface } from "readline/promises";
3710
+ import { parseArgs as parseArgs10 } from "util";
3711
+ async function loadKeyOps() {
3712
+ return await import("@toon-protocol/client");
3713
+ }
3714
+ var IdentityExistsError = class extends Error {
3715
+ constructor(pubkey, sourceLabel) {
3716
+ super(
3717
+ `an identity already resolves (${pubkey}, from ${sourceLabel}) \u2014 rig will not overwrite it. Inspect it with \`rig identity show\`, or pass \`--force\` to replace the keystore with a brand-new identity (the old phrase becomes unrecoverable unless you backed it up).`
3718
+ );
3719
+ this.pubkey = pubkey;
3720
+ this.sourceLabel = sourceLabel;
3721
+ this.name = "IdentityExistsError";
3722
+ }
3723
+ pubkey;
3724
+ sourceLabel;
3725
+ };
3726
+ var KeystoreFileExistsError = class extends Error {
3727
+ constructor(keystorePath) {
3728
+ super(
3729
+ `a keystore file already exists at ${keystorePath} \u2014 rig will not overwrite it. Link it via the client config, or pass \`--force\` to replace it with a new identity.`
3730
+ );
3731
+ this.keystorePath = keystorePath;
3732
+ this.name = "KeystoreFileExistsError";
3733
+ }
3734
+ keystorePath;
3735
+ };
3736
+ async function prepareKeystoreTarget(input) {
3737
+ const keyOps = await (input.loadKeyOps ?? loadKeyOps)();
3738
+ const resolveImpl = input.resolveIdentityImpl ?? resolveIdentity;
3739
+ let existing;
3740
+ try {
3741
+ existing = await resolveImpl({
3742
+ env: input.env,
3743
+ cwd: input.cwd,
3744
+ warn: input.warn
3745
+ });
3746
+ } catch (err) {
3747
+ if (!(err instanceof MissingIdentityError)) throw err;
3748
+ }
3749
+ if (existing && !input.force) {
3750
+ throw new IdentityExistsError(existing.pubkey, existing.sourceLabel);
3751
+ }
3752
+ const keystorePath = clientKeystorePath(input.env);
3753
+ if (existsSync(keystorePath) && !input.force) {
3754
+ throw new KeystoreFileExistsError(keystorePath);
3755
+ }
3756
+ const envPassword = input.env["TOON_CLIENT_KEYSTORE_PASSWORD"]?.trim() || void 0;
3757
+ const autoPassword = envPassword === void 0;
3758
+ const password = envPassword ?? DEFAULT_KEYSTORE_PASSWORD;
3759
+ mkdirSync(dirname2(keystorePath), { recursive: true });
3760
+ return { keystorePath, password, autoPassword, existing, keyOps };
3761
+ }
3762
+ function readAccountIndex(env) {
3763
+ const file = readClientConfigFile(clientConfigPath(env));
3764
+ return typeof file.mnemonicAccountIndex === "number" ? file.mnemonicAccountIndex : 0;
3765
+ }
3766
+ function finishMint(input, target, mnemonic) {
3767
+ const configPath = linkKeystoreInClientConfig(
3768
+ input.env,
3769
+ target.keystorePath,
3770
+ target.autoPassword
3771
+ );
3772
+ const accountIndex = readAccountIndex(input.env);
3773
+ const { pubkey } = target.keyOps.deriveNostrKeyFromMnemonic(
3774
+ mnemonic,
3775
+ accountIndex
3776
+ );
3777
+ const result = {
3778
+ mnemonic,
3779
+ pubkey,
3780
+ keystorePath: target.keystorePath,
3781
+ configPath,
3782
+ autoPassword: target.autoPassword,
3783
+ accountIndex
3784
+ };
3785
+ if (target.existing && target.existing.pubkey !== pubkey) {
3786
+ result.shadowedBy = {
3787
+ sourceLabel: target.existing.sourceLabel,
3788
+ pubkey: target.existing.pubkey
3789
+ };
3790
+ }
3791
+ return result;
3792
+ }
3793
+ async function createIdentity(input) {
3794
+ const target = await prepareKeystoreTarget(input);
3795
+ const { mnemonic } = target.keyOps.generateKeystore(
3796
+ target.keystorePath,
3797
+ target.password
3798
+ );
3799
+ return finishMint(input, target, mnemonic);
3800
+ }
3801
+ async function importIdentity(input) {
3802
+ const target = await prepareKeystoreTarget(input);
3803
+ target.keyOps.importKeystore(
3804
+ target.keystorePath,
3805
+ input.mnemonic,
3806
+ target.password
3807
+ );
3808
+ return finishMint(input, target, input.mnemonic);
3809
+ }
3810
+ function createdIdentityBanner(result) {
3811
+ return [
3812
+ "",
3813
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
3814
+ " rig: generated a new identity",
3815
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
3816
+ ` Nostr pubkey : ${result.pubkey}`,
3817
+ "",
3818
+ " Seed phrase \u2014 this phrase CONTROLS YOUR FUNDS. Write it down and",
3819
+ " store it safely. It is shown ONCE and CANNOT be recovered:",
3820
+ "",
3821
+ ` ${result.mnemonic}`,
3822
+ "",
3823
+ ` Encrypted keystore: ${result.keystorePath}`,
3824
+ result.autoPassword ? " Encrypted with the default password, so the identity reloads with no\n env var. To use your own password: set TOON_CLIENT_KEYSTORE_PASSWORD\n and re-import with `rig identity import`." : " Encrypted with your TOON_CLIENT_KEYSTORE_PASSWORD.",
3825
+ "\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550",
3826
+ ""
3827
+ ];
3828
+ }
3829
+ function shadowNote(shadowedBy) {
3830
+ return `note: a higher-precedence identity (${shadowedBy.pubkey}, from ${shadowedBy.sourceLabel}) still shadows the new keystore \u2014 rig keeps using it until you remove that source (unset the env var / edit .env).`;
3831
+ }
3832
+ var IDENTITY_USAGE = `Usage: rig identity <command> [options]
3833
+
3834
+ Manage the BIP-39 signing identity rig uses to sign and pay:
3835
+
3836
+ create generate a fresh identity into the encrypted keystore under
3837
+ TOON_CLIENT_HOME. The seed phrase is shown ONCE \u2014 back it up.
3838
+ Refuses to overwrite an existing identity without --force.
3839
+ show report the ACTIVE identity's source + derived pubkey (never the
3840
+ phrase); errors with the resolution remediation when none exists.
3841
+ import read an EXISTING phrase from stdin (never a CLI argument) and write
3842
+ it to the encrypted keystore.
3843
+
3844
+ Options:
3845
+ --force (create/import) replace an existing identity/keystore
3846
+ --json machine-readable output. NOTE: \`identity create --json\`
3847
+ DELIBERATELY emits the seed phrase in a \`mnemonic\`
3848
+ field \u2014 treat that output as SECRET. \`identity show\`
3849
+ and \`identity import\` never emit the phrase.
3850
+ -h, --help show this help
3851
+
3852
+ The keystore is encrypted with TOON_CLIENT_KEYSTORE_PASSWORD when that env var
3853
+ is set, else a default password so the identity reloads with no configuration.
3854
+ rig deliberately does NOT accept the keystore password as a CLI flag \u2014 that
3855
+ would leak the value that decrypts your keys to shell history and \`ps\`.`;
3856
+ async function runIdentity(args, deps) {
3857
+ const [sub, ...rest] = args;
3858
+ switch (sub) {
3859
+ case "create":
3860
+ return runIdentityCreate(rest, deps);
3861
+ case "show":
3862
+ return runIdentityShow(rest, deps);
3863
+ case "import":
3864
+ return runIdentityImport(rest, deps);
3865
+ case "help":
3866
+ case "--help":
3867
+ case "-h":
3868
+ deps.io.out(IDENTITY_USAGE);
3869
+ return 0;
3870
+ case void 0:
3871
+ deps.io.err("rig identity needs a subcommand: create | show | import");
3872
+ deps.io.err(IDENTITY_USAGE);
3873
+ return 2;
3874
+ default:
3875
+ deps.io.err(`unknown rig identity subcommand: ${sub}`);
3876
+ deps.io.err(IDENTITY_USAGE);
3877
+ return 2;
3878
+ }
3879
+ }
3880
+ function parseMintArgs(args, verb) {
3881
+ const { values, positionals } = parseArgs10({
3882
+ args,
3883
+ options: {
3884
+ force: { type: "boolean", default: false },
3885
+ json: { type: "boolean", default: false },
3886
+ help: { type: "boolean", short: "h", default: false }
3887
+ },
3888
+ allowPositionals: false
3889
+ });
3890
+ if (positionals.length > 0) {
3891
+ throw new Error(`rig identity ${verb} takes no positional arguments`);
3892
+ }
3893
+ return {
3894
+ force: values.force ?? false,
3895
+ json: values.json ?? false,
3896
+ help: values.help ?? false
3897
+ };
3898
+ }
3899
+ async function runIdentityCreate(args, deps) {
3900
+ const { io: io2 } = deps;
3901
+ let flags;
3902
+ try {
3903
+ flags = parseMintArgs(args, "create");
3904
+ } catch (err) {
3905
+ io2.err(err instanceof Error ? err.message : String(err));
3906
+ io2.err(IDENTITY_USAGE);
3907
+ return 2;
3908
+ }
3909
+ if (flags.help) {
3910
+ io2.out(IDENTITY_USAGE);
3911
+ return 0;
3912
+ }
3913
+ try {
3914
+ const result = await createIdentity({
3915
+ env: deps.env,
3916
+ cwd: deps.cwd,
3917
+ force: flags.force,
3918
+ warn: (line) => io2.err(line),
3919
+ ...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {},
3920
+ ...deps.loadKeyOps ? { loadKeyOps: deps.loadKeyOps } : {}
3921
+ });
3922
+ emitCreated(io2, flags.json, "identity create", result);
3923
+ return 0;
3924
+ } catch (err) {
3925
+ return emitMintError(io2, flags.json, "identity create", err);
3926
+ }
3927
+ }
3928
+ async function runIdentityImport(args, deps) {
3929
+ const { io: io2 } = deps;
3930
+ let flags;
3931
+ try {
3932
+ flags = parseMintArgs(args, "import");
3933
+ } catch (err) {
3934
+ io2.err(err instanceof Error ? err.message : String(err));
3935
+ io2.err(IDENTITY_USAGE);
3936
+ return 2;
3937
+ }
3938
+ if (flags.help) {
3939
+ io2.out(IDENTITY_USAGE);
3940
+ return 0;
3941
+ }
3942
+ try {
3943
+ const read = deps.readSecretLine ?? defaultReadSecretLine;
3944
+ const phrase = (await read(
3945
+ "Paste your BIP-39 seed phrase (read from stdin, not the terminal history): ",
3946
+ io2.isInteractive
3947
+ )).trim();
3948
+ if (phrase === "") {
3949
+ throw new Error(
3950
+ "no seed phrase provided on stdin \u2014 pipe it in or type it at the prompt (never pass a phrase as a CLI argument; it leaks to shell history and `ps`)."
3951
+ );
3952
+ }
3953
+ const result = await importIdentity({
3954
+ env: deps.env,
3955
+ cwd: deps.cwd,
3956
+ force: flags.force,
3957
+ mnemonic: phrase,
3958
+ warn: (line) => io2.err(line),
3959
+ ...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {},
3960
+ ...deps.loadKeyOps ? { loadKeyOps: deps.loadKeyOps } : {}
3961
+ });
3962
+ if (flags.json) {
3963
+ io2.emitJson({
3964
+ command: "identity import",
3965
+ imported: true,
3966
+ pubkey: result.pubkey,
3967
+ keystorePath: result.keystorePath,
3968
+ autoPassword: result.autoPassword,
3969
+ ...result.shadowedBy ? { shadowedBy: result.shadowedBy } : {}
3970
+ });
3971
+ } else {
3972
+ io2.out(`Imported identity ${result.pubkey}`);
3973
+ io2.out(` Encrypted keystore: ${result.keystorePath}`);
3974
+ }
3975
+ if (result.shadowedBy) io2.err(shadowNote(result.shadowedBy));
3976
+ return 0;
3977
+ } catch (err) {
3978
+ return emitMintError(io2, flags.json, "identity import", err);
3979
+ }
3980
+ }
3981
+ async function runIdentityShow(args, deps) {
3982
+ const { io: io2 } = deps;
3983
+ let json = false;
3984
+ let help = false;
3985
+ try {
3986
+ const parsed = parseArgs10({
3987
+ args,
3988
+ options: {
3989
+ json: { type: "boolean", default: false },
3990
+ help: { type: "boolean", short: "h", default: false }
3991
+ },
3992
+ allowPositionals: false
3993
+ });
3994
+ json = parsed.values.json ?? false;
3995
+ help = parsed.values.help ?? false;
3996
+ } catch (err) {
3997
+ io2.err(err instanceof Error ? err.message : String(err));
3998
+ io2.err(IDENTITY_USAGE);
3999
+ return 2;
4000
+ }
4001
+ if (help) {
4002
+ io2.out(IDENTITY_USAGE);
4003
+ return 0;
4004
+ }
4005
+ try {
4006
+ const identity = await (deps.resolveIdentityImpl ?? resolveIdentity)({
4007
+ env: deps.env,
4008
+ cwd: deps.cwd,
4009
+ warn: (line) => io2.err(line)
4010
+ });
4011
+ if (json) {
4012
+ io2.emitJson({
4013
+ command: "identity show",
4014
+ source: identity.source,
4015
+ sourceLabel: identity.sourceLabel,
4016
+ pubkey: identity.pubkey,
4017
+ accountIndex: identity.accountIndex
4018
+ });
4019
+ } else {
4020
+ io2.out(renderIdentityLine(identity));
4021
+ io2.out(` source : ${identity.source} (${identity.sourceLabel})`);
4022
+ io2.out(` pubkey : ${identity.pubkey}`);
4023
+ }
4024
+ return 0;
4025
+ } catch (err) {
4026
+ return emitCliError(io2, json, "identity show", err);
4027
+ }
4028
+ }
4029
+ function emitCreated(io2, json, command, result) {
4030
+ if (json) {
4031
+ io2.emitJson({
4032
+ command,
4033
+ created: true,
4034
+ pubkey: result.pubkey,
4035
+ keystorePath: result.keystorePath,
4036
+ autoPassword: result.autoPassword,
4037
+ // DELIBERATE, documented exception to "never print the phrase": the
4038
+ // scripting/agent path needs the generated phrase to back it up.
4039
+ mnemonic: result.mnemonic,
4040
+ ...result.shadowedBy ? { shadowedBy: result.shadowedBy } : {}
4041
+ });
4042
+ io2.err(
4043
+ "rig: `--json` emitted your seed phrase in the `mnemonic` field \u2014 this output is SECRET; store it safely and do not log or share it."
4044
+ );
4045
+ } else {
4046
+ for (const line of createdIdentityBanner(result)) io2.out(line);
4047
+ }
4048
+ if (result.shadowedBy) io2.err(shadowNote(result.shadowedBy));
4049
+ }
4050
+ function emitMintError(io2, json, command, err) {
4051
+ if (err instanceof IdentityExistsError) {
4052
+ if (json) {
4053
+ io2.emitJson({
4054
+ command,
4055
+ error: "identity_exists",
4056
+ pubkey: err.pubkey,
4057
+ detail: err.message
4058
+ });
4059
+ }
4060
+ for (const line of err.message.split("\n")) io2.err(line);
4061
+ return 1;
4062
+ }
4063
+ if (err instanceof KeystoreFileExistsError) {
4064
+ if (json) {
4065
+ io2.emitJson({
4066
+ command,
4067
+ error: "keystore_exists",
4068
+ keystorePath: err.keystorePath,
4069
+ detail: err.message
4070
+ });
4071
+ }
4072
+ for (const line of err.message.split("\n")) io2.err(line);
4073
+ return 1;
4074
+ }
4075
+ return emitCliError(io2, json, command, err);
4076
+ }
4077
+ async function defaultReadSecretLine(prompt, isInteractive) {
4078
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
4079
+ if (isInteractive) {
4080
+ process.stderr.write(prompt);
4081
+ rl._writeToOutput = () => {
4082
+ };
4083
+ }
4084
+ try {
4085
+ const answer = await rl.question("");
4086
+ if (isInteractive) process.stderr.write("\n");
4087
+ return answer;
4088
+ } finally {
4089
+ rl.close();
4090
+ }
4091
+ }
4092
+
3671
4093
  // src/cli/init.ts
3672
4094
  import { basename as basename2 } from "path";
3673
- import { parseArgs as parseArgs10 } from "util";
4095
+ import { parseArgs as parseArgs11 } from "util";
3674
4096
  var INIT_USAGE = `Usage: rig init [options]
3675
4097
 
3676
4098
  Set up the current git repository for rig (one-shot, idempotent, free):
@@ -3684,16 +4106,23 @@ The follow-up step is adding a relay: \`rig remote add origin <relay-url>\`
3684
4106
  \`git config toon.relay\` is migrated to the origin remote automatically
3685
4107
  when no origin exists (the old key stays readable and is removed in v0.3).
3686
4108
 
4109
+ When NO identity resolves, init does not dead-end: in a terminal it offers to
4110
+ generate one (\`Create a new identity now? [y/N]\`), and \`--generate-identity\`
4111
+ does it non-interactively (equivalent to \`rig identity create\` \u2014 the phrase
4112
+ is shown once to back up). Nothing is ever auto-generated without your yes.
4113
+
3687
4114
  Options:
3688
- --repo-id <id> repository id / NIP-34 d-tag (default: the existing
3689
- toon.repoid, then the repo directory basename)
3690
- --json machine-readable report
3691
- -h, --help show this help`;
4115
+ --repo-id <id> repository id / NIP-34 d-tag (default: the existing
4116
+ toon.repoid, then the repo directory basename)
4117
+ --generate-identity when no identity resolves, mint a fresh one (no prompt)
4118
+ --json machine-readable report
4119
+ -h, --help show this help`;
3692
4120
  function parseInitArgs(args) {
3693
- const { values, positionals } = parseArgs10({
4121
+ const { values, positionals } = parseArgs11({
3694
4122
  args,
3695
4123
  options: {
3696
4124
  "repo-id": { type: "string" },
4125
+ "generate-identity": { type: "boolean", default: false },
3697
4126
  json: { type: "boolean", default: false },
3698
4127
  help: { type: "boolean", short: "h", default: false }
3699
4128
  },
@@ -3703,6 +4132,7 @@ function parseInitArgs(args) {
3703
4132
  throw new Error(`rig init takes no positional arguments`);
3704
4133
  }
3705
4134
  const flags = {
4135
+ generateIdentity: values["generate-identity"] ?? false,
3706
4136
  json: values.json ?? false,
3707
4137
  help: values.help ?? false
3708
4138
  };
@@ -3713,8 +4143,38 @@ function parseInitArgs(args) {
3713
4143
  }
3714
4144
  return flags;
3715
4145
  }
3716
- async function runInit(args, deps) {
4146
+ async function resolveOrGenerateIdentity(deps, flags) {
3717
4147
  const { io: io2, env } = deps;
4148
+ const resolve2 = deps.resolveIdentityImpl ?? resolveIdentity;
4149
+ try {
4150
+ return { identity: await resolve2({ env, cwd: deps.cwd, warn: io2.err }) };
4151
+ } catch (err) {
4152
+ if (!(err instanceof MissingIdentityError)) throw err;
4153
+ let generate = flags.generateIdentity;
4154
+ if (!generate && io2.isInteractive && !flags.json) {
4155
+ io2.err("No identity found \u2014 rig needs one to sign and pay.");
4156
+ generate = await io2.confirm("Create a new identity now? [y/N] ");
4157
+ }
4158
+ if (!generate) throw err;
4159
+ const created = await (deps.createIdentityImpl ?? createIdentity)({
4160
+ env,
4161
+ cwd: deps.cwd,
4162
+ force: false,
4163
+ warn: io2.err,
4164
+ ...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {}
4165
+ });
4166
+ const identity = {
4167
+ mnemonic: created.mnemonic,
4168
+ accountIndex: created.accountIndex,
4169
+ source: "keystore",
4170
+ sourceLabel: created.keystorePath,
4171
+ pubkey: created.pubkey
4172
+ };
4173
+ return { identity, generated: created };
4174
+ }
4175
+ }
4176
+ async function runInit(args, deps) {
4177
+ const { io: io2 } = deps;
3718
4178
  let flags;
3719
4179
  try {
3720
4180
  flags = parseInitArgs(args);
@@ -3734,11 +4194,7 @@ async function runInit(args, deps) {
3734
4194
  } catch {
3735
4195
  throw new NotAGitRepositoryError(deps.cwd);
3736
4196
  }
3737
- const identity = await (deps.resolveIdentityImpl ?? resolveIdentity)({
3738
- env,
3739
- cwd: deps.cwd,
3740
- warn: (line) => io2.err(line)
3741
- });
4197
+ const { identity, generated } = await resolveOrGenerateIdentity(deps, flags);
3742
4198
  const existing = await readToonConfig(repoRoot);
3743
4199
  const repoId = flags.repoId ?? existing.repoId ?? basename2(repoRoot);
3744
4200
  const changed = {
@@ -3772,11 +4228,29 @@ async function runInit(args, deps) {
3772
4228
  remotes,
3773
4229
  origin: originRelay ?? null,
3774
4230
  migratedToonRelay,
3775
- changed
4231
+ changed,
4232
+ ...generated ? {
4233
+ generatedIdentity: {
4234
+ mnemonic: generated.mnemonic,
4235
+ pubkey: generated.pubkey,
4236
+ keystorePath: generated.keystorePath,
4237
+ autoPassword: generated.autoPassword
4238
+ }
4239
+ } : {}
3776
4240
  };
3777
4241
  io2.emitJson(output);
4242
+ if (generated) {
4243
+ io2.err(
4244
+ "rig: `--json` emitted your new seed phrase in `generatedIdentity.mnemonic` \u2014 this output is SECRET; store it safely and do not log or share it."
4245
+ );
4246
+ if (generated.shadowedBy) io2.err(shadowNote(generated.shadowedBy));
4247
+ }
3778
4248
  return 0;
3779
4249
  }
4250
+ if (generated) {
4251
+ for (const line of createdIdentityBanner(generated)) io2.out(line);
4252
+ if (generated.shadowedBy) io2.err(shadowNote(generated.shadowedBy));
4253
+ }
3780
4254
  io2.out(`Initialized rig for ${repoRoot}`);
3781
4255
  io2.out(renderIdentityLine(identity));
3782
4256
  io2.out(
@@ -3816,7 +4290,7 @@ async function runInit(args, deps) {
3816
4290
  }
3817
4291
 
3818
4292
  // src/cli/maintainers.ts
3819
- import { parseArgs as parseArgs11 } from "util";
4293
+ import { parseArgs as parseArgs12 } from "util";
3820
4294
  var HEX64_RE3 = /^[0-9a-f]{64}$/;
3821
4295
  var WS_URL_RE4 = /^wss?:\/\//i;
3822
4296
  var MAINTAINERS_USAGE = `Usage: rig maintainers <list|add|remove> [<pubkey>] [options]
@@ -3921,7 +4395,7 @@ async function runList2(args, deps) {
3921
4395
  const { io: io2 } = deps;
3922
4396
  let flags;
3923
4397
  try {
3924
- const { values, positionals } = parseArgs11({
4398
+ const { values, positionals } = parseArgs12({
3925
4399
  args,
3926
4400
  options: MAINT_OPTIONS,
3927
4401
  allowPositionals: true
@@ -3988,7 +4462,7 @@ async function runMutate(op, args, deps) {
3988
4462
  let flags;
3989
4463
  let pubkey;
3990
4464
  try {
3991
- const { values, positionals } = parseArgs11({
4465
+ const { values, positionals } = parseArgs12({
3992
4466
  args,
3993
4467
  options: MAINT_OPTIONS,
3994
4468
  allowPositionals: true
@@ -4141,8 +4615,15 @@ var USAGE = `rig \u2014 git with a TOON remote (pay-to-write Nostr + Arweave)
4141
4615
  Usage: rig <command> [options]
4142
4616
 
4143
4617
  Commands rig owns:
4144
- init set up this repo: resolve your identity
4145
- (RIG_MNEMONIC) and write the toon.* git config
4618
+ identity create generate a fresh identity (BIP-39) into the
4619
+ encrypted keystore \u2014 the phrase is shown ONCE;
4620
+ back it up. This is your first step on a new box
4621
+ identity show the active identity's source + derived pubkey
4622
+ (never the phrase)
4623
+ identity import write an EXISTING phrase (read from stdin) to the
4624
+ encrypted keystore
4625
+ init set up this repo: resolve your identity (or offer
4626
+ to generate one) and write the toon.* git config
4146
4627
  remote add <name> <url> add a relay as a REAL git remote ("origin" is
4147
4628
  remote remove <name> the default publish target); remove/list manage
4148
4629
  remote list them \u2014 \`git remote -v\` shows the same data
@@ -4219,6 +4700,8 @@ async function dispatch(argv2, deps) {
4219
4700
  switch (command) {
4220
4701
  case "init":
4221
4702
  return runInit(rest, deps);
4703
+ case "identity":
4704
+ return runIdentity(rest, deps);
4222
4705
  case "remote":
4223
4706
  return runRemote(rest, deps);
4224
4707
  // The #278 read path — FREE (relay + Arweave gateway reads, no payment).
@@ -4306,6 +4789,7 @@ function makeCliIo(options) {
4306
4789
  }
4307
4790
  var RIG_OWNED_VERBS = /* @__PURE__ */ new Set([
4308
4791
  "init",
4792
+ "identity",
4309
4793
  "remote",
4310
4794
  "clone",
4311
4795
  "fetch",
@@ -4383,7 +4867,7 @@ function makeIo(jsonMode) {
4383
4867
  },
4384
4868
  isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
4385
4869
  confirm: async (question) => {
4386
- const rl = createInterface({
4870
+ const rl = createInterface2({
4387
4871
  input: process.stdin,
4388
4872
  output: process.stderr
4389
4873
  });