@alchemy/cli 0.7.4 → 0.9.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.
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ registerAuth,
5
+ selectAppAfterAuth
6
+ } from "./chunk-XCKUCXC6.js";
7
+ import "./chunk-RGVM5SNE.js";
8
+ import "./chunk-75ICFV5K.js";
9
+ import "./chunk-7ZSEELHZ.js";
10
+ import "./chunk-MV7O3XBG.js";
11
+ import "./chunk-GDLPBPG3.js";
12
+ import "./chunk-OVLQH6KL.js";
13
+ export {
14
+ registerAuth,
15
+ selectAppAfterAuth
16
+ };
@@ -11,8 +11,8 @@ import {
11
11
  prepareLogin,
12
12
  revokeToken,
13
13
  waitForCallback
14
- } from "./chunk-C5HNQOLB.js";
15
- import "./chunk-2BALTY22.js";
14
+ } from "./chunk-RGVM5SNE.js";
15
+ import "./chunk-OVLQH6KL.js";
16
16
  export {
17
17
  AUTH_PORT,
18
18
  DEFAULT_EXPIRES_IN_SECONDS,
@@ -2,7 +2,7 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  isJSONMode
5
- } from "./chunk-2BALTY22.js";
5
+ } from "./chunk-OVLQH6KL.js";
6
6
 
7
7
  // src/lib/interaction.ts
8
8
  import { stdin, stdout } from "process";
@@ -4,7 +4,7 @@ import {
4
4
  configDir,
5
5
  load,
6
6
  save
7
- } from "./chunk-JUCUKTP3.js";
7
+ } from "./chunk-GDLPBPG3.js";
8
8
  import {
9
9
  CLIError,
10
10
  ErrorCode,
@@ -17,6 +17,7 @@ import {
17
17
  errInvalidAPIKey,
18
18
  errInvalidAccessKey,
19
19
  errInvalidArgs,
20
+ errLoginRequired,
20
21
  errNetwork,
21
22
  errNetworkNotEnabled,
22
23
  errNotFound,
@@ -29,7 +30,7 @@ import {
29
30
  parseBaseURLOverride,
30
31
  redactSensitiveText,
31
32
  verbose
32
- } from "./chunk-2BALTY22.js";
33
+ } from "./chunk-OVLQH6KL.js";
33
34
 
34
35
  // src/lib/resolve.ts
35
36
  import { readFileSync as readFileSync2 } from "fs";
@@ -293,6 +294,12 @@ var NATIVE_TOKEN_SYMBOLS = {
293
294
  function isSolanaNetwork(networkId) {
294
295
  return networkId.startsWith("solana-");
295
296
  }
297
+ function toAdminNetworkId(slug) {
298
+ return slug.trim().toUpperCase().replace(/-/g, "_");
299
+ }
300
+ function fromAdminNetworkId(id) {
301
+ return id.trim().toLowerCase().replace(/_/g, "-");
302
+ }
296
303
  function nativeTokenSymbol(networkId) {
297
304
  const prefix = networkId.replace(/-(mainnet|testnet|sepolia|holesky|hoodi|devnet|amoy|fuji|cardona|saigon|chiado|signet|mocha|blaze|curtis|bepolia).*$/, "");
298
305
  return NATIVE_TOKEN_SYMBOLS[prefix] ?? "ETH";
@@ -862,6 +869,166 @@ var AdminClient = class _AdminClient {
862
869
  }
863
870
  };
864
871
 
872
+ // src/lib/gas-manager-client.ts
873
+ var GasManagerClient = class _GasManagerClient {
874
+ static get HOST() {
875
+ return `manage.g.${getBaseDomain()}`;
876
+ }
877
+ // Test/debug only: route requests to a local mock.
878
+ static BASE_URL_ENV = "ALCHEMY_GAS_MANAGER_BASE_URL";
879
+ credential;
880
+ constructor(credential) {
881
+ if (typeof credential === "string") {
882
+ this.validateAccessKey(credential);
883
+ this.credential = { type: "access_key", key: credential };
884
+ } else {
885
+ if (credential.type === "access_key") {
886
+ this.validateAccessKey(credential.key);
887
+ } else if (!credential.token.trim()) {
888
+ throw errAuthRequired();
889
+ }
890
+ this.credential = credential;
891
+ }
892
+ }
893
+ baseURL() {
894
+ const override = this.baseURLOverride();
895
+ if (override) return override.toString().replace(/\/$/, "");
896
+ return `https://manage.g.${getBaseDomain()}`;
897
+ }
898
+ allowedHosts() {
899
+ const hosts = /* @__PURE__ */ new Set([_GasManagerClient.HOST]);
900
+ const override = this.baseURLOverride();
901
+ if (override) hosts.add(override.hostname);
902
+ return hosts;
903
+ }
904
+ allowInsecureTransport(hostname) {
905
+ return isLocalhost(hostname);
906
+ }
907
+ baseURLOverride() {
908
+ return parseBaseURLOverride(_GasManagerClient.BASE_URL_ENV);
909
+ }
910
+ validateAccessKey(accessKey) {
911
+ if (!accessKey.trim() || /\s/.test(accessKey)) {
912
+ throw errInvalidAccessKey();
913
+ }
914
+ }
915
+ assertSafeRequestTarget(url) {
916
+ let parsed;
917
+ try {
918
+ parsed = new URL(url);
919
+ } catch {
920
+ throw errInvalidArgs("Invalid gas manager API URL.");
921
+ }
922
+ if (!this.allowedHosts().has(parsed.hostname)) {
923
+ throw errInvalidArgs(`Refusing to send credentials to unexpected host: ${parsed.hostname}`);
924
+ }
925
+ if (parsed.protocol !== "https:" && !this.allowInsecureTransport(parsed.hostname)) {
926
+ throw errInvalidArgs("Refusing to send credentials over non-HTTPS connection.");
927
+ }
928
+ }
929
+ async request(method, path, body) {
930
+ const url = `${this.baseURL()}${path}`;
931
+ debug(`${method} ${url}`);
932
+ this.assertSafeRequestTarget(url);
933
+ const token = this.credential.type === "access_key" ? this.credential.key : this.credential.token;
934
+ const resp = await fetchWithTimeout(url, {
935
+ method,
936
+ redirect: "error",
937
+ headers: {
938
+ Authorization: `Bearer ${token}`,
939
+ "Content-Type": "application/json",
940
+ Accept: "application/json"
941
+ },
942
+ ...body !== void 0 && { body: JSON.stringify(body) }
943
+ });
944
+ if (resp.status === 401) {
945
+ throw this.credential.type === "auth_token" ? errLoginRequired() : errInvalidAccessKey();
946
+ }
947
+ if (resp.status === 403) {
948
+ const detail = await resp.text().catch(() => "");
949
+ let reason;
950
+ try {
951
+ const parsed = JSON.parse(detail);
952
+ reason = parsed?.error?.msg ?? parsed?.message ?? parsed?.error ?? void 0;
953
+ } catch {
954
+ reason = detail || void 0;
955
+ }
956
+ if (this.credential.type === "auth_token") {
957
+ const tail = reason ? ` (${reason})` : "";
958
+ throw new CLIError(
959
+ ErrorCode.AUTH_REQUIRED,
960
+ `Access denied for this Alchemy account or app${tail}. Re-authenticate with 'alchemy auth login' or check that the default app is one you have permission to manage.`,
961
+ "alchemy auth login"
962
+ );
963
+ }
964
+ throw errAccessDenied(typeof reason === "string" ? reason : void 0);
965
+ }
966
+ if (resp.status === 404) {
967
+ const text = await resp.text().catch(() => "");
968
+ throw errNotFound(text || path);
969
+ }
970
+ if (resp.status === 429) throw errRateLimited();
971
+ if (!resp.ok) {
972
+ const text = await resp.text().catch(() => "");
973
+ throw errAdminAPI(resp.status, text);
974
+ }
975
+ return resp.json();
976
+ }
977
+ async listPolicies(opts) {
978
+ const params = new URLSearchParams();
979
+ params.set("appId", opts.appId);
980
+ if (opts.limit !== void 0) params.set("limit", String(opts.limit));
981
+ if (opts.after) params.set("after", opts.after);
982
+ if (opts.before) params.set("before", opts.before);
983
+ const resp = await this.request(
984
+ "GET",
985
+ `/api/gasManager/policies?${params.toString()}`
986
+ );
987
+ return resp.data;
988
+ }
989
+ async listAllPolicies(opts) {
990
+ const policies = [];
991
+ const seen = /* @__PURE__ */ new Set();
992
+ let after;
993
+ do {
994
+ const page = await this.listPolicies({
995
+ appId: opts.appId,
996
+ ...opts.limit !== void 0 && { limit: opts.limit },
997
+ ...after && { after }
998
+ });
999
+ policies.push(...page.policies);
1000
+ const next = page.after ?? void 0;
1001
+ if (next && seen.has(next)) break;
1002
+ if (next) seen.add(next);
1003
+ after = next;
1004
+ } while (after);
1005
+ return policies;
1006
+ }
1007
+ async getPolicy(policyId) {
1008
+ const resp = await this.request(
1009
+ "GET",
1010
+ `/api/gasManager/policy/${encodeURIComponent(policyId)}`
1011
+ );
1012
+ return resp.data.policy;
1013
+ }
1014
+ async createPolicy(payload) {
1015
+ const resp = await this.request(
1016
+ "POST",
1017
+ "/api/gasManager/policy",
1018
+ payload
1019
+ );
1020
+ return resp.data.policy;
1021
+ }
1022
+ async setPolicyStatus(policyId, status) {
1023
+ const resp = await this.request(
1024
+ "PUT",
1025
+ `/api/gasManager/policy/${encodeURIComponent(policyId)}/status`,
1026
+ { status }
1027
+ );
1028
+ return resp.data.policy;
1029
+ }
1030
+ };
1031
+
865
1032
  // src/lib/wallet-session.ts
866
1033
  import { generateKeyPairSync, randomUUID } from "crypto";
867
1034
  import { readFileSync, writeFileSync, mkdirSync, rmSync, existsSync } from "fs";
@@ -884,7 +1051,20 @@ var walletSessionEnvironmentSchema = z.object({
884
1051
  clientInstanceId: z.string().uuid().optional(),
885
1052
  clientInstanceName: z.string().min(1).max(64).optional()
886
1053
  }).strict();
1054
+ var chainWalletSessionSchema = z.object({
1055
+ sessionId: z.string().uuid(),
1056
+ status: z.enum(["pending", "approved", "revoked", "expired"]),
1057
+ expiresAt: z.string().datetime(),
1058
+ chainType: z.enum(["evm", "solana"]),
1059
+ walletId: z.string().min(1).optional(),
1060
+ walletAddress: z.string().min(1).optional(),
1061
+ providerKeyQuorumId: z.string().min(1).optional(),
1062
+ providerSignerId: z.string().min(1).optional(),
1063
+ capabilities: walletSessionCapabilitiesSchema.optional()
1064
+ }).strict();
887
1065
  var walletSessionSchema = z.object({
1066
+ version: z.number().int().positive().optional(),
1067
+ connectionRequestId: z.string().min(1).optional(),
888
1068
  sessionId: z.string().uuid(),
889
1069
  status: z.enum(["pending", "approved", "revoked", "expired"]),
890
1070
  createdAt: z.string().datetime(),
@@ -903,7 +1083,11 @@ var walletSessionSchema = z.object({
903
1083
  chainType: z.string().min(1).optional(),
904
1084
  backendBaseUrl: z.string().min(1).optional(),
905
1085
  environment: walletSessionEnvironmentSchema.optional(),
906
- capabilities: walletSessionCapabilitiesSchema.optional()
1086
+ capabilities: walletSessionCapabilitiesSchema.optional(),
1087
+ sessionsByChain: z.object({
1088
+ evm: chainWalletSessionSchema.optional(),
1089
+ solana: chainWalletSessionSchema.optional()
1090
+ }).strict().optional()
907
1091
  }).strict();
908
1092
  function sessionPath() {
909
1093
  return join(configDir(), SESSION_FILE);
@@ -911,7 +1095,59 @@ function sessionPath() {
911
1095
  function parseStoredSession(value) {
912
1096
  const parsed = walletSessionSchema.safeParse(value);
913
1097
  if (!parsed.success) return null;
914
- return parsed.data;
1098
+ return withLegacyChainSessions(parsed.data);
1099
+ }
1100
+ function withLegacyChainSessions(session) {
1101
+ if (session.sessionsByChain) {
1102
+ return withCompatibilityAliases(session);
1103
+ }
1104
+ const chainType = session.chainType === "solana" ? "solana" : "evm";
1105
+ const walletId = chainType === "solana" ? session.solanaWalletId ?? session.walletId : session.evmWalletId ?? session.walletId;
1106
+ const walletAddress = chainType === "solana" ? session.solanaAddress : session.evmAddress;
1107
+ return withCompatibilityAliases({
1108
+ ...session,
1109
+ version: session.version ?? 1,
1110
+ sessionsByChain: {
1111
+ [chainType]: {
1112
+ sessionId: session.sessionId,
1113
+ status: session.status,
1114
+ expiresAt: session.expiresAt,
1115
+ chainType,
1116
+ ...walletId ? { walletId } : {},
1117
+ ...walletAddress ? { walletAddress } : {},
1118
+ ...session.privyKeyQuorumId ? { providerKeyQuorumId: session.privyKeyQuorumId } : {},
1119
+ ...session.privySignerId ? { providerSignerId: session.privySignerId } : {},
1120
+ ...session.capabilities ? { capabilities: session.capabilities } : {}
1121
+ }
1122
+ }
1123
+ });
1124
+ }
1125
+ function withCompatibilityAliases(session) {
1126
+ const evm = session.sessionsByChain?.evm;
1127
+ const solana = session.sessionsByChain?.solana;
1128
+ const preferred = evm ?? solana;
1129
+ if (!preferred) {
1130
+ return session;
1131
+ }
1132
+ return {
1133
+ ...session,
1134
+ sessionId: preferred.sessionId,
1135
+ status: preferred.status,
1136
+ expiresAt: preferred.expiresAt,
1137
+ walletId: evm?.walletId ?? solana?.walletId ?? session.walletId,
1138
+ evmWalletId: evm?.walletId ?? session.evmWalletId,
1139
+ evmAddress: evm?.walletAddress ?? session.evmAddress,
1140
+ solanaWalletId: solana?.walletId ?? session.solanaWalletId,
1141
+ solanaAddress: solana?.walletAddress ?? session.solanaAddress,
1142
+ privyKeyQuorumId: evm?.providerKeyQuorumId ?? solana?.providerKeyQuorumId ?? session.privyKeyQuorumId,
1143
+ privySignerId: evm?.providerSignerId ?? solana?.providerSignerId ?? session.privySignerId,
1144
+ chainType: evm && solana ? "both" : preferred.chainType,
1145
+ capabilities: {
1146
+ ...evm?.capabilities ?? {},
1147
+ ...solana?.capabilities ?? {},
1148
+ ...session.capabilities ?? {}
1149
+ }
1150
+ };
915
1151
  }
916
1152
  function loadStoredSessionFromPath(path) {
917
1153
  if (!existsSync(path)) return null;
@@ -925,13 +1161,23 @@ function isExpired(session) {
925
1161
  const expiresAt = new Date(session.expiresAt);
926
1162
  return !Number.isNaN(expiresAt.getTime()) && expiresAt <= /* @__PURE__ */ new Date();
927
1163
  }
1164
+ function isSessionLoadable(session) {
1165
+ if (session.sessionsByChain) {
1166
+ return Object.values(session.sessionsByChain).some((chainSession) => {
1167
+ return Boolean(
1168
+ chainSession && chainSession.status !== "revoked" && !isExpired(chainSession)
1169
+ );
1170
+ });
1171
+ }
1172
+ if (session.status === "revoked") return false;
1173
+ return !isExpired(session);
1174
+ }
928
1175
  function createFileWalletSessionStore(path = sessionPath()) {
929
1176
  return {
930
1177
  load() {
931
1178
  const session = loadStoredSessionFromPath(path);
932
1179
  if (!session) return null;
933
- if (session.status === "revoked") return null;
934
- if (isExpired(session)) return null;
1180
+ if (!isSessionLoadable(session)) return null;
935
1181
  return session;
936
1182
  },
937
1183
  loadRaw() {
@@ -975,6 +1221,7 @@ function createPendingSession() {
975
1221
  const now = /* @__PURE__ */ new Date();
976
1222
  const expiresAt = new Date(now.getTime() + SESSION_TTL_DAYS * 24 * 60 * 60 * 1e3);
977
1223
  const session = {
1224
+ version: 2,
978
1225
  sessionId: randomUUID(),
979
1226
  status: "pending",
980
1227
  createdAt: now.toISOString(),
@@ -997,12 +1244,12 @@ function loadStoredSession() {
997
1244
  return store.load();
998
1245
  }
999
1246
  function saveSession(session) {
1000
- getWalletSessionStore().save(session);
1247
+ getWalletSessionStore().save(withCompatibilityAliases(session));
1001
1248
  }
1002
1249
  function updateSession(updates) {
1003
1250
  const session = loadSession();
1004
1251
  if (!session) return null;
1005
- const updated = { ...session, ...updates };
1252
+ const updated = withCompatibilityAliases({ ...session, ...updates });
1006
1253
  saveSession(updated);
1007
1254
  return updated;
1008
1255
  }
@@ -1010,11 +1257,54 @@ function clearSession() {
1010
1257
  return getWalletSessionStore().clear();
1011
1258
  }
1012
1259
  function isSessionValid(session) {
1260
+ if (session.sessionsByChain) {
1261
+ return Object.values(session.sessionsByChain).some((chainSession) => {
1262
+ return Boolean(chainSession && isChainSessionValid(chainSession));
1263
+ });
1264
+ }
1013
1265
  if (session.status !== "approved") return false;
1014
1266
  const expiresAt = new Date(session.expiresAt);
1015
1267
  if (Number.isNaN(expiresAt.getTime())) return false;
1016
1268
  return expiresAt > /* @__PURE__ */ new Date();
1017
1269
  }
1270
+ function isChainSessionValid(session) {
1271
+ if (session.status !== "approved") return false;
1272
+ const expiresAt = new Date(session.expiresAt);
1273
+ if (Number.isNaN(expiresAt.getTime())) return false;
1274
+ return expiresAt > /* @__PURE__ */ new Date();
1275
+ }
1276
+ function getWalletSessionByChain(session, chainType) {
1277
+ const chainSession = session.sessionsByChain?.[chainType];
1278
+ if (!chainSession) {
1279
+ if (!isSessionValid(session)) return null;
1280
+ const legacyChainType = session.chainType === "solana" ? "solana" : "evm";
1281
+ if (chainType !== legacyChainType) return null;
1282
+ if (legacyChainType === "evm" && session.evmAddress) return session;
1283
+ if (legacyChainType === "solana" && session.solanaAddress) return session;
1284
+ return null;
1285
+ }
1286
+ if (!isChainSessionValid(chainSession)) {
1287
+ return null;
1288
+ }
1289
+ return withCompatibilityAliases({
1290
+ ...session,
1291
+ sessionId: chainSession.sessionId,
1292
+ status: chainSession.status,
1293
+ expiresAt: chainSession.expiresAt,
1294
+ walletId: chainSession.walletId,
1295
+ evmWalletId: chainType === "evm" ? chainSession.walletId : session.evmWalletId,
1296
+ evmAddress: chainType === "evm" ? chainSession.walletAddress : session.evmAddress,
1297
+ solanaWalletId: chainType === "solana" ? chainSession.walletId : session.solanaWalletId,
1298
+ solanaAddress: chainType === "solana" ? chainSession.walletAddress : session.solanaAddress,
1299
+ privyKeyQuorumId: chainSession.providerKeyQuorumId,
1300
+ privySignerId: chainSession.providerSignerId,
1301
+ chainType,
1302
+ capabilities: chainSession.capabilities ?? session.capabilities,
1303
+ sessionsByChain: {
1304
+ [chainType]: chainSession
1305
+ }
1306
+ });
1307
+ }
1018
1308
 
1019
1309
  // src/lib/resolve.ts
1020
1310
  function getCommandOptions(program) {
@@ -1094,6 +1384,15 @@ function adminClientFromFlags(program) {
1094
1384
  if (authToken) return new AdminClient({ type: "auth_token", token: authToken });
1095
1385
  throw errAccessKeyRequired();
1096
1386
  }
1387
+ function hasAuthLoginToken(cfg) {
1388
+ return Boolean(resolveAuthToken(cfg));
1389
+ }
1390
+ function gasManagerClientFromFlags(_program) {
1391
+ const cfg = load();
1392
+ const authToken = resolveAuthToken(cfg);
1393
+ if (!authToken) throw errLoginRequired();
1394
+ return new GasManagerClient({ type: "auth_token", token: authToken });
1395
+ }
1097
1396
  function resolveX402(program, cfg) {
1098
1397
  const opts = getCommandOptions(program);
1099
1398
  if (opts.x402) return true;
@@ -1240,6 +1539,8 @@ export {
1240
1539
  getRPCNetworks,
1241
1540
  getRPCNetworkIds,
1242
1541
  isSolanaNetwork,
1542
+ toAdminNetworkId,
1543
+ fromAdminNetworkId,
1243
1544
  nativeTokenSymbol,
1244
1545
  AdminClient,
1245
1546
  createPendingSession,
@@ -1249,6 +1550,7 @@ export {
1249
1550
  updateSession,
1250
1551
  clearSession,
1251
1552
  isSessionValid,
1553
+ getWalletSessionByChain,
1252
1554
  resolveAPIKey,
1253
1555
  resolveAccessKey,
1254
1556
  resolveNetwork,
@@ -1256,6 +1558,8 @@ export {
1256
1558
  resolveAppId,
1257
1559
  resolveAuthToken,
1258
1560
  adminClientFromFlags,
1561
+ hasAuthLoginToken,
1562
+ gasManagerClientFromFlags,
1259
1563
  resolveX402,
1260
1564
  resolveX402Client,
1261
1565
  resolveWalletKey,
@@ -0,0 +1,186 @@
1
+ #!/usr/bin/env node
2
+ if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
+ import {
4
+ gasManagerClientFromFlags,
5
+ toAdminNetworkId
6
+ } from "./chunk-7ZSEELHZ.js";
7
+ import {
8
+ dim,
9
+ green,
10
+ promptAutocomplete,
11
+ promptConfirm,
12
+ promptText,
13
+ withSpinner
14
+ } from "./chunk-MV7O3XBG.js";
15
+ import {
16
+ load,
17
+ save
18
+ } from "./chunk-GDLPBPG3.js";
19
+ import {
20
+ errAppRequired,
21
+ errInvalidArgs,
22
+ errLoginRequired
23
+ } from "./chunk-OVLQH6KL.js";
24
+
25
+ // src/lib/policy-prompt.ts
26
+ var CREATE_NEW_SENTINEL = "__create_new__";
27
+ var DEFAULT_SPONSORSHIP_EXPIRY_MS = "600000";
28
+ var DEFAULT_SOLANA_MAX_COUNT = "1000";
29
+ function flavorNoun(flavor) {
30
+ return flavor === "solana" ? "fee sponsorship" : "gas sponsorship";
31
+ }
32
+ async function selectOrCreatePolicy(opts) {
33
+ const cfg = load();
34
+ const appId = cfg.app?.id;
35
+ if (!appId) throw errAppRequired();
36
+ const client = opts.client ?? gasManagerClientFromFlags(opts.program);
37
+ const flavorLabel = flavorNoun(opts.flavor);
38
+ const policies = await withSpinner(
39
+ "Fetching gas policies\u2026",
40
+ "Policies fetched",
41
+ () => client.listAllPolicies({ appId })
42
+ );
43
+ const matching = filterPolicies(policies, opts.flavor, opts.network);
44
+ let selectedId = null;
45
+ if (matching.length === 0) {
46
+ console.log(
47
+ ` ${dim(`No ${flavorLabel} policies found for ${opts.network} on app ${appId}. Let's create one.`)}`
48
+ );
49
+ const created = await runCreate(opts, client, appId);
50
+ if (!created) return null;
51
+ selectedId = created.policyId;
52
+ } else {
53
+ const selected = await promptAutocomplete({
54
+ message: `Select a ${flavorLabel} policy for ${opts.network}`,
55
+ placeholder: "Policy name or ID",
56
+ options: [
57
+ ...matching.map((p) => ({
58
+ label: formatPolicyOption(p),
59
+ value: p.policyId
60
+ })),
61
+ { label: "Create a new policy", value: CREATE_NEW_SENTINEL }
62
+ ],
63
+ cancelMessage: "Cancelled policy selection.",
64
+ commitLabel: null
65
+ });
66
+ if (selected === null) return null;
67
+ if (selected === CREATE_NEW_SENTINEL) {
68
+ const created = await runCreate(opts, client, appId);
69
+ if (!created) return null;
70
+ selectedId = created.policyId;
71
+ } else {
72
+ selectedId = selected;
73
+ }
74
+ }
75
+ if (!opts.skipPersistPrompt && selectedId) {
76
+ await maybePersist(opts.flavor, selectedId);
77
+ }
78
+ return selectedId;
79
+ }
80
+ async function createPolicyInteractive(opts) {
81
+ const cfg = load();
82
+ const appId = cfg.app?.id;
83
+ if (!appId) throw errAppRequired();
84
+ const client = opts.client ?? gasManagerClientFromFlags(opts.program);
85
+ const result = await runCreate(opts, client, appId);
86
+ return result;
87
+ }
88
+ function filterPolicies(policies, flavor, network) {
89
+ const adminId = toAdminNetworkId(network);
90
+ return policies.filter((p) => {
91
+ if (p.policyType !== flavor) return false;
92
+ if (!Array.isArray(p.networks)) return false;
93
+ return p.networks.includes(adminId);
94
+ });
95
+ }
96
+ function formatPolicyOption(p) {
97
+ const status = p.status === "active" ? green("active") : dim(p.status);
98
+ return `${p.policyName} (${p.policyId.slice(0, 10)}\u2026) \u2014 ${status}`;
99
+ }
100
+ async function runCreate(opts, client, appId) {
101
+ const name = await promptText({
102
+ message: "Policy name",
103
+ cancelMessage: "Cancelled policy creation."
104
+ });
105
+ if (name === null) return null;
106
+ if (!name.trim()) {
107
+ console.log(` ${dim("Skipped policy creation (no name).")}`);
108
+ return null;
109
+ }
110
+ const adminNetworkId = toAdminNetworkId(opts.network);
111
+ const nowUnix = String(Math.floor(Date.now() / 1e3));
112
+ const payload = opts.flavor === "sponsorship" ? {
113
+ policyName: name.trim(),
114
+ policyType: "sponsorship",
115
+ appId,
116
+ networks: [adminNetworkId],
117
+ rules: {
118
+ startTimeUnix: nowUnix,
119
+ sponsorshipExpiryMs: DEFAULT_SPONSORSHIP_EXPIRY_MS
120
+ }
121
+ } : {
122
+ policyName: name.trim(),
123
+ policyType: "solana",
124
+ appId,
125
+ networks: [adminNetworkId],
126
+ solanaRules: {
127
+ startTimeUnix: nowUnix,
128
+ maxCount: DEFAULT_SOLANA_MAX_COUNT
129
+ }
130
+ };
131
+ const created = await withSpinner(
132
+ "Creating policy\u2026",
133
+ "Policy created",
134
+ () => client.createPolicy(payload)
135
+ );
136
+ console.log(` ${green("\u2713")} Created ${created.policyName} (${created.policyId})`);
137
+ const activate = await promptConfirm({
138
+ message: "Activate this policy now? (required before use)",
139
+ initialValue: true,
140
+ cancelMessage: "Skipped activation."
141
+ });
142
+ let activated = false;
143
+ if (activate === true) {
144
+ await withSpinner(
145
+ "Activating policy\u2026",
146
+ "Policy active",
147
+ () => client.setPolicyStatus(created.policyId, "active")
148
+ );
149
+ activated = true;
150
+ } else {
151
+ console.log(
152
+ ` ${dim("Policy left inactive. Activate later with `alchemy gas-manager policy activate " + created.policyId + "`.")}`
153
+ );
154
+ }
155
+ return { policyId: created.policyId, status: "created", activated };
156
+ }
157
+ async function maybePersist(flavor, policyId) {
158
+ const confirmed = await promptConfirm({
159
+ message: "Save as default policy for this CLI?",
160
+ initialValue: true,
161
+ cancelMessage: "Skipped saving default."
162
+ });
163
+ if (confirmed !== true) return;
164
+ const cfg = load();
165
+ const updated = flavor === "sponsorship" ? { ...cfg, evm_gas_policy_id: policyId } : { ...cfg, solana_fee_policy_id: policyId };
166
+ save(updated);
167
+ const key = flavor === "sponsorship" ? "evm-gas-policy-id" : "solana-fee-policy-id";
168
+ console.log(` ${green("\u2713")} Saved ${key} = ${policyId}`);
169
+ }
170
+ function errSponsorshipNeedsPolicy(flavor) {
171
+ const flag = flavor === "sponsorship" ? "--gas-policy-id" : "--fee-policy-id";
172
+ const label = flavor === "sponsorship" ? "Gas sponsorship" : "Fee sponsorship";
173
+ return errInvalidArgs(
174
+ `${label} requires a policy ID. Run 'alchemy gas-manager policy list' to pick one, pass ${flag} <id>, or run interactively to be prompted.`
175
+ );
176
+ }
177
+ function errNotLoggedInForPolicyLookup() {
178
+ return errLoginRequired();
179
+ }
180
+
181
+ export {
182
+ selectOrCreatePolicy,
183
+ createPolicyInteractive,
184
+ errSponsorshipNeedsPolicy,
185
+ errNotLoggedInForPolicyLookup
186
+ };
@@ -2,7 +2,7 @@
2
2
  if(process.argv.includes("--no-color"))process.env.NO_COLOR="1";
3
3
  import {
4
4
  isRevealMode
5
- } from "./chunk-2BALTY22.js";
5
+ } from "./chunk-OVLQH6KL.js";
6
6
 
7
7
  // src/lib/secrets.ts
8
8
  function maskSecret(value) {
@@ -5,7 +5,7 @@ import {
5
5
  isJSONMode,
6
6
  quiet,
7
7
  rgb
8
- } from "./chunk-2BALTY22.js";
8
+ } from "./chunk-OVLQH6KL.js";
9
9
 
10
10
  // src/lib/terminal-ui.ts
11
11
  import * as readline from "readline";