@toon-protocol/rig 2.4.1 → 2.5.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
@@ -6,6 +6,7 @@ import {
6
6
  OversizeObjectsError,
7
7
  collectRepoObjects,
8
8
  executePush,
9
+ hexToNpub,
9
10
  isSafeRefname,
10
11
  missingObjectsMessage,
11
12
  ownerToHex,
@@ -17,7 +18,7 @@ import {
17
18
  setHeadSymref,
18
19
  updateRef,
19
20
  writeGitObjects
20
- } from "../chunk-W2SDL2PE.js";
21
+ } from "../chunk-PTXKCR5R.js";
21
22
  import {
22
23
  DEFAULT_KEYSTORE_PASSWORD,
23
24
  MissingIdentityError,
@@ -27,6 +28,15 @@ import {
27
28
  readClientConfigFile,
28
29
  resolveIdentity
29
30
  } from "../chunk-XGFBDUQX.js";
31
+ import {
32
+ ChannelMapStore,
33
+ channelStatus,
34
+ defaultDaemonPort,
35
+ resolveChannelPaths
36
+ } from "../chunk-SW7ZHMGS.js";
37
+ import {
38
+ MAX_OBJECT_SIZE
39
+ } from "../chunk-X2CZPPDM.js";
30
40
  import {
31
41
  COMMENT_KIND,
32
42
  authorizedStatusAuthors,
@@ -39,15 +49,6 @@ import {
39
49
  parseMaintainers,
40
50
  queryRelay
41
51
  } from "../chunk-3HRFDH7H.js";
42
- import {
43
- ChannelMapStore,
44
- channelStatus,
45
- defaultDaemonPort,
46
- resolveChannelPaths
47
- } from "../chunk-SW7ZHMGS.js";
48
- import {
49
- MAX_OBJECT_SIZE
50
- } from "../chunk-X2CZPPDM.js";
51
52
 
52
53
  // src/cli/rig.ts
53
54
  import { createInterface as createInterface2 } from "readline/promises";
@@ -263,7 +264,7 @@ var NotAGitRepositoryError = class extends Error {
263
264
  constructor(cwd) {
264
265
  super(
265
266
  `not a git repository: ${cwd}
266
- rig works inside an existing repo \u2014 create one first with \`git init\` (rig never runs it for you), then re-run.`
267
+ rig can create one for you: re-run \`rig init --git-init\` (in a terminal, plain \`rig init\` offers the same via a prompt). Or run \`git init\` yourself first, then re-run.`
267
268
  );
268
269
  this.name = "NotAGitRepositoryError";
269
270
  }
@@ -607,6 +608,9 @@ async function resolveRepoRoot(cwd) {
607
608
  );
608
609
  }
609
610
  }
611
+ async function initGitRepository(dir) {
612
+ await git(dir, ["init"]);
613
+ }
610
614
  async function readToonConfig(repoPath) {
611
615
  const [repoId, owner, relays] = await Promise.all([
612
616
  git(repoPath, ["config", "--get", "toon.repoid"], [1]),
@@ -639,6 +643,10 @@ async function listGitRemotes(repoPath) {
639
643
  }
640
644
  return remotes;
641
645
  }
646
+ async function setGitAuthor(repoPath, author) {
647
+ await git(repoPath, ["config", "--local", "user.name", author.name]);
648
+ await git(repoPath, ["config", "--local", "user.email", author.email]);
649
+ }
642
650
  async function addGitRemote(repoPath, name, url) {
643
651
  await git(repoPath, ["remote", "add", name, url]);
644
652
  }
@@ -905,7 +913,7 @@ function loadPaidSession(deps, relayUrl) {
905
913
  });
906
914
  }
907
915
  var defaultLoadStandalone = async (options) => {
908
- const mod = await import("../standalone-mode-VH2XPOPK.js");
916
+ const mod = await import("../standalone-mode-BIFFNDH3.js");
909
917
  return mod.createStandaloneContext(options);
910
918
  };
911
919
  function identityReport(ctx) {
@@ -1215,14 +1223,26 @@ async function runPush(args, deps) {
1215
1223
  // src/cli/balance.ts
1216
1224
  var BALANCE_USAGE = `Usage: rig balance [--json]
1217
1225
 
1218
- Show the active identity's money: on-chain wallet balances (per configured
1219
- chain, read from the settlement chain the payment channels actually use) and
1220
- recorded payment-channel holdings (deposited / claimed / available). Free \u2014
1221
- reads chain RPCs and local state only; nothing is signed or paid.
1226
+ Show the active identity's money: the full on-chain wallet view \u2014 native coin
1227
+ plus USDC on every configured chain (EVM / Solana / Mina) \u2014 and recorded
1228
+ payment-channel holdings (deposited / claimed / available). Free \u2014 reads chain
1229
+ RPCs and local state only; nothing is signed or paid. An unreachable chain RPC
1230
+ degrades to a per-chain notice without failing the others.
1222
1231
 
1223
1232
  Options:
1224
1233
  --json machine-readable envelope (base units as strings)
1225
1234
  -h, --help show this help`;
1235
+ function formatUnits(amount, decimals) {
1236
+ if (decimals === void 0 || decimals <= 0) return amount;
1237
+ const neg = amount.startsWith("-");
1238
+ const digits = (neg ? amount.slice(1) : amount).padStart(decimals + 1, "0");
1239
+ const whole = digits.slice(0, digits.length - decimals);
1240
+ const frac = digits.slice(digits.length - decimals).replace(/0+$/, "");
1241
+ return (neg ? "-" : "") + (frac ? `${whole}.${frac}` : whole);
1242
+ }
1243
+ function renderAmount(a) {
1244
+ return `${a.symbol ?? "token"} ${formatUnits(a.amount, a.decimals)}`;
1245
+ }
1226
1246
  async function runBalance(args, deps) {
1227
1247
  const { io: io2, env } = deps;
1228
1248
  let json = false;
@@ -1254,7 +1274,7 @@ async function runBalance(args, deps) {
1254
1274
  requireUplink: false
1255
1275
  });
1256
1276
  const identity = identityReport(ctx);
1257
- const wallet = ctx.money ? await ctx.money.walletBalances() : [];
1277
+ const wallet = ctx.money ? await ctx.money.walletChainBalances() : [];
1258
1278
  const store = new ChannelMapStore(resolveChannelPaths(env));
1259
1279
  const channels = store.list().filter((record) => record.identity === identity.pubkey).map((record) => {
1260
1280
  const watermark = store.readWatermark(record.channelId);
@@ -1291,13 +1311,23 @@ async function runBalance(args, deps) {
1291
1311
  io2.out("Wallet (on-chain):");
1292
1312
  if (wallet.length === 0) {
1293
1313
  io2.out(
1294
- " (no balance readable \u2014 no chain configured, the RPC is unreachable, or the chain's keys derive only during a client start)"
1314
+ " (no chain configured for this identity \u2014 nothing to read)"
1295
1315
  );
1296
1316
  }
1297
- for (const balance of wallet) {
1298
- io2.out(
1299
- ` ${balance.chain.padEnd(7)} ${balance.address} ${balance.amount}` + (balance.asset ? ` ${balance.asset}` : " base units") + (balance.assetScale !== void 0 ? ` (scale ${balance.assetScale})` : "")
1300
- );
1317
+ for (const chain of wallet) {
1318
+ io2.out(` ${chain.chainKey.padEnd(11)} ${chain.address}`);
1319
+ if (chain.unreadable) {
1320
+ io2.out(
1321
+ ` unreadable (RPC unreachable)` + (chain.error ? ` \u2014 ${chain.error}` : "")
1322
+ );
1323
+ continue;
1324
+ }
1325
+ const assets = [...chain.native ? [chain.native] : [], ...chain.tokens];
1326
+ if (assets.length === 0) {
1327
+ io2.out(" (no balance readable)");
1328
+ continue;
1329
+ }
1330
+ io2.out(` ${assets.map(renderAmount).join(" ")}`);
1301
1331
  }
1302
1332
  io2.out("");
1303
1333
  io2.out("Channels (recorded):");
@@ -4093,6 +4123,82 @@ async function defaultReadSecretLine(prompt, isInteractive) {
4093
4123
  // src/cli/init.ts
4094
4124
  import { basename as basename2 } from "path";
4095
4125
  import { parseArgs as parseArgs11 } from "util";
4126
+
4127
+ // src/cli/git-author.ts
4128
+ var PROFILE_KIND = 0;
4129
+ var DEFAULT_PROFILE_TIMEOUT_MS = 3e3;
4130
+ var PROFILE_TIMEOUT_ENV = "RIG_PROFILE_TIMEOUT_MS";
4131
+ function isWebSocketRelay(url) {
4132
+ return /^wss?:\/\//i.test(url);
4133
+ }
4134
+ function displayNameFromKind0(content) {
4135
+ let parsed;
4136
+ try {
4137
+ parsed = JSON.parse(content);
4138
+ } catch {
4139
+ return void 0;
4140
+ }
4141
+ if (typeof parsed !== "object" || parsed === null) return void 0;
4142
+ const fields = parsed;
4143
+ for (const key of ["display_name", "name"]) {
4144
+ const value = fields[key];
4145
+ if (typeof value === "string" && value.trim() !== "") return value.trim();
4146
+ }
4147
+ return void 0;
4148
+ }
4149
+ async function resolveGitAuthor(options) {
4150
+ const npub = hexToNpub(options.pubkey);
4151
+ const email = `${npub}@nostr`;
4152
+ const fallback = { name: npub, email, npub, source: "npub" };
4153
+ const { relayUrl } = options;
4154
+ if (relayUrl === void 0 || !isWebSocketRelay(relayUrl)) {
4155
+ return fallback;
4156
+ }
4157
+ const query = options.queryRelayImpl ?? queryRelay;
4158
+ const timeoutMs = options.timeoutMs ?? DEFAULT_PROFILE_TIMEOUT_MS;
4159
+ const factory = options.webSocketFactory ?? defaultWebSocketFactory2;
4160
+ try {
4161
+ const events = await query(
4162
+ relayUrl,
4163
+ { kinds: [PROFILE_KIND], authors: [options.pubkey], limit: 20 },
4164
+ timeoutMs,
4165
+ factory
4166
+ );
4167
+ let latest;
4168
+ for (const event of events) {
4169
+ if (event.kind !== PROFILE_KIND) continue;
4170
+ if (event.pubkey !== options.pubkey) continue;
4171
+ if (!latest || event.created_at > latest.created_at) {
4172
+ latest = { created_at: event.created_at, content: event.content };
4173
+ }
4174
+ }
4175
+ if (latest) {
4176
+ const name = displayNameFromKind0(latest.content);
4177
+ if (name !== void 0) {
4178
+ return { name, email, npub, source: "profile" };
4179
+ }
4180
+ }
4181
+ } catch {
4182
+ }
4183
+ return fallback;
4184
+ }
4185
+ function profileTimeoutFromEnv(env) {
4186
+ const raw = env[PROFILE_TIMEOUT_ENV]?.trim();
4187
+ if (!raw) return void 0;
4188
+ const ms = Number.parseInt(raw, 10);
4189
+ return Number.isFinite(ms) && ms > 0 ? ms : void 0;
4190
+ }
4191
+ function defaultWebSocketFactory2(url) {
4192
+ const ctor = globalThis.WebSocket;
4193
+ if (!ctor) {
4194
+ throw new Error(
4195
+ "No global WebSocket constructor (Node >= 22 required) for the kind:0 read"
4196
+ );
4197
+ }
4198
+ return new ctor(url);
4199
+ }
4200
+
4201
+ // src/cli/init.ts
4096
4202
  var INIT_USAGE = `Usage: rig init [options]
4097
4203
 
4098
4204
  Set up the current git repository for rig (one-shot, idempotent, free):
@@ -4101,19 +4207,35 @@ keystore/config), then writes toon.repoid and toon.owner to the repo's
4101
4207
  LOCAL git config. Re-running updates and reports; it never errors on an
4102
4208
  already-initialized repo. The seed phrase is never written anywhere.
4103
4209
 
4210
+ It also sets the repo's LOCAL git commit-author from your nostr identity
4211
+ (never --global) so \`rig commit\`/\`git commit\` work out of the box and every
4212
+ commit is attributed to the signer: user.email = <npub>@nostr, user.name =
4213
+ your kind:0 profile display name when published (read best-effort from a
4214
+ relay, latest-wins) else the npub. Re-running refreshes the name from a
4215
+ now-readable profile.
4216
+
4104
4217
  The follow-up step is adding a relay: \`rig remote add origin <relay-url>\`
4105
4218
  (a real git remote \u2014 \`git remote -v\` shows it). A deprecated v0.1
4106
4219
  \`git config toon.relay\` is migrated to the origin remote automatically
4107
4220
  when no origin exists (the old key stays readable and is removed in v0.3).
4108
4221
 
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.
4222
+ When the directory is NOT inside a git repository, init does not dead-end: in
4223
+ a terminal it offers to create one (\`Initialize a git repository here?
4224
+ [y/N]\`), and \`--git-init\` does it non-interactively. Likewise, when NO
4225
+ identity resolves it offers to generate one (\`Create a new identity now?
4226
+ [y/N]\`), and \`--generate-identity\` does it non-interactively (equivalent to
4227
+ \`rig identity create\` \u2014 the phrase is shown once to back up). Nothing \u2014 repo
4228
+ or identity \u2014 is ever created without your yes. \`--git-init
4229
+ --generate-identity\` is a fully non-interactive fresh setup.
4113
4230
 
4114
4231
  Options:
4115
4232
  --repo-id <id> repository id / NIP-34 d-tag (default: the existing
4116
4233
  toon.repoid, then the repo directory basename)
4234
+ --git-init when the cwd is not a git repo, run \`git init\` here
4235
+ first, then proceed (no prompt)
4236
+ --relay <url> relay to read your kind:0 profile from for the git
4237
+ author name (default: origin/toon.relay, then the
4238
+ genesis seed; skipped if none is resolvable)
4117
4239
  --generate-identity when no identity resolves, mint a fresh one (no prompt)
4118
4240
  --json machine-readable report
4119
4241
  -h, --help show this help`;
@@ -4122,6 +4244,8 @@ function parseInitArgs(args) {
4122
4244
  args,
4123
4245
  options: {
4124
4246
  "repo-id": { type: "string" },
4247
+ "git-init": { type: "boolean", default: false },
4248
+ relay: { type: "string" },
4125
4249
  "generate-identity": { type: "boolean", default: false },
4126
4250
  json: { type: "boolean", default: false },
4127
4251
  help: { type: "boolean", short: "h", default: false }
@@ -4132,6 +4256,7 @@ function parseInitArgs(args) {
4132
4256
  throw new Error(`rig init takes no positional arguments`);
4133
4257
  }
4134
4258
  const flags = {
4259
+ gitInit: values["git-init"] ?? false,
4135
4260
  generateIdentity: values["generate-identity"] ?? false,
4136
4261
  json: values.json ?? false,
4137
4262
  help: values.help ?? false
@@ -4141,8 +4266,46 @@ function parseInitArgs(args) {
4141
4266
  if (repoId.trim() === "") throw new Error("--repo-id must not be empty");
4142
4267
  flags.repoId = repoId;
4143
4268
  }
4269
+ const relay = values.relay;
4270
+ if (relay !== void 0) {
4271
+ if (relay.trim() === "") throw new Error("--relay must not be empty");
4272
+ flags.relay = relay.trim();
4273
+ }
4144
4274
  return flags;
4145
4275
  }
4276
+ function isWebSocketRelay2(url) {
4277
+ return /^wss?:\/\//i.test(url);
4278
+ }
4279
+ async function resolveProfileRelay(flagRelay, originRelay, toonRelays) {
4280
+ const candidates = [
4281
+ flagRelay,
4282
+ originRelay,
4283
+ ...toonRelays
4284
+ ].filter((u) => u !== void 0 && isWebSocketRelay2(u));
4285
+ if (candidates[0] !== void 0) return candidates[0];
4286
+ try {
4287
+ const { loadGenesisSeed } = await import("../network-bootstrap-WNSHRC5P.js");
4288
+ const seed = loadGenesisSeed();
4289
+ if (seed?.relayUrl && isWebSocketRelay2(seed.relayUrl)) return seed.relayUrl;
4290
+ } catch {
4291
+ }
4292
+ return void 0;
4293
+ }
4294
+ async function resolveOrInitGitRepo(deps, flags) {
4295
+ const { io: io2 } = deps;
4296
+ try {
4297
+ return { repoRoot: await resolveRepoRoot(deps.cwd), initialized: false };
4298
+ } catch {
4299
+ let doInit = flags.gitInit;
4300
+ if (!doInit && io2.isInteractive && !flags.json) {
4301
+ io2.err("Not a git repository \u2014 rig keeps its config and objects in one.");
4302
+ doInit = await io2.confirm("Initialize a git repository here? [y/N] ");
4303
+ }
4304
+ if (!doInit) throw new NotAGitRepositoryError(deps.cwd);
4305
+ await initGitRepository(deps.cwd);
4306
+ return { repoRoot: await resolveRepoRoot(deps.cwd), initialized: true };
4307
+ }
4308
+ }
4146
4309
  async function resolveOrGenerateIdentity(deps, flags) {
4147
4310
  const { io: io2, env } = deps;
4148
4311
  const resolve2 = deps.resolveIdentityImpl ?? resolveIdentity;
@@ -4188,12 +4351,7 @@ async function runInit(args, deps) {
4188
4351
  return 0;
4189
4352
  }
4190
4353
  try {
4191
- let repoRoot;
4192
- try {
4193
- repoRoot = await resolveRepoRoot(deps.cwd);
4194
- } catch {
4195
- throw new NotAGitRepositoryError(deps.cwd);
4196
- }
4354
+ const { repoRoot, initialized: initializedGitRepo } = await resolveOrInitGitRepo(deps, flags);
4197
4355
  const { identity, generated } = await resolveOrGenerateIdentity(deps, flags);
4198
4356
  const existing = await readToonConfig(repoRoot);
4199
4357
  const repoId = flags.repoId ?? existing.repoId ?? basename2(repoRoot);
@@ -4212,10 +4370,26 @@ async function runInit(args, deps) {
4212
4370
  }
4213
4371
  const origin = remotes.find((r) => r.name === "origin");
4214
4372
  const originRelay = origin !== void 0 && origin.urls.length === 1 && isRelayUrl(origin.urls[0]) ? origin.urls[0] : void 0;
4373
+ const profileRelay = await resolveProfileRelay(
4374
+ flags.relay,
4375
+ originRelay,
4376
+ existing.relays
4377
+ );
4378
+ const resolveAuthor = deps.resolveGitAuthorImpl ?? resolveGitAuthor;
4379
+ const gitAuthor = await resolveAuthor({
4380
+ pubkey: identity.pubkey,
4381
+ relayUrl: profileRelay,
4382
+ ...profileTimeoutFromEnv(deps.env) !== void 0 ? { timeoutMs: profileTimeoutFromEnv(deps.env) } : {}
4383
+ });
4384
+ await setGitAuthor(repoRoot, {
4385
+ name: gitAuthor.name,
4386
+ email: gitAuthor.email
4387
+ });
4215
4388
  if (flags.json) {
4216
4389
  const output = {
4217
4390
  command: "init",
4218
4391
  repoRoot,
4392
+ initializedGitRepo,
4219
4393
  repoId,
4220
4394
  owner: identity.pubkey,
4221
4395
  identity: {
@@ -4228,6 +4402,11 @@ async function runInit(args, deps) {
4228
4402
  remotes,
4229
4403
  origin: originRelay ?? null,
4230
4404
  migratedToonRelay,
4405
+ gitAuthor: {
4406
+ name: gitAuthor.name,
4407
+ email: gitAuthor.email,
4408
+ source: gitAuthor.source
4409
+ },
4231
4410
  changed,
4232
4411
  ...generated ? {
4233
4412
  generatedIdentity: {
@@ -4251,6 +4430,7 @@ async function runInit(args, deps) {
4251
4430
  for (const line of createdIdentityBanner(generated)) io2.out(line);
4252
4431
  if (generated.shadowedBy) io2.err(shadowNote(generated.shadowedBy));
4253
4432
  }
4433
+ if (initializedGitRepo) io2.out(`Created a git repository at ${repoRoot}`);
4254
4434
  io2.out(`Initialized rig for ${repoRoot}`);
4255
4435
  io2.out(renderIdentityLine(identity));
4256
4436
  io2.out(
@@ -4259,6 +4439,9 @@ async function runInit(args, deps) {
4259
4439
  io2.out(
4260
4440
  ` toon.owner = ${identity.pubkey}` + (changed.owner ? "" : " (unchanged)") + (existing.owner && changed.owner ? ` (was ${existing.owner})` : "")
4261
4441
  );
4442
+ io2.out(
4443
+ `Git author: ${gitAuthor.name} <${gitAuthor.email}> (from ${gitAuthor.source === "profile" ? "nostr profile" : "npub"})`
4444
+ );
4262
4445
  if (originRelay !== void 0) {
4263
4446
  io2.out(
4264
4447
  ` origin = ${originRelay}` + (migratedToonRelay ? " (migrated from git config toon.relay)" : "")