@rubytech/create-maxy-code 0.1.459 → 0.1.461
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/__tests__/cron-heartbeat-registration.test.js +57 -28
- package/dist/__tests__/launchd-plist.test.js +26 -0
- package/dist/cron-registration.js +30 -8
- package/dist/index.js +50 -9
- package/dist/launchd-plist.js +1 -1
- package/dist/uninstall.js +9 -3
- package/package.json +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +116 -6
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js +20 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.js +41 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/resource-limits.test.js.map +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts +1 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +116 -48
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.d.ts +1 -0
- package/payload/platform/lib/storage-broker/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.js +1 -0
- package/payload/platform/lib/storage-broker/dist/index.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts +27 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/object-limits.js +27 -7
- package/payload/platform/lib/storage-broker/dist/object-limits.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/resource-limits.d.ts +102 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.js +105 -0
- package/payload/platform/lib/storage-broker/dist/resource-limits.js.map +1 -0
- package/payload/platform/lib/storage-broker/src/__tests__/cf-exec.test.ts +130 -7
- package/payload/platform/lib/storage-broker/src/__tests__/object-limits.test.ts +30 -0
- package/payload/platform/lib/storage-broker/src/__tests__/resource-limits.test.ts +49 -0
- package/payload/platform/lib/storage-broker/src/cf-exec.ts +129 -56
- package/payload/platform/lib/storage-broker/src/index.ts +1 -0
- package/payload/platform/lib/storage-broker/src/object-limits.ts +26 -6
- package/payload/platform/lib/storage-broker/src/resource-limits.ts +105 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +5 -4
- package/payload/platform/plugins/cloudflare/PLUGIN.md +3 -1
- package/payload/platform/plugins/cloudflare/bin/portal-enrol.mjs +96 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-route.test.ts +220 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/authorize.test.ts +201 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/file-routes.test.ts +178 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/harness.test.ts +32 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/passcode.test.ts +204 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/portal-enrol.test.ts +207 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/ratelimit.test.ts +77 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/skill-contract.test.ts +46 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/template-config.test.ts +55 -0
- package/payload/platform/plugins/cloudflare/mcp/__tests__/upload-route.test.ts +255 -0
- package/payload/platform/plugins/cloudflare/mcp/package.json +5 -2
- package/payload/platform/plugins/cloudflare/references/r2-object-storage.md +46 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +125 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/authorize.ts +32 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/log.ts +12 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/passcode.mjs +193 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/ratelimit.ts +55 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/session.ts +85 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/_lib/types.ts +44 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/auth.ts +119 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/delete.ts +89 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/download.ts +70 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/files.ts +41 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/functions/api/upload.ts +151 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/index.html +42 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/portal.css +113 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/public/portal.js +155 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/schema.sql +73 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/template/wrangler.toml +21 -0
- package/payload/platform/plugins/cloudflare/skills/site-deploy/SKILL.md +8 -0
- package/payload/platform/plugins/docs/references/outlook-guide.md +3 -3
- package/payload/platform/plugins/docs/references/voice-mirror-guide.md +1 -0
- package/payload/platform/plugins/filesystem/PLUGIN.md +0 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts +14 -10
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.d.ts.map +1 -1
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js +14 -10
- package/payload/platform/plugins/filesystem/mcp/dist/lib/scope.js.map +1 -1
- package/payload/platform/plugins/outlook/.claude-plugin/plugin.json +1 -1
- package/payload/platform/plugins/outlook/PLUGIN.md +3 -5
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.js +75 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/detach.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.js +187 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-removal.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit.test.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/index.js +3 -2
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.d.ts +43 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.js +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attachment-list.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.d.ts +65 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.js +99 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/detach.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts +6 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts +42 -22
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js +59 -23
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts +15 -16
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js +8 -41
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-attachment.js.map +1 -1
- package/payload/platform/plugins/outlook/references/auth.md +1 -1
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +7 -3
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +1 -1
- package/payload/platform/plugins/storage-broker/PLUGIN.md +1 -1
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +19 -7
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js +35 -16
- package/payload/platform/plugins/voice-mirror/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.d.ts +31 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.d.ts.map +1 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.js +87 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/lib/admin-user.js.map +1 -0
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js +111 -12
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-distil-profile.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js +2 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-ingest-session-text.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js +2 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-record-feedback.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js +54 -21
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-retrieve-conditioning.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts +2 -2
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.d.ts.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js +12 -3
- package/payload/platform/plugins/voice-mirror/mcp/dist/tools/voice-tag-content.js.map +1 -1
- package/payload/platform/plugins/voice-mirror/mcp/scripts/smoke.mjs +444 -0
- package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +18 -2
- package/payload/platform/scripts/__tests__/logs-rotate.test.sh +256 -0
- package/payload/platform/scripts/__tests__/resume-tunnel.test.sh +79 -0
- package/payload/platform/scripts/logs-rotate.sh +204 -0
- package/payload/platform/scripts/resume-tunnel.sh +27 -0
- package/payload/platform/scripts/voice-author-key-audit.sh +155 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +41 -5
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.d.ts +1 -0
- package/payload/platform/services/claude-session-manager/dist/index.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +22 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.d.ts +2 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.js +12 -0
- package/payload/platform/services/claude-session-manager/dist/install-start-counter.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.d.ts +17 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.js +88 -0
- package/payload/platform/services/claude-session-manager/dist/mcp-descriptor-resolve.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts +122 -9
- package/payload/platform/services/claude-session-manager/dist/pty-census.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-census.js +214 -29
- package/payload/platform/services/claude-session-manager/dist/pty-census.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +0 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +36 -24
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +13 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.js +4 -2
- package/payload/platform/services/claude-session-manager/dist/session-memory-cap.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts +150 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +333 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.d.ts +10 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.js +55 -0
- package/payload/platform/services/claude-session-manager/dist/start-counter.js.map +1 -0
- package/payload/server/{chunk-Q6W4U6HL.js → chunk-JXWFVE5X.js} +83 -39
- package/payload/server/server.js +268 -73
- package/payload/server/{src-3I2RYZFB.js → src-4F37OHLK.js} +11 -1
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.d.ts +0 -2
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js +0 -215
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/pkce-flow.test.js.map +0 -1
|
@@ -48,6 +48,7 @@ export interface CfExec {
|
|
|
48
48
|
r2List(): Promise<{ name: string }[]>;
|
|
49
49
|
r2Create(name: string): Promise<void>;
|
|
50
50
|
r2ObjectList(bucket: string, prefix?: string): Promise<R2Object[]>;
|
|
51
|
+
r2ObjectFind(bucket: string, key: string): Promise<R2Object | null>;
|
|
51
52
|
r2ObjectGet(bucket: string, key: string): Promise<Uint8Array>;
|
|
52
53
|
r2ObjectPut(bucket: string, key: string, body: Uint8Array): Promise<void>;
|
|
53
54
|
r2ObjectDelete(bucket: string, key: string): Promise<void>;
|
|
@@ -122,6 +123,81 @@ async function cfApiJson(
|
|
|
122
123
|
return { parsed, body };
|
|
123
124
|
}
|
|
124
125
|
|
|
126
|
+
// The one pagination loop for the object listing, shared by r2ObjectList and
|
|
127
|
+
// r2ObjectFind so the two cannot drift apart on cursor discipline — the same
|
|
128
|
+
// reason objectUrl is shared by get/put/delete.
|
|
129
|
+
//
|
|
130
|
+
// 1665's rules live here and nowhere else: follow the cursor to exhaustion,
|
|
131
|
+
// is_truncated is authoritative when present, a truncated page naming no cursor
|
|
132
|
+
// is an anomaly to surface rather than a partial list, and a repeated cursor is
|
|
133
|
+
// a cycle rather than progress. per_page is deliberately never sent — its
|
|
134
|
+
// default is unconfirmed, and omitting it takes whatever page size Cloudflare
|
|
135
|
+
// serves rather than guessing a value.
|
|
136
|
+
//
|
|
137
|
+
// It yields pages rather than returning a list, which is what lets r2ObjectFind
|
|
138
|
+
// stop early: an async generator is pull-based, so a consumer that stops
|
|
139
|
+
// iterating never triggers the next request.
|
|
140
|
+
async function* r2ObjectPages(
|
|
141
|
+
fetchFn: FetchFn,
|
|
142
|
+
cred: HouseCredential,
|
|
143
|
+
bucket: string,
|
|
144
|
+
prefix?: string,
|
|
145
|
+
): AsyncGenerator<R2Object[]> {
|
|
146
|
+
const base = `https://api.cloudflare.com/client/v4/accounts/${cred.accountId}/r2/buckets/${encodeURIComponent(bucket)}/objects`;
|
|
147
|
+
const seen = new Set<string>();
|
|
148
|
+
let cursor: string | undefined;
|
|
149
|
+
for (;;) {
|
|
150
|
+
const params = new URLSearchParams();
|
|
151
|
+
if (prefix !== undefined) params.set("prefix", prefix);
|
|
152
|
+
if (cursor !== undefined) params.set("cursor", cursor);
|
|
153
|
+
const qs = params.toString();
|
|
154
|
+
const { parsed, body } = await cfApiJson(
|
|
155
|
+
fetchFn,
|
|
156
|
+
qs ? `${base}?${qs}` : base,
|
|
157
|
+
{ method: "GET", headers: { Authorization: `Bearer ${cred.apiToken}` } },
|
|
158
|
+
"r2 object list",
|
|
159
|
+
);
|
|
160
|
+
const page = parsed as {
|
|
161
|
+
result?: Array<{ key: string; size: number; etag: string; last_modified: string }>;
|
|
162
|
+
result_info?: { cursor?: string | null; is_truncated?: boolean };
|
|
163
|
+
};
|
|
164
|
+
const objects: R2Object[] = [];
|
|
165
|
+
for (const o of page.result ?? []) {
|
|
166
|
+
objects.push({ key: o.key, size: o.size, etag: o.etag, lastModified: o.last_modified });
|
|
167
|
+
}
|
|
168
|
+
yield objects;
|
|
169
|
+
const truncated = page.result_info?.is_truncated;
|
|
170
|
+
const next = page.result_info?.cursor;
|
|
171
|
+
// is_truncated is authoritative *when present*: a last page may still carry
|
|
172
|
+
// a cursor, and following it would issue a pointless request or trip the
|
|
173
|
+
// cycle guard below on a healthy response.
|
|
174
|
+
if (truncated === false) return;
|
|
175
|
+
// The API says there is more but names no cursor: the response is not the
|
|
176
|
+
// shape we understand. Surface it rather than stop, which would silently
|
|
177
|
+
// truncate the audit's listing — the failure this loop exists to prevent.
|
|
178
|
+
if (truncated === true && !next) {
|
|
179
|
+
throw new Error(
|
|
180
|
+
`storage-broker: r2 object list reported truncated but gave no pagination cursor: ${body}`,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
// is_truncated absent: it is itself an unmeasured fact, so it must not be
|
|
184
|
+
// what decides correctness. Fall back to cursor presence — the rule r2List
|
|
185
|
+
// uses on the same envelope — so an envelope without the flag paginates
|
|
186
|
+
// rather than truncating silently.
|
|
187
|
+
if (!next) return;
|
|
188
|
+
// A cursor seen before means the API is cycling rather than advancing: it
|
|
189
|
+
// would loop forever, re-yielding the same objects each pass. Tracking every
|
|
190
|
+
// cursor (not just the last) also catches a cycle of period >= 2.
|
|
191
|
+
if (seen.has(next)) {
|
|
192
|
+
throw new Error(
|
|
193
|
+
`storage-broker: r2 object list returned a repeated pagination cursor: ${next}`,
|
|
194
|
+
);
|
|
195
|
+
}
|
|
196
|
+
seen.add(next);
|
|
197
|
+
cursor = next;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
125
201
|
export function makeCfExec(
|
|
126
202
|
cred: HouseCredential,
|
|
127
203
|
run: RunFn = defaultRun,
|
|
@@ -233,65 +309,62 @@ export function makeCfExec(
|
|
|
233
309
|
// The r2/api/tokens/ note that "Object Read & Write" is S3-only constrains
|
|
234
310
|
// the bucket-item-scoped *permission types*, not these endpoints.
|
|
235
311
|
//
|
|
236
|
-
//
|
|
237
|
-
//
|
|
238
|
-
//
|
|
312
|
+
// The endpoint's existence and token class are doc-confirmed. Its behaviour
|
|
313
|
+
// was unmeasured until Task 1697, which probed the live v4 API on 2026-07-16
|
|
314
|
+
// from the laptop's own maxy-code install (no house Cloudflare credential
|
|
315
|
+
// exists on the dev Mac, which is why the tests fake the fetch seam). What
|
|
316
|
+
// that probe established, so nobody re-guesses it:
|
|
317
|
+
//
|
|
318
|
+
// - The listing is NOT in ascending UTF-8 binary order. `prefix=a/photo.jpg`
|
|
319
|
+
// returns `a/photo.jpg.bak` before `a/photo.jpg`, so the exact key is not
|
|
320
|
+
// the first element and `per_page=1` returns a neighbour. S3's
|
|
321
|
+
// ListObjectsV2 ordering guarantee does not transfer to this endpoint.
|
|
322
|
+
// - HEAD is routed only for single-segment keys: any key containing "/"
|
|
323
|
+
// returns 405 while GET on the identical URL returns 404.
|
|
324
|
+
// - A ranged GET is honoured at 2 MB and ignored at 12 B, so it cannot size
|
|
325
|
+
// an object without an undocumented size threshold deciding correctness.
|
|
326
|
+
// - per_page is bounded 0..1000 (error 10029), and per_page=0 returns zero
|
|
327
|
+
// objects with is_truncated:true — a loop trap. It is still never sent.
|
|
328
|
+
//
|
|
329
|
+
// The design keeps every unmeasured fact unable to affect correctness:
|
|
330
|
+
// per_page is never sent, order is never assumed, and nothing fails
|
|
331
|
+
// silently. Evidence:
|
|
332
|
+
// docs/superpowers/specs/2026-07-16-storage-broker-get-sizing-early-exit-design.md
|
|
239
333
|
async r2ObjectList(bucket, prefix) {
|
|
240
|
-
//
|
|
241
|
-
//
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
334
|
+
// The standing audit needs the complete set: a truncated object listing
|
|
335
|
+
// reads as "those objects are absent", producing false phantom rows and
|
|
336
|
+
// hiding real orphans — the same bug r2List had, one endpoint over. So
|
|
337
|
+
// this drains every page. r2ObjectFind is the caller that needs one
|
|
338
|
+
// record and must not pay for this.
|
|
245
339
|
const objects: R2Object[] = [];
|
|
246
|
-
const
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
// The API says there is more but names no cursor: the response is not
|
|
273
|
-
// the shape we understand. Surface it rather than return a partial
|
|
274
|
-
// list, which is the failure this loop exists to prevent.
|
|
275
|
-
if (truncated === true && !next) {
|
|
276
|
-
throw new Error(
|
|
277
|
-
`storage-broker: r2 object list reported truncated but gave no pagination cursor: ${body}`,
|
|
278
|
-
);
|
|
279
|
-
}
|
|
280
|
-
// is_truncated absent: it is itself an unmeasured fact, so it must not
|
|
281
|
-
// be what decides correctness. Fall back to cursor presence — the rule
|
|
282
|
-
// r2List uses on the same envelope — so an envelope without the flag
|
|
283
|
-
// paginates rather than truncating silently.
|
|
284
|
-
if (!next) return objects;
|
|
285
|
-
// A cursor seen before means the API is cycling rather than advancing:
|
|
286
|
-
// it would loop forever, re-appending the same objects each pass.
|
|
287
|
-
if (seen.has(next)) {
|
|
288
|
-
throw new Error(
|
|
289
|
-
`storage-broker: r2 object list returned a repeated pagination cursor: ${next}`,
|
|
290
|
-
);
|
|
291
|
-
}
|
|
292
|
-
seen.add(next);
|
|
293
|
-
cursor = next;
|
|
340
|
+
for await (const page of r2ObjectPages(fetchFn, cred, bucket, prefix)) {
|
|
341
|
+
for (const o of page) objects.push(o);
|
|
342
|
+
}
|
|
343
|
+
return objects;
|
|
344
|
+
},
|
|
345
|
+
async r2ObjectFind(bucket, key) {
|
|
346
|
+
// Task 1697 — the size source for a get. Scans each page for the exact key
|
|
347
|
+
// and returns on the first match, so it holds one page rather than every
|
|
348
|
+
// object sharing the key as a prefix, and issues no request past the page
|
|
349
|
+
// carrying the match.
|
|
350
|
+
//
|
|
351
|
+
// Exact match, not prefix: a prefix of `a/photo.jpg` also matches
|
|
352
|
+
// `a/photo.jpg.bak`, and sizing the neighbour would either reject a legal
|
|
353
|
+
// get or admit an oversized one. Exiting on the match rather than on
|
|
354
|
+
// position is what makes this independent of listing order — measured
|
|
355
|
+
// 2026-07-16 against the live v4 API, the listing is NOT in ascending
|
|
356
|
+
// UTF-8 binary order, so the exact key is not the first element and
|
|
357
|
+
// `per_page=1` returns a neighbour.
|
|
358
|
+
//
|
|
359
|
+
// An absent key still drains every page, because proving absence requires
|
|
360
|
+
// seeing them all. That residual is accepted and permanent; nothing on
|
|
361
|
+
// this endpoint sizes one object in bounded work. See
|
|
362
|
+
// docs/superpowers/specs/2026-07-16-storage-broker-get-sizing-early-exit-design.md
|
|
363
|
+
for await (const page of r2ObjectPages(fetchFn, cred, bucket, key)) {
|
|
364
|
+
const hit = page.find((o) => o.key === key);
|
|
365
|
+
if (hit) return hit;
|
|
294
366
|
}
|
|
367
|
+
return null;
|
|
295
368
|
},
|
|
296
369
|
async r2ObjectGet(bucket, key) {
|
|
297
370
|
// The one method cfApiJson cannot wrap: get returns the raw object body,
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
export * from "./authorize.js";
|
|
10
10
|
export * from "./object-limits.js";
|
|
11
11
|
export * from "./registry.js";
|
|
12
|
+
export * from "./resource-limits.js";
|
|
12
13
|
export * from "./house-credential.js";
|
|
13
14
|
export * from "./cf-exec.js";
|
|
14
15
|
export * from "./house-scoped-token.js";
|
|
@@ -37,12 +37,32 @@ export function base64Ceiling(bytes: number): number {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
40
|
+
* The largest JSON body an object route's metadata envelope can legitimately
|
|
41
|
+
* carry: a bucket name, one key or prefix, and JSON punctuation. No payload term.
|
|
42
|
+
*
|
|
43
|
+
* Used twice, because it answers one question — how large can an envelope
|
|
44
|
+
* legitimately get. `/r2/object/list`, `get` and `delete` carry an envelope and
|
|
45
|
+
* nothing else, so this is their whole body limit (Task 1698). `put` carries the
|
|
46
|
+
* same envelope plus base64 object bytes, so it adds this to the base64 ceiling
|
|
47
|
+
* below, which is the allowance that keeps a legal at-cap put from being rejected
|
|
48
|
+
* by its own key.
|
|
49
|
+
*
|
|
50
|
+
* Deliberately one constant rather than two that happen to match. This is the
|
|
51
|
+
* opposite ruling to the email cap above, for the opposite reason: that pair must
|
|
52
|
+
* stay independent because the numbers track different constraints and would drift
|
|
53
|
+
* for different reasons. Here the constraint is identical, so a change to either
|
|
54
|
+
* use must move both.
|
|
55
|
+
*
|
|
56
|
+
* Sized from confirmed R2 limits rather than assumed. An object key is at most
|
|
57
|
+
* 1,024 bytes (developers.cloudflare.com/r2/platform/limits/) and a bucket name is
|
|
58
|
+
* 3-63 characters (developers.cloudflare.com/r2/buckets/create-buckets/), so a
|
|
59
|
+
* real envelope is about 1.1 KB, or roughly 6 KiB if every key byte takes a
|
|
60
|
+
* six-character \uXXXX escape. This clears that by about ten times and sits about
|
|
61
|
+
* 2,100 times below put's body limit: far above anything a real caller sends, far
|
|
62
|
+
* below anything that threatens house memory. Nothing here depends on either
|
|
63
|
+
* figure being exact.
|
|
44
64
|
*/
|
|
45
|
-
const
|
|
65
|
+
export const R2_OBJECT_ENVELOPE_MAX_BODY_BYTES = 64 * 1024;
|
|
46
66
|
|
|
47
67
|
/**
|
|
48
68
|
* The largest JSON body /r2/object/put can legitimately carry.
|
|
@@ -51,7 +71,7 @@ const PUT_ENVELOPE_ALLOWANCE_BYTES = 64 * 1024;
|
|
|
51
71
|
* decoded-byte rule lives in the MCP, which knows the source's size exactly.
|
|
52
72
|
*/
|
|
53
73
|
export const R2_OBJECT_PUT_MAX_BODY_BYTES =
|
|
54
|
-
base64Ceiling(R2_OBJECT_MAX_BYTES) +
|
|
74
|
+
base64Ceiling(R2_OBJECT_MAX_BYTES) + R2_OBJECT_ENVELOPE_MAX_BODY_BYTES;
|
|
55
75
|
|
|
56
76
|
function formatMiB(bytes: number): string {
|
|
57
77
|
return (bytes / 1024 / 1024).toFixed(1);
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bounds on the request bodies the resource-level routes will buffer:
|
|
3
|
+
* /d1/create, /d1/query and /r2/create (Task 1702).
|
|
4
|
+
*
|
|
5
|
+
* Parallel to object-limits.ts, which bounds the object routes. Kept separate
|
|
6
|
+
* because the facts differ: an object envelope is dominated by R2's 1,024-byte
|
|
7
|
+
* object key limit, and these routes carry no key at all. Coupling them would tie
|
|
8
|
+
* a D1 database name to R2's key limit, so a revision to either would move a bound
|
|
9
|
+
* that has no reason to move.
|
|
10
|
+
*
|
|
11
|
+
* These are bounds on house memory. They do not adjudicate the last byte of a name
|
|
12
|
+
* or a statement — the same stance object-limits.ts takes, for the same reason. The
|
|
13
|
+
* house process holds the account-wide credential, so an out-of-memory kill there
|
|
14
|
+
* takes the brand server down, and on the target hardware that is a hard freeze
|
|
15
|
+
* that emits nothing.
|
|
16
|
+
*
|
|
17
|
+
* Deliberately dependency-free, like object-limits.ts: the ui route reads these
|
|
18
|
+
* through the barrel, and nothing here needs the rest of the lib.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Cloudflare's documented maximum SQL statement length.
|
|
23
|
+
*
|
|
24
|
+
* "Maximum SQL statement length | 100,000 bytes (100 KB)"
|
|
25
|
+
* https://developers.cloudflare.com/d1/platform/limits/
|
|
26
|
+
*
|
|
27
|
+
* Recorded because it is what governs a legitimate `sql`, and because
|
|
28
|
+
* D1_QUERY_MAX_BODY_BYTES below is only knowable as safe by way of it.
|
|
29
|
+
*
|
|
30
|
+
* A second, independent bound applies to the same string on the way out, and is
|
|
31
|
+
* recorded here so nobody rediscovers it as a bug: cf-exec's d1Query shells
|
|
32
|
+
* `execFile("npx", ["wrangler", "d1", "execute", ..., "--command", sql])`, so the
|
|
33
|
+
* decoded statement travels as a single argv element. Linux caps one such element
|
|
34
|
+
* at MAX_ARG_STRLEN = 131,072 bytes and fails execve with E2BIG above it
|
|
35
|
+
* (https://man7.org/linux/man-pages/man2/execve.2.html). That is a property of the
|
|
36
|
+
* spawn path, not of D1. It does not bind first: 100,000 < 131,072, so a statement
|
|
37
|
+
* D1 would accept always fits the spawn.
|
|
38
|
+
*/
|
|
39
|
+
export const D1_MAX_SQL_STATEMENT_BYTES = 100_000;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* The worst-case expansion of a byte when it is serialized into a JSON string.
|
|
43
|
+
*
|
|
44
|
+
* A single ASCII control byte becomes the six characters \uXXXX. Multi-byte
|
|
45
|
+
* characters are cheaper per byte — a 3-byte UTF-8 character also becomes six
|
|
46
|
+
* characters (2x), and a non-BMP character becomes a 12-character surrogate pair
|
|
47
|
+
* for 4 bytes (3x) — so 6 is a true ceiling over bytes, reached by ASCII.
|
|
48
|
+
*
|
|
49
|
+
* This is why a body limit is not simply the statement limit: the cap bounds the
|
|
50
|
+
* wire body, and escaping moves the two apart.
|
|
51
|
+
*/
|
|
52
|
+
export const JSON_STRING_WORST_CASE_EXPANSION = 6;
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* The largest JSON body /d1/create or /r2/create can legitimately carry: a single
|
|
56
|
+
* resource name and JSON punctuation. No payload term, and no key term.
|
|
57
|
+
*
|
|
58
|
+
* Sized on headroom rather than a tight derivation, because only one of the two
|
|
59
|
+
* names it bounds has a documented hard limit:
|
|
60
|
+
*
|
|
61
|
+
* - R2 bucket name: 3-63 characters, lowercase letters, digits and hyphens.
|
|
62
|
+
* https://developers.cloudflare.com/r2/buckets/create-buckets/
|
|
63
|
+
* - D1 database name: no documented limit. The API reference states only
|
|
64
|
+
* `name: string`, "D1 database name" — no maxLength, pattern or charset.
|
|
65
|
+
* https://developers.cloudflare.com/api/resources/d1/subresources/database/
|
|
66
|
+
* The get-started guide gives guidance, not a limit: names "should use a
|
|
67
|
+
* combination of ASCII characters, shorter than 32 characters".
|
|
68
|
+
*
|
|
69
|
+
* So a real body is under ~100 bytes and 64 KiB clears it by roughly 650x, while
|
|
70
|
+
* sitting ~2,100x below put's body limit: far above anything a real caller sends,
|
|
71
|
+
* far below anything that threatens house memory. Nothing here depends on either
|
|
72
|
+
* figure being exact. An undocumented D1 name limit cannot plausibly approach 64
|
|
73
|
+
* KiB, and a request body is the wrong place to discover that it had.
|
|
74
|
+
*
|
|
75
|
+
* Deliberately not R2_OBJECT_ENVELOPE_MAX_BODY_BYTES, which happens to hold the
|
|
76
|
+
* same value. That constant is governed by R2's object key limit; this one is not,
|
|
77
|
+
* and the two must be free to drift. Couple what is one fact; separate what merely
|
|
78
|
+
* matches today.
|
|
79
|
+
*/
|
|
80
|
+
export const STORAGE_RESOURCE_NAME_MAX_BODY_BYTES = 64 * 1024;
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* The largest JSON body /d1/query can legitimately carry: a database name and a
|
|
84
|
+
* SQL statement.
|
|
85
|
+
*
|
|
86
|
+
* The envelope reasoning behind the object routes' bound does not govern this. A
|
|
87
|
+
* large but entirely legal migration or batch statement is not a 1,024-byte object
|
|
88
|
+
* key, so applying that constant here would reject correct queries.
|
|
89
|
+
*
|
|
90
|
+
* The bound is on the wire body, not the decoded statement, and JSON escaping moves
|
|
91
|
+
* those apart. The largest legal statement (D1_MAX_SQL_STATEMENT_BYTES) can present
|
|
92
|
+
* a wire body of up to
|
|
93
|
+
*
|
|
94
|
+
* JSON_STRING_WORST_CASE_EXPANSION * 100,000 + 65,536 = 665,536 bytes
|
|
95
|
+
*
|
|
96
|
+
* so capping at 100,000 would 413 a statement D1 would have accepted. 1 MiB clears
|
|
97
|
+
* the worst case by about 1.6x and sits ~133x below put's body limit.
|
|
98
|
+
*
|
|
99
|
+
* It is a flat number rather than that expression, for legibility at the call site.
|
|
100
|
+
* The arithmetic is not decoration: it is the only thing that makes 1 MiB knowable
|
|
101
|
+
* as safe, so resource-limits.test.ts asserts the relationship. If D1 ever raises
|
|
102
|
+
* its statement limit above 163,840 bytes, that test fails rather than this route
|
|
103
|
+
* quietly beginning to refuse legal SQL.
|
|
104
|
+
*/
|
|
105
|
+
export const D1_QUERY_MAX_BODY_BYTES = 1024 * 1024;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:c7d0d9f383f8d9ee8ed50b8869ce43477e3cb5ebf62305a39041c072e34bfeb1
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -2057,7 +2057,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
2057
2057
|
| `outlook-mail-list` | Mail newest-first. Default top=25, folder=Inbox. `folder` takes a well-known name or alias (case/space-insensitive): Inbox, Sent Items/sent, Drafts, Deleted Items/trash, Junk Email/junk/spam, Archive, Outbox — or a raw folder id. Custom folder names are not resolved. `since`/`before` (ISO date-times) narrow by receipt date. The 250-per-page figure is not a ceiling: the reply carries `nextCursor` when older mail remains — pass it back as `cursor` to page further back. Filter by sender/subject via `outlook-mail-search`. |
|
|
2058
2058
|
| `outlook-mail-search` | Microsoft Graph `$search` (KQL) over the mailbox. Preview-only, like `outlook-mail-list`, and pages the same way via `nextCursor`/`cursor`. Put sender/subject/recipient/date filters in the query: `from:`, `to:`, `subject:`, `received>=`. Graph forbids `$search`+`$filter`, so a strict date window is on `outlook-mail-list`. |
|
|
2059
2059
|
| `outlook-mail-fetch-body` | Complete body of one message by id (`GET /me/messages/{id}?$select=body`). HTML decoded to text, no preview cap. The full-read path — list/search return Microsoft's ~255-char preview only. Returns the envelope plus the whole body. |
|
|
2060
|
-
| `outlook-mail-attachment` | List or download
|
|
2060
|
+
| `outlook-mail-attachment` | List or download any message's attachments — received or draft. With `messageId` alone, LISTS them (`id`, `name`, `contentType`, `size`, `isInline`, and a `kind` of file/item/reference) via `GET /me/messages/{id}/attachments` and `$select` (no bytes). A draft is a message in the same collection, so passing a draftId lists what that draft carries; the returned `name`/`id` is what `outlook-draft-edit`'s `removeAttachments` takes. With `messageId`+`attachmentId`, DOWNLOADS one file attachment: its bytes are written to `{accountDir}/uploads/outlook/<messageHash>/<bytesHash>-<name>` at `0o600` and the saved path is returned, ready for the Read tool, `SendUserFile`, or an `outlook-mail-reply` attachment. Only file attachments download — item/reference kinds, empty payloads, and anything over 25 MB are refused with nothing written. `outlook-mail-list` / `outlook-mail-search` / `outlook-mail-fetch-body` report `hasAttachments` so the agent knows when to call this. Needs `Mail.Read`. |
|
|
2061
2061
|
| `outlook-mail-reply` | Reply in-thread (`createReply` / `createReplyAll` + send). Threads natively via conversationId. `replyAll`, added cc/bcc, and account-scoped file attachments (up to 25 MB per file; files over Graph's ~3 MB inline limit use a chunked upload session). Body is plain text, prepended above the quote. Needs `Mail.ReadWrite` + `Mail.Send`. |
|
|
2062
2062
|
| `outlook-mail-delete` | Move messages to Deleted Items (`POST /me/messages/{id}/move`). Recoverable — never hard-deletes. An id already gone is counted not-moved. Needs `Mail.ReadWrite`. |
|
|
2063
2063
|
| `outlook-mail-otp-extract` | Poll the inbox for a one-time code from a sender (domain). Reads full bodies to extract it; optional subject regex and timeout (default 60 s). |
|
|
@@ -2073,7 +2073,7 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
2073
2073
|
| `outlook-mailbox-list` | Every connected mailbox with its email, scopes, and token health. Local only — no network — so it also flags a mailbox whose stored credentials cannot be read. |
|
|
2074
2074
|
| `outlook-mail-send` | Send a message. No attachments → `POST /me/sendMail`; with attachments → draft, attach, send (`/me/sendMail` carries attachments inline and cannot fit a large file). to/cc/bcc arrays, subject, body, isHtml, attachments (account-dir paths, 25 MB/file). Success asserted on Graph 202; no message id on either route. Needs `Mail.Send` (+ `Mail.ReadWrite` to attach). |
|
|
2075
2075
|
| `outlook-draft` | Create a draft in Drafts (`POST /me/messages`). Same body model, including attachments — files are attached before the id is returned, so the draft is ready to send. Returns the draft id. Needs `Mail.ReadWrite`. |
|
|
2076
|
-
| `outlook-draft-edit` | Update an existing draft in place (`PATCH /me/messages/{draftId}`) and/or add attachments. Graph drafts are mutable — pass draftId plus the fields to change (to/cc/bcc/subject/body/isHtml/attachments).
|
|
2076
|
+
| `outlook-draft-edit` | Update an existing draft in place (`PATCH /me/messages/{draftId}`) and/or add and remove attachments. Graph drafts are mutable — pass draftId plus the fields to change (to/cc/bcc/subject/body/isHtml/attachments/removeAttachments). `attachments` adds; `removeAttachments` takes files off by name or attachment id; both together replace a file in one call. Sent messages cannot be edited. Needs `Mail.ReadWrite`. |
|
|
2077
2077
|
| `outlook-draft-send` | Send an existing draft by id (`POST /me/messages/{id}/send`). Graph consumes the draft, returns 202. Needs `Mail.Send`. |
|
|
2078
2078
|
|
|
2079
2079
|
## Observability
|
|
@@ -2148,7 +2148,7 @@ Latency triage: `mail-list count=0 elapsedMs<200` consistent → permissions iss
|
|
|
2148
2148
|
|
|
2149
2149
|
## Out of scope
|
|
2150
2150
|
|
|
2151
|
-
Mail move to arbitrary folders / flag, hard (permanent) delete, removing
|
|
2151
|
+
Mail move to arbitrary folders / flag, hard (permanent) delete, removing an attachment from a **sent** message (Graph forbids it), ingesting reviewed mail into the business graph (the `email-ingest` analogue), contacts write, recurring-series occurrence editing, the `scheduling` Neo4j calendar and any auto-sync with it, OneDrive / Files, push notifications, on-premises Exchange, M365 admin scopes (`User.Read.All`, `AuditLog.Read.All`), public-agent exposure, multi-tenant federation. Mail read, full-body read, inbound attachment list/download, reply, delete (to Deleted Items), and compose — all four outbound paths carrying attachments, with a draft's attachments editable in place (add, remove, replace) — are supported (`outlook-mail-fetch-body`, `outlook-mail-attachment`, `outlook-mail-reply`, `outlook-mail-delete`, `outlook-mail-otp-extract`, `outlook-mail-send`, `outlook-draft`, `outlook-draft-edit`, `outlook-draft-send`), and the calendar is read plus control. See `platform/plugins/outlook/PLUGIN.md` for the full out-of-scope list.
|
|
2152
2152
|
|
|
2153
2153
|
---
|
|
2154
2154
|
# QuickBooks Online
|
|
@@ -2715,6 +2715,7 @@ Voice mirror is on by default for every drafting skill. To opt out for one, set
|
|
|
2715
2715
|
- **Copy public figures** — voice mirror only learns from your own writing.
|
|
2716
2716
|
- **Clone audio** — text only, no speech synthesis.
|
|
2717
2717
|
- **Guess** — historical content stays `unknown` until you mark it. Maxy never auto-classifies your writing. (Automatic ingestion applies only to your live PTY sessions where authorship is certain.)
|
|
2718
|
+
- **Invent an identity** — a voice belongs to a real admin user on the account. Attribution is refused outright if it names anyone else, and nothing is written. A person's name is not an identity, so tagging someone's writing on a shared account needs their actual admin id, not their name.
|
|
2718
2719
|
|
|
2719
2720
|
## Status
|
|
2720
2721
|
|
|
@@ -32,7 +32,9 @@ The plugin registers no agent-facing MCP tools. Every Cloudflare operation is th
|
|
|
32
32
|
| Skill | Purpose |
|
|
33
33
|
|---|---|
|
|
34
34
|
| [cloudflare/SKILL.md](skills/cloudflare/SKILL.md) | The entry point for every Cloudflare operation. Routes each operation class to its reference, names the outcome contracts (tunnel external HTTP 200; hosting deployed URL; D1 round-trip), and states the tool-discipline + secret-redaction rules that bind the agent. |
|
|
35
|
-
| [site-deploy/SKILL.md](skills/site-deploy/SKILL.md) | Assemble one canonical content tree into a live Pages deployment on its custom domain that survives the install device being offline. Names the single-source-tree convention, the reused per-scope token, the form-to-D1 trigger, Open Graph correctness, and the cache-busted live-`200` done-gate; defers the command blocks to `hosting-sites.md
|
|
35
|
+
| [site-deploy/SKILL.md](skills/site-deploy/SKILL.md) | Assemble one canonical content tree into a live Pages deployment on its custom domain that survives the install device being offline. Names the single-source-tree convention, the reused per-scope token, the form-to-D1 trigger, the R2 binding trigger, Open Graph correctness, and the cache-busted live-`200` done-gate; defers the command blocks to `hosting-sites.md`, `d1-data-capture.md` and `r2-object-storage.md`. Admin-run; a content specialist hands the deploy off to it. |
|
|
36
|
+
| [calendar-site/SKILL.md](skills/calendar-site/SKILL.md) | Assemble and deploy one account's public booking page — a static page plus edge Functions over a D1 `bookings` table — on its own custom domain, so a submission survives the install device being offline. Admin-run; defers the deploy and the live done-gate to `site-deploy`. |
|
|
37
|
+
| [data-portal/SKILL.md](skills/data-portal/SKILL.md) | Stand up one account's gated file-drop portal on its own custom domain: each enrolled person signs in with their own passcode and can upload, list, download and delete only their own files, into one R2 bucket under a per-person prefix with a D1 manifest row per upload. The bucket is created by the owning account's own session — the broker registers ownership to the creator and never reassigns it — while the admin assembles and deploys. Admin-run; defers the deploy and the live done-gate to `site-deploy`. |
|
|
36
38
|
|
|
37
39
|
### References
|
|
38
40
|
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Enrol one person into a data portal: mint a passcode, hash it, print the
|
|
3
|
+
// passcode once, and emit the INSERT.
|
|
4
|
+
//
|
|
5
|
+
// This exists because "generate a high-entropy passcode" in a skill's prose is
|
|
6
|
+
// not a constraint — an agent satisfies that sentence with a six-digit number.
|
|
7
|
+
// The strength now lives in code (template/functions/api/_lib/passcode.mjs), and
|
|
8
|
+
// this is the entry point that applies it. Hashing a passcode any other way
|
|
9
|
+
// bypasses the pinned length, so enrolment runs through here.
|
|
10
|
+
//
|
|
11
|
+
// It deliberately stops at emitting SQL. Running `wrangler d1 execute` itself
|
|
12
|
+
// would put the script's only path behind a Cloudflare credential and a network,
|
|
13
|
+
// which makes it untestable; the skill pipes stdout instead.
|
|
14
|
+
//
|
|
15
|
+
// The stream split is load-bearing, not cosmetic. stdout is SQL and NOTHING else,
|
|
16
|
+
// so it pipes straight into wrangler. The passcode goes to stderr: the operator
|
|
17
|
+
// still reads it off their terminal, but no pipe can carry it into wrangler,
|
|
18
|
+
// which would echo it back inside a syntax error — a plaintext passcode in a log
|
|
19
|
+
// is the one thing enrolment must never produce.
|
|
20
|
+
//
|
|
21
|
+
// Usage:
|
|
22
|
+
// node platform/plugins/cloudflare/bin/portal-enrol.mjs --owner <ownerId> --name <name> \
|
|
23
|
+
// | npx wrangler d1 execute <db> --remote --file=-
|
|
24
|
+
|
|
25
|
+
import {
|
|
26
|
+
PASSCODE_LENGTH,
|
|
27
|
+
generatePasscode,
|
|
28
|
+
hashPasscode,
|
|
29
|
+
randomSaltHex,
|
|
30
|
+
} from '../skills/data-portal/template/functions/api/_lib/passcode.mjs'
|
|
31
|
+
|
|
32
|
+
/** The isolation rule is that an R2 key starts with `<ownerId>/`, so an ownerId
|
|
33
|
+
* containing a separator would let one person read another's files. The gate
|
|
34
|
+
* enforces this too; enrolment refuses rather than minting a row the gate will
|
|
35
|
+
* later reject. */
|
|
36
|
+
const OWNER_ID = /^[a-z0-9-]+$/
|
|
37
|
+
|
|
38
|
+
/** @param {string} message */
|
|
39
|
+
function die(message) {
|
|
40
|
+
console.error(`portal-enrol: ${message}`)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** @param {string} flag @returns {string | undefined} */
|
|
45
|
+
function arg(flag) {
|
|
46
|
+
const i = process.argv.indexOf(flag)
|
|
47
|
+
return i === -1 ? undefined : process.argv[i + 1]
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const ownerId = arg('--owner')
|
|
51
|
+
const name = arg('--name')
|
|
52
|
+
|
|
53
|
+
if (!ownerId) die('--owner <id> is required')
|
|
54
|
+
if (!name) die('--name <name> is required')
|
|
55
|
+
if (!OWNER_ID.test(/** @type {string} */ (ownerId))) {
|
|
56
|
+
die(`--owner must match ${OWNER_ID.source} — the isolation gate rejects anything else`)
|
|
57
|
+
}
|
|
58
|
+
// A newline in the name would split the one statement stdout promises to be.
|
|
59
|
+
// It is not an injection — SQLite has no escape but '' and a newline sits inside
|
|
60
|
+
// the literal harmlessly — but it breaks the pipeable contract, so refuse it.
|
|
61
|
+
if (/[\r\n]/.test(/** @type {string} */ (name))) die('--name cannot contain a line break')
|
|
62
|
+
|
|
63
|
+
const saltBytes = new Uint8Array(16)
|
|
64
|
+
crypto.getRandomValues(saltBytes)
|
|
65
|
+
const saltHex = randomSaltHex(saltBytes)
|
|
66
|
+
|
|
67
|
+
const passcode = generatePasscode()
|
|
68
|
+
const hash = await hashPasscode(passcode, saltHex)
|
|
69
|
+
|
|
70
|
+
// Doubled single quotes: the only SQL literal here not already constrained to a
|
|
71
|
+
// charset is the name, which comes from an operator.
|
|
72
|
+
const sqlName = /** @type {string} */ (name).replace(/'/g, "''")
|
|
73
|
+
const createdAt = new Date().toISOString()
|
|
74
|
+
|
|
75
|
+
// stdout: the statement, alone, pipeable.
|
|
76
|
+
//
|
|
77
|
+
// An upsert, not a bare INSERT, because `ownerId` is TEXT PRIMARY KEY and
|
|
78
|
+
// re-running this script for an existing person IS the rotation path — the only
|
|
79
|
+
// one there is. A bare INSERT dies on the UNIQUE violation inside wrangler, a
|
|
80
|
+
// separate process, AFTER this script has already printed a new passcode and
|
|
81
|
+
// exited 0: the operator hands out a passcode that never worked, believing the
|
|
82
|
+
// leaked one is revoked, while it and its sessions keep resolving. Rotation is
|
|
83
|
+
// precisely the case where a remedy that silently no-ops is worse than none.
|
|
84
|
+
//
|
|
85
|
+
// `createdAt` is deliberately absent from the DO UPDATE: it records when the
|
|
86
|
+
// person was enrolled, and rotating a passcode is not creating a person.
|
|
87
|
+
console.log(
|
|
88
|
+
`INSERT INTO people (ownerId, name, salt, hash, createdAt) VALUES ('${ownerId}', '${sqlName}', '${saltHex}', '${hash}', '${createdAt}') ON CONFLICT(ownerId) DO UPDATE SET name=excluded.name, salt=excluded.salt, hash=excluded.hash;`,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
// stderr: everything for the human. Never stdout — see the note at the top.
|
|
92
|
+
console.error(`passcode: ${passcode}`)
|
|
93
|
+
console.error('')
|
|
94
|
+
console.error(
|
|
95
|
+
`Give the passcode to ${name} out of band, and do not store it anywhere. It is ${PASSCODE_LENGTH} characters and it is not recoverable — re-running this rotates it, which also revokes their live sessions.`,
|
|
96
|
+
)
|