@palettelab/cli 0.3.39 → 0.3.41

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/README.md CHANGED
@@ -467,14 +467,16 @@ pltt dev --platform
467
467
 
468
468
  By default this starts:
469
469
 
470
- - A small local app shell on the first available port starting at http://localhost:3000
471
- - A local FastAPI backend runner on the first available port starting at http://localhost:8000
470
+ - A small local app shell on the first available port starting at http://localhost:7321
471
+ - A local FastAPI backend runner on the first available port starting at http://localhost:8732
472
472
  - A mock Palette platform context for `usePlatform()`, OS language, toasts, org/user data, and authenticated API calls
473
473
  - A plugin-local SQLite database under `.palette/dev/` when `capabilities.database` or `database` is enabled
474
474
 
475
475
  Your frontend entry is bundled and watched. Your backend entry is loaded under
476
476
  `/api/v1/plugins/<your-id>/*` with a dev `PluginContext`, so normal SDK calls
477
- work without Docker or platform source.
477
+ work without Docker or platform source. The terminal streams local frontend
478
+ requests, frontend rebuilds, and backend process output while `pltt dev` is
479
+ running.
478
480
 
479
481
  The simulator also provides the same language fields that Palette OS provides:
480
482
  `language`, `fallbackLanguage`, `supportedLanguages`, and `setLanguage()`.
@@ -487,9 +489,9 @@ local dev. The simulator imports `backend/api/models.py` when present and create
487
489
  tables from `palette_sdk.db.PluginBase.metadata`. Production installs still use
488
490
  the declared Alembic migrations and platform-managed Postgres/RLS.
489
491
 
490
- `3000` and `8000` are preferred defaults, not hard requirements. If either port is already in use, `pltt dev` automatically picks the next free port and prints the actual URLs.
492
+ `7321` and `8732` are preferred defaults, not hard requirements. If either port is already in use, `pltt dev` automatically picks the next free port and prints the actual URLs.
491
493
 
492
- `pltt dev --sandbox` skips Docker and publishes a reviewable preview to a configured Palette sandbox. This is the payment-gateway-style flow: your app uses local SDKs, while real platform services such as login, Data Room, storage, database policies, logs, review, and publish lifecycle run in the hosted sandbox. It defaults to `--env staging` unless `--env` or `PALETTE_ENV` is set, adds a 24-hour preview TTL by default, and prints the preview/status/log commands returned by the platform.
494
+ `pltt dev --sandbox` skips Docker and publishes a reviewable preview to a configured Palette sandbox. This is the payment-gateway-style flow: your app uses local SDKs, while real platform services such as login, Data Room, storage, database policies, logs, review, and publish lifecycle run in the hosted sandbox. It defaults to `--env staging` unless `--env` or `PALETTE_ENV` is set, adds a 24-hour preview TTL by default, prints the preview/status URLs, then tails hosted app logs in the same terminal. Set `PALETTE_DEV_LOG_TAIL` to change the initial hosted log tail size.
493
495
 
494
496
  For developer sandboxes where manual approval should not block OS testing, run the sandbox backend with `APPSTORE_AUTO_APPROVE_SANDBOX_PREVIEWS=true`. Preview publishes are still checked by the automated review gates; passing preview publishes are marked active and synced immediately so backend routes, installs, logs, permissions, and platform APIs can be tested in the OS without Docker.
495
497
 
@@ -508,9 +510,10 @@ Environment variables:
508
510
  | Name | Default | Purpose |
509
511
  |---|---|---|
510
512
  | `PALETTE_DEV_IMAGE` | `ghcr.io/palette-lab/platform-dev:latest` | Override the platform image for `--platform` |
511
- | `PALETTE_FRONTEND_PORT` | `3000` | Preferred starting host port for the frontend |
512
- | `PALETTE_BACKEND_PORT` | `8000` | Preferred starting host port for the backend |
513
+ | `PALETTE_FRONTEND_PORT` | `7321` | Preferred starting host port for the frontend |
514
+ | `PALETTE_BACKEND_PORT` | `8732` | Preferred starting host port for the backend |
513
515
  | `PALETTE_DEV_DATABASE_URL` | `.palette/dev/<plugin-id>.sqlite3` | Override the local dev database URL |
516
+ | `PALETTE_DEV_LOG_TAIL` | `200` | Initial hosted log events shown by `pltt dev --sandbox` / `--cloud` |
514
517
  | `APPSTORE_AUTO_APPROVE_SANDBOX_PREVIEWS` | `false` | Backend setting for hosted sandboxes; auto-approve passing preview publishes so developers can test full OS behavior without manual review |
515
518
 
516
519
  ### `pltt secrets`
@@ -4,11 +4,12 @@ const path = require("path")
4
4
  const { spawn, spawnSync } = require("child_process")
5
5
  const { loadManifest } = require("../manifest")
6
6
  const { watchFrontend } = require("../bundler")
7
- const { parseFlags } = require("../environments")
7
+ const { parseFlags, resolveEnvironment } = require("../environments")
8
8
  const { resolveDevPorts } = require("../ports")
9
9
  const { startSimulator } = require("../dev-simulator")
10
10
  const { loadLocalEnv } = require("../secrets")
11
11
  const publish = require("./publish")
12
+ const logs = require("./logs")
12
13
 
13
14
  const DEFAULT_PLATFORM_IMAGE = "ghcr.io/palette-lab/platform-dev:latest"
14
15
  const DEFAULT_IMAGE = process.env.PALETTE_DEV_IMAGE || DEFAULT_PLATFORM_IMAGE
@@ -102,8 +103,22 @@ async function run(args, { cwd }) {
102
103
  console.log(`[pltt] preview URL: ${record.preview_url}`)
103
104
  }
104
105
  if (!json && record?.id) {
105
- console.log(`[pltt] status: pltt status ${record.id} --env ${flags.env || process.env.PALETTE_ENV || "staging"}`)
106
- console.log("[pltt] logs: pltt logs --follow")
106
+ const envName = flags.env || process.env.PALETTE_ENV || "staging"
107
+ console.log(`[pltt] status: pltt status ${record.id} --env ${envName}`)
108
+ const logPluginId = record.plugin_id || loadManifest(cwd).id
109
+ console.log(`[pltt] logs: streaming hosted app logs for ${logPluginId} (Ctrl+C to stop)`)
110
+ try {
111
+ const env = resolveEnvironment({ cwd, flags: { ...flags, env: envName } })
112
+ await logs.streamPluginLogs({
113
+ env,
114
+ pluginId: logPluginId,
115
+ tail: Number(process.env.PALETTE_DEV_LOG_TAIL || "200") || 200,
116
+ follow: true,
117
+ })
118
+ } catch (err) {
119
+ console.warn(`[pltt] could not stream hosted logs: ${err instanceof Error ? err.message : String(err)}`)
120
+ console.warn(`[pltt] run manually: pltt logs ${logPluginId} --follow --env ${envName}`)
121
+ }
107
122
  }
108
123
  return
109
124
  }
@@ -5,13 +5,13 @@ const path = require("path")
5
5
  const { spawnSync } = require("child_process")
6
6
  const { loadManifest, validateManifest } = require("../manifest")
7
7
  const { bundleFrontend } = require("../bundler")
8
- const { resolveDevPorts } = require("../ports")
8
+ const { DEFAULT_BACKEND_DEV_PORT, DEFAULT_FRONTEND_DEV_PORT, resolveDevPorts } = require("../ports")
9
9
  const { declaredSecrets, loadLocalEnv } = require("../secrets")
10
10
 
11
11
  const DEFAULT_IMAGE =
12
12
  process.env.PALETTE_DEV_IMAGE || "ghcr.io/palette-lab/platform-dev:latest"
13
- const FRONTEND_PORT = Number(process.env.PALETTE_FRONTEND_PORT || "3000")
14
- const BACKEND_PORT = Number(process.env.PALETTE_BACKEND_PORT || "8000")
13
+ const FRONTEND_PORT = Number(process.env.PALETTE_FRONTEND_PORT || DEFAULT_FRONTEND_DEV_PORT)
14
+ const BACKEND_PORT = Number(process.env.PALETTE_BACKEND_PORT || DEFAULT_BACKEND_DEV_PORT)
15
15
 
16
16
  function ok(message) {
17
17
  console.log(`OK ${message}`)
@@ -31,6 +31,39 @@ async function fetchLogs(env, pluginId, tail) {
31
31
  return res.json()
32
32
  }
33
33
 
34
+ function formatLogEvent(e) {
35
+ const ts = e.created_at
36
+ const tag = String(e.event_type || "event").padEnd(11)
37
+ const route = e.route || "-"
38
+ const code = e.status_code != null ? `[${e.status_code}]` : ""
39
+ const err = e.error_message ? ` err=${e.error_message}` : ""
40
+ return `${ts} ${tag} ${route} ${code}${err}`
41
+ }
42
+
43
+ async function streamPluginLogs({
44
+ env,
45
+ pluginId,
46
+ tail = 50,
47
+ follow = false,
48
+ json = false,
49
+ intervalMs = 3000,
50
+ log = console.log,
51
+ }) {
52
+ let lastSeenId = 0
53
+ do {
54
+ const data = await fetchLogs(env, pluginId, tail)
55
+ const events = data.events || []
56
+ const fresh = events.filter((e) => e.id > lastSeenId)
57
+ if (fresh.length) lastSeenId = Math.max(...fresh.map((e) => e.id))
58
+ if (json) {
59
+ for (const e of fresh) log(JSON.stringify(e))
60
+ } else {
61
+ for (const e of fresh) log(formatLogEvent(e))
62
+ }
63
+ if (follow) await new Promise((r) => setTimeout(r, intervalMs))
64
+ } while (follow)
65
+ }
66
+
34
67
  async function run(argv, { cwd }) {
35
68
  const json = argv.includes("--json")
36
69
  const follow = argv.includes("--follow") || argv.includes("-f")
@@ -68,32 +101,15 @@ async function run(argv, { cwd }) {
68
101
  process.exit(1)
69
102
  }
70
103
 
71
- let lastSeenId = 0
72
- do {
73
- let data
74
- try {
75
- data = await fetchLogs(env, pluginId, tail)
76
- } catch (err) {
77
- console.error(`[pltt] ${err.message}`)
78
- process.exit(1)
79
- }
80
- const events = data.events || []
81
- const fresh = events.filter((e) => e.id > lastSeenId)
82
- if (fresh.length) lastSeenId = Math.max(...fresh.map((e) => e.id))
83
- if (json) {
84
- for (const e of fresh) console.log(JSON.stringify(e))
85
- } else {
86
- for (const e of fresh) {
87
- const ts = e.created_at
88
- const tag = e.event_type.padEnd(11)
89
- const route = e.route || "-"
90
- const code = e.status_code != null ? `[${e.status_code}]` : ""
91
- const err = e.error_message ? ` err=${e.error_message}` : ""
92
- console.log(`${ts} ${tag} ${route} ${code}${err}`)
93
- }
94
- }
95
- if (follow) await new Promise((r) => setTimeout(r, 3000))
96
- } while (follow)
104
+ try {
105
+ await streamPluginLogs({ env, pluginId, tail, follow, json })
106
+ } catch (err) {
107
+ console.error(`[pltt] ${err.message}`)
108
+ process.exit(1)
109
+ }
97
110
  }
98
111
 
99
112
  module.exports = run
113
+ module.exports.fetchLogs = fetchLogs
114
+ module.exports.formatLogEvent = formatLogEvent
115
+ module.exports.streamPluginLogs = streamPluginLogs
@@ -242,6 +242,7 @@ function startBackend(cwd, devDir, manifest, backendPort) {
242
242
  if (sdkPath) {
243
243
  env.PYTHONPATH = [sdkPath, env.PYTHONPATH].filter(Boolean).join(path.delimiter)
244
244
  }
245
+ console.log(`[pltt] backend logs: ${backendEntry} via uvicorn on http://localhost:${backendPort}`)
245
246
  const child = spawn(
246
247
  python,
247
248
  ["-m", "uvicorn", `${path.basename(runner, ".py")}:app`, "--host", "127.0.0.1", "--port", String(backendPort), "--reload", "--reload-dir", path.dirname(absEntry)],
@@ -444,10 +445,12 @@ async function startFrontend(cwd, devDir, manifest, frontendPort, backendPort) {
444
445
  const server = http.createServer((req, res) => {
445
446
  const url = new URL(req.url || "/", `http://127.0.0.1:${frontendPort}`)
446
447
  if (url.pathname === "/simulator.js") {
448
+ console.log(`[pltt] frontend GET ${url.pathname} -> 200`)
447
449
  res.writeHead(200, { "Content-Type": "text/javascript; charset=utf-8", "Cache-Control": "no-store" })
448
450
  res.end(fs.readFileSync(bundlePath))
449
451
  return
450
452
  }
453
+ console.log(`[pltt] frontend GET ${url.pathname} -> 200`)
451
454
  res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", "Cache-Control": "no-store" })
452
455
  res.end(indexHtml(manifest))
453
456
  })
@@ -467,6 +470,7 @@ async function startSimulator({ cwd, frontendPort, backendPort }) {
467
470
  console.log("[pltt] running local SDK simulator (no Docker)")
468
471
  console.log(`[pltt] app: http://localhost:${frontendPort}/`)
469
472
  if (backend) console.log(`[pltt] backend: http://localhost:${backendPort}/api/v1/plugins/${manifest.id}`)
473
+ console.log("[pltt] logs: streaming frontend requests, frontend bundle rebuilds, and backend output")
470
474
  console.log("[pltt] use `pltt dev --platform` for full Docker platform parity")
471
475
 
472
476
  const stop = async () => {
@@ -29,7 +29,7 @@ const path = require("path")
29
29
  const DEFAULTS = {
30
30
  environments: {
31
31
  local: {
32
- url: "http://localhost:8000",
32
+ url: "http://localhost:8732",
33
33
  token_env: "PALETTE_LOCAL_TOKEN",
34
34
  production: false,
35
35
  },
package/lib/ports.js CHANGED
@@ -2,6 +2,9 @@
2
2
 
3
3
  const net = require("net")
4
4
 
5
+ const DEFAULT_FRONTEND_DEV_PORT = 7321
6
+ const DEFAULT_BACKEND_DEV_PORT = 8732
7
+
5
8
  function canBindPort(port, host = "0.0.0.0") {
6
9
  return new Promise((resolve) => {
7
10
  const server = net.createServer()
@@ -50,8 +53,8 @@ async function findFreePort(preferred, { host = "0.0.0.0", maxAttempts = 100 } =
50
53
  }
51
54
 
52
55
  async function resolveDevPorts({
53
- frontend = process.env.PALETTE_FRONTEND_PORT || 3000,
54
- backend = process.env.PALETTE_BACKEND_PORT || 8000,
56
+ frontend = process.env.PALETTE_FRONTEND_PORT || DEFAULT_FRONTEND_DEV_PORT,
57
+ backend = process.env.PALETTE_BACKEND_PORT || DEFAULT_BACKEND_DEV_PORT,
55
58
  host = "0.0.0.0",
56
59
  } = {}) {
57
60
  const frontendPort = await findFreePort(frontend, { host })
@@ -65,4 +68,12 @@ async function resolveDevPorts({
65
68
  }
66
69
  }
67
70
 
68
- module.exports = { canBindPort, canConnectPort, isPortAvailable, findFreePort, resolveDevPorts }
71
+ module.exports = {
72
+ DEFAULT_FRONTEND_DEV_PORT,
73
+ DEFAULT_BACKEND_DEV_PORT,
74
+ canBindPort,
75
+ canConnectPort,
76
+ isPortAvailable,
77
+ findFreePort,
78
+ resolveDevPorts,
79
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@palettelab/cli",
3
- "version": "0.3.39",
3
+ "version": "0.3.41",
4
4
  "description": "Developer CLI for building Palette platform plugins — no platform source access required.",
5
5
  "bin": {
6
6
  "pltt": "bin/pltt.js"
@@ -10,8 +10,8 @@ services:
10
10
  platform:
11
11
  image: ${PALETTE_DEV_IMAGE:-ghcr.io/palette-lab/platform-dev:latest}
12
12
  ports:
13
- - "${PALETTE_FRONTEND_PORT:-3000}:3000"
14
- - "${PALETTE_BACKEND_PORT:-8000}:8000"
13
+ - "${PALETTE_FRONTEND_PORT:-7321}:3000"
14
+ - "${PALETTE_BACKEND_PORT:-8732}:8000"
15
15
  environment:
16
16
  PALETTE_DEV_MODE: "1"
17
17
  NEXT_PUBLIC_PALETTE_DEV_MODE: "1"
@@ -19,9 +19,9 @@ services:
19
19
  DATABASE_URL: "postgresql+asyncpg://postgres:postgres@postgres:5432/app"
20
20
  REDIS_URL: "redis://redis:6379/0"
21
21
  JWT_SECRET: "dev-secret-do-not-use-in-prod"
22
- FRONTEND_URL: "http://localhost:${PALETTE_FRONTEND_PORT:-3000}"
23
- BACKEND_BASE_URL: "http://localhost:${PALETTE_BACKEND_PORT:-8000}"
24
- NEXT_PUBLIC_API_URL: "http://localhost:${PALETTE_BACKEND_PORT:-8000}"
22
+ FRONTEND_URL: "http://localhost:${PALETTE_FRONTEND_PORT:-7321}"
23
+ BACKEND_BASE_URL: "http://localhost:${PALETTE_BACKEND_PORT:-8732}"
24
+ NEXT_PUBLIC_API_URL: "http://localhost:${PALETTE_BACKEND_PORT:-8732}"
25
25
  STORAGE_BACKEND: "local"
26
26
  LOCAL_STORAGE_DIR: "/srv/storage"
27
27
  # Disable optional features that need real credentials