@zeroxyz/cli 0.0.39 → 0.0.40

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.
Files changed (2) hide show
  1. package/dist/index.js +201 -55
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { join as join9 } from "path";
7
7
  // package.json
8
8
  var package_default = {
9
9
  name: "@zeroxyz/cli",
10
- version: "0.0.39",
10
+ version: "0.0.40",
11
11
  type: "module",
12
12
  bin: {
13
13
  zero: "dist/index.js",
@@ -205,7 +205,11 @@ var searchResultSchema = z.object({
205
205
  reviewCount: z.number().optional(),
206
206
  rating: ratingSchema,
207
207
  availabilityStatus: z.enum(["healthy", "degraded", "down", "unknown"]).nullable().optional(),
208
- displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional()
208
+ displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
209
+ // Pass-through: API stamps this on Zero-published / withzero.{ai,xyz}
210
+ // services so `zero search --json` consumers can render their own
211
+ // provenance UI. CLI doesn't render a badge today.
212
+ isFirstParty: z.boolean().optional().default(false)
209
213
  });
210
214
  var searchResponseSchema = z.object({
211
215
  searchId: z.string(),
@@ -258,7 +262,9 @@ var capabilityResponseSchema = z.object({
258
262
  displayStatus: z.enum(["healthy", "stable", "degraded", "unhealthy", "unknown"]).optional(),
259
263
  activationCount: z.number().optional(),
260
264
  lastUsedAt: z.string().nullable().optional(),
261
- lastSuccessfullyRanAt: z.string().nullable().optional()
265
+ lastSuccessfullyRanAt: z.string().nullable().optional(),
266
+ // Pass-through (see searchResultSchema). Available on `zero get --json`.
267
+ isFirstParty: z.boolean().optional().default(false)
262
268
  });
263
269
  var createRunResponseSchema = z.object({
264
270
  runId: z.string()
@@ -344,6 +350,16 @@ var userDtoSchema = z.object({
344
350
  createdAt: z.union([z.string(), z.date()]).optional(),
345
351
  lastLoginAt: z.union([z.string(), z.date()]).nullable().optional()
346
352
  });
353
+ var userWalletDtoSchema = z.object({
354
+ walletAddress: z.string(),
355
+ source: z.enum(["self_custody", "privy_embedded"]),
356
+ isPrimary: z.boolean(),
357
+ linkedAt: z.union([z.string(), z.date()])
358
+ });
359
+ var signResultSchema = z.object({
360
+ signature: z.string(),
361
+ walletAddress: z.string()
362
+ });
347
363
  var deviceStartResultSchema = z.object({
348
364
  deviceCode: z.string(),
349
365
  userCode: z.string(),
@@ -361,6 +377,10 @@ var devicePollResponseSchema = z.union([
361
377
  user: userDtoSchema
362
378
  })
363
379
  ]);
380
+ var jsonStringifyBigintSafe = (value) => JSON.stringify(
381
+ value,
382
+ (_key, v) => typeof v === "bigint" ? v.toString() : v
383
+ );
364
384
  var buildCanonicalMessage = (method, path, body, timestamp, nonce) => {
365
385
  const bodyHash = createHash("sha256").update(body ?? "").digest("hex");
366
386
  return `${method}:${path}:${bodyHash}:${timestamp}:${nonce}`;
@@ -378,6 +398,9 @@ var ApiService = class _ApiService {
378
398
  account;
379
399
  credentials;
380
400
  onSessionRefreshed;
401
+ setWalletAddress = (address) => {
402
+ this.walletAddress = address;
403
+ };
381
404
  withAccount = (account) => new _ApiService(this.baseUrl, account);
382
405
  signRequest = async (method, path, body) => {
383
406
  if (!this.account) throw new Error("No private key configured");
@@ -392,13 +415,28 @@ var ApiService = class _ApiService {
392
415
  "x-zero-signature": signature
393
416
  };
394
417
  };
395
- // Session credentials take precedence over EIP-191. We never combine them
396
- // in a single request: if the user has signed in, the JWT is the identity
397
- // the API trusts, and the wallet signing path stays out of the picture.
398
- buildHeaders = async (method, path, bodyStr) => {
418
+ // Auth modes per request. `this.account` is only ever the local private key
419
+ // (the managed Privy proxy lives on PaymentService, never here), so an
420
+ // account here means a BYO key is present.
421
+ // - "default": session JWT takes precedence over EIP-191. If signed in, the
422
+ // JWT is the identity the API trusts; otherwise fall back to a wallet
423
+ // signature, else anonymous. Used by session-scoped endpoints
424
+ // (/users/me, sign-*, wallets) and read endpoints.
425
+ // - "wallet-attributed": the action is attributed to a wallet (runs,
426
+ // reviews, bug reports). Prefer the local private key (EIP-191) so the run
427
+ // is attributed to the wallet that actually paid; when there's no local
428
+ // key (managed user) fall back to the session Bearer and let the server
429
+ // resolve the wallet from the authenticated user. We never send both — a
430
+ // present session would make the server skip EIP-191 verification, so a
431
+ // Bearer + wallet headers combo must never happen.
432
+ buildHeaders = async (method, path, bodyStr, auth) => {
399
433
  const base2 = {
400
434
  "content-type": "application/json"
401
435
  };
436
+ if (auth === "wallet-attributed" && this.account) {
437
+ const walletHeaders = await this.signRequest(method, path, bodyStr);
438
+ return { ...base2, ...walletHeaders };
439
+ }
402
440
  if (this.credentials.kind === "session") {
403
441
  base2.authorization = `Bearer ${this.credentials.accessToken}`;
404
442
  return base2;
@@ -430,12 +468,13 @@ var ApiService = class _ApiService {
430
468
  await this.onSessionRefreshed(body);
431
469
  return true;
432
470
  };
433
- request = async (method, path, body) => {
471
+ request = async (method, path, body, opts = {}) => {
434
472
  const url = `${this.baseUrl}${path}`;
435
- const bodyStr = body ? JSON.stringify(body) : void 0;
473
+ const bodyStr = body ? jsonStringifyBigintSafe(body) : void 0;
474
+ const auth = opts.auth ?? "default";
436
475
  const makeInit = async () => ({
437
476
  method,
438
- headers: await this.buildHeaders(method, path, bodyStr),
477
+ headers: await this.buildHeaders(method, path, bodyStr, auth),
439
478
  body: bodyStr
440
479
  });
441
480
  let response = await fetch(url, await makeInit());
@@ -504,7 +543,9 @@ var ApiService = class _ApiService {
504
543
  return capabilityResponseSchema.parse(json);
505
544
  };
506
545
  createRun = async (data) => {
507
- const json = await this.request("POST", "/v1/runs", data);
546
+ const json = await this.request("POST", "/v1/runs", data, {
547
+ auth: "wallet-attributed"
548
+ });
508
549
  return createRunResponseSchema.parse(json);
509
550
  };
510
551
  listRuns = async (params = {}) => {
@@ -514,19 +555,32 @@ var ApiService = class _ApiService {
514
555
  if (params.limit) qs.set("limit", String(params.limit));
515
556
  if (params.cursor) qs.set("cursor", params.cursor);
516
557
  const suffix = qs.toString() ? `?${qs.toString()}` : "";
517
- const json = await this.request("GET", `/v1/runs${suffix}`);
558
+ const json = await this.request("GET", `/v1/runs${suffix}`, void 0, {
559
+ auth: "wallet-attributed"
560
+ });
518
561
  return listRunsResponseSchema.parse(json);
519
562
  };
520
563
  createReview = async (data) => {
521
- const json = await this.request("POST", "/v1/reviews", data);
564
+ const json = await this.request("POST", "/v1/reviews", data, {
565
+ auth: "wallet-attributed"
566
+ });
522
567
  return createReviewResponseSchema.parse(json);
523
568
  };
524
569
  createReviewsBatch = async (reviews) => {
525
- const json = await this.request("POST", "/v1/reviews/batch", { reviews });
570
+ const json = await this.request(
571
+ "POST",
572
+ "/v1/reviews/batch",
573
+ { reviews },
574
+ {
575
+ auth: "wallet-attributed"
576
+ }
577
+ );
526
578
  return batchReviewResponseSchema.parse(json);
527
579
  };
528
580
  createBugReport = async (data) => {
529
- const json = await this.request("POST", "/v1/bug-reports", data);
581
+ const json = await this.request("POST", "/v1/bug-reports", data, {
582
+ auth: "wallet-attributed"
583
+ });
530
584
  return createBugReportResponseSchema.parse(json);
531
585
  };
532
586
  getFundingUrl = async (amount, provider = "coinbase") => {
@@ -543,6 +597,44 @@ var ApiService = class _ApiService {
543
597
  return null;
544
598
  }
545
599
  };
600
+ getWallets = async () => {
601
+ const json = await this.request("GET", "/v1/users/me/wallets");
602
+ return z.array(userWalletDtoSchema).parse(json);
603
+ };
604
+ provisionWallet = async () => {
605
+ const json = await this.request("POST", "/v1/users/me/wallets/provision");
606
+ return userWalletDtoSchema.parse(json);
607
+ };
608
+ signTypedDataRemote = async (typedData) => {
609
+ const json = await this.request("POST", "/v1/users/me/sign-typed-data", {
610
+ typedData
611
+ });
612
+ const parsed = signResultSchema.parse(json);
613
+ return {
614
+ signature: parsed.signature,
615
+ walletAddress: parsed.walletAddress
616
+ };
617
+ };
618
+ signMessageRemote = async (message) => {
619
+ const json = await this.request("POST", "/v1/users/me/sign-message", {
620
+ message
621
+ });
622
+ const parsed = signResultSchema.parse(json);
623
+ return {
624
+ signature: parsed.signature,
625
+ walletAddress: parsed.walletAddress
626
+ };
627
+ };
628
+ signTransactionRemote = async (input) => {
629
+ const json = await this.request("POST", "/v1/users/me/sign-transaction", {
630
+ unsignedTransaction: input.unsignedTransaction
631
+ });
632
+ const parsed = signResultSchema.parse(json);
633
+ return {
634
+ signature: parsed.signature,
635
+ walletAddress: parsed.walletAddress
636
+ };
637
+ };
546
638
  };
547
639
 
548
640
  // src/commands/bug-report-command.ts
@@ -1139,6 +1231,11 @@ var PaymentService = class {
1139
1231
  `Insufficient pathUSD on Tempo testnet: have ${formatUnits(tempoBalance, 6)}, need ${capturedAmount}. Fund your wallet with the Tempo testnet faucet: https://docs.tempo.xyz/quickstart/faucet`
1140
1232
  );
1141
1233
  }
1234
+ if (this.config.managed) {
1235
+ throw new Error(
1236
+ `Insufficient USDC on Tempo: have ${formatUnits(tempoBalance, 6)}, need ${capturedAmount}. Managed wallets can't auto-bridge from Base yet \u2014 fund your Tempo wallet${this.account ? ` (${this.account.address})` : ""} directly, or set a local ZERO_PRIVATE_KEY to bridge.`
1237
+ );
1238
+ }
1142
1239
  await this.bridgeToTempo(requiredRaw, onProgress);
1143
1240
  }
1144
1241
  return capturedAmount;
@@ -3822,6 +3919,7 @@ var envSchema = z4.object({
3822
3919
  ZERO_API_URL: z4.string().default("https://api.zero.xyz"),
3823
3920
  ZERO_WEB_URL: z4.string().default("https://zero.xyz"),
3824
3921
  ZERO_PRIVATE_KEY: z4.string().optional(),
3922
+ ZERO_SESSION_TOKEN: z4.string().optional(),
3825
3923
  ZERO_ENV: z4.enum(["development", "production"]).default("production")
3826
3924
  });
3827
3925
  var getEnv = () => {
@@ -3888,15 +3986,17 @@ var AnalyticsService = class {
3888
3986
  } else {
3889
3987
  const newAnonId = randomUUID();
3890
3988
  this.distinctId = newAnonId;
3891
- try {
3892
- const dir = dirname2(opts.configPath);
3893
- mkdirSync4(dir, { recursive: true });
3894
- const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync9(opts.configPath, "utf8")) : {};
3895
- writeFileSync4(
3896
- opts.configPath,
3897
- JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
3898
- );
3899
- } catch {
3989
+ if (!process.env.VITEST) {
3990
+ try {
3991
+ const dir = dirname2(opts.configPath);
3992
+ mkdirSync4(dir, { recursive: true });
3993
+ const existing = existsSync5(opts.configPath) ? JSON.parse(readFileSync9(opts.configPath, "utf8")) : {};
3994
+ writeFileSync4(
3995
+ opts.configPath,
3996
+ JSON.stringify({ ...existing, anonId: newAnonId }, null, 2)
3997
+ );
3998
+ } catch {
3999
+ }
3900
4000
  }
3901
4001
  }
3902
4002
  const originalConsoleError = console.error;
@@ -3939,6 +4039,7 @@ var AnalyticsService = class {
3939
4039
  }
3940
4040
  if (aliasedTo === walletAddress) return;
3941
4041
  this.posthog.alias({ distinctId: walletAddress, alias: anonId });
4042
+ if (process.env.VITEST) return;
3942
4043
  try {
3943
4044
  const config = existsSync5(configPath) ? JSON.parse(readFileSync9(configPath, "utf8")) : {};
3944
4045
  writeFileSync4(
@@ -4015,6 +4116,34 @@ var AnalyticsService = class {
4015
4116
  }
4016
4117
  };
4017
4118
 
4119
+ // src/services/api-account.ts
4120
+ import {
4121
+ bytesToHex,
4122
+ parseSignature,
4123
+ serializeTransaction
4124
+ } from "viem";
4125
+ import { toAccount } from "viem/accounts";
4126
+ var createApiAccount = (walletAddress, api) => toAccount({
4127
+ address: walletAddress,
4128
+ async signMessage({ message }) {
4129
+ const asMessage = typeof message === "string" ? message : typeof message.raw === "string" ? message.raw : bytesToHex(message.raw);
4130
+ const { signature } = await api.signMessageRemote(asMessage);
4131
+ return signature;
4132
+ },
4133
+ async signTypedData(typedData) {
4134
+ const { signature } = await api.signTypedDataRemote(typedData);
4135
+ return signature;
4136
+ },
4137
+ async signTransaction(transaction, options) {
4138
+ const serialize = options?.serializer ?? serializeTransaction;
4139
+ const unsigned = await serialize(transaction);
4140
+ const { signature } = await api.signTransactionRemote({
4141
+ unsignedTransaction: unsigned
4142
+ });
4143
+ return await serialize(transaction, parseSignature(signature));
4144
+ }
4145
+ });
4146
+
4018
4147
  // src/services/state-service.ts
4019
4148
  import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync10, writeFileSync as writeFileSync5 } from "fs";
4020
4149
  import { join as join6 } from "path";
@@ -4154,30 +4283,20 @@ var detectAgentHost = (env = process.env) => {
4154
4283
  // src/app/app-services.ts
4155
4284
  var CLI_VERSION = package_default.version;
4156
4285
  var resolveCredentials = (env, config) => {
4157
- if (config.session) {
4158
- return {
4159
- credentials: {
4160
- kind: "session",
4161
- accessToken: config.session.accessToken,
4162
- refreshToken: config.session.refreshToken,
4163
- userId: config.session.userId
4164
- },
4165
- privateKey: null
4166
- };
4167
- }
4168
- if (env.ZERO_PRIVATE_KEY) {
4169
- return {
4170
- credentials: { kind: "none" },
4171
- privateKey: env.ZERO_PRIVATE_KEY
4172
- };
4173
- }
4174
- if (config.privateKey) {
4175
- return {
4176
- credentials: { kind: "none" },
4177
- privateKey: config.privateKey
4178
- };
4179
- }
4180
- return { credentials: { kind: "none" }, privateKey: null };
4286
+ const injectedToken = env.ZERO_SESSION_TOKEN;
4287
+ const credentials = injectedToken ? {
4288
+ kind: "session",
4289
+ accessToken: injectedToken,
4290
+ refreshToken: "",
4291
+ userId: ""
4292
+ } : config.session ? {
4293
+ kind: "session",
4294
+ accessToken: config.session.accessToken,
4295
+ refreshToken: config.session.refreshToken,
4296
+ userId: config.session.userId
4297
+ } : { kind: "none" };
4298
+ const privateKey = env.ZERO_PRIVATE_KEY ?? config.privateKey ?? null;
4299
+ return { credentials, privateKey };
4181
4300
  };
4182
4301
  var buildOnSessionRefreshed = (configPath) => async (tokens) => {
4183
4302
  const current = readConfig(configPath);
@@ -4192,20 +4311,34 @@ var buildOnSessionRefreshed = (configPath) => async (tokens) => {
4192
4311
  };
4193
4312
  writeSecureFile(configPath, JSON.stringify(next, null, 2));
4194
4313
  };
4195
- var getServices = (env) => {
4314
+ var getServices = async (env) => {
4196
4315
  const zeroDir = join7(homedir6(), ".zero");
4197
4316
  const configPath = join7(zeroDir, "config.json");
4198
4317
  const config = existsSync7(configPath) ? readConfig(configPath) : {};
4199
4318
  const { credentials, privateKey } = resolveCredentials(env, config);
4200
- const account = privateKey ? privateKeyToAccount4(privateKey) : null;
4201
4319
  const lowBalanceWarning = typeof config.lowBalanceWarning === "number" ? config.lowBalanceWarning : 1;
4202
4320
  const apiService = new ApiService(
4203
4321
  env.ZERO_API_URL,
4204
- account,
4322
+ privateKey ? privateKeyToAccount4(privateKey) : null,
4205
4323
  credentials,
4206
4324
  buildOnSessionRefreshed(configPath)
4207
4325
  );
4208
- const paymentService = new PaymentService(account, { lowBalanceWarning });
4326
+ let account = privateKey ? privateKeyToAccount4(privateKey) : null;
4327
+ let managed = false;
4328
+ if (!account && credentials.kind === "session") {
4329
+ const address = await resolveManagedWalletAddress(apiService);
4330
+ if (address) {
4331
+ account = createApiAccount(address, apiService);
4332
+ managed = true;
4333
+ }
4334
+ }
4335
+ if (account && !apiService.walletAddress) {
4336
+ apiService.setWalletAddress(account.address);
4337
+ }
4338
+ const paymentService = new PaymentService(account, {
4339
+ lowBalanceWarning,
4340
+ managed
4341
+ });
4209
4342
  const stateService = new StateService(zeroDir);
4210
4343
  const walletService = new WalletService(
4211
4344
  apiService.walletAddress,
@@ -4228,16 +4361,29 @@ var getServices = (env) => {
4228
4361
  walletService
4229
4362
  };
4230
4363
  };
4364
+ var resolveManagedWalletAddress = async (api) => {
4365
+ try {
4366
+ const wallets = await api.getWallets();
4367
+ const primary = wallets.find(
4368
+ (w) => w.source === "privy_embedded" && w.isPrimary
4369
+ );
4370
+ if (primary) return primary.walletAddress;
4371
+ const provisioned = await api.provisionWallet();
4372
+ return provisioned.walletAddress ?? null;
4373
+ } catch {
4374
+ return null;
4375
+ }
4376
+ };
4231
4377
 
4232
4378
  // src/app/app-context.ts
4233
- var createAppContext = () => {
4379
+ var createAppContext = async () => {
4234
4380
  const env = getEnv();
4235
4381
  if (!env) {
4236
4382
  return null;
4237
4383
  }
4238
4384
  return {
4239
4385
  env,
4240
- services: getServices(env),
4386
+ services: await getServices(env),
4241
4387
  invocation: { current: null }
4242
4388
  };
4243
4389
  };
@@ -4393,7 +4539,7 @@ var maybePrintUpdateBanner = (zeroDir, currentVersion) => {
4393
4539
 
4394
4540
  // src/index.ts
4395
4541
  var main = async () => {
4396
- const appContext = createAppContext();
4542
+ const appContext = await createAppContext();
4397
4543
  if (!appContext) {
4398
4544
  console.error("Failed to create app context");
4399
4545
  process.exit(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zeroxyz/cli",
3
- "version": "0.0.39",
3
+ "version": "0.0.40",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "zero": "dist/index.js",