promptlineapp 1.8.0 → 1.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.
Files changed (2) hide show
  1. package/bin/cli.js +101 -40
  2. package/package.json +1 -1
package/bin/cli.js CHANGED
@@ -2187,15 +2187,15 @@ function getApiKey() {
2187
2187
  return localStorage.getItem(API_KEY_STORAGE) || ''
2188
2188
  }
2189
2189
 
2190
- // Convert PromptLine URLs to use local proxy
2191
- function toProxyUrl(endpointId) {
2192
- return '/api/promptline/' + endpointId
2190
+ // Get proxy path based on environment (local vs prod)
2191
+ function getProxyPath(env) {
2192
+ return env === 'local' ? '/api/promptline-local' : '/api/promptline'
2193
2193
  }
2194
2194
 
2195
2195
  // Call PromptLine API
2196
- async function callPromptLineAPI(endpointId, apiKey, input, variables = {}) {
2197
- const proxyUrl = toProxyUrl(endpointId)
2198
- const fullUrl = proxyUrl + '?mode=sync'
2196
+ async function callPromptLineAPI(epData, apiKey, input, variables = {}) {
2197
+ const proxyPath = getProxyPath(epData.env)
2198
+ const fullUrl = proxyPath + '/' + epData.id + '?mode=sync'
2199
2199
 
2200
2200
  const res = await fetch(fullUrl, {
2201
2201
  method: 'POST',
@@ -2239,13 +2239,17 @@ export function usePromptLine(bindingName) {
2239
2239
  const context = useContext(PromptLineContext)
2240
2240
  const [isLoading, setIsLoading] = useState(false)
2241
2241
 
2242
- const endpointId = useMemo(() => {
2242
+ const epData = useMemo(() => {
2243
2243
  const config = getConfig()
2244
- return config[bindingName] || null
2244
+ const data = config[bindingName]
2245
+ // Handle both new format { id, env } and legacy string format
2246
+ if (!data) return null
2247
+ if (typeof data === 'string') return { id: data, env: 'prod' }
2248
+ return data
2245
2249
  }, [bindingName])
2246
2250
 
2247
2251
  const execute = async (input, variables = {}) => {
2248
- if (!endpointId) {
2252
+ if (!epData?.id) {
2249
2253
  console.warn(\`[PromptLine] Binding "\${bindingName}" not configured. Go to /_dev\`)
2250
2254
  return {
2251
2255
  response: \`[Not Configured] Binding "\${bindingName}" not set. Go to /_dev to configure it.\`,
@@ -2263,7 +2267,7 @@ export function usePromptLine(bindingName) {
2263
2267
 
2264
2268
  setIsLoading(true)
2265
2269
  try {
2266
- const result = await callPromptLineAPI(endpointId, apiKey, input, variables)
2270
+ const result = await callPromptLineAPI(epData, apiKey, input, variables)
2267
2271
  return result
2268
2272
  } catch (err) {
2269
2273
  console.error(\`[PromptLine] Error calling "\${bindingName}":\`, err)
@@ -2276,7 +2280,7 @@ export function usePromptLine(bindingName) {
2276
2280
  return {
2277
2281
  execute,
2278
2282
  isLoading,
2279
- isConfigured: !!endpointId,
2283
+ isConfigured: !!epData?.id,
2280
2284
  config: context?.config || {},
2281
2285
  user: context?.user || {},
2282
2286
  // Aliases for compatibility
@@ -2294,11 +2298,13 @@ export const sdk = {
2294
2298
  ai: {
2295
2299
  call: async (bindingName, input) => {
2296
2300
  const config = getConfig()
2297
- const endpointId = config[bindingName]
2298
- if (!endpointId) {
2301
+ const data = config[bindingName]
2302
+ // Handle both new format { id, env } and legacy string format
2303
+ const epData = !data ? null : (typeof data === 'string' ? { id: data, env: 'prod' } : data)
2304
+ if (!epData?.id) {
2299
2305
  throw new Error(\`Binding "\${bindingName}" not configured\`)
2300
2306
  }
2301
- return callPromptLineAPI(endpointId, getApiKey(), input)
2307
+ return callPromptLineAPI(epData, getApiKey(), input)
2302
2308
  }
2303
2309
  },
2304
2310
  collections: {
@@ -2332,14 +2338,29 @@ import { availableBindings } from './sdk-mock'
2332
2338
  const STORAGE_KEY = 'promptline_endpoints_config'
2333
2339
  const API_KEY_STORAGE = 'promptline_api_key'
2334
2340
 
2335
- // Helper to extract endpoint ID from URL or return as-is
2336
- function extractEndpointId(value) {
2337
- if (!value) return ''
2338
- // If it's a full URL, extract the endpoint ID
2341
+ // Helper to extract endpoint ID and environment from URL
2342
+ function parseEndpointUrl(value) {
2343
+ if (!value) return { id: '', env: 'prod' }
2344
+
2345
+ // Detect environment from URL
2346
+ let env = 'prod'
2347
+ if (value.includes('app.local.promptlineops.com')) {
2348
+ env = 'local'
2349
+ } else if (value.includes('app.promptlineops.com')) {
2350
+ env = 'prod'
2351
+ }
2352
+
2353
+ // Extract endpoint ID from URL
2339
2354
  const match = value.match(/\\/api\\/v1\\/live\\/([^?/]+)/) || value.match(/\\/live\\/([^?/]+)/)
2340
- if (match) return match[1]
2341
- // If it starts with ep_ or similar, use as-is
2342
- return value
2355
+ if (match) return { id: match[1], env }
2356
+
2357
+ // If it starts with ep_ or similar, use as-is with prod default
2358
+ return { id: value.trim(), env }
2359
+ }
2360
+
2361
+ // Get the proxy path for a given environment
2362
+ function getProxyPath(env) {
2363
+ return env === 'local' ? '/api/promptline-local' : '/api/promptline'
2343
2364
  }
2344
2365
 
2345
2366
  export default function DevAdmin() {
@@ -2363,14 +2384,18 @@ export default function DevAdmin() {
2363
2384
  }, [])
2364
2385
 
2365
2386
  const save = () => {
2366
- // Clean endpoint values (extract IDs from URLs)
2367
- const cleanedEndpoints = {}
2387
+ // Parse endpoint values (extract IDs and environments from URLs)
2388
+ const parsedEndpoints = {}
2368
2389
  Object.entries(endpoints).forEach(([key, value]) => {
2369
- cleanedEndpoints[key] = extractEndpointId(value)
2390
+ if (typeof value === 'string') {
2391
+ parsedEndpoints[key] = parseEndpointUrl(value)
2392
+ } else {
2393
+ parsedEndpoints[key] = value // Already parsed
2394
+ }
2370
2395
  })
2371
- setEndpoints(cleanedEndpoints)
2396
+ setEndpoints(parsedEndpoints)
2372
2397
  localStorage.setItem(API_KEY_STORAGE, apiKey)
2373
- localStorage.setItem(STORAGE_KEY, JSON.stringify(cleanedEndpoints))
2398
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(parsedEndpoints))
2374
2399
  setSaved(true)
2375
2400
  setTimeout(() => setSaved(false), 2000)
2376
2401
  }
@@ -2381,8 +2406,10 @@ export default function DevAdmin() {
2381
2406
 
2382
2407
  // Check if endpoint is alive
2383
2408
  const checkIsAlive = async (bindingName) => {
2384
- const endpointId = extractEndpointId(endpoints[bindingName])
2385
- if (!endpointId) {
2409
+ const epData = typeof endpoints[bindingName] === 'string'
2410
+ ? parseEndpointUrl(endpoints[bindingName])
2411
+ : endpoints[bindingName]
2412
+ if (!epData?.id) {
2386
2413
  setEndpointStatus(prev => ({ ...prev, [bindingName]: { ...prev[bindingName], alive: false, error: 'No endpoint ID' } }))
2387
2414
  return
2388
2415
  }
@@ -2390,7 +2417,8 @@ export default function DevAdmin() {
2390
2417
  setEndpointStatus(prev => ({ ...prev, [bindingName]: { ...prev[bindingName], loading: 'alive' } }))
2391
2418
 
2392
2419
  try {
2393
- const res = await fetch('/api/promptline/' + endpointId + '/isAlive', {
2420
+ const proxyPath = getProxyPath(epData.env)
2421
+ const res = await fetch(proxyPath + '/' + epData.id + '/isAlive', {
2394
2422
  method: 'GET',
2395
2423
  headers: apiKey ? { 'X-API-Key': apiKey } : {}
2396
2424
  })
@@ -2401,7 +2429,8 @@ export default function DevAdmin() {
2401
2429
  ...prev[bindingName],
2402
2430
  alive: res.ok && data.alive === true,
2403
2431
  loading: null,
2404
- error: res.ok ? null : (data.detail || res.statusText)
2432
+ error: res.ok ? null : (data.detail || res.statusText),
2433
+ env: epData.env
2405
2434
  }
2406
2435
  }))
2407
2436
  } catch (err) {
@@ -2414,8 +2443,10 @@ export default function DevAdmin() {
2414
2443
 
2415
2444
  // Get endpoint info
2416
2445
  const fetchInfo = async (bindingName) => {
2417
- const endpointId = extractEndpointId(endpoints[bindingName])
2418
- if (!endpointId) {
2446
+ const epData = typeof endpoints[bindingName] === 'string'
2447
+ ? parseEndpointUrl(endpoints[bindingName])
2448
+ : endpoints[bindingName]
2449
+ if (!epData?.id) {
2419
2450
  setEndpointStatus(prev => ({ ...prev, [bindingName]: { ...prev[bindingName], error: 'No endpoint ID' } }))
2420
2451
  return
2421
2452
  }
@@ -2423,7 +2454,8 @@ export default function DevAdmin() {
2423
2454
  setEndpointStatus(prev => ({ ...prev, [bindingName]: { ...prev[bindingName], loading: 'info' } }))
2424
2455
 
2425
2456
  try {
2426
- const res = await fetch('/api/promptline/' + endpointId + '/info', {
2457
+ const proxyPath = getProxyPath(epData.env)
2458
+ const res = await fetch(proxyPath + '/' + epData.id + '/info', {
2427
2459
  method: 'GET',
2428
2460
  headers: apiKey ? { 'X-API-Key': apiKey } : {}
2429
2461
  })
@@ -2434,7 +2466,8 @@ export default function DevAdmin() {
2434
2466
  ...prev[bindingName],
2435
2467
  info: res.ok ? data : null,
2436
2468
  loading: null,
2437
- error: res.ok ? null : (data.detail || res.statusText)
2469
+ error: res.ok ? null : (data.detail || res.statusText),
2470
+ env: epData.env
2438
2471
  }
2439
2472
  }))
2440
2473
  } catch (err) {
@@ -2446,8 +2479,10 @@ export default function DevAdmin() {
2446
2479
  }
2447
2480
 
2448
2481
  const testEndpoint = async () => {
2449
- const endpointId = extractEndpointId(endpoints[testBinding])
2450
- if (!testBinding || !endpointId) {
2482
+ const epData = typeof endpoints[testBinding] === 'string'
2483
+ ? parseEndpointUrl(endpoints[testBinding])
2484
+ : endpoints[testBinding]
2485
+ if (!testBinding || !epData?.id) {
2451
2486
  setTestResult('Error: Endpoint not configured')
2452
2487
  return
2453
2488
  }
@@ -2460,7 +2495,8 @@ export default function DevAdmin() {
2460
2495
  setTestResult('Calling API...')
2461
2496
 
2462
2497
  try {
2463
- const res = await fetch('/api/promptline/' + endpointId + '?mode=sync', {
2498
+ const proxyPath = getProxyPath(epData.env)
2499
+ const res = await fetch(proxyPath + '/' + epData.id + '?mode=sync', {
2464
2500
  method: 'POST',
2465
2501
  headers: {
2466
2502
  'Content-Type': 'application/json',
@@ -2537,6 +2573,10 @@ export default function DevAdmin() {
2537
2573
  <tbody>
2538
2574
  {availableBindings.map(b => {
2539
2575
  const status = endpointStatus[b.name] || {}
2576
+ const epValue = endpoints[b.name]
2577
+ const displayValue = typeof epValue === 'object' ? epValue?.id || '' : epValue || ''
2578
+ const epEnv = typeof epValue === 'object' ? epValue?.env : (status.env || null)
2579
+ const hasValue = !!(typeof epValue === 'object' ? epValue?.id : epValue)
2540
2580
  return (
2541
2581
  <tr key={b.name} style={{ borderBottom: '1px solid #f3f4f6' }}>
2542
2582
  <td style={{ padding: 8 }}>
@@ -2546,11 +2586,24 @@ export default function DevAdmin() {
2546
2586
  <td style={{ padding: 8 }}>
2547
2587
  <input
2548
2588
  type="text"
2549
- value={endpoints[b.name] || ''}
2589
+ value={displayValue}
2550
2590
  onChange={e => updateEndpoint(b.name, e.target.value)}
2551
- placeholder="ep_... or full URL"
2591
+ placeholder="Paste full endpoint URL (app.local or app)"
2552
2592
  style={{ width: '100%', padding: 8, border: '1px solid #d1d5db', borderRadius: 4, fontSize: 12 }}
2553
2593
  />
2594
+ {epEnv && (
2595
+ <span style={{
2596
+ display: 'inline-block',
2597
+ marginTop: 4,
2598
+ padding: '2px 6px',
2599
+ fontSize: 10,
2600
+ borderRadius: 4,
2601
+ background: epEnv === 'local' ? '#dbeafe' : '#dcfce7',
2602
+ color: epEnv === 'local' ? '#1d4ed8' : '#166534'
2603
+ }}>
2604
+ {epEnv === 'local' ? 'LOCAL' : 'PROD'}
2605
+ </span>
2606
+ )}
2554
2607
  </td>
2555
2608
  <td style={{ padding: 8, textAlign: 'center' }}>
2556
2609
  <button
@@ -2577,7 +2630,7 @@ export default function DevAdmin() {
2577
2630
  width: 12,
2578
2631
  height: 12,
2579
2632
  borderRadius: '50%',
2580
- background: status.alive === true ? '#22c55e' : status.alive === false ? '#ef4444' : (endpoints[b.name] ? '#fbbf24' : '#d1d5db')
2633
+ background: status.alive === true ? '#22c55e' : status.alive === false ? '#ef4444' : (hasValue ? '#fbbf24' : '#d1d5db')
2581
2634
  }}
2582
2635
  title={status.error || (status.alive ? 'Alive' : status.alive === false ? 'Not responding' : 'Not checked')}
2583
2636
  />
@@ -2691,6 +2744,14 @@ export default function DevAdmin() {
2691
2744
  viteProxyConfig: `
2692
2745
  // PromptLine API Proxy (added by npx promptlineapp get)
2693
2746
  proxy: {
2747
+ // Local dev: app.local.promptlineops.com (more specific path first)
2748
+ '/api/promptline-local': {
2749
+ target: 'https://app.local.promptlineops.com',
2750
+ changeOrigin: true,
2751
+ rewrite: (path) => path.replace(/^\\/api\\/promptline-local/, '/api/v1/live'),
2752
+ secure: true
2753
+ },
2754
+ // Production: app.promptlineops.com
2694
2755
  '/api/promptline': {
2695
2756
  target: 'https://app.promptlineops.com',
2696
2757
  changeOrigin: true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "promptlineapp",
3
- "version": "1.8.0",
3
+ "version": "1.9.0",
4
4
  "description": "Create PromptLine applications with ease",
5
5
  "author": "PromptLine <support@promptlineops.com>",
6
6
  "license": "MIT",