@pplethai/cli 0.4.0 → 0.4.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.
@@ -1,238 +0,0 @@
1
- import { CliError } from "../errors.js";
2
- import { findLink } from "../link.js";
3
- import { client } from "../session.js";
4
- /** Same cap the Console / Provisioner enforce for Beta testers. */
5
- const BETA_INVITE_CAP = 20;
6
- /**
7
- * Thai combining vowels and tone marks render with zero width, so `padEnd`
8
- * over-pads any label containing them. Measure without them to keep the
9
- * two-column layout below actually aligned in a terminal.
10
- */
11
- const THAI_COMBINING = /[\u0E31\u0E34-\u0E3A\u0E47-\u0E4E]/g;
12
- export function pad(text, width) {
13
- const visible = text.replace(THAI_COMBINING, "").length;
14
- return text + " ".repeat(Math.max(0, width - visible));
15
- }
16
- /** API values are English; these are what a Builder reads. Mirrors the Console. */
17
- const STATUS_LABEL = {
18
- ready: "พร้อมใช้งาน",
19
- provisioning: "กำลังจัดเตรียม",
20
- failed: "ล้มเหลว",
21
- retired: "ปลดระวางแล้ว",
22
- };
23
- const TIER_LABEL = {
24
- draft: "ฉบับร่าง",
25
- beta: "ทดลองใช้",
26
- live: "เผยแพร่จริง",
27
- };
28
- const ROLE_LABEL = {
29
- owner: "เจ้าของ",
30
- collaborator: "ผู้ร่วมพัฒนา",
31
- };
32
- const DEPLOY_STATUS_LABEL = {
33
- live: "ใช้งานอยู่",
34
- building: "กำลังบิลด์",
35
- unverified: "ยังไม่ยืนยัน",
36
- failed: "ล้มเหลว",
37
- };
38
- const REQUEST_STATUS_LABEL = {
39
- pending: "รอดำเนินการ",
40
- approved: "อนุมัติแล้ว",
41
- denied: "ปฏิเสธแล้ว",
42
- };
43
- /** An unknown value from the platform is shown as-is rather than hidden. */
44
- function label(map, value) {
45
- return map[value] ?? value;
46
- }
47
- /**
48
- * Read-only mirror of the Console dashboard for one app: overview, usage vs
49
- * Resource Limits, recent deploys, env keys, and — for Owners — publication,
50
- * collaborators, and request history. No mutations; point Builders at Console
51
- * for those.
52
- */
53
- export async function info(deps, slugArg) {
54
- const slug = await resolveSlug(deps, slugArg);
55
- const api = await client(deps);
56
- const app = await api.app(slug);
57
- const isOwner = app.role !== "collaborator";
58
- const [metrics, deploys, envEntries, ownerSurfaces] = await Promise.all([
59
- api.metrics(slug),
60
- api.deploys(slug),
61
- api.env(slug),
62
- isOwner ? loadOwnerSurfaces(api, slug) : Promise.resolve(null),
63
- ]);
64
- printOverview(deps, app);
65
- printUsage(deps, metrics);
66
- printDeploys(deps, deploys);
67
- printEnv(deps, envEntries);
68
- if (ownerSurfaces) {
69
- printPublication(deps, app, ownerSurfaces.requests, ownerSurfaces.invites);
70
- printCollaborators(deps, ownerSurfaces.collaborators);
71
- printRequests(deps, ownerSurfaces.requests);
72
- }
73
- deps.out.say("");
74
- deps.out.say("หากต้องการเปลี่ยนโควตา ค่า env ผู้ร่วมพัฒนา หรือการเผยแพร่ ใช้ https://console.ppleth.ai");
75
- }
76
- async function resolveSlug(deps, slugArg) {
77
- if (slugArg)
78
- return slugArg;
79
- try {
80
- return (await findLink(deps)).slug;
81
- }
82
- catch (e) {
83
- // Only rewrite the "no App Link" case; damaged links keep findLink's hint.
84
- if (e instanceof CliError &&
85
- e.message === "โฟลเดอร์นี้ไม่ใช่แอป PPLE") {
86
- throw new CliError("แอปไหน? `pple info` ต้องมี App Link หรือ slug", "เข้าไปในโฟลเดอร์ของแอป ใส่ slug (`pple info <slug>`) หรือเปิด https://console.ppleth.ai");
87
- }
88
- throw e;
89
- }
90
- }
91
- async function loadOwnerSurfaces(api, slug) {
92
- const [collaborators, requests, invites] = await Promise.all([
93
- api.collaborators(slug),
94
- api.requests(slug),
95
- api.invites(slug),
96
- ]);
97
- return { collaborators, requests, invites };
98
- }
99
- function printOverview(deps, app) {
100
- deps.out.say(app.name);
101
- deps.out.say(` ${pad("slug", 18)}${app.slug}`);
102
- deps.out.say(` ${pad("URL", 18)}${app.url}`);
103
- if (app.customAddress) {
104
- deps.out.say(` ${pad("ที่อยู่ที่กำหนดเอง", 18)}${app.customAddress}`);
105
- }
106
- deps.out.say(` ${pad("สถานะ", 18)}${label(STATUS_LABEL, app.status)}`);
107
- deps.out.say(` ${pad("การเผยแพร่", 18)}${label(TIER_LABEL, app.tier ?? "draft")}`);
108
- if (app.role) {
109
- deps.out.say(` ${pad("บทบาทของคุณ", 18)}${label(ROLE_LABEL, app.role)}`);
110
- }
111
- if (app.deploy) {
112
- deps.out.say(` ${pad("การดีพลอยปัจจุบัน", 18)}${label(DEPLOY_STATUS_LABEL, app.deploy.status)}` +
113
- (app.deploy.message ? ` — ${app.deploy.message}` : ""));
114
- }
115
- if (app.error) {
116
- deps.out.say(` ${pad("ข้อผิดพลาด", 18)}${app.error}`);
117
- }
118
- }
119
- function printUsage(deps, metrics) {
120
- deps.out.say("");
121
- deps.out.say("การใช้งานและโควตาทรัพยากร");
122
- deps.out.say(` ${pad("CPU ต่อ request", 20)}${metrics.limits.cpuMs} ms`);
123
- deps.out.say(` ${pad("การแจ้งเตือน", 20)}${formatCount(metrics.usage.notificationsSent)}` +
124
- (metrics.usage.notificationsSent === null
125
- ? " (ดูข้อมูลไม่ได้ในขณะนี้)"
126
- : ` จากโควตา ${formatCount(metrics.limits.notificationsPerDay)} ครั้งวันนี้`));
127
- deps.out.say(` ${pad("ข้อผิดพลาด (24 ชม.)", 20)}${formatCount(metrics.usage.errors)}` +
128
- (metrics.usage.errors === null
129
- ? " (ดูข้อมูลไม่ได้ในขณะนี้)"
130
- : metrics.usage.errors === 0
131
- ? " (ไม่มีใน 24 ชั่วโมงที่ผ่านมา)"
132
- : metrics.usage.requests === null
133
- ? " (คำขอที่ล้มเหลวใน 24 ชั่วโมงที่ผ่านมา)"
134
- : ` จากทั้งหมด ${formatCount(metrics.usage.requests)} คำขอ`));
135
- deps.out.say(` ${pad("ผู้ใช้แอป", 20)}${formatCount(metrics.usage.appUsers)}` +
136
- (metrics.usage.appUsers === null ? " (ดูข้อมูลไม่ได้ในขณะนี้)" : ""));
137
- }
138
- function formatCount(value) {
139
- // Same contract as Console: null means unavailable, never render as "0".
140
- return value === null ? "—" : String(value);
141
- }
142
- function printDeploys(deps, deploys) {
143
- deps.out.say("");
144
- deps.out.say("การดีพลอยล่าสุด");
145
- if (deploys.length === 0) {
146
- deps.out.say(" ยังไม่มี");
147
- return;
148
- }
149
- for (const deploy of deploys.slice(0, 10)) {
150
- const short = deploy.commit.slice(0, 7);
151
- const note = deploy.message ? ` — ${deploy.message}` : "";
152
- const status = label(DEPLOY_STATUS_LABEL, deploy.status);
153
- deps.out.say(` ${pad(status, 13)} ${short}${note}`);
154
- }
155
- }
156
- function printEnv(deps, entries) {
157
- deps.out.say("");
158
- deps.out.say("ค่า env");
159
- if (entries.length === 0) {
160
- deps.out.say(" ยังไม่ได้ตั้งค่าใด ๆ");
161
- return;
162
- }
163
- for (const entry of entries) {
164
- // Secrets: key present only, never values — same contract as Console.
165
- if (entry.secret) {
166
- deps.out.say(` ${entry.key}=(secret)`);
167
- }
168
- else {
169
- deps.out.say(` ${entry.key}=${entry.value ?? ""}`);
170
- }
171
- }
172
- }
173
- function printPublication(deps, app, requests, invites) {
174
- deps.out.say("");
175
- deps.out.say("การเผยแพร่");
176
- deps.out.say(` ${pad("ระดับ", 18)}${label(TIER_LABEL, app.tier ?? "draft")}`);
177
- if (app.customAddress) {
178
- deps.out.say(` ${pad("ที่อยู่ที่กำหนดเอง", 18)}${app.customAddress}`);
179
- }
180
- const pendingPublish = requests.find((r) => r.type === "publish" && r.status === "pending");
181
- if (pendingPublish) {
182
- deps.out.say(` ${pad("คำขอที่รออยู่", 18)}${requestSummary(pendingPublish)}`);
183
- }
184
- if (app.tier === "beta") {
185
- // Held = any invite that isn't declined — same slot count as Console.
186
- const held = invites.filter((i) => i.status !== "DECLINED").length;
187
- deps.out.say(` ${pad("คำเชิญทดลองใช้", 18)}ใช้ไป ${held} จาก ${BETA_INVITE_CAP} ที่นั่ง`);
188
- }
189
- }
190
- function printCollaborators(deps, collaborators) {
191
- deps.out.say("");
192
- deps.out.say("ผู้ร่วมพัฒนา");
193
- if (collaborators.length === 0) {
194
- deps.out.say(" ยังไม่มี");
195
- return;
196
- }
197
- for (const c of collaborators) {
198
- const who = c.maskedName ?? "ผู้ร่วมพัฒนา";
199
- const phone = c.phoneNumber ? ` (${c.phoneNumber})` : "";
200
- deps.out.say(` ${who}${phone}`);
201
- }
202
- }
203
- function printRequests(deps, requests) {
204
- deps.out.say("");
205
- deps.out.say("ประวัติคำขอ");
206
- if (requests.length === 0) {
207
- deps.out.say(" ยังไม่มี");
208
- return;
209
- }
210
- for (const r of requests.slice(0, 10)) {
211
- const reason = r.reason ? ` — ${r.reason}` : "";
212
- const status = label(REQUEST_STATUS_LABEL, r.status);
213
- deps.out.say(` ${pad(status, 13)} ${requestSummary(r)}${reason}`);
214
- }
215
- }
216
- /** One-line summary of what a typed Request asked for — mirrors Console. */
217
- function requestSummary(r) {
218
- if (r.type === "publish") {
219
- const roles = Array.isArray(r.payload.roles)
220
- ? r.payload.roles
221
- : [];
222
- const address = typeof r.payload.customAddress === "string"
223
- ? ` · ที่ ${r.payload.customAddress}`
224
- : "";
225
- const who = roles.length === 0 ? "ทุกคน" : roles.join(", ");
226
- return `คำขอเผยแพร่ · ขึ้นเผยแพร่จริง · ${who}${address}`;
227
- }
228
- if (r.type === "retire")
229
- return "คำขอปลดระวาง · ปลดระวางแอป";
230
- const parts = [];
231
- if (typeof r.payload.cpuMs === "number") {
232
- parts.push(`CPU ${r.payload.cpuMs} ms/request`);
233
- }
234
- if (typeof r.payload.notificationsPerDay === "number") {
235
- parts.push(`การแจ้งเตือน ${r.payload.notificationsPerDay} ครั้ง/วัน`);
236
- }
237
- return `คำขอเพิ่มโควตา · ขอเพิ่ม ${parts.length > 0 ? parts.join(" และ ") : "โควตา"}`;
238
- }
@@ -1,159 +0,0 @@
1
- import fs from "node:fs/promises";
2
- import path from "node:path";
3
- import { CliError } from "../errors.js";
4
- import { cloneApp, hasLocalChanges, recordChanges } from "../git.js";
5
- import { peekLink, writeLink } from "../link.js";
6
- import { scaffold } from "../scaffold.js";
7
- import { client } from "../session.js";
8
- import { refreshSkills } from "../skills.js";
9
- /** How often to ask whether a newly created app is ready to scaffold. */
10
- const PROVISION_POLL_MS = 3000;
11
- /** How long create may wait before telling the Builder to retry by slug. */
12
- const PROVISION_TIMEOUT_MS = 5 * 60_000;
13
- /**
14
- * Bring a Builder App onto this computer: either create one (App Name via
15
- * prompt or `--name`) or scaffold an app that already exists (`<slug>`), with
16
- * the golden-path template in place and linked, ready for the Builder's AI
17
- * agent to work in. The Provisioner still creates the infra (ADR-0001).
18
- */
19
- export async function init(deps, options) {
20
- const api = await client(deps);
21
- const app = options.slug
22
- ? await api.app(options.slug)
23
- : await createAndWait(deps, api, await resolveName(deps, options.name));
24
- if (!app.repo) {
25
- throw new CliError(`"${app.name}" ยังตั้งค่าไม่เสร็จ`, `รอสักครู่แล้วรัน \`pple init ${app.slug}\` หากยังเป็นแบบนี้ กรุณาติดต่อทีมแพลตฟอร์ม`);
26
- }
27
- if (app.status === "provisioning") {
28
- deps.out.say("แอปของคุณยังอยู่ระหว่างการตั้งค่าบนแพลตฟอร์ม…");
29
- }
30
- const targetDir = path.resolve(deps.cwd, options.dir ?? app.slug);
31
- await ensureAvailable(targetDir);
32
- // The Builder's machine holds no credential of its own; the platform hands
33
- // out one that opens this app's code and nothing else (ADR-0005).
34
- const access = await api.repoToken(app.slug);
35
- deps.out.say(`กำลังตั้งค่า "${app.name}" ใน ${path.basename(targetDir)}/…`);
36
- await cloneApp(deps, repoUrl(deps, app.repo), targetDir, access);
37
- if (await isAlreadyBuilt(targetDir)) {
38
- throw new CliError(`"${app.name}" มีโค้ดอยู่แล้ว จึงไม่มีอะไรต้องตั้งค่า`, `ทำงานในโฟลเดอร์นั้นแล้วรัน \`pple deploy\` เพื่อนำการเปลี่ยนแปลงขึ้นใช้งานจริง`);
39
- }
40
- await scaffold(deps.templateDir, targetDir, app.slug);
41
- await writeLink(targetDir, {
42
- slug: app.slug,
43
- apiBase: deps.config.apiBase,
44
- url: app.url,
45
- });
46
- // Skills are a local overlay (ADR-0006), not app source — refresh before the
47
- // first commit so the ignore rule is in place and Skill files stay untracked.
48
- await refreshSkills(deps, targetDir);
49
- if (await hasLocalChanges(deps, targetDir)) {
50
- await recordChanges(deps, targetDir, "เริ่มจากเทมเพลตมาตรฐานของ PPLE", access);
51
- }
52
- // Install dependencies and bring the local database up before handing over,
53
- // so the Builder (or their agent) can `npm run dev` straight away. Done after
54
- // the scaffold commit so node_modules/.wrangler stay out of it.
55
- const setupOk = await setupWorkspace(deps, targetDir);
56
- report(deps, app, targetDir, setupOk);
57
- }
58
- /**
59
- * Install packages and apply local migrations. Non-fatal by design: the app
60
- * already exists and is committed, so a flaky network or wrong Node version
61
- * must not strand the Builder — we warn, and `report` prints the two commands
62
- * to run by hand.
63
- */
64
- async function setupWorkspace(deps, targetDir) {
65
- deps.out.say("กำลังติดตั้งแพ็กเกจและเตรียมฐานข้อมูลบนเครื่อง…");
66
- const steps = [
67
- ["npm", ["install"]],
68
- ["npm", ["run", "db:migrate:local"]],
69
- ];
70
- for (const [command, args] of steps) {
71
- const result = await deps
72
- .run(command, args, { cwd: targetDir })
73
- .catch(() => null);
74
- if (!result || result.code !== 0)
75
- return false;
76
- }
77
- return true;
78
- }
79
- async function resolveName(deps, name) {
80
- if (await peekLink(deps)) {
81
- throw new CliError("โฟลเดอร์นี้เชื่อมกับแอป PPLE อยู่แล้ว", "รัน `pple info` หรือ `pple deploy` ที่นี่ หรือย้ายไปโฟลเดอร์อื่นเพื่อสร้างแอปใหม่");
82
- }
83
- if (name !== undefined) {
84
- const trimmed = name.trim();
85
- if (!trimmed) {
86
- throw new CliError("`--name` ต้องระบุชื่อแอป", 'ตัวอย่าง: `pple init --name "ลงทะเบียนอาสาสมัคร"`');
87
- }
88
- return trimmed;
89
- }
90
- if (!deps.interactive) {
91
- throw new CliError("`pple init` ต้องระบุชื่อแอป", 'ใส่ `--name "ชื่อแอปของคุณ"` หรือรันในเทอร์มินัลเพื่อให้ระบบถาม');
92
- }
93
- const answered = (await deps.ask("ชื่อแอป:")).trim();
94
- if (!answered) {
95
- throw new CliError("ต้องระบุชื่อแอปเพื่อสร้างแอป", 'ลองใหม่อีกครั้ง หรือใส่ `--name "ชื่อแอปของคุณ"`');
96
- }
97
- return answered;
98
- }
99
- async function createAndWait(deps, api, name) {
100
- deps.out.say(`กำลังสร้าง "${name}" บนแพลตฟอร์ม…`);
101
- let app = await api.createApp(name);
102
- if (readyToScaffold(app))
103
- return app;
104
- deps.out.say("กำลังรอแพลตฟอร์มตั้งค่าให้เสร็จ…");
105
- const started = deps.now();
106
- for (;;) {
107
- if (app.status === "failed") {
108
- throw new CliError(`ตั้งค่า "${app.name}" บนแพลตฟอร์มไม่สำเร็จ`, app.error
109
- ? `${app.error} slug ของแอปคือ ${app.slug} — กรุณาติดต่อทีมแพลตฟอร์ม หรือลอง \`pple init ${app.slug}\` ภายหลัง`
110
- : `slug ของแอปคือ ${app.slug} — กรุณาติดต่อทีมแพลตฟอร์ม หรือลอง \`pple init ${app.slug}\` ภายหลัง`);
111
- }
112
- if (deps.now() - started >= PROVISION_TIMEOUT_MS) {
113
- throw new CliError(`"${app.name}" ยังอยู่ระหว่างการตั้งค่าบนแพลตฟอร์ม`, `ลองใหม่ด้วย \`pple init ${app.slug}\` ในอีกสักครู่`);
114
- }
115
- await deps.sleep(PROVISION_POLL_MS);
116
- app = await api.app(app.slug);
117
- if (readyToScaffold(app))
118
- return app;
119
- }
120
- }
121
- /** Ready with a repo — safe to clone and scaffold. */
122
- function readyToScaffold(app) {
123
- return app.status === "ready" && app.repo !== null;
124
- }
125
- /** A repo the Builder may not have cloned before; https works with any helper. */
126
- function repoUrl(deps, repoFullName) {
127
- return `${deps.config.repoBase}/${repoFullName}.git`;
128
- }
129
- async function ensureAvailable(targetDir) {
130
- const entries = await fs.readdir(targetDir).catch(() => null);
131
- if (entries === null)
132
- return;
133
- if (entries.length > 0) {
134
- throw new CliError(`โฟลเดอร์ ${path.basename(targetDir)}/ มีไฟล์อยู่แล้ว`, "รัน `pple init` ที่อื่น หรือใส่ `--dir <folder>` เพื่อเลือกโฟลเดอร์อื่น");
135
- }
136
- }
137
- /** A repo that already carries an app is one somebody has already set up. */
138
- async function isAlreadyBuilt(targetDir) {
139
- return fs
140
- .access(path.join(targetDir, "package.json"))
141
- .then(() => true)
142
- .catch(() => false);
143
- }
144
- function report(deps, app, targetDir, setupOk) {
145
- const folder = path.basename(targetDir);
146
- deps.out.say();
147
- deps.out.say(`"${app.name}" พร้อมให้ทำงานแล้วใน ${folder}/`);
148
- deps.out.say(`แอปจะใช้งานได้ที่ ${app.url}`);
149
- deps.out.say();
150
- deps.out.say("ขั้นต่อไป:");
151
- deps.out.say(` cd ${folder}`);
152
- if (!setupOk) {
153
- // Auto-setup didn't finish — the Builder runs these once by hand.
154
- deps.out.say(" npm install");
155
- deps.out.say(" npm run db:migrate:local");
156
- }
157
- deps.out.say(" npm run dev # ดูผลบนเครื่องนี้");
158
- deps.out.say(" pple deploy # นำขึ้นใช้งานจริง");
159
- }
@@ -1,29 +0,0 @@
1
- // `pple upgrade` — update the CLI itself, which is also how Skills and the
2
- // golden-path template move forward (ADR-0006). It replaces only an install
3
- // npm put there, never with sudo: a global folder the Builder can't write to
4
- // is theirs to fix, not the CLI's to force.
5
- import { CliError } from "../errors.js";
6
- import { installGlobally, isNewer, isNpmGlobalInstall, latestVersion, MANUAL_INSTALL, } from "../npm.js";
7
- import { announceUpdate, manualHint, sayOtherChannel } from "../update.js";
8
- export async function upgrade(deps) {
9
- const latest = await latestVersion(deps);
10
- if (latest === null) {
11
- throw new CliError("เชื่อมต่อ npm เพื่อตรวจหาเวอร์ชันใหม่ของ pple ไม่ได้", `ตรวจสอบการเชื่อมต่ออินเทอร์เน็ต หรืออัปเกรดโดยตรง:\n\n ${MANUAL_INSTALL}`);
12
- }
13
- if (!isNewer(latest, deps.version)) {
14
- deps.out.say(`คุณใช้ pple เวอร์ชันล่าสุดอยู่แล้ว (${deps.version})`);
15
- return;
16
- }
17
- if (!(await isNpmGlobalInstall(deps))) {
18
- announceUpdate(deps, latest);
19
- deps.out.say();
20
- sayOtherChannel(deps);
21
- return;
22
- }
23
- deps.out.say(`กำลังอัปเดต pple ${deps.version} → ${latest}…`);
24
- const result = await installGlobally(deps, latest);
25
- if (result.code !== 0) {
26
- throw new CliError(`อัปเดต pple เป็น ${latest} ไม่สำเร็จ`, manualHint(result));
27
- }
28
- deps.out.say(`pple เป็นเวอร์ชัน ${latest} แล้ว`);
29
- }
@@ -1,53 +0,0 @@
1
- // Where the Builder's PPLE ID tokens live between commands.
2
- //
3
- // One file, owner-readable only, outside any app repo — a token that lands in
4
- // a working copy is a token that gets pushed to a party-owned repo.
5
- import fs from "node:fs/promises";
6
- import path from "node:path";
7
- const FILE = "credentials.json";
8
- const OWNER_ONLY_FILE = 0o600;
9
- const OWNER_ONLY_DIR = 0o700;
10
- function credentialsPath(deps) {
11
- return path.join(deps.configDir, FILE);
12
- }
13
- export async function loadCredentials(deps) {
14
- const raw = await fs.readFile(credentialsPath(deps), "utf8").catch(() => null);
15
- if (raw === null)
16
- return null;
17
- let parsed;
18
- try {
19
- parsed = JSON.parse(raw);
20
- }
21
- catch {
22
- return null;
23
- }
24
- if (typeof parsed.accessToken !== "string")
25
- return null;
26
- // A file written against another PPLE ID is as good as no file: it would
27
- // only produce confusing 401s against the current one.
28
- if (parsed.ssoBase !== deps.config.ssoBase)
29
- return null;
30
- return {
31
- accessToken: parsed.accessToken,
32
- refreshToken: typeof parsed.refreshToken === "string" ? parsed.refreshToken : null,
33
- expiresAt: typeof parsed.expiresAt === "number" ? parsed.expiresAt : null,
34
- ssoBase: parsed.ssoBase,
35
- };
36
- }
37
- export async function saveCredentials(deps, credentials) {
38
- await fs.mkdir(deps.configDir, { recursive: true, mode: OWNER_ONLY_DIR });
39
- await fs.writeFile(credentialsPath(deps), `${JSON.stringify(credentials, null, 2)}\n`, { mode: OWNER_ONLY_FILE });
40
- // mkdir/writeFile only apply the mode when they create; an existing file
41
- // keeps whatever it had, so tighten it explicitly.
42
- await fs.chmod(credentialsPath(deps), OWNER_ONLY_FILE);
43
- }
44
- /** Returns whether there was anything to forget. */
45
- export async function clearCredentials(deps) {
46
- try {
47
- await fs.unlink(credentialsPath(deps));
48
- return true;
49
- }
50
- catch {
51
- return false;
52
- }
53
- }
package/dist/deps.js DELETED
@@ -1,87 +0,0 @@
1
- import { existsSync, readFileSync } from "node:fs";
2
- import path from "node:path";
3
- import { fileURLToPath } from "node:url";
4
- export const DEFAULT_CONFIG = {
5
- apiBase: "https://api.ppleth.ai",
6
- ssoBase: "https://id.peoplesparty.or.th",
7
- clientId: "382216793067947795",
8
- repoBase: "https://github.com",
9
- registryBase: "https://registry.npmjs.org",
10
- };
11
- export function platformConfig(env) {
12
- return {
13
- apiBase: env.PPLE_API_BASE ?? DEFAULT_CONFIG.apiBase,
14
- ssoBase: env.PPLE_SSO_BASE ?? DEFAULT_CONFIG.ssoBase,
15
- clientId: env.PPLE_CLIENT_ID ?? DEFAULT_CONFIG.clientId,
16
- repoBase: env.PPLE_REPO_BASE ?? DEFAULT_CONFIG.repoBase,
17
- registryBase: env.PPLE_REGISTRY_BASE ?? DEFAULT_CONFIG.registryBase,
18
- };
19
- }
20
- /** XDG-style config home, so credentials never land in the app's repo. */
21
- export function configDir(env, homeDir) {
22
- const base = env.XDG_CONFIG_HOME?.trim();
23
- return path.join(base && base !== "" ? base : path.join(homeDir, ".config"), "pple");
24
- }
25
- /**
26
- * Whether the CLI may check npm for a newer version. `PPLE_NO_UPDATE_CHECK`
27
- * is the scripted-environment escape hatch; a local config file is the other
28
- * half of the opt-out, and lives with the check itself.
29
- */
30
- export function updateChecksAllowed(env) {
31
- const raw = env.PPLE_NO_UPDATE_CHECK?.trim().toLowerCase();
32
- return !raw || raw === "0" || raw === "false";
33
- }
34
- /** The CLI's own package directory — one above `src/` in a checkout, `dist/` once built. */
35
- export function packageRoot() {
36
- return path.dirname(path.dirname(fileURLToPath(import.meta.url)));
37
- }
38
- /**
39
- * The version this CLI was published as, read from its own `package.json`.
40
- * A checkout mid-build can be missing or unreadable — an unknown version
41
- * simply means upgrade treats anything on npm as newer.
42
- */
43
- export function packageVersion() {
44
- try {
45
- const manifest = readFileSync(path.join(packageRoot(), "package.json"), "utf8");
46
- return String(JSON.parse(manifest).version ?? "0.0.0");
47
- }
48
- catch {
49
- return "0.0.0";
50
- }
51
- }
52
- /**
53
- * The template ships inside the published package (`npm run build:template`
54
- * copies it there); running from a checkout, it is the repo's own directory.
55
- */
56
- export function resolveTemplateDir(env) {
57
- const override = env.PPLE_TEMPLATE_DIR?.trim();
58
- if (override)
59
- return override;
60
- const bundled = path.join(packageRoot(), "templates", "golden-path");
61
- if (existsSync(bundled))
62
- return bundled;
63
- // Running from a checkout of the platform repo, before `build:template`.
64
- return path.join(path.dirname(packageRoot()), "template");
65
- }
66
- /**
67
- * Platform Skills ship inside the published package (`npm run build:skills`
68
- * copies them there); running from a checkout, they are the repo's own
69
- * `skills/pple-platform/`.
70
- */
71
- export function resolveSkillsDir(env) {
72
- const override = env.PPLE_SKILLS_DIR?.trim();
73
- if (override)
74
- return override;
75
- const bundled = path.join(packageRoot(), "skills", "pple-platform");
76
- if (existsSync(bundled))
77
- return bundled;
78
- return path.join(path.dirname(packageRoot()), "skills", "pple-platform");
79
- }
80
- /**
81
- * The helper GIT_ASKPASS points at, which feeds git the app's repo token.
82
- * git execs it directly rather than through a shell, so it ships with its
83
- * executable bit set and finds node from its own shebang.
84
- */
85
- export function askpassHelper() {
86
- return path.join(packageRoot(), "askpass", "pple-askpass.mjs");
87
- }