@symbo.ls/cli 2.33.37 → 2.33.38

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/bin/login.js CHANGED
@@ -101,6 +101,11 @@ function extractConfirmToken(data) {
101
101
  )
102
102
  }
103
103
 
104
+ function withCliSessionKeyHeaders(headers, cliSessionKey) {
105
+ if (!cliSessionKey) return headers
106
+ return { ...headers, 'X-Symbols-Cli-Session-Key': cliSessionKey }
107
+ }
108
+
104
109
  program
105
110
  .command('login')
106
111
  .description('Sign in to Symbols')
@@ -111,12 +116,18 @@ program
111
116
 
112
117
  // Prompt for credentials
113
118
  const currentConfig = loadCliConfig()
119
+ const credManager = new CredentialManager()
120
+ // If the user selected an active server via `smbls servers -s`, prefer that as the login default.
121
+ // Env vars should still override everything (useful for CI / debugging).
122
+ const envApiBaseUrl = process.env.SYMBOLS_API_BASE_URL || process.env.SMBLS_API_URL
123
+ const defaultApiBaseUrl =
124
+ envApiBaseUrl || credManager.getCurrentApiBaseUrl() || currentConfig.apiBaseUrl || getApiUrl()
114
125
  const first = await inquirer.prompt([
115
126
  {
116
127
  type: 'input',
117
128
  name: 'apiBaseUrl',
118
129
  message: 'API Base URL:',
119
- default: currentConfig.apiBaseUrl || getApiUrl(),
130
+ default: defaultApiBaseUrl,
120
131
  validate: input => /^https?:\/\//.test(input) || '❌ Please enter a valid URL'
121
132
  },
122
133
  {
@@ -170,6 +181,21 @@ program
170
181
  throw err
171
182
  }
172
183
  } else if (first.method === 'google_browser' || first.method === 'github_browser') {
184
+ const defaultCliSessionKey = credManager.getCliSessionKey(apiBaseUrl) || ''
185
+ const { cliSessionKey } = await inquirer.prompt([
186
+ {
187
+ type: 'input',
188
+ name: 'cliSessionKey',
189
+ message: 'CLI session key (for long-lived tokens, optional):',
190
+ default: defaultCliSessionKey,
191
+ filter: (v) => (v || '').trim()
192
+ }
193
+ ])
194
+
195
+ // Persist per-server key so users can change it per environment
196
+ // (empty value clears and disables long-lived token request)
197
+ credManager.setCliSessionKey(apiBaseUrl, cliSessionKey)
198
+
173
199
  const sessionId = crypto.randomUUID()
174
200
  const codeVerifier = randomVerifier(64)
175
201
  const codeChallenge = sha256Base64url(codeVerifier)
@@ -177,11 +203,12 @@ program
177
203
  console.log(chalk.dim('\nCreating secure sign-in session...'))
178
204
  const sessionResp = await fetch(`${apiBaseUrl}/core/auth/session`, {
179
205
  method: 'POST',
180
- headers: { 'Content-Type': 'application/json' },
206
+ headers: withCliSessionKeyHeaders({ 'Content-Type': 'application/json' }, cliSessionKey),
181
207
  body: JSON.stringify({
182
208
  session_id: sessionId,
183
209
  code_challenge: codeChallenge,
184
- plugin_info: { version: 'cli', figma_env: 'cli' }
210
+ plugin_info: { version: 'cli', figma_env: 'cli' },
211
+ ...(cliSessionKey ? { cli_session_key: cliSessionKey } : {})
185
212
  })
186
213
  })
187
214
 
@@ -214,7 +241,8 @@ program
214
241
  }
215
242
 
216
243
  const statusResp = await fetch(
217
- `${apiBaseUrl}/core/auth/session/status?session=${encodeURIComponent(sessionId)}`
244
+ `${apiBaseUrl}/core/auth/session/status?session=${encodeURIComponent(sessionId)}`,
245
+ { headers: withCliSessionKeyHeaders({}, cliSessionKey) }
218
246
  )
219
247
  const statusData = await statusResp.json().catch(async () => ({ message: await statusResp.text() }))
220
248
 
@@ -235,8 +263,12 @@ program
235
263
  console.log(chalk.dim('Confirming session...'))
236
264
  const confirmResp = await fetch(`${apiBaseUrl}/core/auth/session/confirm`, {
237
265
  method: 'POST',
238
- headers: { 'Content-Type': 'application/json' },
239
- body: JSON.stringify({ session_id: sessionId, code_verifier: codeVerifier })
266
+ headers: withCliSessionKeyHeaders({ 'Content-Type': 'application/json' }, cliSessionKey),
267
+ body: JSON.stringify({
268
+ session_id: sessionId,
269
+ code_verifier: codeVerifier,
270
+ ...(cliSessionKey ? { cli_session_key: cliSessionKey } : {})
271
+ })
240
272
  })
241
273
 
242
274
  const confirmData = await confirmResp.json().catch(async () => ({ message: await confirmResp.text() }))
@@ -262,7 +294,6 @@ program
262
294
  }
263
295
 
264
296
  // Save credentials
265
- const credManager = new CredentialManager()
266
297
  credManager.saveCredentials({
267
298
  apiBaseUrl,
268
299
  authToken: accessToken,
@@ -4,12 +4,33 @@ import os from 'os'
4
4
 
5
5
  const RC_FILE = '.smblsrc'
6
6
  const DEFAULT_API = 'https://api.symbols.app'
7
+ const DEFAULT_LOCAL_CLI_SESSION_KEY = 'supersecret...change-in-production!!!'
8
+ const DEFAULT_PROD_CLI_SESSION_KEY =
9
+ 'r5jMdknqYjuGeWAsApTvPjaYHjWkdyw70veJye11Mb_vInBmsBZ9RM4GKU2_rm17Z2qvNahbyIV5gQVXY0V9DlUYdflLKd1jYCKSuc9r_xreC9hVEovfbZqmfbOl-JFCN1w1MXEsPNWkWj48nfF6IFfoWH-0hsdQlPBFjW1mv10'
7
10
 
8
11
  export class CredentialManager {
9
12
  constructor() {
10
13
  this.rcPath = path.join(os.homedir(), RC_FILE)
11
14
  }
12
15
 
16
+ static defaultCliSessionKeyForApiBaseUrl(apiBaseUrl) {
17
+ const baseUrl = (apiBaseUrl || '').trim()
18
+ if (!baseUrl) return null
19
+
20
+ // Match the environments used by login.js's websiteFromApi:
21
+ // - production: api.symbols.app -> production session key by default
22
+ // - everything else (dev/staging/test/local/custom): local key by default
23
+ try {
24
+ const u = new URL(baseUrl)
25
+ if (u.host === 'api.symbols.app') return DEFAULT_PROD_CLI_SESSION_KEY
26
+ return DEFAULT_LOCAL_CLI_SESSION_KEY
27
+ } catch (_) {
28
+ // Best-effort fallback if apiBaseUrl is malformed
29
+ if (baseUrl.includes('api.symbols.app')) return DEFAULT_PROD_CLI_SESSION_KEY
30
+ return DEFAULT_LOCAL_CLI_SESSION_KEY
31
+ }
32
+ }
33
+
13
34
  // --- Low-level helpers ----------------------------------------------------
14
35
 
15
36
  loadRaw() {
@@ -89,6 +110,66 @@ export class CredentialManager {
89
110
  return state.currentApiBaseUrl || DEFAULT_API
90
111
  }
91
112
 
113
+ getCliSessionKey(apiBaseUrl) {
114
+ // Allow overriding via env (useful for CI / debugging)
115
+ const envKey =
116
+ process.env.SYMBOLS_CLI_SESSION_KEY ||
117
+ process.env.SMBLS_CLI_SESSION_KEY ||
118
+ process.env.PLUGIN_CLI_SESSION_KEY
119
+ if (envKey) return envKey
120
+
121
+ const state = this.loadState() || {}
122
+ const baseUrl = apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
123
+
124
+ // Prefer explicit mapping, then per-profile field
125
+ const mapped = state.cliSessionKeys && typeof state.cliSessionKeys === 'object'
126
+ ? state.cliSessionKeys[baseUrl]
127
+ : null
128
+ if (mapped) return mapped
129
+
130
+ const profileKey =
131
+ state.profiles && typeof state.profiles === 'object'
132
+ ? state.profiles[baseUrl]?.cliSessionKey
133
+ : null
134
+ if (profileKey) return profileKey
135
+
136
+ // Fall back to environment defaults (e.g. localhost)
137
+ return CredentialManager.defaultCliSessionKeyForApiBaseUrl(baseUrl)
138
+ }
139
+
140
+ setCliSessionKey(apiBaseUrl, cliSessionKey) {
141
+ const state = this.loadState() || {}
142
+ const baseUrl = apiBaseUrl || state.currentApiBaseUrl || DEFAULT_API
143
+ const nextKey = (cliSessionKey || '').trim()
144
+
145
+ const next = { ...state }
146
+
147
+ // Store in a dedicated map keyed by API base URL
148
+ const prevMap = (state.cliSessionKeys && typeof state.cliSessionKeys === 'object')
149
+ ? state.cliSessionKeys
150
+ : {}
151
+ const nextMap = { ...prevMap }
152
+
153
+ if (!nextKey) {
154
+ delete nextMap[baseUrl]
155
+ } else {
156
+ nextMap[baseUrl] = nextKey
157
+ }
158
+
159
+ next.cliSessionKeys = nextMap
160
+
161
+ // Also mirror into the profile (handy for humans inspecting ~/.smblsrc)
162
+ const profiles = (state.profiles && typeof state.profiles === 'object') ? state.profiles : {}
163
+ const existing = profiles[baseUrl] || {}
164
+ const updatedProfile = { ...existing }
165
+ if (!nextKey) delete updatedProfile.cliSessionKey
166
+ else updatedProfile.cliSessionKey = nextKey
167
+ next.profiles = { ...profiles, [baseUrl]: updatedProfile }
168
+
169
+ this.saveRaw(next)
170
+ return next
171
+ }
172
+
92
173
  setCurrentApiBaseUrl(apiBaseUrl) {
93
174
  const state = this.loadState()
94
175
  const next = { ...state, currentApiBaseUrl: apiBaseUrl || DEFAULT_API }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@symbo.ls/cli",
3
- "version": "2.33.37",
3
+ "version": "2.33.38",
4
4
  "description": "Fetch your Symbols configuration",
5
5
  "main": "bin/fetch.js",
6
6
  "author": "Symbols",
@@ -15,18 +15,18 @@
15
15
  "vpatch": "npm version patch && npm publish"
16
16
  },
17
17
  "dependencies": {
18
- "@symbo.ls/fetch": "^2.33.37",
19
- "@symbo.ls/init": "^2.33.37",
20
- "@symbo.ls/socket": "^2.33.37",
18
+ "@symbo.ls/fetch": "^2.33.38",
19
+ "@symbo.ls/init": "^2.33.38",
20
+ "@symbo.ls/socket": "^2.33.38",
21
21
  "chalk": "^5.4.1",
22
22
  "chokidar": "^4.0.3",
23
23
  "commander": "^13.1.0",
24
24
  "diff": "^5.2.0",
25
- "esbuild": "^0.25.1",
25
+ "esbuild": "^0.26.0",
26
26
  "inquirer": "^12.9.0",
27
27
  "node-fetch": "^3.3.2",
28
28
  "socket.io-client": "^4.8.1",
29
29
  "v8-compile-cache": "^2.4.0"
30
30
  },
31
- "gitHead": "383eb71ccab284ca3b27477941b24d7152d51e67"
31
+ "gitHead": "7048f2fd761c7aa6a4233019c1b91318d88e64ce"
32
32
  }