@micropage-sh/mcp 0.1.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/LICENSE +21 -0
- package/README.md +38 -0
- package/dist/annotations.d.ts +20 -0
- package/dist/annotations.js +36 -0
- package/dist/annotations.js.map +1 -0
- package/dist/client/assets.d.ts +85 -0
- package/dist/client/assets.js +473 -0
- package/dist/client/assets.js.map +1 -0
- package/dist/client/auth-provider.d.ts +24 -0
- package/dist/client/auth-provider.js +10 -0
- package/dist/client/auth-provider.js.map +1 -0
- package/dist/client/builds.d.ts +82 -0
- package/dist/client/builds.js +166 -0
- package/dist/client/builds.js.map +1 -0
- package/dist/client/config.d.ts +18 -0
- package/dist/client/config.js +29 -0
- package/dist/client/config.js.map +1 -0
- package/dist/client/deploy-events.d.ts +98 -0
- package/dist/client/deploy-events.js +170 -0
- package/dist/client/deploy-events.js.map +1 -0
- package/dist/client/deploy-token.d.ts +48 -0
- package/dist/client/deploy-token.js +130 -0
- package/dist/client/deploy-token.js.map +1 -0
- package/dist/client/errors.d.ts +18 -0
- package/dist/client/errors.js +21 -0
- package/dist/client/errors.js.map +1 -0
- package/dist/client/http.d.ts +68 -0
- package/dist/client/http.js +191 -0
- package/dist/client/http.js.map +1 -0
- package/dist/client/jwt.d.ts +13 -0
- package/dist/client/jwt.js +23 -0
- package/dist/client/jwt.js.map +1 -0
- package/dist/client/lock.d.ts +16 -0
- package/dist/client/lock.js +67 -0
- package/dist/client/lock.js.map +1 -0
- package/dist/client/pages.d.ts +47 -0
- package/dist/client/pages.js +131 -0
- package/dist/client/pages.js.map +1 -0
- package/dist/client/posts.d.ts +108 -0
- package/dist/client/posts.js +187 -0
- package/dist/client/posts.js.map +1 -0
- package/dist/client/project-ref.d.ts +68 -0
- package/dist/client/project-ref.js +118 -0
- package/dist/client/project-ref.js.map +1 -0
- package/dist/client/session-store.d.ts +81 -0
- package/dist/client/session-store.js +271 -0
- package/dist/client/session-store.js.map +1 -0
- package/dist/client/submissions.d.ts +59 -0
- package/dist/client/submissions.js +111 -0
- package/dist/client/submissions.js.map +1 -0
- package/dist/client/tier.d.ts +42 -0
- package/dist/client/tier.js +76 -0
- package/dist/client/tier.js.map +1 -0
- package/dist/content/index.d.ts +5 -0
- package/dist/content/index.js +28 -0
- package/dist/content/index.js.map +1 -0
- package/dist/context.d.ts +30 -0
- package/dist/context.js +2 -0
- package/dist/context.js.map +1 -0
- package/dist/guards.d.ts +55 -0
- package/dist/guards.js +168 -0
- package/dist/guards.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/progress.d.ts +7 -0
- package/dist/progress.js +25 -0
- package/dist/progress.js.map +1 -0
- package/dist/reference/prompts.d.ts +6 -0
- package/dist/reference/prompts.js +110 -0
- package/dist/reference/prompts.js.map +1 -0
- package/dist/reference/resources.d.ts +2 -0
- package/dist/reference/resources.js +41 -0
- package/dist/reference/resources.js.map +1 -0
- package/dist/reference/tool.d.ts +21 -0
- package/dist/reference/tool.js +50 -0
- package/dist/reference/tool.js.map +1 -0
- package/dist/reference/topics.d.ts +29 -0
- package/dist/reference/topics.js +91 -0
- package/dist/reference/topics.js.map +1 -0
- package/dist/reference.d.ts +9 -0
- package/dist/reference.js +20 -0
- package/dist/reference.js.map +1 -0
- package/dist/server.d.ts +29 -0
- package/dist/server.js +75 -0
- package/dist/server.js.map +1 -0
- package/dist/tools/account.d.ts +34 -0
- package/dist/tools/account.js +128 -0
- package/dist/tools/account.js.map +1 -0
- package/dist/tools/builds.d.ts +95 -0
- package/dist/tools/builds.js +403 -0
- package/dist/tools/builds.js.map +1 -0
- package/dist/tools/files.d.ts +71 -0
- package/dist/tools/files.js +205 -0
- package/dist/tools/files.js.map +1 -0
- package/dist/tools/forms.d.ts +59 -0
- package/dist/tools/forms.js +177 -0
- package/dist/tools/forms.js.map +1 -0
- package/dist/tools/posts.d.ts +191 -0
- package/dist/tools/posts.js +600 -0
- package/dist/tools/posts.js.map +1 -0
- package/dist/tools/projects.d.ts +201 -0
- package/dist/tools/projects.js +553 -0
- package/dist/tools/projects.js.map +1 -0
- package/dist/tools/shared.d.ts +33 -0
- package/dist/tools/shared.js +41 -0
- package/dist/tools/shared.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +8 -0
- package/dist/version.js.map +1 -0
- package/package.json +67 -0
|
@@ -0,0 +1,271 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { readFile, rename, stat, unlink, writeFile } from "node:fs/promises";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
import { VERSION } from "../version.js";
|
|
6
|
+
import { MicropageError } from "./errors.js";
|
|
7
|
+
import { isJwtExpiring } from "./jwt.js";
|
|
8
|
+
import { withFileLock } from "./lock.js";
|
|
9
|
+
export const LOGIN_HINT = "Run `micropage login` in a terminal, then retry.";
|
|
10
|
+
/** Same location as the CLI (cli/src/auth.js), plus a MICROPAGE_CONFIG_DIR override. */
|
|
11
|
+
export function configDir(env = process.env) {
|
|
12
|
+
const override = env.MICROPAGE_CONFIG_DIR?.trim();
|
|
13
|
+
if (override)
|
|
14
|
+
return override;
|
|
15
|
+
return join(env.HOME || env.USERPROFILE || homedir(), ".micropage");
|
|
16
|
+
}
|
|
17
|
+
export function sessionFilePath(env = process.env) {
|
|
18
|
+
return join(configDir(env), "config.json");
|
|
19
|
+
}
|
|
20
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
21
|
+
/**
|
|
22
|
+
* The CLI's ~/.micropage/config.json. Reads tolerate the CLI's non-atomic
|
|
23
|
+
* writes; our writes are atomic and only ever replace the token keys, so the
|
|
24
|
+
* session is never cleared from here.
|
|
25
|
+
*/
|
|
26
|
+
export class SessionStore {
|
|
27
|
+
path;
|
|
28
|
+
constructor(path) {
|
|
29
|
+
this.path = path;
|
|
30
|
+
}
|
|
31
|
+
get lockPath() {
|
|
32
|
+
return `${this.path}.lock`;
|
|
33
|
+
}
|
|
34
|
+
/** The whole config object, or null when there is no file. */
|
|
35
|
+
async readConfig() {
|
|
36
|
+
for (let attempt = 0;; attempt++) {
|
|
37
|
+
let text;
|
|
38
|
+
try {
|
|
39
|
+
text = await readFile(this.path, "utf8");
|
|
40
|
+
}
|
|
41
|
+
catch (err) {
|
|
42
|
+
if (err.code === "ENOENT")
|
|
43
|
+
return null;
|
|
44
|
+
throw new MicropageError("SESSION_UNREADABLE", `Could not read ${this.path}: ${err.message}`, {
|
|
45
|
+
cause: err,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
try {
|
|
49
|
+
const parsed = JSON.parse(text);
|
|
50
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed)
|
|
51
|
+
? parsed
|
|
52
|
+
: {};
|
|
53
|
+
}
|
|
54
|
+
catch (err) {
|
|
55
|
+
// The CLI writes this file in place, so a read can land mid-write.
|
|
56
|
+
if (attempt < 3) {
|
|
57
|
+
await sleep(25);
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
throw new MicropageError("SESSION_UNREADABLE", `${this.path} is not valid JSON. ${LOGIN_HINT}`, { cause: err });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
async readSession() {
|
|
65
|
+
const config = await this.readConfig();
|
|
66
|
+
if (!config || !hasTokens(config))
|
|
67
|
+
return null;
|
|
68
|
+
return { access_token: config.access_token, refresh_token: config.refresh_token, user: config.user ?? null };
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Replaces the token keys (and user, when given), keeping every other key.
|
|
72
|
+
* Re-reads the file first and writes nothing when it no longer holds a
|
|
73
|
+
* session: the user logged out while a refresh was in flight, and writing
|
|
74
|
+
* the tokens back would undo that. Returns whether it wrote.
|
|
75
|
+
*/
|
|
76
|
+
async writeTokens(tokens) {
|
|
77
|
+
const config = await this.readConfig();
|
|
78
|
+
if (!config || !hasTokens(config))
|
|
79
|
+
return false;
|
|
80
|
+
config.access_token = tokens.access_token;
|
|
81
|
+
config.refresh_token = tokens.refresh_token;
|
|
82
|
+
if (tokens.user !== undefined && tokens.user !== null)
|
|
83
|
+
config.user = tokens.user;
|
|
84
|
+
const mode = (await stat(this.path).catch(() => null))?.mode ?? 0o600;
|
|
85
|
+
const tmp = `${this.path}.${process.pid}.${randomBytes(6).toString("hex")}.tmp`;
|
|
86
|
+
try {
|
|
87
|
+
await writeFile(tmp, JSON.stringify(config, null, 2), { encoding: "utf8", mode: mode & 0o777 });
|
|
88
|
+
await rename(tmp, this.path);
|
|
89
|
+
}
|
|
90
|
+
catch (err) {
|
|
91
|
+
await unlink(tmp).catch(() => undefined);
|
|
92
|
+
throw err;
|
|
93
|
+
}
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function hasTokens(config) {
|
|
98
|
+
const { access_token, refresh_token } = config;
|
|
99
|
+
return typeof access_token === "string" && access_token !== "" && typeof refresh_token === "string" && refresh_token !== "";
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Access tokens from the CLI's login session. Refreshes proactively within
|
|
103
|
+
* 60 s of expiry; refreshes are single-flight in-process and serialised across
|
|
104
|
+
* MCP processes by a lockfile next to the config. The CLI does not take that
|
|
105
|
+
* lock, so a failed refresh re-reads the file and retries once if the CLI
|
|
106
|
+
* rotated the refresh token meanwhile.
|
|
107
|
+
*/
|
|
108
|
+
export class SessionAuthProvider {
|
|
109
|
+
mode = "session";
|
|
110
|
+
store;
|
|
111
|
+
config;
|
|
112
|
+
fetchImpl;
|
|
113
|
+
now;
|
|
114
|
+
lockOptions;
|
|
115
|
+
timeoutMs;
|
|
116
|
+
inflight = null;
|
|
117
|
+
lastIssued = null;
|
|
118
|
+
memory = null;
|
|
119
|
+
warnedWriteFailure = false;
|
|
120
|
+
log;
|
|
121
|
+
constructor(options) {
|
|
122
|
+
this.config = options.config;
|
|
123
|
+
this.store = new SessionStore(options.path);
|
|
124
|
+
this.fetchImpl = options.fetch ?? ((input, init) => fetch(input, init));
|
|
125
|
+
this.now = options.now ?? Date.now;
|
|
126
|
+
this.lockOptions = options.lock ?? {};
|
|
127
|
+
this.timeoutMs = options.timeoutMs ?? 20_000;
|
|
128
|
+
this.log = options.log ?? ((message) => process.stderr.write(`${message}\n`));
|
|
129
|
+
}
|
|
130
|
+
get path() {
|
|
131
|
+
return this.store.path;
|
|
132
|
+
}
|
|
133
|
+
async hasSession() {
|
|
134
|
+
return (await this.currentSession()) !== null;
|
|
135
|
+
}
|
|
136
|
+
/** The file's session, or the in-memory one while the file still holds the refresh token it replaced. */
|
|
137
|
+
async currentSession() {
|
|
138
|
+
const file = await this.store.readSession();
|
|
139
|
+
const memory = this.memory;
|
|
140
|
+
if (memory) {
|
|
141
|
+
if (file && file.refresh_token === memory.fileRefreshToken)
|
|
142
|
+
return memory.session;
|
|
143
|
+
// Logged in again, rotated by the CLI, or logged out: the file wins.
|
|
144
|
+
this.memory = null;
|
|
145
|
+
}
|
|
146
|
+
return file;
|
|
147
|
+
}
|
|
148
|
+
async getAccessToken(options = {}) {
|
|
149
|
+
// Re-read every call: the CLI may have logged in again or rotated tokens.
|
|
150
|
+
const session = await this.requireSession();
|
|
151
|
+
const fresh = !isJwtExpiring(session.access_token, this.now());
|
|
152
|
+
if (!options.forceRefresh && fresh)
|
|
153
|
+
return this.issue(session.access_token);
|
|
154
|
+
// The token being given up on: the expiring one, or after a 401 the one we handed out.
|
|
155
|
+
const stale = options.forceRefresh ? (this.lastIssued ?? session.access_token) : session.access_token;
|
|
156
|
+
if (session.access_token !== stale && fresh)
|
|
157
|
+
return this.issue(session.access_token);
|
|
158
|
+
this.inflight ??= this.refreshUnderLock(stale).finally(() => {
|
|
159
|
+
this.inflight = null;
|
|
160
|
+
});
|
|
161
|
+
return this.inflight;
|
|
162
|
+
}
|
|
163
|
+
issue(token) {
|
|
164
|
+
this.lastIssued = token;
|
|
165
|
+
return token;
|
|
166
|
+
}
|
|
167
|
+
async requireSession() {
|
|
168
|
+
const session = await this.currentSession();
|
|
169
|
+
if (!session) {
|
|
170
|
+
throw new MicropageError("NOT_LOGGED_IN", `Not logged in to micropage. ${LOGIN_HINT}`);
|
|
171
|
+
}
|
|
172
|
+
return session;
|
|
173
|
+
}
|
|
174
|
+
refreshUnderLock(stale) {
|
|
175
|
+
return withFileLock(this.store.lockPath, async () => {
|
|
176
|
+
const current = await this.requireSession();
|
|
177
|
+
if (current.access_token !== stale && !isJwtExpiring(current.access_token, this.now())) {
|
|
178
|
+
return this.issue(current.access_token);
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
return this.issue(await this.exchange(current));
|
|
182
|
+
}
|
|
183
|
+
catch (err) {
|
|
184
|
+
const again = await this.currentSession().catch(() => null);
|
|
185
|
+
if (!again || again.refresh_token === current.refresh_token)
|
|
186
|
+
throw finalError(err);
|
|
187
|
+
if (again.access_token !== stale && !isJwtExpiring(again.access_token, this.now())) {
|
|
188
|
+
return this.issue(again.access_token);
|
|
189
|
+
}
|
|
190
|
+
try {
|
|
191
|
+
return this.issue(await this.exchange(again));
|
|
192
|
+
}
|
|
193
|
+
catch (retryErr) {
|
|
194
|
+
throw finalError(retryErr);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}, this.lockOptions);
|
|
198
|
+
}
|
|
199
|
+
async exchange(session) {
|
|
200
|
+
let res;
|
|
201
|
+
try {
|
|
202
|
+
res = await this.fetchImpl(`${this.config.supabaseUrl}/auth/v1/token?grant_type=refresh_token`, {
|
|
203
|
+
method: "POST",
|
|
204
|
+
headers: {
|
|
205
|
+
apikey: this.config.supabaseAnonKey,
|
|
206
|
+
"Content-Type": "application/json",
|
|
207
|
+
"User-Agent": `micropage-mcp/${VERSION}`,
|
|
208
|
+
},
|
|
209
|
+
body: JSON.stringify({ refresh_token: session.refresh_token }),
|
|
210
|
+
signal: AbortSignal.timeout(this.timeoutMs),
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
catch (err) {
|
|
214
|
+
throw new MicropageError("NETWORK", `Refreshing the micropage login failed before a response arrived (${err instanceof Error ? err.message : String(err)}). Check the network and retry.`, { cause: err });
|
|
215
|
+
}
|
|
216
|
+
const text = await res.text().catch(() => "");
|
|
217
|
+
let data = null;
|
|
218
|
+
try {
|
|
219
|
+
data = text ? JSON.parse(text) : null;
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
data = null;
|
|
223
|
+
}
|
|
224
|
+
if (res.status >= 500) {
|
|
225
|
+
throw new MicropageError("AUTH_UNAVAILABLE", `The micropage auth server returned HTTP ${res.status} while refreshing the login. Retry shortly.`, { status: res.status, data });
|
|
226
|
+
}
|
|
227
|
+
if (!res.ok || typeof data?.access_token !== "string" || !data.access_token) {
|
|
228
|
+
throw new MicropageError("REFRESH_REJECTED", `Token refresh was rejected (HTTP ${res.status}).`, {
|
|
229
|
+
status: res.status,
|
|
230
|
+
data,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
const next = {
|
|
234
|
+
access_token: data.access_token,
|
|
235
|
+
refresh_token: typeof data.refresh_token === "string" && data.refresh_token ? data.refresh_token : session.refresh_token,
|
|
236
|
+
user: data.user ?? session.user,
|
|
237
|
+
};
|
|
238
|
+
// While the memory session is in use the file still holds the refresh
|
|
239
|
+
// token it replaced; keep pointing at that one.
|
|
240
|
+
const fileRefreshToken = this.memory?.session === session ? this.memory.fileRefreshToken : session.refresh_token;
|
|
241
|
+
let written;
|
|
242
|
+
try {
|
|
243
|
+
written = await this.store.writeTokens(next);
|
|
244
|
+
}
|
|
245
|
+
catch (err) {
|
|
246
|
+
this.memory = { session: next, fileRefreshToken };
|
|
247
|
+
if (!this.warnedWriteFailure) {
|
|
248
|
+
this.warnedWriteFailure = true;
|
|
249
|
+
this.log(`micropage-mcp: could not save the refreshed login to ${this.store.path} (${err instanceof Error ? err.message : String(err)}). ` +
|
|
250
|
+
"This server keeps using it in memory, but the micropage CLI's session there is now spent and may need `micropage login`.");
|
|
251
|
+
}
|
|
252
|
+
return next.access_token;
|
|
253
|
+
}
|
|
254
|
+
if (!written) {
|
|
255
|
+
this.memory = null;
|
|
256
|
+
throw new MicropageError("NOT_LOGGED_IN", `The micropage CLI was logged out while the login was being refreshed. ${LOGIN_HINT}`);
|
|
257
|
+
}
|
|
258
|
+
this.memory = null;
|
|
259
|
+
return next.access_token;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
/** Transport trouble stays retryable; anything else means the stored login is no good. */
|
|
263
|
+
function finalError(err) {
|
|
264
|
+
if (err instanceof MicropageError && (err.code === "NETWORK" || err.code === "AUTH_UNAVAILABLE" || err.code === "NOT_LOGGED_IN")) {
|
|
265
|
+
return err;
|
|
266
|
+
}
|
|
267
|
+
return new MicropageError("SESSION_EXPIRED", `The micropage login session has expired. ${LOGIN_HINT}`, {
|
|
268
|
+
cause: err,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
//# sourceMappingURL=session-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-store.js","sourceRoot":"","sources":["../../src/client/session-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type Filters, type Http } from "./http.js";
|
|
2
|
+
export interface FormWithCount {
|
|
3
|
+
id: string;
|
|
4
|
+
form_name: string;
|
|
5
|
+
/** form_name, plus "(n)" for the nth same-named form on one page, as the CLI prints it. */
|
|
6
|
+
label: string;
|
|
7
|
+
page_url: string | null;
|
|
8
|
+
is_footer: boolean;
|
|
9
|
+
form_ordinal: number;
|
|
10
|
+
created_at: string | null;
|
|
11
|
+
submission_count: number;
|
|
12
|
+
}
|
|
13
|
+
/** Occurrence number among same-named forms on the same page, 1-based. */
|
|
14
|
+
export declare function formLabel(form: {
|
|
15
|
+
form_name: string;
|
|
16
|
+
form_ordinal?: number | null;
|
|
17
|
+
}): string;
|
|
18
|
+
/**
|
|
19
|
+
* Forms with non-spam submission counts. form_ordinal is newer than some
|
|
20
|
+
* deployed databases and selecting a missing column fails the whole request,
|
|
21
|
+
* so an HTTP error on the first shape retries without it, as the CLI does.
|
|
22
|
+
*/
|
|
23
|
+
export declare function listFormsWithCounts(http: Http, projectId: number): Promise<FormWithCount[]>;
|
|
24
|
+
export declare const MAX_SUBMISSIONS = 50;
|
|
25
|
+
export declare const DEFAULT_SUBMISSIONS = 20;
|
|
26
|
+
export interface SubmissionRow {
|
|
27
|
+
id: string;
|
|
28
|
+
form_id: string | null;
|
|
29
|
+
form_name: string | null;
|
|
30
|
+
page_url: string | null;
|
|
31
|
+
form_index: number | null;
|
|
32
|
+
build_id: number | null;
|
|
33
|
+
created_at: string | null;
|
|
34
|
+
/** Visitor-typed data: untrusted, and personal data. */
|
|
35
|
+
payload: unknown;
|
|
36
|
+
spam_reason: string | null;
|
|
37
|
+
flagged_at: string | null;
|
|
38
|
+
flagged_by: string | null;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The CLI's applySpamFilter for the inbox: spam is `flagged_at IS NOT NULL`,
|
|
42
|
+
* so the default view is `flagged_at IS NULL`. Including spam drops the
|
|
43
|
+
* filter (inbox and spam together) rather than switching to spam only.
|
|
44
|
+
*/
|
|
45
|
+
export declare function spamFilter(includeSpam: boolean): Filters;
|
|
46
|
+
export interface ListSubmissionsOptions {
|
|
47
|
+
/** A form uuid (matches form_id) or a form name (matches form_name). */
|
|
48
|
+
form?: string | undefined;
|
|
49
|
+
includeSpam?: boolean | undefined;
|
|
50
|
+
limit?: number | undefined;
|
|
51
|
+
}
|
|
52
|
+
export declare function listSubmissions(http: Http, projectId: number, options?: ListSubmissionsOptions): Promise<SubmissionRow[]>;
|
|
53
|
+
/** One submission, scoped to the project so an id from another project reads as not found. */
|
|
54
|
+
export declare function getSubmission(http: Http, projectId: number, id: string): Promise<SubmissionRow>;
|
|
55
|
+
/** The label/value pairs of a payload: `payload.fields` as submit-form stores it, else the raw keys of older rows. */
|
|
56
|
+
export declare function submissionFields(payload: unknown): Array<{
|
|
57
|
+
label: string;
|
|
58
|
+
value: unknown;
|
|
59
|
+
}>;
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { MicropageError, isMicropageError } from "./errors.js";
|
|
2
|
+
import { eq, is } from "./http.js";
|
|
3
|
+
const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
// Forms (cli/src/commands/forms.js)
|
|
6
|
+
// ---------------------------------------------------------------------------
|
|
7
|
+
const FORM_COLUMNS = "id,form_name,page_url,is_footer,created_at";
|
|
8
|
+
/** Occurrence number among same-named forms on the same page, 1-based. */
|
|
9
|
+
export function formLabel(form) {
|
|
10
|
+
const ordinal = Number(form.form_ordinal) || 0;
|
|
11
|
+
return ordinal > 0 ? `${form.form_name} (${ordinal + 1})` : form.form_name;
|
|
12
|
+
}
|
|
13
|
+
function fetchForms(http, projectId, withOrdinal) {
|
|
14
|
+
return http.select("forms", {
|
|
15
|
+
select: withOrdinal ? `${FORM_COLUMNS},form_ordinal` : FORM_COLUMNS,
|
|
16
|
+
filters: { project_id: eq(projectId) },
|
|
17
|
+
// Same order strings the CLI's query builder produces.
|
|
18
|
+
order: withOrdinal ? "page_url,form_ordinal.asc" : "created_at.asc",
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Forms with non-spam submission counts. form_ordinal is newer than some
|
|
23
|
+
* deployed databases and selecting a missing column fails the whole request,
|
|
24
|
+
* so an HTTP error on the first shape retries without it, as the CLI does.
|
|
25
|
+
*/
|
|
26
|
+
export async function listFormsWithCounts(http, projectId) {
|
|
27
|
+
let forms;
|
|
28
|
+
try {
|
|
29
|
+
forms = await fetchForms(http, projectId, true);
|
|
30
|
+
}
|
|
31
|
+
catch (err) {
|
|
32
|
+
if (!isMicropageError(err) || err.code !== "HTTP")
|
|
33
|
+
throw err;
|
|
34
|
+
forms = await fetchForms(http, projectId, false);
|
|
35
|
+
}
|
|
36
|
+
if (forms.length === 0)
|
|
37
|
+
return [];
|
|
38
|
+
// One exact count per form: selecting the rows would stop at PostgREST's max_rows.
|
|
39
|
+
const totals = await Promise.all(forms.map((f) => http.count("form_submissions", { project_id: eq(projectId), form_id: eq(f.id), flagged_at: is("null") })));
|
|
40
|
+
const counts = new Map(forms.map((f, i) => [f.id, totals[i]]));
|
|
41
|
+
return forms.map((f) => ({
|
|
42
|
+
id: f.id,
|
|
43
|
+
form_name: f.form_name,
|
|
44
|
+
label: formLabel(f),
|
|
45
|
+
page_url: f.page_url ?? null,
|
|
46
|
+
is_footer: Boolean(f.is_footer),
|
|
47
|
+
form_ordinal: Number(f.form_ordinal) || 0,
|
|
48
|
+
created_at: f.created_at ?? null,
|
|
49
|
+
submission_count: counts.get(f.id) ?? 0,
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
52
|
+
// ---------------------------------------------------------------------------
|
|
53
|
+
// Submissions (cli/src/commands/submissions.js)
|
|
54
|
+
// ---------------------------------------------------------------------------
|
|
55
|
+
export const MAX_SUBMISSIONS = 50;
|
|
56
|
+
export const DEFAULT_SUBMISSIONS = 20;
|
|
57
|
+
const SUBMISSION_COLUMNS = "id,form_id,form_name,page_url,form_index,build_id,created_at,payload,spam_reason,flagged_at,flagged_by";
|
|
58
|
+
/**
|
|
59
|
+
* The CLI's applySpamFilter for the inbox: spam is `flagged_at IS NOT NULL`,
|
|
60
|
+
* so the default view is `flagged_at IS NULL`. Including spam drops the
|
|
61
|
+
* filter (inbox and spam together) rather than switching to spam only.
|
|
62
|
+
*/
|
|
63
|
+
export function spamFilter(includeSpam) {
|
|
64
|
+
return includeSpam ? {} : { flagged_at: is("null") };
|
|
65
|
+
}
|
|
66
|
+
export async function listSubmissions(http, projectId, options = {}) {
|
|
67
|
+
const limit = Math.min(Math.max(1, Math.trunc(options.limit ?? DEFAULT_SUBMISSIONS)), MAX_SUBMISSIONS);
|
|
68
|
+
const filters = { project_id: eq(projectId), ...spamFilter(Boolean(options.includeSpam)) };
|
|
69
|
+
const form = options.form?.trim();
|
|
70
|
+
if (form) {
|
|
71
|
+
if (UUID_RE.test(form))
|
|
72
|
+
filters.form_id = eq(form.toLowerCase());
|
|
73
|
+
else
|
|
74
|
+
filters.form_name = eq(form);
|
|
75
|
+
}
|
|
76
|
+
return http.select("form_submissions", {
|
|
77
|
+
select: SUBMISSION_COLUMNS,
|
|
78
|
+
filters,
|
|
79
|
+
order: "created_at.desc",
|
|
80
|
+
limit,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
/** One submission, scoped to the project so an id from another project reads as not found. */
|
|
84
|
+
export async function getSubmission(http, projectId, id) {
|
|
85
|
+
const trimmed = id.trim();
|
|
86
|
+
if (!UUID_RE.test(trimmed)) {
|
|
87
|
+
throw new MicropageError("INVALID_ARGUMENT", `"${id}" is not a submission id (a uuid). Call list_submissions to get ids.`);
|
|
88
|
+
}
|
|
89
|
+
const row = await http.selectOne("form_submissions", {
|
|
90
|
+
select: SUBMISSION_COLUMNS,
|
|
91
|
+
filters: { id: eq(trimmed.toLowerCase()), project_id: eq(projectId) },
|
|
92
|
+
});
|
|
93
|
+
if (!row) {
|
|
94
|
+
throw new MicropageError("NOT_FOUND", `No submission ${trimmed} in this project. Call list_submissions to see valid ids.`);
|
|
95
|
+
}
|
|
96
|
+
return row;
|
|
97
|
+
}
|
|
98
|
+
/** The label/value pairs of a payload: `payload.fields` as submit-form stores it, else the raw keys of older rows. */
|
|
99
|
+
export function submissionFields(payload) {
|
|
100
|
+
if (!payload || typeof payload !== "object")
|
|
101
|
+
return [];
|
|
102
|
+
const fields = payload.fields;
|
|
103
|
+
if (Array.isArray(fields)) {
|
|
104
|
+
return fields.map((f) => {
|
|
105
|
+
const o = (f && typeof f === "object" ? f : {});
|
|
106
|
+
return { label: String(o.label ?? o.name ?? ""), value: o.value ?? null };
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
return Object.entries(payload).map(([label, value]) => ({ label, value }));
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=submissions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submissions.js","sourceRoot":"","sources":["../../src/client/submissions.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { AuthProvider } from "./auth-provider.js";
|
|
2
|
+
import { type Http } from "./http.js";
|
|
3
|
+
export type PlanTier = "free" | "pro" | "pro_plus";
|
|
4
|
+
export declare const PRICING_URL = "https://micropage.sh/pricing";
|
|
5
|
+
/** Same wording as the CLI (cli/src/plan.js), addressed to the model. */
|
|
6
|
+
export declare const UPGRADE_MESSAGE: string;
|
|
7
|
+
export declare const PLAN_CACHE_TTL_MS: number;
|
|
8
|
+
export declare function isPaidTier(tier: string | null | undefined): tier is "pro" | "pro_plus";
|
|
9
|
+
export interface PlanGateOptions {
|
|
10
|
+
http: Http;
|
|
11
|
+
auth: AuthProvider;
|
|
12
|
+
now?: () => number;
|
|
13
|
+
ttlMs?: number;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* The CLI's client-side Pro gate: reads customers.plan_tier under RLS and
|
|
17
|
+
* caches it for 5 minutes per user. A product gate, not a security boundary;
|
|
18
|
+
* the server enforces its own per-feature limits.
|
|
19
|
+
*/
|
|
20
|
+
export declare class PlanGate {
|
|
21
|
+
private readonly http;
|
|
22
|
+
private readonly auth;
|
|
23
|
+
private readonly now;
|
|
24
|
+
private readonly ttlMs;
|
|
25
|
+
private cache;
|
|
26
|
+
private inflight;
|
|
27
|
+
constructor(options: PlanGateOptions);
|
|
28
|
+
/** The signed-in user's id, read from the access token's `sub`. */
|
|
29
|
+
currentUserId(): Promise<string>;
|
|
30
|
+
/**
|
|
31
|
+
* Missing customer row means free, as in the CLI. A failed lookup is not
|
|
32
|
+
* cached and propagates, so a network blip does not read as "free plan".
|
|
33
|
+
*/
|
|
34
|
+
getPlanTier(): Promise<PlanTier>;
|
|
35
|
+
/**
|
|
36
|
+
* Throws PLAN_REQUIRED unless the account is pro or pro_plus. Deploy-token
|
|
37
|
+
* mode skips the lookup: deploy tokens can only be created on Pro+
|
|
38
|
+
* (create-deploy-token), so holding one already implies a paid plan.
|
|
39
|
+
*/
|
|
40
|
+
requirePaidPlan(): Promise<void>;
|
|
41
|
+
clearCache(): void;
|
|
42
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { MicropageError } from "./errors.js";
|
|
2
|
+
import { eq } from "./http.js";
|
|
3
|
+
import { decodeJwtClaims } from "./jwt.js";
|
|
4
|
+
export const PRICING_URL = "https://micropage.sh/pricing";
|
|
5
|
+
/** Same wording as the CLI (cli/src/plan.js), addressed to the model. */
|
|
6
|
+
export const UPGRADE_MESSAGE = "The micropage MCP server, like the micropage CLI, is available on paid plans only. " +
|
|
7
|
+
`This account is on the free plan. Tell the user they can upgrade at ${PRICING_URL}, then retry.`;
|
|
8
|
+
export const PLAN_CACHE_TTL_MS = 5 * 60 * 1000;
|
|
9
|
+
export function isPaidTier(tier) {
|
|
10
|
+
return tier === "pro" || tier === "pro_plus";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The CLI's client-side Pro gate: reads customers.plan_tier under RLS and
|
|
14
|
+
* caches it for 5 minutes per user. A product gate, not a security boundary;
|
|
15
|
+
* the server enforces its own per-feature limits.
|
|
16
|
+
*/
|
|
17
|
+
export class PlanGate {
|
|
18
|
+
http;
|
|
19
|
+
auth;
|
|
20
|
+
now;
|
|
21
|
+
ttlMs;
|
|
22
|
+
cache = null;
|
|
23
|
+
inflight = null;
|
|
24
|
+
constructor(options) {
|
|
25
|
+
this.http = options.http;
|
|
26
|
+
this.auth = options.auth;
|
|
27
|
+
this.now = options.now ?? Date.now;
|
|
28
|
+
this.ttlMs = options.ttlMs ?? PLAN_CACHE_TTL_MS;
|
|
29
|
+
}
|
|
30
|
+
/** The signed-in user's id, read from the access token's `sub`. */
|
|
31
|
+
async currentUserId() {
|
|
32
|
+
const sub = decodeJwtClaims(await this.auth.getAccessToken())?.sub;
|
|
33
|
+
if (typeof sub !== "string" || !sub) {
|
|
34
|
+
throw new MicropageError("SESSION_EXPIRED", "The micropage access token has no user id. Run `micropage login` in a terminal, then retry.");
|
|
35
|
+
}
|
|
36
|
+
return sub;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Missing customer row means free, as in the CLI. A failed lookup is not
|
|
40
|
+
* cached and propagates, so a network blip does not read as "free plan".
|
|
41
|
+
*/
|
|
42
|
+
async getPlanTier() {
|
|
43
|
+
const userId = await this.currentUserId();
|
|
44
|
+
const hit = this.cache;
|
|
45
|
+
if (hit && hit.userId === userId && this.now() - hit.at < this.ttlMs)
|
|
46
|
+
return hit.tier;
|
|
47
|
+
this.inflight ??= (async () => {
|
|
48
|
+
const row = await this.http.selectOne("customers", {
|
|
49
|
+
select: "plan_tier",
|
|
50
|
+
filters: { user_id: eq(userId) },
|
|
51
|
+
});
|
|
52
|
+
const tier = isPaidTier(row?.plan_tier) ? row.plan_tier : "free";
|
|
53
|
+
this.cache = { userId, tier, at: this.now() };
|
|
54
|
+
return tier;
|
|
55
|
+
})().finally(() => {
|
|
56
|
+
this.inflight = null;
|
|
57
|
+
});
|
|
58
|
+
return this.inflight;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Throws PLAN_REQUIRED unless the account is pro or pro_plus. Deploy-token
|
|
62
|
+
* mode skips the lookup: deploy tokens can only be created on Pro+
|
|
63
|
+
* (create-deploy-token), so holding one already implies a paid plan.
|
|
64
|
+
*/
|
|
65
|
+
async requirePaidPlan() {
|
|
66
|
+
if (this.auth.mode === "deploy_token")
|
|
67
|
+
return;
|
|
68
|
+
const tier = await this.getPlanTier();
|
|
69
|
+
if (!isPaidTier(tier))
|
|
70
|
+
throw new MicropageError("PLAN_REQUIRED", UPGRADE_MESSAGE);
|
|
71
|
+
}
|
|
72
|
+
clearCache() {
|
|
73
|
+
this.cache = null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=tier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tier.js","sourceRoot":"","sources":["../../src/client/tier.ts"],"names":[],"mappings":"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"}
|