@toon-protocol/rig 2.3.0 → 2.4.0

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) {
@@ -3668,9 +3673,396 @@ var runGitPassthrough = (argv2, options = {}) => {
3668
3673
  });
3669
3674
  };
3670
3675
 
3676
+ // src/cli/identity-cmd.ts
3677
+ import { existsSync, mkdirSync } from "fs";
3678
+ import { dirname as dirname2 } from "path";
3679
+ import { createInterface } from "readline/promises";
3680
+ import { parseArgs as parseArgs10 } from "util";
3681
+ async function loadKeyOps() {
3682
+ return await import("@toon-protocol/client");
3683
+ }
3684
+ var IdentityExistsError = class extends Error {
3685
+ constructor(pubkey, sourceLabel) {
3686
+ super(
3687
+ `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).`
3688
+ );
3689
+ this.pubkey = pubkey;
3690
+ this.sourceLabel = sourceLabel;
3691
+ this.name = "IdentityExistsError";
3692
+ }
3693
+ pubkey;
3694
+ sourceLabel;
3695
+ };
3696
+ var KeystoreFileExistsError = class extends Error {
3697
+ constructor(keystorePath) {
3698
+ super(
3699
+ `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.`
3700
+ );
3701
+ this.keystorePath = keystorePath;
3702
+ this.name = "KeystoreFileExistsError";
3703
+ }
3704
+ keystorePath;
3705
+ };
3706
+ async function prepareKeystoreTarget(input) {
3707
+ const keyOps = await (input.loadKeyOps ?? loadKeyOps)();
3708
+ const resolveImpl = input.resolveIdentityImpl ?? resolveIdentity;
3709
+ let existing;
3710
+ try {
3711
+ existing = await resolveImpl({
3712
+ env: input.env,
3713
+ cwd: input.cwd,
3714
+ warn: input.warn
3715
+ });
3716
+ } catch (err) {
3717
+ if (!(err instanceof MissingIdentityError)) throw err;
3718
+ }
3719
+ if (existing && !input.force) {
3720
+ throw new IdentityExistsError(existing.pubkey, existing.sourceLabel);
3721
+ }
3722
+ const keystorePath = clientKeystorePath(input.env);
3723
+ if (existsSync(keystorePath) && !input.force) {
3724
+ throw new KeystoreFileExistsError(keystorePath);
3725
+ }
3726
+ const envPassword = input.env["TOON_CLIENT_KEYSTORE_PASSWORD"]?.trim() || void 0;
3727
+ const autoPassword = envPassword === void 0;
3728
+ const password = envPassword ?? DEFAULT_KEYSTORE_PASSWORD;
3729
+ mkdirSync(dirname2(keystorePath), { recursive: true });
3730
+ return { keystorePath, password, autoPassword, existing, keyOps };
3731
+ }
3732
+ function readAccountIndex(env) {
3733
+ const file = readClientConfigFile(clientConfigPath(env));
3734
+ return typeof file.mnemonicAccountIndex === "number" ? file.mnemonicAccountIndex : 0;
3735
+ }
3736
+ function finishMint(input, target, mnemonic) {
3737
+ const configPath = linkKeystoreInClientConfig(
3738
+ input.env,
3739
+ target.keystorePath,
3740
+ target.autoPassword
3741
+ );
3742
+ const accountIndex = readAccountIndex(input.env);
3743
+ const { pubkey } = target.keyOps.deriveNostrKeyFromMnemonic(
3744
+ mnemonic,
3745
+ accountIndex
3746
+ );
3747
+ const result = {
3748
+ mnemonic,
3749
+ pubkey,
3750
+ keystorePath: target.keystorePath,
3751
+ configPath,
3752
+ autoPassword: target.autoPassword,
3753
+ accountIndex
3754
+ };
3755
+ if (target.existing && target.existing.pubkey !== pubkey) {
3756
+ result.shadowedBy = {
3757
+ sourceLabel: target.existing.sourceLabel,
3758
+ pubkey: target.existing.pubkey
3759
+ };
3760
+ }
3761
+ return result;
3762
+ }
3763
+ async function createIdentity(input) {
3764
+ const target = await prepareKeystoreTarget(input);
3765
+ const { mnemonic } = target.keyOps.generateKeystore(
3766
+ target.keystorePath,
3767
+ target.password
3768
+ );
3769
+ return finishMint(input, target, mnemonic);
3770
+ }
3771
+ async function importIdentity(input) {
3772
+ const target = await prepareKeystoreTarget(input);
3773
+ target.keyOps.importKeystore(
3774
+ target.keystorePath,
3775
+ input.mnemonic,
3776
+ target.password
3777
+ );
3778
+ return finishMint(input, target, input.mnemonic);
3779
+ }
3780
+ function createdIdentityBanner(result) {
3781
+ return [
3782
+ "",
3783
+ "\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",
3784
+ " rig: generated a new identity",
3785
+ "\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",
3786
+ ` Nostr pubkey : ${result.pubkey}`,
3787
+ "",
3788
+ " Seed phrase \u2014 this phrase CONTROLS YOUR FUNDS. Write it down and",
3789
+ " store it safely. It is shown ONCE and CANNOT be recovered:",
3790
+ "",
3791
+ ` ${result.mnemonic}`,
3792
+ "",
3793
+ ` Encrypted keystore: ${result.keystorePath}`,
3794
+ 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.",
3795
+ "\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",
3796
+ ""
3797
+ ];
3798
+ }
3799
+ function shadowNote(shadowedBy) {
3800
+ 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).`;
3801
+ }
3802
+ var IDENTITY_USAGE = `Usage: rig identity <command> [options]
3803
+
3804
+ Manage the BIP-39 signing identity rig uses to sign and pay:
3805
+
3806
+ create generate a fresh identity into the encrypted keystore under
3807
+ TOON_CLIENT_HOME. The seed phrase is shown ONCE \u2014 back it up.
3808
+ Refuses to overwrite an existing identity without --force.
3809
+ show report the ACTIVE identity's source + derived pubkey (never the
3810
+ phrase); errors with the resolution remediation when none exists.
3811
+ import read an EXISTING phrase from stdin (never a CLI argument) and write
3812
+ it to the encrypted keystore.
3813
+
3814
+ Options:
3815
+ --force (create/import) replace an existing identity/keystore
3816
+ --json machine-readable output. NOTE: \`identity create --json\`
3817
+ DELIBERATELY emits the seed phrase in a \`mnemonic\`
3818
+ field \u2014 treat that output as SECRET. \`identity show\`
3819
+ and \`identity import\` never emit the phrase.
3820
+ -h, --help show this help
3821
+
3822
+ The keystore is encrypted with TOON_CLIENT_KEYSTORE_PASSWORD when that env var
3823
+ is set, else a default password so the identity reloads with no configuration.
3824
+ rig deliberately does NOT accept the keystore password as a CLI flag \u2014 that
3825
+ would leak the value that decrypts your keys to shell history and \`ps\`.`;
3826
+ async function runIdentity(args, deps) {
3827
+ const [sub, ...rest] = args;
3828
+ switch (sub) {
3829
+ case "create":
3830
+ return runIdentityCreate(rest, deps);
3831
+ case "show":
3832
+ return runIdentityShow(rest, deps);
3833
+ case "import":
3834
+ return runIdentityImport(rest, deps);
3835
+ case "help":
3836
+ case "--help":
3837
+ case "-h":
3838
+ deps.io.out(IDENTITY_USAGE);
3839
+ return 0;
3840
+ case void 0:
3841
+ deps.io.err("rig identity needs a subcommand: create | show | import");
3842
+ deps.io.err(IDENTITY_USAGE);
3843
+ return 2;
3844
+ default:
3845
+ deps.io.err(`unknown rig identity subcommand: ${sub}`);
3846
+ deps.io.err(IDENTITY_USAGE);
3847
+ return 2;
3848
+ }
3849
+ }
3850
+ function parseMintArgs(args, verb) {
3851
+ const { values, positionals } = parseArgs10({
3852
+ args,
3853
+ options: {
3854
+ force: { type: "boolean", default: false },
3855
+ json: { type: "boolean", default: false },
3856
+ help: { type: "boolean", short: "h", default: false }
3857
+ },
3858
+ allowPositionals: false
3859
+ });
3860
+ if (positionals.length > 0) {
3861
+ throw new Error(`rig identity ${verb} takes no positional arguments`);
3862
+ }
3863
+ return {
3864
+ force: values.force ?? false,
3865
+ json: values.json ?? false,
3866
+ help: values.help ?? false
3867
+ };
3868
+ }
3869
+ async function runIdentityCreate(args, deps) {
3870
+ const { io: io2 } = deps;
3871
+ let flags;
3872
+ try {
3873
+ flags = parseMintArgs(args, "create");
3874
+ } catch (err) {
3875
+ io2.err(err instanceof Error ? err.message : String(err));
3876
+ io2.err(IDENTITY_USAGE);
3877
+ return 2;
3878
+ }
3879
+ if (flags.help) {
3880
+ io2.out(IDENTITY_USAGE);
3881
+ return 0;
3882
+ }
3883
+ try {
3884
+ const result = await createIdentity({
3885
+ env: deps.env,
3886
+ cwd: deps.cwd,
3887
+ force: flags.force,
3888
+ warn: (line) => io2.err(line),
3889
+ ...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {},
3890
+ ...deps.loadKeyOps ? { loadKeyOps: deps.loadKeyOps } : {}
3891
+ });
3892
+ emitCreated(io2, flags.json, "identity create", result);
3893
+ return 0;
3894
+ } catch (err) {
3895
+ return emitMintError(io2, flags.json, "identity create", err);
3896
+ }
3897
+ }
3898
+ async function runIdentityImport(args, deps) {
3899
+ const { io: io2 } = deps;
3900
+ let flags;
3901
+ try {
3902
+ flags = parseMintArgs(args, "import");
3903
+ } catch (err) {
3904
+ io2.err(err instanceof Error ? err.message : String(err));
3905
+ io2.err(IDENTITY_USAGE);
3906
+ return 2;
3907
+ }
3908
+ if (flags.help) {
3909
+ io2.out(IDENTITY_USAGE);
3910
+ return 0;
3911
+ }
3912
+ try {
3913
+ const read = deps.readSecretLine ?? defaultReadSecretLine;
3914
+ const phrase = (await read(
3915
+ "Paste your BIP-39 seed phrase (read from stdin, not the terminal history): ",
3916
+ io2.isInteractive
3917
+ )).trim();
3918
+ if (phrase === "") {
3919
+ throw new Error(
3920
+ "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`)."
3921
+ );
3922
+ }
3923
+ const result = await importIdentity({
3924
+ env: deps.env,
3925
+ cwd: deps.cwd,
3926
+ force: flags.force,
3927
+ mnemonic: phrase,
3928
+ warn: (line) => io2.err(line),
3929
+ ...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {},
3930
+ ...deps.loadKeyOps ? { loadKeyOps: deps.loadKeyOps } : {}
3931
+ });
3932
+ if (flags.json) {
3933
+ io2.emitJson({
3934
+ command: "identity import",
3935
+ imported: true,
3936
+ pubkey: result.pubkey,
3937
+ keystorePath: result.keystorePath,
3938
+ autoPassword: result.autoPassword,
3939
+ ...result.shadowedBy ? { shadowedBy: result.shadowedBy } : {}
3940
+ });
3941
+ } else {
3942
+ io2.out(`Imported identity ${result.pubkey}`);
3943
+ io2.out(` Encrypted keystore: ${result.keystorePath}`);
3944
+ }
3945
+ if (result.shadowedBy) io2.err(shadowNote(result.shadowedBy));
3946
+ return 0;
3947
+ } catch (err) {
3948
+ return emitMintError(io2, flags.json, "identity import", err);
3949
+ }
3950
+ }
3951
+ async function runIdentityShow(args, deps) {
3952
+ const { io: io2 } = deps;
3953
+ let json = false;
3954
+ let help = false;
3955
+ try {
3956
+ const parsed = parseArgs10({
3957
+ args,
3958
+ options: {
3959
+ json: { type: "boolean", default: false },
3960
+ help: { type: "boolean", short: "h", default: false }
3961
+ },
3962
+ allowPositionals: false
3963
+ });
3964
+ json = parsed.values.json ?? false;
3965
+ help = parsed.values.help ?? false;
3966
+ } catch (err) {
3967
+ io2.err(err instanceof Error ? err.message : String(err));
3968
+ io2.err(IDENTITY_USAGE);
3969
+ return 2;
3970
+ }
3971
+ if (help) {
3972
+ io2.out(IDENTITY_USAGE);
3973
+ return 0;
3974
+ }
3975
+ try {
3976
+ const identity = await (deps.resolveIdentityImpl ?? resolveIdentity)({
3977
+ env: deps.env,
3978
+ cwd: deps.cwd,
3979
+ warn: (line) => io2.err(line)
3980
+ });
3981
+ if (json) {
3982
+ io2.emitJson({
3983
+ command: "identity show",
3984
+ source: identity.source,
3985
+ sourceLabel: identity.sourceLabel,
3986
+ pubkey: identity.pubkey,
3987
+ accountIndex: identity.accountIndex
3988
+ });
3989
+ } else {
3990
+ io2.out(renderIdentityLine(identity));
3991
+ io2.out(` source : ${identity.source} (${identity.sourceLabel})`);
3992
+ io2.out(` pubkey : ${identity.pubkey}`);
3993
+ }
3994
+ return 0;
3995
+ } catch (err) {
3996
+ return emitCliError(io2, json, "identity show", err);
3997
+ }
3998
+ }
3999
+ function emitCreated(io2, json, command, result) {
4000
+ if (json) {
4001
+ io2.emitJson({
4002
+ command,
4003
+ created: true,
4004
+ pubkey: result.pubkey,
4005
+ keystorePath: result.keystorePath,
4006
+ autoPassword: result.autoPassword,
4007
+ // DELIBERATE, documented exception to "never print the phrase": the
4008
+ // scripting/agent path needs the generated phrase to back it up.
4009
+ mnemonic: result.mnemonic,
4010
+ ...result.shadowedBy ? { shadowedBy: result.shadowedBy } : {}
4011
+ });
4012
+ io2.err(
4013
+ "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."
4014
+ );
4015
+ } else {
4016
+ for (const line of createdIdentityBanner(result)) io2.out(line);
4017
+ }
4018
+ if (result.shadowedBy) io2.err(shadowNote(result.shadowedBy));
4019
+ }
4020
+ function emitMintError(io2, json, command, err) {
4021
+ if (err instanceof IdentityExistsError) {
4022
+ if (json) {
4023
+ io2.emitJson({
4024
+ command,
4025
+ error: "identity_exists",
4026
+ pubkey: err.pubkey,
4027
+ detail: err.message
4028
+ });
4029
+ }
4030
+ for (const line of err.message.split("\n")) io2.err(line);
4031
+ return 1;
4032
+ }
4033
+ if (err instanceof KeystoreFileExistsError) {
4034
+ if (json) {
4035
+ io2.emitJson({
4036
+ command,
4037
+ error: "keystore_exists",
4038
+ keystorePath: err.keystorePath,
4039
+ detail: err.message
4040
+ });
4041
+ }
4042
+ for (const line of err.message.split("\n")) io2.err(line);
4043
+ return 1;
4044
+ }
4045
+ return emitCliError(io2, json, command, err);
4046
+ }
4047
+ async function defaultReadSecretLine(prompt, isInteractive) {
4048
+ const rl = createInterface({ input: process.stdin, output: process.stderr });
4049
+ if (isInteractive) {
4050
+ process.stderr.write(prompt);
4051
+ rl._writeToOutput = () => {
4052
+ };
4053
+ }
4054
+ try {
4055
+ const answer = await rl.question("");
4056
+ if (isInteractive) process.stderr.write("\n");
4057
+ return answer;
4058
+ } finally {
4059
+ rl.close();
4060
+ }
4061
+ }
4062
+
3671
4063
  // src/cli/init.ts
3672
4064
  import { basename as basename2 } from "path";
3673
- import { parseArgs as parseArgs10 } from "util";
4065
+ import { parseArgs as parseArgs11 } from "util";
3674
4066
  var INIT_USAGE = `Usage: rig init [options]
3675
4067
 
3676
4068
  Set up the current git repository for rig (one-shot, idempotent, free):
@@ -3684,16 +4076,23 @@ The follow-up step is adding a relay: \`rig remote add origin <relay-url>\`
3684
4076
  \`git config toon.relay\` is migrated to the origin remote automatically
3685
4077
  when no origin exists (the old key stays readable and is removed in v0.3).
3686
4078
 
4079
+ When NO identity resolves, init does not dead-end: in a terminal it offers to
4080
+ generate one (\`Create a new identity now? [y/N]\`), and \`--generate-identity\`
4081
+ does it non-interactively (equivalent to \`rig identity create\` \u2014 the phrase
4082
+ is shown once to back up). Nothing is ever auto-generated without your yes.
4083
+
3687
4084
  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`;
4085
+ --repo-id <id> repository id / NIP-34 d-tag (default: the existing
4086
+ toon.repoid, then the repo directory basename)
4087
+ --generate-identity when no identity resolves, mint a fresh one (no prompt)
4088
+ --json machine-readable report
4089
+ -h, --help show this help`;
3692
4090
  function parseInitArgs(args) {
3693
- const { values, positionals } = parseArgs10({
4091
+ const { values, positionals } = parseArgs11({
3694
4092
  args,
3695
4093
  options: {
3696
4094
  "repo-id": { type: "string" },
4095
+ "generate-identity": { type: "boolean", default: false },
3697
4096
  json: { type: "boolean", default: false },
3698
4097
  help: { type: "boolean", short: "h", default: false }
3699
4098
  },
@@ -3703,6 +4102,7 @@ function parseInitArgs(args) {
3703
4102
  throw new Error(`rig init takes no positional arguments`);
3704
4103
  }
3705
4104
  const flags = {
4105
+ generateIdentity: values["generate-identity"] ?? false,
3706
4106
  json: values.json ?? false,
3707
4107
  help: values.help ?? false
3708
4108
  };
@@ -3713,8 +4113,38 @@ function parseInitArgs(args) {
3713
4113
  }
3714
4114
  return flags;
3715
4115
  }
3716
- async function runInit(args, deps) {
4116
+ async function resolveOrGenerateIdentity(deps, flags) {
3717
4117
  const { io: io2, env } = deps;
4118
+ const resolve2 = deps.resolveIdentityImpl ?? resolveIdentity;
4119
+ try {
4120
+ return { identity: await resolve2({ env, cwd: deps.cwd, warn: io2.err }) };
4121
+ } catch (err) {
4122
+ if (!(err instanceof MissingIdentityError)) throw err;
4123
+ let generate = flags.generateIdentity;
4124
+ if (!generate && io2.isInteractive && !flags.json) {
4125
+ io2.err("No identity found \u2014 rig needs one to sign and pay.");
4126
+ generate = await io2.confirm("Create a new identity now? [y/N] ");
4127
+ }
4128
+ if (!generate) throw err;
4129
+ const created = await (deps.createIdentityImpl ?? createIdentity)({
4130
+ env,
4131
+ cwd: deps.cwd,
4132
+ force: false,
4133
+ warn: io2.err,
4134
+ ...deps.resolveIdentityImpl ? { resolveIdentityImpl: deps.resolveIdentityImpl } : {}
4135
+ });
4136
+ const identity = {
4137
+ mnemonic: created.mnemonic,
4138
+ accountIndex: created.accountIndex,
4139
+ source: "keystore",
4140
+ sourceLabel: created.keystorePath,
4141
+ pubkey: created.pubkey
4142
+ };
4143
+ return { identity, generated: created };
4144
+ }
4145
+ }
4146
+ async function runInit(args, deps) {
4147
+ const { io: io2 } = deps;
3718
4148
  let flags;
3719
4149
  try {
3720
4150
  flags = parseInitArgs(args);
@@ -3734,11 +4164,7 @@ async function runInit(args, deps) {
3734
4164
  } catch {
3735
4165
  throw new NotAGitRepositoryError(deps.cwd);
3736
4166
  }
3737
- const identity = await (deps.resolveIdentityImpl ?? resolveIdentity)({
3738
- env,
3739
- cwd: deps.cwd,
3740
- warn: (line) => io2.err(line)
3741
- });
4167
+ const { identity, generated } = await resolveOrGenerateIdentity(deps, flags);
3742
4168
  const existing = await readToonConfig(repoRoot);
3743
4169
  const repoId = flags.repoId ?? existing.repoId ?? basename2(repoRoot);
3744
4170
  const changed = {
@@ -3772,11 +4198,29 @@ async function runInit(args, deps) {
3772
4198
  remotes,
3773
4199
  origin: originRelay ?? null,
3774
4200
  migratedToonRelay,
3775
- changed
4201
+ changed,
4202
+ ...generated ? {
4203
+ generatedIdentity: {
4204
+ mnemonic: generated.mnemonic,
4205
+ pubkey: generated.pubkey,
4206
+ keystorePath: generated.keystorePath,
4207
+ autoPassword: generated.autoPassword
4208
+ }
4209
+ } : {}
3776
4210
  };
3777
4211
  io2.emitJson(output);
4212
+ if (generated) {
4213
+ io2.err(
4214
+ "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."
4215
+ );
4216
+ if (generated.shadowedBy) io2.err(shadowNote(generated.shadowedBy));
4217
+ }
3778
4218
  return 0;
3779
4219
  }
4220
+ if (generated) {
4221
+ for (const line of createdIdentityBanner(generated)) io2.out(line);
4222
+ if (generated.shadowedBy) io2.err(shadowNote(generated.shadowedBy));
4223
+ }
3780
4224
  io2.out(`Initialized rig for ${repoRoot}`);
3781
4225
  io2.out(renderIdentityLine(identity));
3782
4226
  io2.out(
@@ -3816,7 +4260,7 @@ async function runInit(args, deps) {
3816
4260
  }
3817
4261
 
3818
4262
  // src/cli/maintainers.ts
3819
- import { parseArgs as parseArgs11 } from "util";
4263
+ import { parseArgs as parseArgs12 } from "util";
3820
4264
  var HEX64_RE3 = /^[0-9a-f]{64}$/;
3821
4265
  var WS_URL_RE4 = /^wss?:\/\//i;
3822
4266
  var MAINTAINERS_USAGE = `Usage: rig maintainers <list|add|remove> [<pubkey>] [options]
@@ -3921,7 +4365,7 @@ async function runList2(args, deps) {
3921
4365
  const { io: io2 } = deps;
3922
4366
  let flags;
3923
4367
  try {
3924
- const { values, positionals } = parseArgs11({
4368
+ const { values, positionals } = parseArgs12({
3925
4369
  args,
3926
4370
  options: MAINT_OPTIONS,
3927
4371
  allowPositionals: true
@@ -3988,7 +4432,7 @@ async function runMutate(op, args, deps) {
3988
4432
  let flags;
3989
4433
  let pubkey;
3990
4434
  try {
3991
- const { values, positionals } = parseArgs11({
4435
+ const { values, positionals } = parseArgs12({
3992
4436
  args,
3993
4437
  options: MAINT_OPTIONS,
3994
4438
  allowPositionals: true
@@ -4141,8 +4585,15 @@ var USAGE = `rig \u2014 git with a TOON remote (pay-to-write Nostr + Arweave)
4141
4585
  Usage: rig <command> [options]
4142
4586
 
4143
4587
  Commands rig owns:
4144
- init set up this repo: resolve your identity
4145
- (RIG_MNEMONIC) and write the toon.* git config
4588
+ identity create generate a fresh identity (BIP-39) into the
4589
+ encrypted keystore \u2014 the phrase is shown ONCE;
4590
+ back it up. This is your first step on a new box
4591
+ identity show the active identity's source + derived pubkey
4592
+ (never the phrase)
4593
+ identity import write an EXISTING phrase (read from stdin) to the
4594
+ encrypted keystore
4595
+ init set up this repo: resolve your identity (or offer
4596
+ to generate one) and write the toon.* git config
4146
4597
  remote add <name> <url> add a relay as a REAL git remote ("origin" is
4147
4598
  remote remove <name> the default publish target); remove/list manage
4148
4599
  remote list them \u2014 \`git remote -v\` shows the same data
@@ -4219,6 +4670,8 @@ async function dispatch(argv2, deps) {
4219
4670
  switch (command) {
4220
4671
  case "init":
4221
4672
  return runInit(rest, deps);
4673
+ case "identity":
4674
+ return runIdentity(rest, deps);
4222
4675
  case "remote":
4223
4676
  return runRemote(rest, deps);
4224
4677
  // The #278 read path — FREE (relay + Arweave gateway reads, no payment).
@@ -4306,6 +4759,7 @@ function makeCliIo(options) {
4306
4759
  }
4307
4760
  var RIG_OWNED_VERBS = /* @__PURE__ */ new Set([
4308
4761
  "init",
4762
+ "identity",
4309
4763
  "remote",
4310
4764
  "clone",
4311
4765
  "fetch",
@@ -4383,7 +4837,7 @@ function makeIo(jsonMode) {
4383
4837
  },
4384
4838
  isInteractive: Boolean(process.stdin.isTTY && process.stdout.isTTY),
4385
4839
  confirm: async (question) => {
4386
- const rl = createInterface({
4840
+ const rl = createInterface2({
4387
4841
  input: process.stdin,
4388
4842
  output: process.stderr
4389
4843
  });