percher 0.5.21 → 0.5.23

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/dist/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "percher",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "CLI for the Percher hosting platform — publish apps with one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,9 +27,9 @@
27
27
  "typecheck": "tsc --noEmit"
28
28
  },
29
29
  "dependencies": {
30
- "@percher/client": "^0.2.20",
31
- "@percher/core": "^0.4.24",
32
- "@percher/toml": "^0.1.9",
30
+ "@percher/client": "^0.2.22",
31
+ "@percher/core": "^0.4.28",
32
+ "@percher/toml": "^0.1.10",
33
33
  "cac": "^7.0.0",
34
34
  "picocolors": "^1.1.1"
35
35
  },
@@ -0,0 +1,4 @@
1
+ import type { Context } from "@percher/core";
2
+ import type { CAC } from "cac";
3
+ export declare function registerAnalyticsCommands(cli: CAC, ctx: Context): void;
4
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../../src/commands/analytics.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B,wBAAgB,yBAAyB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CA+DtE"}
@@ -0,0 +1,66 @@
1
+ import * as core from "@percher/core";
2
+ import pc from "picocolors";
3
+ import { writeLine } from "../cli-utils";
4
+ export function registerAnalyticsCommands(cli, ctx) {
5
+ cli
6
+ .command("analytics [app]", "Show visitor analytics for an app")
7
+ .option("--days <n>", "Window in days (1-90, default 7)")
8
+ .action(async (app, options) => {
9
+ // `Number()` is the only conversion here — the RANGE is the core command's
10
+ // to enforce, so there is exactly one place that decides what a valid
11
+ // window is. `Number("abc")` is NaN, which the schema rejects with the
12
+ // same message as 0 or 365.
13
+ const days = options.days === undefined ? undefined : Number(options.days);
14
+ const a = await core.analytics(ctx, { app, days });
15
+ await writeLine(ctx, pc.bold(`Visitors — last ${a.period}`));
16
+ await writeLine(ctx, "");
17
+ // Daily uniques SUMMED: identity is per-UTC-day, so nothing can dedupe
18
+ // across days. The label says so rather than implying otherwise.
19
+ await writeLine(ctx, ` Unique visitors ${a.totalVisitors.toLocaleString()} ${pc.dim("(daily, summed)")}`);
20
+ await writeLine(ctx, ` Page views ${a.totalPageviews.toLocaleString()}`);
21
+ await writeLine(ctx, ` Requests ${a.totalRequests.toLocaleString()}`);
22
+ await writeLine(ctx, ` Sessions ${a.totalSessions.toLocaleString()}`);
23
+ await writeLine(ctx, ` Bounce rate ${a.totalSessions > 0 ? `${a.bounceRate}%` : "—"}`);
24
+ await writeLine(ctx, ` Error rate ${a.errorRate}%`);
25
+ await writeLine(ctx, pc.dim(` Filtered ${(a.totalBotRequests + a.totalScannerRequests).toLocaleString()} bot/scanner requests`));
26
+ await printList(ctx, "Top pages", a.topPages.map((p) => [p.path, p.views]));
27
+ await printList(ctx, "Traffic sources", a.topReferrers.map((r) => [r.source, r.visits]));
28
+ await printList(ctx, "Entry pages", a.topEntryPaths.map((p) => [p.path, p.sessions]));
29
+ await printList(ctx, "Countries", a.byCountry.map((c) => [c.country, c.visitors]));
30
+ await printList(ctx, "Devices", a.byDevice.map((d) => [d.key, d.visitors]));
31
+ if (a.totalRequests === 0 && a.totalBotRequests === 0) {
32
+ await writeLine(ctx, "");
33
+ await writeLine(ctx, pc.dim("No traffic recorded yet. Analytics update every 15 minutes."));
34
+ }
35
+ });
36
+ }
37
+ /**
38
+ * Strip anything that lets a stored label control the terminal.
39
+ *
40
+ * Paths, referrer hosts and device strings all originate from request traffic,
41
+ * so a visitor chooses them. Printed raw, an OSC-8 sequence renders as a
42
+ * clickable link to somewhere else entirely, and a bidi override reorders the
43
+ * text so `/safe\u202Etxt.exe` reads as something it is not. C0/C1, CSI/OSC and
44
+ * the bidi controls all go.
45
+ */
46
+ function safeLabel(label) {
47
+ return (label
48
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point
49
+ .replace(/\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)?/g, "")
50
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point
51
+ .replace(/\u001b\[[0-9;?]*[ -/]*[@-~]/g, "")
52
+ // biome-ignore lint/suspicious/noControlCharactersInRegex: stripping them is the point
53
+ .replace(/[\u0000-\u001f\u007f-\u009f]/g, "")
54
+ .replace(/[\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, "")
55
+ .slice(0, 120));
56
+ }
57
+ async function printList(ctx, title, rows) {
58
+ if (rows.length === 0)
59
+ return;
60
+ await writeLine(ctx, "");
61
+ await writeLine(ctx, pc.bold(title));
62
+ for (const [label, value] of rows.slice(0, 10)) {
63
+ await writeLine(ctx, ` ${String(value).padStart(7)} ${safeLabel(label)}`);
64
+ }
65
+ }
66
+ //# sourceMappingURL=analytics.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.js","sourceRoot":"","sources":["../../../src/commands/analytics.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAiBjE"}
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAE7C,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,KAAK,CAAC;AAI/B,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,OAAO,GAAG,IAAI,CAsBjE"}
@@ -6,8 +6,13 @@ export function registerAuthCommands(cli, ctx) {
6
6
  .command("login", "Authenticate with Percher")
7
7
  .option("--token <token>", "Dev: skip browser, use a token")
8
8
  .option("--api-url <url>", "Override API URL")
9
+ .option("--force", "Pair again even if already signed in (switch accounts)")
9
10
  .action(async (options) => {
10
- await core.login(ctx, { token: options.token, apiUrl: options.apiUrl });
11
+ await core.login(ctx, {
12
+ token: options.token,
13
+ apiUrl: options.apiUrl,
14
+ force: options.force === true,
15
+ });
11
16
  });
12
17
  cli.command("whoami", "Show the signed-in user").action(async () => {
13
18
  if (!ctx.client.token) {
@@ -1 +1 @@
1
- {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAEtC,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1D,MAAM,UAAU,oBAAoB,CAAC,GAAQ,EAAE,GAAY;IACzD,GAAG;SACA,OAAO,CAAC,OAAO,EAAE,2BAA2B,CAAC;SAC7C,MAAM,CAAC,iBAAiB,EAAE,gCAAgC,CAAC;SAC3D,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,CAAC;SAC7C,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEL,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QACjE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACtB,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;YACzE,MAAM,IAAI,eAAe,EAAE,CAAC;QAC9B,CAAC;QACD,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../../src/commands/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,IAAI,MAAM,eAAe,CAAC;AAEtC,OAAO,EAAE,MAAM,YAAY,CAAC;AAC5B,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAE1D,MAAM,UAAU,oBAAoB,CAAC,GAAQ,EAAE,GAAY;IACzD,GAAG;SACA,OAAO,CAAC,OAAO,EAAE,2BAA2B,CAAC;SAC7C,MAAM,CAAC,iBAAiB,EAAE,gCAAgC,CAAC;SAC3D,MAAM,CAAC,iBAAiB,EAAE,kBAAkB,CAAC;SAC7C,MAAM,CAAC,SAAS,EAAE,wDAAwD,CAAC;SAC3E,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,EAAE;YACpB,KAAK,EAAE,OAAO,CAAC,KAAK;YACpB,MAAM,EAAE,OAAO,CAAC,MAAM;YACtB,KAAK,EAAE,OAAO,CAAC,KAAK,KAAK,IAAI;SAC9B,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;IAEL,GAAG,CAAC,OAAO,CAAC,QAAQ,EAAE,yBAAyB,CAAC,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE;QACjE,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;YACtB,MAAM,SAAS,CAAC,GAAG,EAAE,GAAG,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;YACzE,MAAM,IAAI,eAAe,EAAE,CAAC;QAC9B,CAAC;QACD,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QAClC,MAAM,SAAS,CAAC,GAAG,EAAE,gBAAgB,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,MAAM,GAAG,CAAC,CAAC;IAClE,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAuB7C,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAsEhF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAwB7C,wBAAsB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,WAAW,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,MAAM,CAAC,CAuEhF"}
package/dist/src/index.js CHANGED
@@ -2,6 +2,7 @@ import cac from "cac";
2
2
  import { SilentExitError, writeUnknownCommand } from "./cli-utils";
3
3
  import { registerAccountCommands } from "./commands/account";
4
4
  import { registerAdminCommands } from "./commands/admin";
5
+ import { registerAnalyticsCommands } from "./commands/analytics";
5
6
  import { registerAppsCommands } from "./commands/apps";
6
7
  import { registerAuthCommands } from "./commands/auth";
7
8
  import { registerDataCommands } from "./commands/data";
@@ -23,6 +24,7 @@ export async function run(argv, ctxOverride) {
23
24
  const cli = cac("percher");
24
25
  const ctx = ctxOverride ?? (await createCliContext());
25
26
  registerAppsCommands(cli, ctx);
27
+ registerAnalyticsCommands(cli, ctx);
26
28
  registerDeploysCommands(cli, ctx, argv);
27
29
  registerEnvCommands(cli, ctx);
28
30
  registerExportCommands(cli, ctx);
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,193 @@
1
+ export type ManagedInferenceOptions = {
2
+ model?: string;
3
+ maxTokens?: number;
4
+ signal?: AbortSignal;
5
+ };
6
+
7
+ export class ManagedInferenceError extends Error {
8
+ constructor(
9
+ message: string,
10
+ readonly status?: number,
11
+ readonly code?: string,
12
+ readonly retryAt?: string,
13
+ ) {
14
+ super(message);
15
+ this.name = "ManagedInferenceError";
16
+ }
17
+ }
18
+
19
+ export function createManagedInferenceClient(config: {
20
+ endpoint?: string;
21
+ getSession: () => { valid: boolean; token: string };
22
+ fetch?: typeof globalThis.fetch;
23
+ }) {
24
+ const fetcher = config.fetch ?? globalThis.fetch;
25
+
26
+ function isConfigured(): boolean {
27
+ return Boolean(config.endpoint);
28
+ }
29
+
30
+ function assertReady(): string {
31
+ if (!config.endpoint) {
32
+ throw new ManagedInferenceError(
33
+ "Managed inference is not available for this deployment.",
34
+ 503,
35
+ "INFERENCE_DISABLED",
36
+ );
37
+ }
38
+ const session = config.getSession();
39
+ if (!session.valid || !session.token) {
40
+ throw new ManagedInferenceError("Sign in to use AI.", 401, "INFERENCE_AUTH_REQUIRED");
41
+ }
42
+ return session.token;
43
+ }
44
+
45
+ async function safeError(response: Response): Promise<ManagedInferenceError> {
46
+ let code: string | undefined;
47
+ let resetAt: string | undefined;
48
+ try {
49
+ const body = (await response.json()) as {
50
+ error?: { code?: unknown; resetAt?: unknown; retryAfterSec?: unknown };
51
+ };
52
+ if (typeof body.error?.code === "string") code = body.error.code;
53
+ if (typeof body.error?.resetAt === "string") resetAt = body.error.resetAt;
54
+ if (!resetAt && typeof body.error?.retryAfterSec === "number") {
55
+ resetAt = new Date(Date.now() + body.error.retryAfterSec * 1000).toISOString();
56
+ }
57
+ } catch {
58
+ // Never show raw upstream/proxy response bodies to capsule users.
59
+ }
60
+
61
+ if (response.status === 401) {
62
+ return new ManagedInferenceError("Your session expired. Sign in again.", 401, code);
63
+ }
64
+ if (response.status === 403) {
65
+ return new ManagedInferenceError(
66
+ "Managed inference is not included for this app's plan.",
67
+ 403,
68
+ code,
69
+ );
70
+ }
71
+ if (response.status === 429) {
72
+ return new ManagedInferenceError(
73
+ resetAt ? `AI usage is limited until ${resetAt}.` : "AI usage is temporarily limited.",
74
+ 429,
75
+ code,
76
+ resetAt,
77
+ );
78
+ }
79
+ if (response.status === 503) {
80
+ return new ManagedInferenceError(
81
+ "Managed inference is currently unavailable.",
82
+ 503,
83
+ code ?? "INFERENCE_DISABLED",
84
+ );
85
+ }
86
+ return new ManagedInferenceError(
87
+ "The AI request could not be completed. Try again.",
88
+ response.status,
89
+ code,
90
+ );
91
+ }
92
+
93
+ async function request(prompt: string, opts: ManagedInferenceOptions, stream: boolean) {
94
+ // Validate configuration/auth before the network-error wrapper so a local
95
+ // disabled or signed-out state keeps its precise user-facing error.
96
+ const authorization = `Bearer ${assertReady()}`;
97
+ let response: Response;
98
+ try {
99
+ response = await fetcher(config.endpoint!, {
100
+ method: "POST",
101
+ headers: {
102
+ "content-type": "application/json",
103
+ authorization,
104
+ },
105
+ body: JSON.stringify({
106
+ model: opts.model,
107
+ max_tokens: opts.maxTokens ?? 512,
108
+ messages: [{ role: "user", content: prompt }],
109
+ ...(stream ? { stream: true } : {}),
110
+ }),
111
+ signal: opts.signal,
112
+ });
113
+ } catch (error) {
114
+ if (opts.signal?.aborted) throw error;
115
+ throw new ManagedInferenceError("Managed inference could not be reached. Try again.");
116
+ }
117
+ if (!response.ok) throw await safeError(response);
118
+ return response;
119
+ }
120
+
121
+ async function complete(
122
+ prompt: string,
123
+ opts: ManagedInferenceOptions = {},
124
+ ): Promise<string> {
125
+ const response = await request(prompt, opts, false);
126
+ const data = (await response.json()) as {
127
+ choices?: Array<{ message?: { content?: unknown } }>;
128
+ };
129
+ const content = data.choices?.[0]?.message?.content;
130
+ if (typeof content !== "string") {
131
+ throw new ManagedInferenceError("The AI provider returned an invalid response.");
132
+ }
133
+ return content;
134
+ }
135
+
136
+ async function streamComplete(
137
+ prompt: string,
138
+ onToken: (delta: string) => void,
139
+ opts: ManagedInferenceOptions = {},
140
+ ): Promise<string> {
141
+ const response = await request(prompt, opts, true);
142
+ if (!response.body) throw new ManagedInferenceError("The AI stream could not be opened.");
143
+
144
+ const reader = response.body.getReader();
145
+ const decoder = new TextDecoder();
146
+ let buffer = "";
147
+ let full = "";
148
+ let sawDone = false;
149
+ try {
150
+ for (;;) {
151
+ const { done, value } = await reader.read();
152
+ if (done) break;
153
+ buffer += decoder.decode(value, { stream: true }).replaceAll("\r\n", "\n");
154
+ let separator = buffer.indexOf("\n\n");
155
+ while (separator !== -1) {
156
+ const event = buffer.slice(0, separator);
157
+ buffer = buffer.slice(separator + 2);
158
+ separator = buffer.indexOf("\n\n");
159
+ const payload = event
160
+ .split("\n")
161
+ .filter((line) => line.startsWith("data:"))
162
+ .map((line) => line.slice(5).trimStart())
163
+ .join("\n")
164
+ .trim();
165
+ if (!payload) continue;
166
+ if (payload === "[DONE]") {
167
+ sawDone = true;
168
+ continue;
169
+ }
170
+ try {
171
+ const chunk = JSON.parse(payload) as {
172
+ choices?: Array<{ delta?: { content?: unknown } }>;
173
+ };
174
+ const delta = chunk.choices?.[0]?.delta?.content;
175
+ if (typeof delta === "string" && delta) {
176
+ full += delta;
177
+ onToken(delta);
178
+ }
179
+ } catch {
180
+ // Ignore SSE comments/keep-alives and malformed non-data events.
181
+ }
182
+ }
183
+ }
184
+ if (!sawDone) throw new ManagedInferenceError("The AI stream ended unexpectedly.");
185
+ return full;
186
+ } finally {
187
+ if (opts.signal?.aborted) await reader.cancel().catch(() => undefined);
188
+ reader.releaseLock();
189
+ }
190
+ }
191
+
192
+ return { isConfigured, complete, streamComplete };
193
+ }
@@ -1,91 +1,15 @@
1
+ import { createManagedInferenceClient } from "./ai-client";
1
2
  import { pb } from "./db";
2
3
 
3
- // Managed inference endpoint, injected at build time (a public URL, not a
4
- // secret). The Dockerfile declares ARG VITE_PERCHER_AI_URL.
5
- const AI_URL = import.meta.env.VITE_PERCHER_AI_URL;
6
-
7
- /**
8
- * Keyless managed inference — NO API key, billed to the app owner's Percher
9
- * plan. Only works for SIGNED-IN users (the proxy validates the current PB
10
- * session against this app's sidecar). Returns the completion text.
11
- *
12
- * const reply = await complete("Summarize: " + text);
13
- */
14
- export async function complete(
15
- prompt: string,
16
- opts?: { model?: string; maxTokens?: number },
17
- ): Promise<string> {
18
- if (!AI_URL) throw new Error("Managed inference isn't configured for this app.");
19
- if (!pb.authStore.isValid) throw new Error("Sign in to use AI.");
20
- const res = await fetch(AI_URL, {
21
- method: "POST",
22
- headers: { "content-type": "application/json", authorization: pb.authStore.token },
23
- body: JSON.stringify({
24
- model: opts?.model,
25
- max_tokens: opts?.maxTokens ?? 512,
26
- messages: [{ role: "user", content: prompt }],
27
- }),
28
- });
29
- if (!res.ok) throw new Error(`AI request failed (${res.status})`);
30
- const data = (await res.json()) as { choices?: Array<{ message?: { content?: string } }> };
31
- return data.choices?.[0]?.message?.content ?? "";
32
- }
33
-
34
- /**
35
- * Streaming variant of {@link complete} — calls `onToken` with each text delta
36
- * as it arrives and resolves with the full text. Same auth + billing as
37
- * `complete` (keyless, billed to the app owner). Use it for a typewriter UI:
38
- *
39
- * const full = await streamComplete("Write a haiku", (t) => { output.value += t; });
40
- */
41
- export async function streamComplete(
42
- prompt: string,
43
- onToken: (delta: string) => void,
44
- opts?: { model?: string; maxTokens?: number },
45
- ): Promise<string> {
46
- if (!AI_URL) throw new Error("Managed inference isn't configured for this app.");
47
- if (!pb.authStore.isValid) throw new Error("Sign in to use AI.");
48
- const res = await fetch(AI_URL, {
49
- method: "POST",
50
- headers: { "content-type": "application/json", authorization: pb.authStore.token },
51
- body: JSON.stringify({
52
- model: opts?.model,
53
- max_tokens: opts?.maxTokens ?? 512,
54
- messages: [{ role: "user", content: prompt }],
55
- stream: true,
56
- }),
57
- });
58
- if (!res.ok || !res.body) throw new Error(`AI request failed (${res.status})`);
59
-
60
- const reader = res.body.getReader();
61
- const decoder = new TextDecoder();
62
- let buffer = "";
63
- let full = "";
64
- for (;;) {
65
- const { done, value } = await reader.read();
66
- if (done) break;
67
- buffer += decoder.decode(value, { stream: true });
68
- // SSE events are separated by a blank line; parse each complete one.
69
- let sep = buffer.indexOf("\n\n");
70
- while (sep !== -1) {
71
- const event = buffer.slice(0, sep);
72
- buffer = buffer.slice(sep + 2);
73
- sep = buffer.indexOf("\n\n");
74
- const line = event.startsWith("data:") ? event.slice(5).trim() : "";
75
- if (!line || line === "[DONE]") continue;
76
- try {
77
- const chunk = JSON.parse(line) as {
78
- choices?: Array<{ delta?: { content?: string } }>;
79
- };
80
- const delta = chunk.choices?.[0]?.delta?.content ?? "";
81
- if (delta) {
82
- full += delta;
83
- onToken(delta);
84
- }
85
- } catch {
86
- /* ignore keep-alive / non-JSON lines */
87
- }
88
- }
89
- }
90
- return full;
91
- }
4
+ // This is a public URL, never a provider credential. Percher injects it only
5
+ // when the shared server-side feature gate is operational.
6
+ const client = createManagedInferenceClient({
7
+ endpoint: import.meta.env.VITE_PERCHER_AI_URL as string | undefined,
8
+ getSession: () => ({ valid: pb.authStore.isValid, token: pb.authStore.token }),
9
+ });
10
+
11
+ export { ManagedInferenceError } from "./ai-client";
12
+ export type { ManagedInferenceOptions } from "./ai-client";
13
+ export const isManagedInferenceConfigured = client.isConfigured;
14
+ export const complete = client.complete;
15
+ export const streamComplete = client.streamComplete;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "percher",
3
- "version": "0.5.21",
3
+ "version": "0.5.23",
4
4
  "description": "CLI for the Percher hosting platform — publish apps with one command",
5
5
  "type": "module",
6
6
  "bin": {
@@ -27,9 +27,9 @@
27
27
  "typecheck": "tsc --noEmit"
28
28
  },
29
29
  "dependencies": {
30
- "@percher/client": "^0.2.20",
31
- "@percher/core": "^0.4.24",
32
- "@percher/toml": "^0.1.9",
30
+ "@percher/client": "^0.2.22",
31
+ "@percher/core": "^0.4.28",
32
+ "@percher/toml": "^0.1.10",
33
33
  "cac": "^7.0.0",
34
34
  "picocolors": "^1.1.1"
35
35
  },