@sage-protocol/cli 0.7.9 → 0.8.2

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.

Potentially problematic release.


This version of @sage-protocol/cli might be problematic. Click here for more details.

@@ -1,7 +1,7 @@
1
1
  const fs = require('fs');
2
2
  const path = require('path');
3
3
  const axios = require('axios');
4
- const { getAddress, id, parseUnits, formatUnits, Contract, MaxUint256 } = require('ethers');
4
+ const { getAddress, id, parseUnits, formatUnits, Contract, MaxUint256, ZeroAddress } = require('ethers');
5
5
  const { decryptAesGcm, fromB64 } = require('../utils/aes');
6
6
  const sdk = require('@sage-protocol/sdk');
7
7
  const { formatJson } = require('../utils/format');
@@ -244,6 +244,42 @@ const PERSONAL_REGISTRY_QUERY = `
244
244
  async function resolveOrCreatePersonalRegistry(wallet, opts = {}) {
245
245
  const ownerAddress = getAddress(await wallet.getAddress());
246
246
  const subgraphUrl = resolveSubgraphUrl(opts.subgraph);
247
+ const provider = wallet.provider || await getProvider();
248
+
249
+ const { resolveArtifact } = require('../utils/artifacts');
250
+ const PromptRegistryABI = resolveArtifact('contracts/PromptRegistry.sol/PromptRegistry.json').abi;
251
+
252
+ async function validateRegistryCandidate(registryAddr) {
253
+ try {
254
+ const code = await provider.getCode(registryAddr);
255
+ if (!code || code === '0x') {
256
+ return { ok: false, reason: 'no_code' };
257
+ }
258
+
259
+ const read = new Contract(registryAddr, PromptRegistryABI, provider);
260
+
261
+ // Ensure this registry is actually usable by the current wallet.
262
+ const govRole = await read.GOVERNANCE_ROLE().catch(() => null);
263
+ if (!govRole) return { ok: false, reason: 'missing_governance_role' };
264
+
265
+ const hasGovRole = await read.hasRole(govRole, ownerAddress).catch(() => null);
266
+ if (!hasGovRole) return { ok: false, reason: 'missing_governance_role_for_owner' };
267
+
268
+ // Personal registries should not be linked to a SubDAO.
269
+ const subdao = await read.subDAO().catch(() => null);
270
+ if (subdao && getAddress(subdao) !== getAddress(ZeroAddress)) {
271
+ return { ok: false, reason: 'linked_to_subdao', subdao: getAddress(subdao) };
272
+ }
273
+
274
+ // If paused, addPrompt will revert.
275
+ const paused = await read.paused().catch(() => false);
276
+ if (paused) return { ok: false, reason: 'paused' };
277
+
278
+ return { ok: true };
279
+ } catch (e) {
280
+ return { ok: false, reason: 'validation_error', error: e };
281
+ }
282
+ }
247
283
 
248
284
  // Try subgraph lookup first
249
285
  if (subgraphUrl) {
@@ -254,7 +290,14 @@ async function resolveOrCreatePersonalRegistry(wallet, opts = {}) {
254
290
  });
255
291
  const registries = resp.data?.data?.personalRegistries || [];
256
292
  if (registries.length > 0) {
257
- return getAddress(registries[0].id);
293
+ const candidate = getAddress(registries[0].id);
294
+ const validation = await validateRegistryCandidate(candidate);
295
+ if (validation.ok) return candidate;
296
+
297
+ if (!opts.json) {
298
+ const extra = validation.subdao ? ` (subDAO=${validation.subdao})` : '';
299
+ ui.warn(`Resolved personal registry ${candidate} is not usable (${validation.reason}${extra}). Creating a fresh registry...`);
300
+ }
258
301
  }
259
302
  } catch (_) {
260
303
  // Subgraph unavailable, fall through to create
@@ -309,6 +352,30 @@ async function addPromptToRegistry(wallet, registryAddr, promptData, opts = {})
309
352
  const PromptRegistryABI = resolveArtifact('contracts/PromptRegistry.sol/PromptRegistry.json').abi;
310
353
  const registry = new Contract(registryAddr, PromptRegistryABI, wallet);
311
354
 
355
+ // Proactive checks to surface actionable errors before spending gas.
356
+ try {
357
+ const ownerAddress = getAddress(await wallet.getAddress());
358
+ const govRole = await registry.GOVERNANCE_ROLE().catch(() => null);
359
+ if (!govRole) {
360
+ throw new Error(`Resolved registry ${registryAddr} does not expose GOVERNANCE_ROLE(); is this a valid PromptRegistry?`);
361
+ }
362
+ const hasGov = await registry.hasRole(govRole, ownerAddress).catch(() => null);
363
+ if (!hasGov) {
364
+ throw new Error(`Wallet ${ownerAddress} lacks GOVERNANCE_ROLE on registry ${registryAddr}. This usually means the registry is not your personal registry (or it was not initialized as personal).`);
365
+ }
366
+ const subdao = await registry.subDAO().catch(() => null);
367
+ if (subdao && getAddress(subdao) !== getAddress(ZeroAddress)) {
368
+ throw new Error(`Registry ${registryAddr} is linked to SubDAO ${getAddress(subdao)}; personal publish requires a personal registry (subDAO=0x0).`);
369
+ }
370
+ const paused = await registry.paused().catch(() => false);
371
+ if (paused) {
372
+ throw new Error(`Registry ${registryAddr} is paused; unpause it (DEFAULT_ADMIN_ROLE) or create a fresh personal registry.`);
373
+ }
374
+ } catch (e) {
375
+ // Re-throw with a cleaner message for JSON/non-JSON callers.
376
+ throw e;
377
+ }
378
+
312
379
  const title = promptData.title || promptData.name || promptData.key || 'Untitled';
313
380
  const tags = promptData.tags || [];
314
381
  const contentCID = promptData.contentCID || '';
@@ -289,9 +289,10 @@ const Web3AuthWalletManager = require('../web3auth-wallet-manager');
289
289
  }
290
290
 
291
291
  // Device code flow via relay works with just appId (no secret needed)
292
- // Only --local mode requires appSecret for direct Privy SDK calls
292
+ // Always prefer relay unless --local is explicitly set
293
293
  if (appId) {
294
294
  if (options?.local) {
295
+ process.env.SAGE_PRIVY_LOGIN_MODE = 'local';
295
296
  // Local OAuth requires appSecret for direct Privy SDK authentication
296
297
  if (!appSecret) {
297
298
  ui.error('Local OAuth mode requires PRIVY_APP_SECRET.');
@@ -300,15 +301,9 @@ const Web3AuthWalletManager = require('../web3auth-wallet-manager');
300
301
  }
301
302
  ui.info('Using local OAuth authentication...');
302
303
  } else {
303
- // Web/device code flow - uses relay, no secret needed
304
- // Force device code mode when secret is not available
305
- if (!appSecret) {
306
- process.env.SAGE_PRIVY_LOGIN_MODE = 'device';
307
- ui.info('Using device code authentication via web app relay...');
308
- } else {
309
- process.env.SAGE_PRIVY_LOGIN_MODE = 'web';
310
- ui.info('Using web-based authentication...');
311
- }
304
+ // Web relay flow - uses web app, no localhost callback
305
+ process.env.SAGE_PRIVY_LOGIN_MODE = 'web';
306
+ ui.info('Using web-based authentication via relay...');
312
307
  }
313
308
 
314
309
  const walletManager = new PrivyAuthWalletManager();
package/dist/cli/index.js CHANGED
@@ -19,6 +19,11 @@ if (!process.env.CONTRACT_ARTIFACTS_ROOT) {
19
19
  }
20
20
  }
21
21
 
22
+ // Always trust SAGE_RPC_URL over RPC_URL when provided
23
+ if (process.env.SAGE_RPC_URL) {
24
+ process.env.RPC_URL = process.env.SAGE_RPC_URL;
25
+ }
26
+
22
27
  // Filter out deprecated Lit SDK warning immediately
23
28
  const realStderrWrite = process.stderr.write.bind(process.stderr);
24
29
  process.stderr.write = (chunk, encoding, callback) => {
@@ -47,8 +47,9 @@ class PrivyAuthWalletManager {
47
47
  this.walletId = null;
48
48
  this.accessToken = null;
49
49
 
50
- // Privy app credentials from env
51
- this.appId = process.env.PRIVY_APP_ID || process.env.NEXT_PUBLIC_PRIVY_APP_ID;
50
+ // Privy app credentials from env or defaults
51
+ const defaults = require('./defaults');
52
+ this.appId = process.env.PRIVY_APP_ID || process.env.NEXT_PUBLIC_PRIVY_APP_ID || defaults.PRIVY_APP_ID;
52
53
  this.appSecret = process.env.PRIVY_APP_SECRET;
53
54
  }
54
55
 
@@ -79,14 +80,30 @@ class PrivyAuthWalletManager {
79
80
  */
80
81
  async connect() {
81
82
  try {
83
+ const relayEnabled = process.env.SAGE_DISABLE_PRIVY_RELAY !== '1';
84
+ const hasAppSecret = !!this.appSecret;
85
+ const mode = String(process.env.SAGE_PRIVY_LOGIN_MODE || '').trim().toLowerCase();
86
+ const forceRelay = relayEnabled && mode !== 'local' && mode !== 'oauth';
87
+ const preferSessionFlow = forceRelay;
88
+
82
89
  // Try cached credentials first
83
90
  const cached = readPrivyCredentials();
84
91
  if (cached && cached.userId && cached.walletId && cached.walletAddress) {
85
92
  // Verify the credentials are still valid
86
93
  const valid = await this.verifyCredentials(cached);
87
94
  if (valid) {
88
- // Refresh canonical wallet info from Privy to get a valid wallet_id
89
95
  this.accessToken = cached.accessToken;
96
+ if (preferSessionFlow) {
97
+ this.user = { id: cached.userId };
98
+ this.walletId = cached.walletId;
99
+ this.account = cached.walletAddress;
100
+ await this.setupProvider();
101
+ this.connected = true;
102
+ console.log(`Connected to Privy wallet: ${this.account}`);
103
+ return true;
104
+ }
105
+
106
+ // Refresh canonical wallet info from Privy to get a valid wallet_id
90
107
  const info = await this.getUserFromToken(this.accessToken);
91
108
 
92
109
  // CRITICAL: Verify the JWT user matches the cached user
@@ -125,9 +142,8 @@ class PrivyAuthWalletManager {
125
142
 
126
143
  // No valid cached credentials - need OAuth login
127
144
  console.log('\nNo cached Privy credentials found.');
128
- const mode = String(process.env.SAGE_PRIVY_LOGIN_MODE || '').trim().toLowerCase();
129
- const useSessionFlow = mode === 'web' || mode === 'session';
130
- const useDeviceCodeFlow = mode === 'device' || mode === 'device_code';
145
+ const useSessionFlow = preferSessionFlow || mode === 'web' || mode === 'session';
146
+ const useDeviceCodeFlow = !preferSessionFlow && (mode === 'device' || mode === 'device_code');
131
147
 
132
148
  let authResult;
133
149
 
@@ -162,11 +178,22 @@ class PrivyAuthWalletManager {
162
178
 
163
179
  this.accessToken = authResult.accessToken;
164
180
 
165
- // Always resolve canonical wallet_id + address from Privy after OAuth
166
- const info = await this.getUserFromToken(this.accessToken);
167
- this.user = info.user;
168
- this.walletId = info.walletId;
169
- this.account = info.walletAddress;
181
+ // If relay-only, avoid Privy API lookup and require session flow
182
+ if (preferSessionFlow && (!authResult.walletAddress || !authResult.walletId)) {
183
+ throw new Error('Privy relay login did not return wallet credentials. Re-run with `sage wallet connect`.');
184
+ }
185
+
186
+ if (preferSessionFlow) {
187
+ this.user = { id: authResult.userId };
188
+ this.walletId = authResult.walletId;
189
+ this.account = authResult.walletAddress;
190
+ } else {
191
+ // Always resolve canonical wallet_id + address from Privy after OAuth
192
+ const info = await this.getUserFromToken(this.accessToken);
193
+ this.user = info.user;
194
+ this.walletId = info.walletId;
195
+ this.account = info.walletAddress;
196
+ }
170
197
 
171
198
  // Cache credentials - store both walletId (for API) and walletAddress (for display)
172
199
  writePrivyCredentials({
@@ -200,7 +227,12 @@ class PrivyAuthWalletManager {
200
227
  * Verify cached credentials are still valid
201
228
  */
202
229
  async verifyCredentials(cached) {
203
- if (!this.appId || !this.appSecret) return false;
230
+ const relayEnabled = process.env.SAGE_DISABLE_PRIVY_RELAY !== '1';
231
+ if (!this.appId) return false;
232
+ if (!this.appSecret && relayEnabled) {
233
+ return !isTokenExpired(cached.accessToken);
234
+ }
235
+ if (!this.appSecret) return false;
204
236
 
205
237
  // Check if access token is expired (Privy tokens expire after ~1 hour)
206
238
  if (isTokenExpired(cached.accessToken)) {
@@ -212,7 +244,6 @@ class PrivyAuthWalletManager {
212
244
 
213
245
  // If relay is enabled (default), the CLI only needs a valid user access token.
214
246
  // Session keys are only required when the user disables the relay and signs directly via Privy.
215
- const relayEnabled = process.env.SAGE_DISABLE_PRIVY_RELAY !== '1';
216
247
  if (!relayEnabled) {
217
248
  if (!getAuthorizationPrivateKey() && !cached.sessionPrivateKey) {
218
249
  if (process.env.SAGE_VERBOSE === '1') {
@@ -236,6 +267,9 @@ class PrivyAuthWalletManager {
236
267
  * Get API headers for Privy requests
237
268
  */
238
269
  getApiHeaders() {
270
+ if (!this.appSecret) {
271
+ throw new Error('PRIVY_APP_SECRET required for direct Privy API calls. Use relay login (default) or set PRIVY_APP_SECRET.');
272
+ }
239
273
  const { getPrivyHeaders } = require('./utils/wallet-shared');
240
274
  return getPrivyHeaders(this.appId, this.appSecret);
241
275
  }
@@ -58,7 +58,7 @@ const CHAIN_CONFIGS = {
58
58
  * Used for resolution precedence.
59
59
  */
60
60
  const ENV_VARS = {
61
- rpcUrl: ['RPC_URL', 'BASE_SEPOLIA_RPC', 'BASE_RPC_URL'],
61
+ rpcUrl: ['SAGE_RPC_URL', 'RPC_URL', 'BASE_SEPOLIA_RPC', 'BASE_RPC_URL'],
62
62
  chainId: ['CHAIN_ID', 'BASE_CHAIN_ID'],
63
63
  subgraphUrl: ['SUBGRAPH_URL', 'SAGE_SUBGRAPH_URL', 'NEXT_PUBLIC_GRAPH_ENDPOINT', 'NEXT_PUBLIC_SUBGRAPH_URL']
64
64
  };
@@ -106,8 +106,13 @@ class ConfigManager {
106
106
 
107
107
  try {
108
108
  const dotenv = require('dotenv');
109
+ const home = process.env.HOME || process.env.USERPROFILE || '';
110
+ const globalEnv = home ? path.join(home, '.sage', '.env') : null;
109
111
  // Load from project .env
110
112
  const projectEnv = path.join(this.projectDir, '.env');
113
+ if (globalEnv && fs.existsSync(globalEnv)) {
114
+ dotenv.config({ path: globalEnv });
115
+ }
111
116
  if (fs.existsSync(projectEnv)) {
112
117
  dotenv.config({ path: projectEnv });
113
118
  }
@@ -116,6 +121,9 @@ class ConfigManager {
116
121
  if (fs.existsSync(sageEnv)) {
117
122
  dotenv.config({ path: sageEnv });
118
123
  }
124
+ if (process.env.SAGE_RPC_URL) {
125
+ process.env.RPC_URL = process.env.SAGE_RPC_URL;
126
+ }
119
127
  } catch (_) {
120
128
  // dotenv not available, skip
121
129
  }
@@ -105,6 +105,7 @@ const addressMap = z.record(
105
105
  */
106
106
  const ipfsConfig = z.object({
107
107
  gateway: z.string().url().optional(),
108
+ gateways: z.array(z.string()).optional(),
108
109
  pinataApiKey: z.string().optional(),
109
110
  pinataSecretKey: z.string().optional(),
110
111
  pinataJwt: z.string().optional(),
@@ -2,6 +2,12 @@ const path = require('path');
2
2
  const ui = require('../../utils/cli-ui');
3
3
 
4
4
  const DEFAULT_GATEWAY = 'https://ipfs.dev.sageprotocol.io/ipfs';
5
+ const FALLBACK_GATEWAYS = [
6
+ 'https://dweb.link/ipfs',
7
+ 'https://nftstorage.link/ipfs',
8
+ 'https://ipfs.io/ipfs',
9
+ 'https://gateway.pinata.cloud/ipfs'
10
+ ];
5
11
  const DEFAULT_WORKER_BASE = 'https://api.sageprotocol.io';
6
12
  const DEFAULT_WORKER_CHALLENGE_PATH = '/ipfs/auth/challenge';
7
13
  const DEFAULT_WORKER_UPLOAD_PATH = '/ipfs/upload';
@@ -87,6 +93,7 @@ function createIpfsOnboarding({
87
93
 
88
94
  if (!quiet) {
89
95
  ui.header('Sage IPFS pinning setup', 'Configure your gateway and pinning providers so uploads work out of the box.');
96
+ ui.output(`Gateway fallbacks: ${FALLBACK_GATEWAYS.join(', ')}`);
90
97
  }
91
98
 
92
99
  let scope = initialScope && ['project', 'global'].includes(initialScope) ? initialScope : null;
@@ -332,6 +339,10 @@ function createIpfsOnboarding({
332
339
  const payload = {};
333
340
  if (gateway !== undefined) {
334
341
  payload.gateway = ensureString(gateway) || null;
342
+ if (payload.gateway) {
343
+ const fallback = FALLBACK_GATEWAYS.filter((g) => g !== payload.gateway);
344
+ payload.gateways = fallback;
345
+ }
335
346
  }
336
347
  payload.warmGateway = !!warmGateway;
337
348
  if (provider) payload.provider = provider;
@@ -1,23 +1,26 @@
1
1
  const { loadSdk } = require('../../utils/sdk-resolver');
2
2
 
3
- // Prefer Cast (Foundry) as the default wallet for CLI usage.
4
- // Privy remains available via WALLET_TYPE=privy for browser-based auth.
5
- const DEFAULT_WALLET_TYPE = 'cast';
3
+ // Prefer Privy as the default wallet for npm CLI usage (no local keystore required).
4
+ const DEFAULT_WALLET_TYPE = 'privy';
6
5
 
7
6
  function localResolveWalletType(configInstance, env = process.env) {
8
- const envType = (env.WALLET_TYPE || '').trim();
9
- if (envType.length) return envType;
7
+ const envType = (env.SAGE_WALLET_TYPE || env.WALLET_TYPE || '').trim();
10
8
 
11
9
  try {
12
10
  const cfg = configInstance?.getWalletConfig ? configInstance.getWalletConfig() : null;
13
- const inferred = cfg?.type;
14
- if (typeof inferred === 'string' && inferred.trim().length) {
15
- return inferred.trim();
11
+ const inferred = cfg?.type ? String(cfg.type).trim() : '';
12
+ if (envType.length) {
13
+ if (envType === DEFAULT_WALLET_TYPE && inferred && inferred !== DEFAULT_WALLET_TYPE) {
14
+ return inferred;
15
+ }
16
+ return envType;
16
17
  }
18
+ if (inferred) return inferred;
17
19
  } catch (_) {
18
20
  // ignore config resolution errors
19
21
  }
20
22
 
23
+ if (envType.length) return envType;
21
24
  return DEFAULT_WALLET_TYPE;
22
25
  }
23
26
 
@@ -59,6 +62,10 @@ function getSdkSession() {
59
62
  }
60
63
 
61
64
  function resolveWalletType(configInstance) {
65
+ const envType = (process.env.SAGE_WALLET_TYPE || process.env.WALLET_TYPE || '').trim();
66
+ if (envType.length) {
67
+ return envType;
68
+ }
62
69
  const session = getSdkSession();
63
70
  if (session?.resolveWalletType) {
64
71
  return session.resolveWalletType({ config: configInstance, env: process.env });
@@ -1127,7 +1127,7 @@ class SubDAOManager {
1127
1127
  const signer = walletManager.getSigner();
1128
1128
  const signerAddress = await signer.getAddress();
1129
1129
  const walletLabel = (() => {
1130
- const type = walletManager.getWalletType?.() || process.env.WALLET_TYPE || 'cast';
1130
+ const type = walletManager.getWalletType?.() || process.env.SAGE_WALLET_TYPE || process.env.WALLET_TYPE || 'cast';
1131
1131
  if (type === 'privy') return 'Privy deterministic wallet';
1132
1132
  if (type === 'web3auth') return 'Web3Auth wallet';
1133
1133
  if (type === 'cdp') return 'CDP embedded wallet';
@@ -101,6 +101,7 @@ const COMMAND_CATALOG = {
101
101
  'info',
102
102
  'fork',
103
103
  'personal',
104
+ 'vault',
104
105
  'delete'
105
106
  ],
106
107
  subcommandAliases: {
@@ -402,7 +402,7 @@ function keyValue(data) {
402
402
  * @param {boolean} [options.pretty=true] - Pretty print
403
403
  */
404
404
  function json(data, options = {}) {
405
- const { pretty = true } = options;
405
+ const { pretty = true } = options || {};
406
406
  console.log(pretty ? JSON.stringify(data, null, 2) : JSON.stringify(data));
407
407
  }
408
408
 
@@ -14,11 +14,15 @@ async function getWallet(provider, options = {}) {
14
14
  throw new Error('Signer unavailable. Connect a wallet capable of signing transactions.');
15
15
  }
16
16
 
17
- if (!session.signer.provider && provider) {
18
- session.signer.connect(provider);
17
+ let signer = session.signer;
18
+
19
+ // ethers v6 Signer#connect returns a NEW signer; it does not mutate.
20
+ // Ensure the returned signer is connected so read calls (eth_call) work.
21
+ if (!signer.provider && provider && typeof signer.connect === 'function') {
22
+ signer = signer.connect(provider);
19
23
  }
20
24
 
21
- return session.signer;
25
+ return signer;
22
26
  }
23
27
 
24
28
  module.exports = {
@@ -102,7 +102,8 @@ function getUserIdFromToken(token) {
102
102
  function getPrivyClient() {
103
103
  if (_privyClient) return _privyClient;
104
104
 
105
- const appId = process.env.PRIVY_APP_ID || process.env.NEXT_PUBLIC_PRIVY_APP_ID;
105
+ const defaults = require('../defaults');
106
+ const appId = process.env.PRIVY_APP_ID || process.env.NEXT_PUBLIC_PRIVY_APP_ID || defaults.PRIVY_APP_ID;
106
107
  const appSecret = process.env.PRIVY_APP_SECRET;
107
108
 
108
109
  if (!appId || !appSecret) {
@@ -33,8 +33,9 @@ class WalletManager {
33
33
 
34
34
  async connect() {
35
35
  try {
36
- // Dynamically check the wallet type from environment variable
37
- let walletType = process.env.WALLET_TYPE || this.walletType || 'cast';
36
+ // Explicit env override should win for CLI usage.
37
+ const envWalletType = (process.env.SAGE_WALLET_TYPE || process.env.WALLET_TYPE || '').trim();
38
+ let walletType = envWalletType || resolveWalletType(this.config) || this.walletType || 'cast';
38
39
  // NEVER auto-switch to private key based on env alone; require explicit insecure opt-in
39
40
  const insecurePk = shouldAllowInsecurePrivateKey();
40
41
  if (!insecurePk && (process.env.SAGE_PRIVATE_KEY || process.env.PRIVATE_KEY)) {
@@ -132,19 +133,14 @@ class WalletManager {
132
133
  process.env.SAGE_PRIVY_RELAY_URL = defaults.SAGE_PRIVY_RELAY_URL;
133
134
  }
134
135
 
135
- // Device code flow via relay works with just appId (no secret needed)
136
- // Use PrivyAuthWalletManager when appId is available
136
+ // Prefer relay-based web login by default (no localhost callback).
137
+ // Use PrivyAuthWalletManager when appId is available.
137
138
  if (appId) {
138
- // Force device code mode when secret is not available
139
- if (!appSecret) {
140
- process.env.SAGE_PRIVY_LOGIN_MODE = 'device';
141
- if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') {
142
- console.log('🔐 Using device code authentication via web app relay...');
143
- }
144
- } else {
145
- if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') {
146
- console.log('🔐 Using Privy OAuth authentication (same wallet as web app)');
147
- }
139
+ if (!process.env.SAGE_PRIVY_LOGIN_MODE) {
140
+ process.env.SAGE_PRIVY_LOGIN_MODE = 'web';
141
+ }
142
+ if (!process.env.SAGE_QUIET_JSON && process.env.SAGE_VERBOSE === '1') {
143
+ console.log('🔐 Using web relay authentication (same wallet as web app)');
148
144
  }
149
145
  this.wallet = new PrivyAuthWalletManager();
150
146
  const ok = await this.wallet.initialize();
@@ -495,7 +491,7 @@ class WalletManager {
495
491
  }
496
492
 
497
493
  // Get current wallet type from environment or instance
498
- const currentWalletType = process.env.WALLET_TYPE || this.walletType;
494
+ const currentWalletType = this.getWalletType();
499
495
 
500
496
  if (['cast', 'cdp', 'web3auth', 'privy', 'metamask', 'walletconnect'].includes(currentWalletType) && this.wallet && typeof this.wallet.signTransaction === 'function') {
501
497
  return await this.wallet.signTransaction(transaction);
@@ -530,7 +526,7 @@ class WalletManager {
530
526
  }
531
527
 
532
528
  // Get current wallet type from environment or instance
533
- const currentWalletType = process.env.WALLET_TYPE || this.walletType;
529
+ const currentWalletType = this.getWalletType();
534
530
 
535
531
  if (['cast', 'cdp', 'web3auth', 'privy', 'metamask', 'walletconnect'].includes(currentWalletType) && this.wallet && typeof this.wallet.signMessage === 'function') {
536
532
  return await this.wallet.signMessage(message);
@@ -617,7 +613,7 @@ class WalletManager {
617
613
 
618
614
  async disconnect() {
619
615
  // Get current wallet type from environment or instance
620
- const currentWalletType = process.env.WALLET_TYPE || this.walletType;
616
+ const currentWalletType = this.getWalletType();
621
617
 
622
618
  if ((['cast', 'cdp', 'auth0', 'metamask', 'walletconnect', 'web3auth', 'privy'].includes(currentWalletType)) && this.wallet) {
623
619
  await this.wallet.disconnect();
@@ -649,7 +645,8 @@ class WalletManager {
649
645
  }
650
646
 
651
647
  getWalletType() {
652
- return process.env.WALLET_TYPE || this.walletType;
648
+ const envWalletType = (process.env.SAGE_WALLET_TYPE || process.env.WALLET_TYPE || '').trim();
649
+ return envWalletType || resolveWalletType(this.config) || this.walletType;
653
650
  }
654
651
  }
655
652
 
@@ -0,0 +1,22 @@
1
+ ---
2
+ name: E2E Test Prompt
3
+ description: A test prompt for validating the /prompt/:cid endpoint
4
+ tags: [test, e2e, validation]
5
+ kind: "prompt"
6
+ publishable: true
7
+ targets: []
8
+ ---
9
+
10
+ # E2E Test Prompt
11
+
12
+ This is a test prompt created to validate the prompt allowlist flow.
13
+
14
+ ## Purpose
15
+ - Test that prompts published to a DAO get added to the allowlist
16
+ - Verify that `/prompt/:cid` endpoint serves allowlisted prompts
17
+ - Confirm library sync properly indexes prompt CIDs
18
+
19
+ ## Instructions
20
+ When this prompt is accessible via the API, the allowlist flow is working correctly.
21
+
22
+ Created: 2025-12-26
@@ -0,0 +1,11 @@
1
+ {
2
+ "name": "build-web3",
3
+ "description": "Build Web3 dApps and smart contracts from scratch through shipping. Full lifecycle for EVM/Base development with Solidity, Foundry, Next.js, and viem/wagmi. Covers contracts, frontend, testing, security analysis, and deployment.",
4
+ "version": "1.0.0",
5
+ "author": "Sage Protocol",
6
+ "license": "MIT",
7
+ "keywords": ["web3", "solidity", "foundry", "next.js", "viem", "wagmi", "smart-contracts", "dapp"],
8
+ "category": "development",
9
+ "homepage": "https://github.com/sage-protocol",
10
+ "repository": "https://github.com/sage-protocol/sage-protocol"
11
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sage-protocol/cli",
3
- "version": "0.7.9",
3
+ "version": "0.8.2",
4
4
  "description": "Sage Protocol CLI for managing AI prompt libraries",
5
5
  "bin": {
6
6
  "sage": "./bin/sage.js"