@pplethai/cli 0.2.2 → 0.3.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.
- package/README.md +11 -6
- package/dist/api.js +62 -16
- package/dist/bin.js +1 -1
- package/dist/cli.js +64 -16
- package/dist/commands/auth.js +8 -8
- package/dist/commands/deploy.js +20 -21
- package/dist/commands/env.js +70 -0
- package/dist/commands/info.js +238 -0
- package/dist/commands/init.js +69 -16
- package/dist/commands/upgrade.js +5 -5
- package/dist/device-flow.js +4 -4
- package/dist/errors.js +2 -2
- package/dist/git.js +12 -12
- package/dist/link.js +12 -2
- package/dist/node-deps.js +23 -0
- package/dist/npm.js +1 -1
- package/dist/scaffold.js +1 -1
- package/dist/skills.js +2 -2
- package/dist/update.js +11 -11
- package/package.json +1 -1
- package/skills/pple-platform/SKILL.md +30 -21
- package/skills/pple-platform/evals/evals.json +1 -1
- package/skills/pple-platform/references/deployment.md +75 -7
- package/skills/pple-platform/references/mini-app.md +3 -2
- package/skills/pple-platform/references/pple-id-auth.md +22 -16
- package/skills/pple-platform/references/project-scaffold.md +6 -6
- package/templates/golden-path/.env.example +5 -3
- package/templates/golden-path/CLAUDE.md +6 -2
- package/templates/golden-path/README.md +4 -4
- package/templates/golden-path/package-lock.json +8 -7
- package/templates/golden-path/package.json +2 -1
- package/templates/golden-path/web/App.tsx +22 -4
- package/templates/golden-path/web/app.css +0 -20
- package/templates/golden-path/web/main.tsx +16 -5
- package/templates/golden-path/web/pple.ts +6 -9
- package/templates/golden-path/web/vite-env.d.ts +3 -3
- package/templates/golden-path/web/NotInApp.tsx +0 -23
package/dist/update.js
CHANGED
|
@@ -27,7 +27,7 @@ export function wantsUpdateCheck(argv) {
|
|
|
27
27
|
}
|
|
28
28
|
/** The one line that opens every "there's a newer pple" conversation. */
|
|
29
29
|
export function announceUpdate(deps, latest) {
|
|
30
|
-
deps.out.say(
|
|
30
|
+
deps.out.say(`มี pple เวอร์ชันใหม่ (${latest}) คุณใช้เวอร์ชัน ${deps.version}`);
|
|
31
31
|
}
|
|
32
32
|
/**
|
|
33
33
|
* What to say about an install npm's global root doesn't hold. Installing
|
|
@@ -35,13 +35,13 @@ export function announceUpdate(deps, latest) {
|
|
|
35
35
|
* one just ran (ADR-0007).
|
|
36
36
|
*/
|
|
37
37
|
export function sayOtherChannel(deps) {
|
|
38
|
-
deps.out.say("
|
|
39
|
-
deps.out.say("
|
|
40
|
-
deps.out.say(
|
|
38
|
+
deps.out.say("pple ชุดนี้ไม่ได้ติดตั้งผ่าน npm การอัปเดตที่นี่อาจทำให้คุณ");
|
|
39
|
+
deps.out.say("มี pple สองเวอร์ชัน กรุณาอัปเดตด้วยวิธีเดิมที่คุณติดตั้งไว้");
|
|
40
|
+
deps.out.say(`หรือติดตั้งด้วย npm:\n\n ${MANUAL_INSTALL}`);
|
|
41
41
|
}
|
|
42
42
|
/** Why npm gave up, and the line the Builder can always run themselves. */
|
|
43
43
|
export function manualHint(result) {
|
|
44
|
-
return `${npmMessage(result)}\n\
|
|
44
|
+
return `${npmMessage(result)}\n\nลองรันคำสั่งนี้ด้วยตัวเอง:\n\n ${MANUAL_INSTALL}`;
|
|
45
45
|
}
|
|
46
46
|
/**
|
|
47
47
|
* Look for a newer CLI, and act on it as far as this session allows. Returns
|
|
@@ -68,18 +68,18 @@ export async function checkForUpdate(deps, argv) {
|
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
70
|
if (!deps.interactive) {
|
|
71
|
-
deps.out.say("
|
|
71
|
+
deps.out.say("รัน `pple upgrade` เมื่อคุณพร้อม");
|
|
72
72
|
deps.out.say();
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
|
-
if (!(await deps.confirm("
|
|
75
|
+
if (!(await deps.confirm("อัปเดต pple ตอนนี้เลยไหม?"))) {
|
|
76
76
|
deps.out.say();
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
79
79
|
const result = await installGlobally(deps, latest);
|
|
80
80
|
if (result.code !== 0)
|
|
81
81
|
return await afterFailedUpgrade(deps, latest, result);
|
|
82
|
-
deps.out.say(`pple
|
|
82
|
+
deps.out.say(`pple เป็นเวอร์ชัน ${latest} แล้ว กำลังทำงานต่อจากที่ค้างไว้…`);
|
|
83
83
|
deps.out.say();
|
|
84
84
|
return deps.reexec(argv);
|
|
85
85
|
}
|
|
@@ -91,14 +91,14 @@ export async function checkForUpdate(deps, argv) {
|
|
|
91
91
|
* place — so carrying on is a question, not a default.
|
|
92
92
|
*/
|
|
93
93
|
async function afterFailedUpgrade(deps, latest, result) {
|
|
94
|
-
deps.out.say(
|
|
94
|
+
deps.out.say(`อัปเดต pple เป็น ${latest} ไม่สำเร็จ`);
|
|
95
95
|
deps.out.say(manualHint(result));
|
|
96
96
|
deps.out.say();
|
|
97
|
-
if (await deps.confirm(
|
|
97
|
+
if (await deps.confirm(`ใช้ pple ${deps.version} ทำงานต่อไปก่อนไหม?`)) {
|
|
98
98
|
deps.out.say();
|
|
99
99
|
return null;
|
|
100
100
|
}
|
|
101
|
-
deps.out.say("
|
|
101
|
+
deps.out.say("หยุดแล้ว ไม่มีอะไรเปลี่ยนแปลง");
|
|
102
102
|
return 1;
|
|
103
103
|
}
|
|
104
104
|
async function isDue(deps) {
|
package/package.json
CHANGED
|
@@ -18,14 +18,15 @@ The PPLE Platform lets party workforce members (Builders) ship mini-apps
|
|
|
18
18
|
without holding cloud or GitHub credentials. Every new **Builder App** plugs
|
|
19
19
|
into three pillars on party-managed infrastructure:
|
|
20
20
|
|
|
21
|
-
1. **PPLE ID** — OIDC single sign-on. End users authenticate
|
|
22
|
-
|
|
23
|
-
validates
|
|
24
|
-
ID for the Console (browser login) and
|
|
21
|
+
1. **PPLE ID** — OIDC single sign-on. End users authenticate via the mini-app
|
|
22
|
+
SDK (launch token inside **PPLE Today**, or the SDK's PPLE ID browser
|
|
23
|
+
login); the Worker validates the bearer token against the OIDC `userinfo`
|
|
24
|
+
endpoint. Builders also use PPLE ID for the Console (browser login) and
|
|
25
|
+
`pple auth login` (device flow).
|
|
25
26
|
2. **Mini-app integration** — apps run inside PPLE Today's WebView using
|
|
26
|
-
`@pplethai/pple-today-miniapp-sdk
|
|
27
|
-
(title/body, optional path-only
|
|
28
|
-
platform-provisioned notify helper.
|
|
27
|
+
`@pplethai/pple-today-miniapp-sdk` (and the same SDK in a plain browser),
|
|
28
|
+
and send audience-bound notifications (title/body, optional path-only
|
|
29
|
+
`linkPath` into this app) through the platform-provisioned notify helper.
|
|
29
30
|
3. **PPLE Design System** — `@pplethai/components`, a shadcn/ui-based React
|
|
30
31
|
library with party branding. Docs: https://components.pplethai.org/
|
|
31
32
|
|
|
@@ -63,30 +64,38 @@ Read only the file(s) relevant to the task at hand:
|
|
|
63
64
|
| Task | Read |
|
|
64
65
|
| ---- | ---- |
|
|
65
66
|
| Start a new app / local layout & commands | [references/project-scaffold.md](references/project-scaffold.md) |
|
|
66
|
-
| Login, tokens,
|
|
67
|
+
| Login, tokens, browser PPLE ID flow, DEV_AUTH | [references/pple-id-auth.md](references/pple-id-auth.md) |
|
|
67
68
|
| Mini-app SDK behavior, push notifications | [references/mini-app.md](references/mini-app.md) |
|
|
68
69
|
| UI, styling, components | [references/design-system.md](references/design-system.md) |
|
|
69
|
-
| Create → init → deploy
|
|
70
|
+
| Create → init → deploy; status / usage (`pple info`) | [references/deployment.md](references/deployment.md) |
|
|
70
71
|
|
|
71
72
|
## Golden path for a new app
|
|
72
73
|
|
|
73
|
-
1. **
|
|
74
|
-
provisions the slug, Worker, D1, R2, OAuth client, and hosting.
|
|
75
|
-
2. **Sign in on the machine**: `pple auth login` (PPLE ID device flow; no
|
|
74
|
+
1. **Sign in on the machine**: `pple auth login` (PPLE ID device flow; no
|
|
76
75
|
personal cloud credentials — ADR-0005).
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
76
|
+
2. **Create + scaffold** — pick one path (both use the same Provisioner
|
|
77
|
+
create; the App Slug is always platform-generated):
|
|
78
|
+
- **CLI create**: `pple init` (prompts for App Name) or
|
|
79
|
+
`pple init --name "<App Name>"` (non-interactive) — creates the app,
|
|
80
|
+
waits until it is ready, then scaffolds and writes the App Link.
|
|
81
|
+
- **Console create**: create in the Console (`console.ppleth.ai`), then
|
|
82
|
+
`pple init <slug>` to scaffold and link the folder.
|
|
83
|
+
3. **Build** against the template helpers (`auth`, `db`, `files`, `notify`)
|
|
80
84
|
and `@pplethai/components` (see the reference files).
|
|
81
|
-
|
|
85
|
+
4. **Ship**: `pple deploy` — the platform builds and puts the app live.
|
|
86
|
+
5. **Status / usage**: `pple info` (from a linked folder, or
|
|
87
|
+
`pple info <slug>`) — read-only overview, usage vs Resource Limits,
|
|
88
|
+
recent deploys, env key list. Invites, Requests, and collaborators stay
|
|
89
|
+
Console-only (see [deployment.md](references/deployment.md)).
|
|
82
90
|
|
|
83
91
|
## Invariants — things that are true no matter what you build
|
|
84
92
|
|
|
85
|
-
- **Mini-apps authenticate through
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
93
|
+
- **Mini-apps authenticate through the mini-app SDK.** Inside PPLE Today the
|
|
94
|
+
SDK reads the launch-URL token; in a plain browser it runs the PPLE ID
|
|
95
|
+
OIDC login. Do not add a NotInApp guard or a custom browser sign-in page.
|
|
96
|
+
Local development uses `DEV_AUTH` in `.dev.vars` only — never provisioned
|
|
97
|
+
in production. (Builders separately hold tokens from Console login and
|
|
98
|
+
`pple auth login`.)
|
|
90
99
|
- **Never mention git, branches, remotes, GitHub, Cloudflare accounts, or
|
|
91
100
|
personal deploy credentials** in the happy path. Talk about the app's
|
|
92
101
|
code and `pple init` / `pple deploy` only.
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
{
|
|
23
23
|
"id": 4,
|
|
24
24
|
"prompt": "The app should work when I open it in Chrome on my laptop. Add a normal browser login with PPLE ID.",
|
|
25
|
-
"expected_output": "
|
|
25
|
+
"expected_output": "Use the mini-app SDK's existing browser PPLE ID flow (web/pple.ts bootstrap → sdk.init); do not add a custom OAuth login page or a NotInApp / open-in-Today-only guard; local DEV_AUTH only for vite dev — do not claim tokens only exist inside PPLE Today as a platform-wide absolute (Console/CLI hold Builder tokens; end users can also sign in via the SDK browser flow)",
|
|
26
26
|
"files": []
|
|
27
27
|
}
|
|
28
28
|
]
|
|
@@ -5,16 +5,24 @@ Builder Apps run only on party-managed Cloudflare infrastructure
|
|
|
5
5
|
(ADR-0005). The happy path uses the Console and the `pple` CLI — not git
|
|
6
6
|
vocabulary, Docker, Cloud Build, or personal Cloudflare/Pages projects.
|
|
7
7
|
|
|
8
|
+
Ops detail for create, status, and what stays Console-only lives here
|
|
9
|
+
(ADR-0006: the Skill owns deeper platform guidance; `CLAUDE.md` stays
|
|
10
|
+
short).
|
|
11
|
+
|
|
8
12
|
## Happy path
|
|
9
13
|
|
|
10
14
|
```bash
|
|
11
|
-
# 1. Create the app in the Console (console.ppleth.ai)
|
|
12
|
-
# → slug, Worker, D1, R2, OAuth client, hosting — all provisioned
|
|
13
|
-
|
|
14
15
|
pple auth login # PPLE ID device flow on this machine
|
|
15
|
-
|
|
16
|
+
|
|
17
|
+
# Create + scaffold — either path works:
|
|
18
|
+
pple init # prompt for App Name → create → wait → scaffold
|
|
19
|
+
# or: pple init --name "My App" # non-interactive create (agents/scripts)
|
|
20
|
+
# or: create in Console (console.ppleth.ai), then:
|
|
21
|
+
# pple init <slug> # scaffold an app that already exists
|
|
22
|
+
|
|
16
23
|
# …edit locally with npm run dev…
|
|
17
24
|
pple deploy -m "short note" # send changes; platform builds and goes live
|
|
25
|
+
pple info # read-only status, usage vs limits, deploys
|
|
18
26
|
```
|
|
19
27
|
|
|
20
28
|
What the Builder hears: the app's name and live URL (e.g.
|
|
@@ -24,10 +32,69 @@ source-control jargon.
|
|
|
24
32
|
| Command | Meaning for the Builder |
|
|
25
33
|
| ------- | ----------------------- |
|
|
26
34
|
| `pple auth login` | Sign in with PPLE ID |
|
|
27
|
-
| `pple init
|
|
35
|
+
| `pple init` / `pple init --name "…"` | Create a new app, wait until ready, scaffold + App Link |
|
|
36
|
+
| `pple init <slug>` | Set up an existing app's code on this computer |
|
|
28
37
|
| `pple deploy` | Send changes and put them live |
|
|
38
|
+
| `pple info` / `pple info <slug>` | Read-only status, usage vs Resource Limits, deploys |
|
|
39
|
+
| `pple env list` / `set` / `delete` | Manage env vars and secrets (effect on next deploy) |
|
|
29
40
|
| `pple whoami` / `pple auth logout` | Check / forget this machine's sign-in |
|
|
30
41
|
|
|
42
|
+
## Creating an app (`pple init`)
|
|
43
|
+
|
|
44
|
+
Same Provisioner create as the Console (`POST /apps`). The **App Slug** is
|
|
45
|
+
always platform-generated — Builders choose only the **App Name**.
|
|
46
|
+
|
|
47
|
+
| Invocation | Behavior |
|
|
48
|
+
| ---------- | -------- |
|
|
49
|
+
| `pple init` (TTY, no App Link in cwd) | Prompt for App Name → create → poll until ready → scaffold |
|
|
50
|
+
| `pple init --name "<App Name>"` | Non-interactive create → poll → scaffold (agents/scripts) |
|
|
51
|
+
| `pple init` non-TTY without `--name` or slug | Error clearly — do not hang waiting on stdin |
|
|
52
|
+
| `pple init <slug>` | Scaffold an app that already exists (e.g. Console-created) |
|
|
53
|
+
| `pple init` inside a folder with an App Link | **Refuse** — already linked; use `pple info` / `pple deploy`, or `cd` elsewhere to create another app |
|
|
54
|
+
|
|
55
|
+
After create, the CLI **blocks/polls** until the app is ready to scaffold
|
|
56
|
+
(repo present, not stuck in `provisioning`). On timeout it fails loudly and
|
|
57
|
+
prints the App Slug so the Builder can retry with `pple init <slug>`. If
|
|
58
|
+
scaffold fails after a successful create, the app still exists on the
|
|
59
|
+
platform (same as Console-then-init).
|
|
60
|
+
|
|
61
|
+
## Status and usage (`pple info`)
|
|
62
|
+
|
|
63
|
+
Read-only terminal mirror of the Console app dashboard for one Builder App.
|
|
64
|
+
**No mutations.**
|
|
65
|
+
|
|
66
|
+
- Resolve the app from the **App Link** (`.pple/app.json`) when run inside
|
|
67
|
+
an app folder, **or** from `pple info <slug>`.
|
|
68
|
+
- No App Link and no slug → clear error: `cd` into the app, pass a slug, or
|
|
69
|
+
use the Console.
|
|
70
|
+
- Prints safe dashboard surfaces:
|
|
71
|
+
- Overview (App Name, App Slug, URL, status, Publication Tier, role, …)
|
|
72
|
+
- Usage vs Resource Limits — treat `null` usage fields as unavailable,
|
|
73
|
+
not zero (same semantics as Console)
|
|
74
|
+
- Recent deploys
|
|
75
|
+
- Env key list — secrets: key present only, never values
|
|
76
|
+
- Owner-only surfaces (publication / collaborators / request history as
|
|
77
|
+
the Console shows): include when the caller is Owner; **omit** for
|
|
78
|
+
Collaborators (same gates as Console — do not fail the whole command)
|
|
79
|
+
|
|
80
|
+
## Console vs CLI
|
|
81
|
+
|
|
82
|
+
| Need | Where |
|
|
83
|
+
| ---- | ----- |
|
|
84
|
+
| Create app | Console **or** `pple init` / `pple init --name` |
|
|
85
|
+
| Scaffold / link locally | `pple init <slug>` (or create path above) |
|
|
86
|
+
| Deploy | `pple deploy` |
|
|
87
|
+
| Read status / usage / deploys / env keys | `pple info` (or Console) |
|
|
88
|
+
| Env vars & secrets | Console **or** `pple env` |
|
|
89
|
+
| Beta invites, Publication Tier / PublishRequest | **Console only** |
|
|
90
|
+
| File ScopeRequest / LimitRequest / ChangeRequest / RetireRequest | **Console only** |
|
|
91
|
+
| Manage Collaborators | **Console only** |
|
|
92
|
+
| Dataset catalog / App Scope browsing | **Console only** |
|
|
93
|
+
| Platform Admin approval queue | **Console only** |
|
|
94
|
+
|
|
95
|
+
Point Builders at the Console (`console.ppleth.ai`) for anything in the
|
|
96
|
+
Console-only rows — do not invent CLI flags for those.
|
|
97
|
+
|
|
31
98
|
## What the platform owns
|
|
32
99
|
|
|
33
100
|
- **Hosting**: Workers for Platforms dispatch namespace; one tenant Worker per
|
|
@@ -37,8 +104,9 @@ source-control jargon.
|
|
|
37
104
|
- **The app's code**: stored and shipped by the platform. `pple init` /
|
|
38
105
|
`pple deploy` authenticate under the hood — the Builder never configures
|
|
39
106
|
a source-control account or pastes a deploy token.
|
|
40
|
-
- **Config / secrets**: set in the Console
|
|
41
|
-
**next** deploy. Secrets are write-only (set/unset,
|
|
107
|
+
- **Config / secrets**: set in the Console or via `pple env`; applied as
|
|
108
|
+
Worker bindings on the **next** deploy. Secrets are write-only (set/unset,
|
|
109
|
+
never read back).
|
|
42
110
|
|
|
43
111
|
## Local vs production auth
|
|
44
112
|
|
|
@@ -7,8 +7,9 @@ Builder Apps ship as **mini-apps**: web apps opened in PPLE Today's WebView.
|
|
|
7
7
|
[pple-id-auth.md](pple-id-auth.md). Facts that shape your design:
|
|
8
8
|
|
|
9
9
|
- `sdk.init()` must complete before any other SDK call.
|
|
10
|
-
- Outside PPLE Today (plain browser),
|
|
11
|
-
an error. Locally, `DEV_AUTH` covers
|
|
10
|
+
- Outside PPLE Today (plain browser), the same SDK runs the PPLE ID browser
|
|
11
|
+
login — that is expected, not an error. Locally, `DEV_AUTH` covers
|
|
12
|
+
development.
|
|
12
13
|
- The OAuth client and mini-app URL are provisioned when the app is created in
|
|
13
14
|
the Console; Builders do not register them manually with the Today team.
|
|
14
15
|
- Design mobile-first; test at ~375 px width.
|
|
@@ -9,38 +9,43 @@ PPLE Today ──opens mini-app in WebView──▶ web (React)
|
|
|
9
9
|
Worker ──GET {SSO_BASE_URL}/oidc/v1/userinfo──▶ PPLE ID
|
|
10
10
|
```
|
|
11
11
|
|
|
12
|
+
In a plain browser the same SDK runs the PPLE ID authorization-code login
|
|
13
|
+
(redirect / callback) instead of reading tokens from the launch URL; the
|
|
14
|
+
Worker still validates the bearer token the same way.
|
|
15
|
+
|
|
12
16
|
The Worker never sees a password and holds no session; the bearer token is
|
|
13
17
|
the credential on every request (`src/auth.ts`).
|
|
14
18
|
|
|
15
19
|
Tokens also exist outside mini-apps for **Builders** (Console browser login,
|
|
16
|
-
`pple auth login` device flow).
|
|
17
|
-
|
|
20
|
+
`pple auth login` device flow). End users of a Builder App authenticate
|
|
21
|
+
through the mini-app SDK — either inside PPLE Today or via that SDK's
|
|
22
|
+
browser PPLE ID flow. Do not invent a second custom login page.
|
|
18
23
|
|
|
19
24
|
## Three ways the mini-app can start
|
|
20
25
|
|
|
21
|
-
Canonical wiring: `web/pple.ts` + `web/main.tsx`
|
|
22
|
-
golden-path template.
|
|
26
|
+
Canonical wiring: `web/pple.ts` + `web/main.tsx` in the golden-path template.
|
|
23
27
|
|
|
24
28
|
1. **Local `vite dev`** — set `token` to the `DEV_AUTH` value from `.dev.vars`
|
|
25
29
|
and treat the session as signed-in. No PPLE Today required.
|
|
26
|
-
2. **Inside PPLE Today** —
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
30
|
+
2. **Inside PPLE Today** — init `@pplethai/pple-today-miniapp-sdk`, capture
|
|
31
|
+
the access token from the launch URL, attach it on every `api()` call.
|
|
32
|
+
3. **Plain browser (production)** — the same SDK completes the OIDC sign-in
|
|
33
|
+
callback or redirects to PPLE ID; on return it stores the access token
|
|
34
|
+
and the app continues as signed-in.
|
|
31
35
|
|
|
32
36
|
```tsx
|
|
33
|
-
import { isInMiniAppUserAgent } from "@pplethai/components";
|
|
34
37
|
import { PPLEMiniApp } from "@pplethai/pple-today-miniapp-sdk";
|
|
35
38
|
|
|
36
39
|
// Local: import.meta.env.DEV → DEV_AUTH token ("dev-token" by default)
|
|
37
|
-
//
|
|
38
|
-
// Inside Today: sdk.init() → getAccessToken() → { kind: "signed-in" }
|
|
40
|
+
// Production (Today or browser): sdk.init() → getAccessToken() → { kind: "signed-in" }
|
|
39
41
|
```
|
|
40
42
|
|
|
41
43
|
OAuth client values (`VITE_OAUTH_URL`, `VITE_OAUTH_CLIENT_ID`,
|
|
42
44
|
`VITE_OAUTH_REDIRECT_URI`) are provisioned per app and injected at build
|
|
43
|
-
time — Builders do not register clients by hand.
|
|
45
|
+
time — Builders do not register clients by hand. The redirect URI defaults
|
|
46
|
+
to the app's own origin, which matches the mini-app URL the platform
|
|
47
|
+
registers as an allowed redirect on that OAuth client
|
|
48
|
+
(`docs/integration/today-sso-changes.md` §5).
|
|
44
49
|
|
|
45
50
|
## Worker: validating the token
|
|
46
51
|
|
|
@@ -79,7 +84,8 @@ Rules:
|
|
|
79
84
|
## What not to tell the Builder
|
|
80
85
|
|
|
81
86
|
- Do not claim "tokens only exist inside PPLE Today" as a platform-wide fact
|
|
82
|
-
(Console and CLI also hold Builder tokens
|
|
83
|
-
|
|
84
|
-
|
|
87
|
+
(Console and CLI also hold Builder tokens; end users can also sign in via
|
|
88
|
+
the SDK's browser PPLE ID flow).
|
|
89
|
+
- Do not add a custom OAuth redirect login page, or a NotInApp / "open in
|
|
90
|
+
PPLE Today only" guard — the SDK already handles the browser case.
|
|
85
91
|
- Do not ask for GitHub or Cloudflare credentials to "get a token."
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
# Project scaffold & local development
|
|
2
2
|
|
|
3
3
|
New Builder Apps start from the golden-path template in
|
|
4
|
-
`pple-platform/template`, copied by `pple init
|
|
5
|
-
|
|
6
|
-
`pple-erp` — that layout is the old path.
|
|
4
|
+
`pple-platform/template`, copied by `pple init` (create-and-scaffold, or
|
|
5
|
+
`pple init <slug>` after Console create). Do not scaffold Express/Prisma
|
|
6
|
+
monorepos or copy `pple-erp` — that layout is the old path.
|
|
7
7
|
|
|
8
8
|
## Layout (single package)
|
|
9
9
|
|
|
@@ -21,10 +21,9 @@ my-app/ # folder name matches the App Slug
|
|
|
21
21
|
│ ├── notify.ts # audience-bound notifications
|
|
22
22
|
│ └── env.ts # Env bindings type
|
|
23
23
|
├── web/ # React SPA (Vite)
|
|
24
|
-
│ ├── main.tsx # bootstrap → App |
|
|
24
|
+
│ ├── main.tsx # bootstrap → App | error
|
|
25
25
|
│ ├── pple.ts # SDK / DEV_AUTH / api() helpers
|
|
26
26
|
│ ├── App.tsx
|
|
27
|
-
│ ├── NotInApp.tsx
|
|
28
27
|
│ └── app.css # mobile-first layout only
|
|
29
28
|
├── shared/ # Zod schemas + types used by both sides
|
|
30
29
|
│ └── api.ts
|
|
@@ -52,7 +51,8 @@ there once; both sides import the Zod schema / inferred type.
|
|
|
52
51
|
## Local development
|
|
53
52
|
|
|
54
53
|
```bash
|
|
55
|
-
# After
|
|
54
|
+
# After pple auth login + create/scaffold (pple init / --name, or Console
|
|
55
|
+
# then pple init <slug>):
|
|
56
56
|
cd <slug>
|
|
57
57
|
npm install
|
|
58
58
|
cp .dev.vars.example .dev.vars # enables DEV_AUTH for plain-browser work
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
# Frontend build-time config (Vite exposes VITE_* to the browser bundle).
|
|
2
2
|
# These are the app's PPLE ID OIDC client details, used by the mini-app SDK
|
|
3
|
-
#
|
|
4
|
-
# The platform injects them at deploy time; copy to `.env` to
|
|
3
|
+
# inside PPLE Today and for the browser PPLE ID login. They are public values,
|
|
4
|
+
# safe to ship. The platform injects them at deploy time; copy to `.env` to
|
|
5
|
+
# set locally.
|
|
5
6
|
VITE_OAUTH_URL="https://id.peoplesparty.or.th"
|
|
6
7
|
VITE_OAUTH_CLIENT_ID=""
|
|
7
|
-
# Defaults to the app's own origin if unset
|
|
8
|
+
# Defaults to the app's own origin if unset — must match the redirect URI
|
|
9
|
+
# on the app's provisioned PPLE ID OAuth client (the mini-app URL).
|
|
8
10
|
# VITE_OAUTH_REDIRECT_URI=""
|
|
@@ -5,6 +5,9 @@ inside PPLE Today. The person you're helping is a party workforce member who
|
|
|
5
5
|
may not read stack traces; keep changes simple, explain them plainly, and
|
|
6
6
|
prefer editing the existing demo cards over introducing new patterns.
|
|
7
7
|
|
|
8
|
+
Ops (create / deploy / status): Console, `pple info`, and the Skill
|
|
9
|
+
deployment reference — not a long ops section here.
|
|
10
|
+
|
|
8
11
|
## Architecture (read once)
|
|
9
12
|
|
|
10
13
|
One Cloudflare Worker serves everything:
|
|
@@ -27,8 +30,9 @@ which attach the PPLE ID bearer token. Same origin, so paths are relative
|
|
|
27
30
|
- Inside PPLE Today, the mini-app SDK (`web/pple.ts`) captures the access
|
|
28
31
|
token from the launch URL. The Worker validates it against PPLE ID
|
|
29
32
|
(`src/auth.ts`).
|
|
30
|
-
- In a plain browser, the
|
|
31
|
-
|
|
33
|
+
- In a plain browser, the same SDK runs the PPLE ID OIDC login (redirect /
|
|
34
|
+
callback) and returns an access token — do not add a custom sign-in page
|
|
35
|
+
or resurrect a NotInApp guard.
|
|
32
36
|
- Locally, `DEV_AUTH` in `.dev.vars` bypasses PPLE Today. **Never** set
|
|
33
37
|
`DEV_AUTH` in production; the platform never provisions it.
|
|
34
38
|
|
|
@@ -14,7 +14,7 @@ what you want to your AI coding agent and edit the demo cards. See
|
|
|
14
14
|
| Capability | Where | Demo |
|
|
15
15
|
| ---------- | ----- | ---- |
|
|
16
16
|
| **API** | Hono Worker, `src/index.ts` | routes under `/api/*` |
|
|
17
|
-
| **Identity** | `src/auth.ts` + `web/pple.ts` | validates PPLE ID tokens;
|
|
17
|
+
| **Identity** | `src/auth.ts` + `web/pple.ts` | validates PPLE ID tokens; browser PPLE ID login; dev bypass |
|
|
18
18
|
| **Database** | D1, `src/db.ts` + `migrations/` | the Notes card |
|
|
19
19
|
| **Files** | R2, `src/files.ts` | the Files card |
|
|
20
20
|
| **Notifications** | `src/notify.ts` | the Notify card (audience-bound; optional `linkPath`) |
|
|
@@ -33,9 +33,9 @@ npm run dev # http://localhost:5173
|
|
|
33
33
|
```
|
|
34
34
|
|
|
35
35
|
In local dev you're signed in automatically as a fake user (the `DEV_AUTH`
|
|
36
|
-
bypass), so you can build without PPLE Today. Opened
|
|
37
|
-
|
|
38
|
-
|
|
36
|
+
bypass), so you can build without PPLE Today. Opened inside PPLE Today, the
|
|
37
|
+
mini-app SDK signs the real user in from the launch URL; opened in a plain
|
|
38
|
+
browser after deploy, the same SDK runs the PPLE ID login.
|
|
39
39
|
|
|
40
40
|
## Everyday commands
|
|
41
41
|
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
"": {
|
|
7
7
|
"name": "pple-app",
|
|
8
8
|
"dependencies": {
|
|
9
|
-
"@
|
|
9
|
+
"@fontsource/ibm-plex-sans-thai-looped": "^5.3.0",
|
|
10
|
+
"@pplethai/components": "^1.4.3",
|
|
10
11
|
"@pplethai/pple-today-miniapp-sdk": "^0.1.4",
|
|
11
12
|
"hono": "^4.12.30",
|
|
12
13
|
"react": "^19.2.0",
|
|
@@ -730,9 +731,9 @@
|
|
|
730
731
|
"license": "MIT"
|
|
731
732
|
},
|
|
732
733
|
"node_modules/@fontsource/ibm-plex-sans-thai-looped": {
|
|
733
|
-
"version": "5.
|
|
734
|
-
"resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-sans-thai-looped/-/ibm-plex-sans-thai-looped-5.
|
|
735
|
-
"integrity": "sha512-
|
|
734
|
+
"version": "5.3.0",
|
|
735
|
+
"resolved": "https://registry.npmjs.org/@fontsource/ibm-plex-sans-thai-looped/-/ibm-plex-sans-thai-looped-5.3.0.tgz",
|
|
736
|
+
"integrity": "sha512-5NNySQuBNRMk44sUwQypp971+2Fn1LJ9HAEZg9J76+7803j/aCS9HHH7FNvpq6mJDqzTrTrv+VSNsODgCAhxQQ==",
|
|
736
737
|
"license": "OFL-1.1",
|
|
737
738
|
"funding": {
|
|
738
739
|
"url": "https://github.com/sponsors/ayuhito"
|
|
@@ -1324,9 +1325,9 @@
|
|
|
1324
1325
|
"license": "MIT"
|
|
1325
1326
|
},
|
|
1326
1327
|
"node_modules/@pplethai/components": {
|
|
1327
|
-
"version": "1.4.
|
|
1328
|
-
"resolved": "https://registry.npmjs.org/@pplethai/components/-/components-1.4.
|
|
1329
|
-
"integrity": "sha512-
|
|
1328
|
+
"version": "1.4.3",
|
|
1329
|
+
"resolved": "https://registry.npmjs.org/@pplethai/components/-/components-1.4.3.tgz",
|
|
1330
|
+
"integrity": "sha512-YB8y5WsGduRI0pRos6vrjqpVWYvpxK9gDPXPSAhSC24hM7Xofk1A4o1poZ7g8DFVfG91FWGx8LXtAZ8okHhqsw==",
|
|
1330
1331
|
"license": "MIT",
|
|
1331
1332
|
"dependencies": {
|
|
1332
1333
|
"@fontsource/ibm-plex-sans-thai-looped": "^5.1.0",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"deploy": "vite build && wrangler deploy"
|
|
15
15
|
},
|
|
16
16
|
"dependencies": {
|
|
17
|
-
"@
|
|
17
|
+
"@fontsource/ibm-plex-sans-thai-looped": "^5.3.0",
|
|
18
|
+
"@pplethai/components": "^1.4.3",
|
|
18
19
|
"@pplethai/pple-today-miniapp-sdk": "^0.1.4",
|
|
19
20
|
"hono": "^4.12.30",
|
|
20
21
|
"react": "^19.2.0",
|
|
@@ -67,12 +67,18 @@ function NotesCard() {
|
|
|
67
67
|
const load = () =>
|
|
68
68
|
apiJson<{ notes: Note[] }>("/api/notes")
|
|
69
69
|
.then((r) => setNotes(r.notes))
|
|
70
|
-
.catch((e) =>
|
|
70
|
+
.catch((e) => {
|
|
71
|
+
showToast({ title: "Could not load notes", description: message(e), variant: "error" });
|
|
72
|
+
// Land on the empty state. Leaving this null would spin forever.
|
|
73
|
+
setNotes([]);
|
|
74
|
+
});
|
|
71
75
|
|
|
72
76
|
useEffect(() => void load(), []);
|
|
73
77
|
|
|
74
78
|
async function add() {
|
|
75
|
-
|
|
79
|
+
// `saving` guards the Enter key too — the button is disabled while a save
|
|
80
|
+
// is in flight, but a keypress doesn't go through the button.
|
|
81
|
+
if (saving || !body.trim()) return;
|
|
76
82
|
setSaving(true);
|
|
77
83
|
try {
|
|
78
84
|
await apiJson<Note>("/api/notes", {
|
|
@@ -137,7 +143,11 @@ function FilesCard() {
|
|
|
137
143
|
const load = () =>
|
|
138
144
|
apiJson<{ files: UploadedFile[] }>("/api/files")
|
|
139
145
|
.then((r) => setFiles(r.files))
|
|
140
|
-
.catch((e) =>
|
|
146
|
+
.catch((e) => {
|
|
147
|
+
showToast({ title: "Could not load files", description: message(e), variant: "error" });
|
|
148
|
+
// Land on the empty state. Leaving this null would spin forever.
|
|
149
|
+
setFiles([]);
|
|
150
|
+
});
|
|
141
151
|
|
|
142
152
|
useEffect(() => void load(), []);
|
|
143
153
|
|
|
@@ -168,7 +178,15 @@ function FilesCard() {
|
|
|
168
178
|
<CardContent>
|
|
169
179
|
<div className="stack">
|
|
170
180
|
<div className="row">
|
|
171
|
-
|
|
181
|
+
{/* Disabled while uploading, so picking a second file can't start
|
|
182
|
+
a concurrent upload. */}
|
|
183
|
+
<input
|
|
184
|
+
ref={inputRef}
|
|
185
|
+
type="file"
|
|
186
|
+
className="file-input"
|
|
187
|
+
onChange={upload}
|
|
188
|
+
disabled={uploading}
|
|
189
|
+
/>
|
|
172
190
|
{uploading && <Spinner />}
|
|
173
191
|
</div>
|
|
174
192
|
{files === null ? (
|
|
@@ -90,23 +90,3 @@ body {
|
|
|
90
90
|
flex: 1;
|
|
91
91
|
min-width: 0;
|
|
92
92
|
}
|
|
93
|
-
|
|
94
|
-
.notinapp {
|
|
95
|
-
text-align: center;
|
|
96
|
-
display: flex;
|
|
97
|
-
flex-direction: column;
|
|
98
|
-
gap: 12px;
|
|
99
|
-
align-items: center;
|
|
100
|
-
padding: 16px;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
.notinapp__logo {
|
|
104
|
-
height: 48px;
|
|
105
|
-
width: auto;
|
|
106
|
-
color: #ff6a13;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.notinapp__title {
|
|
110
|
-
margin: 0;
|
|
111
|
-
font-size: 20px;
|
|
112
|
-
}
|
|
@@ -1,12 +1,25 @@
|
|
|
1
1
|
// App entry point. Import the design-system styles first, then our own.
|
|
2
2
|
import "@pplethai/components/styles.css";
|
|
3
|
+
|
|
4
|
+
// The body font, re-declared from @fontsource so it actually loads:
|
|
5
|
+
// @pplethai/components inlines Fontsource's @font-face rules but ships no
|
|
6
|
+
// dist/files/*.woff2, so every `url(files/…)` 404s and the body font silently
|
|
7
|
+
// falls back to system-ui. Anakotmai, the heading face, loads from an absolute
|
|
8
|
+
// URL and is unaffected. Importing the same weights gives Vite real files to
|
|
9
|
+
// bundle; identical @font-face rules declared later win the cascade. These are
|
|
10
|
+
// the all-subset entrypoints, not `thai-400.css` — only those carry
|
|
11
|
+
// `unicode-range`, without which one rule would win for every character and
|
|
12
|
+
// Thai text would match the Latin file, which has no Thai glyphs.
|
|
13
|
+
// Remove once the upstream package ships its font files: PPLEThai/pple-design-system#16.
|
|
14
|
+
import "@fontsource/ibm-plex-sans-thai-looped/400.css";
|
|
15
|
+
import "@fontsource/ibm-plex-sans-thai-looped/600.css";
|
|
16
|
+
|
|
3
17
|
import "./app.css";
|
|
4
18
|
|
|
5
19
|
import { Alert, AlertDescription, AlertTitle, Toaster } from "@pplethai/components";
|
|
6
20
|
import { StrictMode } from "react";
|
|
7
21
|
import { createRoot } from "react-dom/client";
|
|
8
22
|
import { App } from "./App";
|
|
9
|
-
import { NotInApp } from "./NotInApp";
|
|
10
23
|
import { bootstrap } from "./pple";
|
|
11
24
|
|
|
12
25
|
function ErrorScreen({ message }: { message: string }) {
|
|
@@ -22,16 +35,14 @@ function ErrorScreen({ message }: { message: string }) {
|
|
|
22
35
|
|
|
23
36
|
const root = createRoot(document.getElementById("root")!);
|
|
24
37
|
|
|
25
|
-
// Establish the session (PPLE Today token,
|
|
26
|
-
// rendering, so the first paint is the right screen.
|
|
38
|
+
// Establish the session (PPLE Today token, browser PPLE ID login, or local
|
|
39
|
+
// DEV_AUTH) before rendering, so the first paint is the right screen.
|
|
27
40
|
bootstrap().then((result) => {
|
|
28
41
|
root.render(
|
|
29
42
|
<StrictMode>
|
|
30
43
|
<Toaster />
|
|
31
44
|
{result.kind === "signed-in" ? (
|
|
32
45
|
<App />
|
|
33
|
-
) : result.kind === "not-in-app" ? (
|
|
34
|
-
<NotInApp />
|
|
35
46
|
) : (
|
|
36
47
|
<ErrorScreen message={result.message} />
|
|
37
48
|
)}
|