@rubytech/create-maxy-code 0.1.458 → 0.1.460
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/package.json +1 -1
- package/payload/platform/lib/storage-broker/dist/__tests__/cf-exec.test.js +298 -3
- 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.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js +79 -0
- package/payload/platform/lib/storage-broker/dist/__tests__/object-limits.test.js.map +1 -0
- 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 +13 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/cf-exec.js +165 -0
- package/payload/platform/lib/storage-broker/dist/cf-exec.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/house-credential.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/house-credential.js +4 -2
- package/payload/platform/lib/storage-broker/dist/house-credential.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.d.ts +2 -0
- package/payload/platform/lib/storage-broker/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/storage-broker/dist/index.js +2 -0
- package/payload/platform/lib/storage-broker/dist/index.js.map +1 -1
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts +80 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.d.ts.map +1 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.js +92 -0
- package/payload/platform/lib/storage-broker/dist/object-limits.js.map +1 -0
- 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 +334 -5
- package/payload/platform/lib/storage-broker/src/__tests__/object-limits.test.ts +106 -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 +221 -6
- package/payload/platform/lib/storage-broker/src/house-credential.ts +4 -2
- package/payload/platform/lib/storage-broker/src/index.ts +2 -0
- package/payload/platform/lib/storage-broker/src/object-limits.ts +95 -0
- package/payload/platform/lib/storage-broker/src/resource-limits.ts +105 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +7 -6
- package/payload/platform/plugins/cloudflare/PLUGIN.md +3 -1
- package/payload/platform/plugins/cloudflare/bin/cf-token.sh +4 -3
- package/payload/platform/plugins/cloudflare/mcp/__tests__/auth-route.test.ts +206 -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 +74 -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/api.md +1 -1
- package/payload/platform/plugins/cloudflare/references/r2-object-storage.md +46 -0
- package/payload/platform/plugins/cloudflare/skills/data-portal/SKILL.md +123 -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.ts +73 -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 +113 -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 +64 -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 +5 -5
- 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__/attach.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/attach.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/attach.test.js +138 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/attach.test.js.map +1 -0
- 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-attachments.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-attachments.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-attachments.test.js +121 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-attachments.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-attachments.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-attachments.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-attachments.test.js +102 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/draft-edit-attachments.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/__tests__/mail-send-attachments.test.d.ts +2 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-send-attachments.test.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-send-attachments.test.js +209 -0
- package/payload/platform/plugins/outlook/mcp/dist/__tests__/mail-send-attachments.test.js.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/index.js +12 -8
- package/payload/platform/plugins/outlook/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/attach.d.ts +37 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attach.d.ts.map +1 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attach.js +79 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/attach.js.map +1 -0
- 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/message.d.ts +8 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.js +4 -0
- package/payload/platform/plugins/outlook/mcp/dist/lib/message.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.d.ts +36 -8
- 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 +40 -13
- package/payload/platform/plugins/outlook/mcp/dist/lib/outbound-attachments.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.d.ts +9 -5
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.js +12 -8
- package/payload/platform/plugins/outlook/mcp/dist/lib/upload-session.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.d.ts +49 -4
- 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 +82 -13
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft-edit.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts +8 -2
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.js +13 -4
- package/payload/platform/plugins/outlook/mcp/dist/tools/draft.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/mcp/dist/tools/mail-reply.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js +3 -20
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-reply.js.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts +27 -3
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.d.ts.map +1 -1
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js +53 -8
- package/payload/platform/plugins/outlook/mcp/dist/tools/mail-send.js.map +1 -1
- package/payload/platform/plugins/outlook/references/auth.md +1 -1
- package/payload/platform/plugins/outlook/references/graph-surfaces.md +33 -6
- package/payload/platform/plugins/outlook/skills/outlook/SKILL.md +22 -5
- package/payload/platform/plugins/storage-broker/PLUGIN.md +41 -2
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/confine-path.test.d.ts +2 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/confine-path.test.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/confine-path.test.js +85 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/confine-path.test.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/put-source.test.d.ts +2 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/put-source.test.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/put-source.test.js +77 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/__tests__/put-source.test.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.d.ts +12 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.js +107 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/confine-path.js.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js +86 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/storage-broker/mcp/dist/put-source.d.ts +11 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/put-source.d.ts.map +1 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/put-source.js +35 -0
- package/payload/platform/plugins/storage-broker/mcp/dist/put-source.js.map +1 -0
- 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 +13 -3
- 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-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 +334 -0
- package/payload/platform/plugins/voice-mirror/skills/voice-mirror/SKILL.md +13 -0
- package/payload/platform/scripts/logs-read-jsonl.test.sh +118 -0
- package/payload/platform/scripts/logs-read.sh +63 -9
- package/payload/platform/scripts/voice-author-key-audit.sh +155 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +4 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- 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-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 +34 -23
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/server/{chunk-64FGYKNZ.js → chunk-JXWFVE5X.js} +148 -0
- package/payload/server/{chunk-F4D35LKL.js → chunk-POBXIHOF.js} +34 -0
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/server.js +412 -37
- package/payload/server/{src-S7C4P6TT.js → src-4F37OHLK.js} +19 -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
|
@@ -14,14 +14,32 @@ export type RunFn = (
|
|
|
14
14
|
env: Record<string, string>,
|
|
15
15
|
) => Promise<{ stdout: string }>;
|
|
16
16
|
|
|
17
|
-
// The HTTP seam for the Cloudflare API. Injectable so
|
|
17
|
+
// The HTTP seam for the Cloudflare API. Injectable so the API-backed methods are
|
|
18
18
|
// unit-testable without a network. Minimal subset of the global fetch: callers
|
|
19
19
|
// read the body once via text() and branch on ok/status. `body` is set for the
|
|
20
|
-
// POST paths (d1Create); GET paths
|
|
20
|
+
// POST/PUT paths (d1Create, r2ObjectPut); GET/DELETE paths omit it.
|
|
21
|
+
//
|
|
22
|
+
// Task 1691 widened both halves: `body` carries Uint8Array for the object-put
|
|
23
|
+
// path, and `arrayBuffer` reads the raw object body for r2ObjectGet, which is
|
|
24
|
+
// the one method whose response is not a JSON envelope. arrayBuffer is required
|
|
25
|
+
// rather than optional: an optional member would force a fake-only fallback
|
|
26
|
+
// branch in r2ObjectGet that never runs against a real fetch.
|
|
21
27
|
export type FetchFn = (
|
|
22
28
|
url: string,
|
|
23
|
-
init: { method: string; headers: Record<string, string>; body?: string },
|
|
24
|
-
) => Promise<{
|
|
29
|
+
init: { method: string; headers: Record<string, string>; body?: string | Uint8Array },
|
|
30
|
+
) => Promise<{
|
|
31
|
+
ok: boolean;
|
|
32
|
+
status: number;
|
|
33
|
+
text(): Promise<string>;
|
|
34
|
+
arrayBuffer(): Promise<ArrayBuffer>;
|
|
35
|
+
}>;
|
|
36
|
+
|
|
37
|
+
export interface R2Object {
|
|
38
|
+
key: string;
|
|
39
|
+
size: number;
|
|
40
|
+
etag: string;
|
|
41
|
+
lastModified: string;
|
|
42
|
+
}
|
|
25
43
|
|
|
26
44
|
export interface CfExec {
|
|
27
45
|
d1List(): Promise<{ name: string; uuid: string }[]>;
|
|
@@ -29,9 +47,34 @@ export interface CfExec {
|
|
|
29
47
|
d1Query(name: string, sql: string): Promise<unknown>;
|
|
30
48
|
r2List(): Promise<{ name: string }[]>;
|
|
31
49
|
r2Create(name: string): Promise<void>;
|
|
50
|
+
r2ObjectList(bucket: string, prefix?: string): Promise<R2Object[]>;
|
|
51
|
+
r2ObjectFind(bucket: string, key: string): Promise<R2Object | null>;
|
|
52
|
+
r2ObjectGet(bucket: string, key: string): Promise<Uint8Array>;
|
|
53
|
+
r2ObjectPut(bucket: string, key: string, body: Uint8Array): Promise<void>;
|
|
54
|
+
r2ObjectDelete(bucket: string, key: string): Promise<void>;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// An object key carries `/` as a real path separator (the data portal's
|
|
58
|
+
// per-person prefixes), so it is encoded segment-wise rather than whole:
|
|
59
|
+
// separators stay separators, and spaces and specials are escaped. This
|
|
60
|
+
// encoding is not doc-confirmed; it is chosen so that a wrong guess surfaces as
|
|
61
|
+
// a 404 carrying its body, never as a silently misfiled object.
|
|
62
|
+
function encodeObjectKey(key: string): string {
|
|
63
|
+
return key.split("/").map(encodeURIComponent).join("/");
|
|
32
64
|
}
|
|
33
65
|
|
|
34
|
-
|
|
66
|
+
// The single-object endpoint, shared by get/put/delete so the three cannot drift
|
|
67
|
+
// apart on encoding.
|
|
68
|
+
function objectUrl(accountId: string, bucket: string, key: string): string {
|
|
69
|
+
return `https://api.cloudflare.com/client/v4/accounts/${accountId}/r2/buckets/${encodeURIComponent(bucket)}/objects/${encodeObjectKey(key)}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// The cast is confined to this adapter. A Uint8Array is a valid fetch body at
|
|
73
|
+
// runtime, but the DOM lib's BodyInit does not admit TypeScript's generic
|
|
74
|
+
// Uint8Array<ArrayBufferLike>, so consumers compiled against DOM typings (the
|
|
75
|
+
// ui server) reject the call the Node typings accept. Casting here keeps the
|
|
76
|
+
// seam's own type honest for every caller and test.
|
|
77
|
+
const defaultFetch: FetchFn = (url, init) => fetch(url, init as RequestInit);
|
|
35
78
|
|
|
36
79
|
const defaultRun: RunFn = (cmd, args, env) =>
|
|
37
80
|
new Promise((resolve, reject) => {
|
|
@@ -62,7 +105,7 @@ function parseJson(raw: string, context: string): unknown {
|
|
|
62
105
|
async function cfApiJson(
|
|
63
106
|
fetchFn: FetchFn,
|
|
64
107
|
url: string,
|
|
65
|
-
init: { method: string; headers: Record<string, string>; body?: string },
|
|
108
|
+
init: { method: string; headers: Record<string, string>; body?: string | Uint8Array },
|
|
66
109
|
context: string,
|
|
67
110
|
): Promise<{ parsed: unknown; body: string }> {
|
|
68
111
|
const res = await fetchFn(url, init);
|
|
@@ -80,6 +123,81 @@ async function cfApiJson(
|
|
|
80
123
|
return { parsed, body };
|
|
81
124
|
}
|
|
82
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
|
+
|
|
83
201
|
export function makeCfExec(
|
|
84
202
|
cred: HouseCredential,
|
|
85
203
|
run: RunFn = defaultRun,
|
|
@@ -182,6 +300,103 @@ export function makeCfExec(
|
|
|
182
300
|
async r2Create(name) {
|
|
183
301
|
await run("npx", ["wrangler", "r2", "bucket", "create", name], env);
|
|
184
302
|
},
|
|
303
|
+
|
|
304
|
+
// --- R2 objects (Task 1691) -------------------------------------------
|
|
305
|
+
//
|
|
306
|
+
// The v4 REST API exposes the object surface under
|
|
307
|
+
// /accounts/{id}/r2/buckets/{bucket}/objects and accepts the same Bearer
|
|
308
|
+
// token as the bucket endpoints (account-level Workers R2 Storage Write).
|
|
309
|
+
// The r2/api/tokens/ note that "Object Read & Write" is S3-only constrains
|
|
310
|
+
// the bucket-item-scoped *permission types*, not these endpoints.
|
|
311
|
+
//
|
|
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
|
|
333
|
+
async r2ObjectList(bucket, prefix) {
|
|
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.
|
|
339
|
+
const objects: R2Object[] = [];
|
|
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;
|
|
366
|
+
}
|
|
367
|
+
return null;
|
|
368
|
+
},
|
|
369
|
+
async r2ObjectGet(bucket, key) {
|
|
370
|
+
// The one method cfApiJson cannot wrap: get returns the raw object body,
|
|
371
|
+
// not a success envelope, so there is nothing to parse or assert
|
|
372
|
+
// success:true on. The error body is still retained on a non-2xx.
|
|
373
|
+
const res = await fetchFn(objectUrl(cred.accountId, bucket, key), {
|
|
374
|
+
method: "GET",
|
|
375
|
+
headers: { Authorization: `Bearer ${cred.apiToken}` },
|
|
376
|
+
});
|
|
377
|
+
if (!res.ok) {
|
|
378
|
+
throw new Error(
|
|
379
|
+
`storage-broker: r2 object get failed: ${res.status}\n${await res.text()}`,
|
|
380
|
+
);
|
|
381
|
+
}
|
|
382
|
+
return new Uint8Array(await res.arrayBuffer());
|
|
383
|
+
},
|
|
384
|
+
async r2ObjectPut(bucket, key, body) {
|
|
385
|
+
await cfApiJson(
|
|
386
|
+
fetchFn,
|
|
387
|
+
objectUrl(cred.accountId, bucket, key),
|
|
388
|
+
{ method: "PUT", headers: { Authorization: `Bearer ${cred.apiToken}` }, body },
|
|
389
|
+
"r2 object put",
|
|
390
|
+
);
|
|
391
|
+
},
|
|
392
|
+
async r2ObjectDelete(bucket, key) {
|
|
393
|
+
await cfApiJson(
|
|
394
|
+
fetchFn,
|
|
395
|
+
objectUrl(cred.accountId, bucket, key),
|
|
396
|
+
{ method: "DELETE", headers: { Authorization: `Bearer ${cred.apiToken}` } },
|
|
397
|
+
"r2 object delete",
|
|
398
|
+
);
|
|
399
|
+
},
|
|
185
400
|
};
|
|
186
401
|
}
|
|
187
402
|
|
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
// config directory. This file is the one place the account-wide capability
|
|
3
3
|
// lives. House-only is a location convention plus the cf-token.sh caller gate,
|
|
4
4
|
// NOT an OS permission: every account's session runs as the same unix user, so
|
|
5
|
-
// a sub-account agent holding Bash can read this path directly.
|
|
6
|
-
//
|
|
5
|
+
// a sub-account agent holding Bash can read this path directly. That is a
|
|
6
|
+
// permanent property, not a pending gap: OS user separation was costed and
|
|
7
|
+
// declined as disproportionate (Task 1690, archived undone), so nothing tracks
|
|
8
|
+
// it. Do not cite this file's location or mode as a boundary. Parses
|
|
7
9
|
// KEY=value lines without polluting process.env (same discipline as
|
|
8
10
|
// reconcile-bookings' readEnvFile).
|
|
9
11
|
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
// isolation and the isolation logic auditable in one place.
|
|
8
8
|
|
|
9
9
|
export * from "./authorize.js";
|
|
10
|
+
export * from "./object-limits.js";
|
|
10
11
|
export * from "./registry.js";
|
|
12
|
+
export * from "./resource-limits.js";
|
|
11
13
|
export * from "./house-credential.js";
|
|
12
14
|
export * from "./cf-exec.js";
|
|
13
15
|
export * from "./house-scoped-token.js";
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The byte cap on R2 object transfer, and the arithmetic the routes need to
|
|
3
|
+
* enforce it before they buffer anything.
|
|
4
|
+
*
|
|
5
|
+
* One number, one home (Task 1695). The storage-broker MCP tools and the house
|
|
6
|
+
* ui routes both read the cap from here, so the process that rejects a too-large
|
|
7
|
+
* file and the process that would otherwise buffer it cannot drift apart.
|
|
8
|
+
*
|
|
9
|
+
* Deliberately dependency-free: the MCP imports this module directly, without
|
|
10
|
+
* the rest of the lib (cf-exec, registry, audit, remediate), which it has no use
|
|
11
|
+
* for. The ui route reads it through the barrel.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* The maximum size, in bytes, of an object moved through storage-r2-object-get
|
|
16
|
+
* or storage-r2-object-put.
|
|
17
|
+
*
|
|
18
|
+
* Set independently of the email plugin's OUTBOUND_ATTACHMENT_MAX_BYTES (25 MiB)
|
|
19
|
+
* even though the payload class is the same. The two exist for different reasons
|
|
20
|
+
* and must be free to move for different reasons: email's tracks an SMTP-side
|
|
21
|
+
* rejection threshold, this one tracks house-process memory. Do not couple them.
|
|
22
|
+
*
|
|
23
|
+
* Not bounded by R2. A single-part upload may be up to 5 GiB (doc-confirmed:
|
|
24
|
+
* developers.cloudflare.com/r2/platform/limits/), roughly fifty times this, and
|
|
25
|
+
* the v4 REST API states no separate object-size limit. This cap is the only
|
|
26
|
+
* bound on either transfer.
|
|
27
|
+
*
|
|
28
|
+
* The bound is not small: at the cap a get still leaves the house process
|
|
29
|
+
* holding the object and its base64 form, roughly 2.4x the object. That cost was
|
|
30
|
+
* weighed and accepted when the number was chosen.
|
|
31
|
+
*/
|
|
32
|
+
export const R2_OBJECT_MAX_BYTES = 100 * 1024 * 1024;
|
|
33
|
+
|
|
34
|
+
/** base64 emits 4 characters per 3 input bytes, padded up to the next quantum. */
|
|
35
|
+
export function base64Ceiling(bytes: number): number {
|
|
36
|
+
return 4 * Math.ceil(bytes / 3);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
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.
|
|
64
|
+
*/
|
|
65
|
+
export const R2_OBJECT_ENVELOPE_MAX_BODY_BYTES = 64 * 1024;
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* The largest JSON body /r2/object/put can legitimately carry.
|
|
69
|
+
*
|
|
70
|
+
* This bounds house memory; it does not adjudicate the last kilobyte. The exact
|
|
71
|
+
* decoded-byte rule lives in the MCP, which knows the source's size exactly.
|
|
72
|
+
*/
|
|
73
|
+
export const R2_OBJECT_PUT_MAX_BODY_BYTES =
|
|
74
|
+
base64Ceiling(R2_OBJECT_MAX_BYTES) + R2_OBJECT_ENVELOPE_MAX_BODY_BYTES;
|
|
75
|
+
|
|
76
|
+
function formatMiB(bytes: number): string {
|
|
77
|
+
return (bytes / 1024 / 1024).toFixed(1);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The over-cap message, worded once so the MCP and the routes agree. Names the
|
|
82
|
+
* offending object and the limit, which is what makes the failure actionable
|
|
83
|
+
* rather than an opaque out-of-memory kill.
|
|
84
|
+
*
|
|
85
|
+
* Exact bytes accompany the MiB figures because MiB alone rounds: an object one
|
|
86
|
+
* byte over the cap reads "100.0 MiB, which exceeds the 100.0 MiB limit", which
|
|
87
|
+
* is a message that appears to contradict itself and invites the reader to treat
|
|
88
|
+
* the rejection as a bug. The bytes are what make it legible; the MiB is what
|
|
89
|
+
* makes it quick.
|
|
90
|
+
*/
|
|
91
|
+
export function tooLargeMessage(what: string, actualBytes: number): string {
|
|
92
|
+
return `${what} is ${formatMiB(actualBytes)} MiB (${actualBytes} bytes), which exceeds the ${formatMiB(
|
|
93
|
+
R2_OBJECT_MAX_BYTES,
|
|
94
|
+
)} MiB (${R2_OBJECT_MAX_BYTES} bytes) R2 object limit.`;
|
|
95
|
+
}
|
|
@@ -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). |
|
|
@@ -2071,9 +2071,9 @@ The `outlook` plugin gives the admin agent access to Microsoft 365 / Outlook.com
|
|
|
2071
2071
|
| `outlook-contacts-list` | Top contacts. Default top=50. |
|
|
2072
2072
|
| `outlook-mailbox-info` | Health probe for one mailbox — auth state, refresh-window, folder count. Name the mailbox with `mailbox` when several are attached. |
|
|
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
|
-
| `outlook-mail-send` | Send a message
|
|
2075
|
-
| `outlook-draft` | Create a draft in Drafts (`POST /me/messages`). Same body model. Returns the draft id. Needs `Mail.ReadWrite`. |
|
|
2076
|
-
| `outlook-draft-edit` | Update an existing draft in place (`PATCH /me/messages/{draftId}`). Graph drafts are mutable — pass draftId plus the fields to change (to/cc/bcc/subject/body/isHtml). Sent messages cannot be edited. Needs `Mail.ReadWrite`. |
|
|
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
|
+
| `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 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, 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 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.
|
|
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
|
|
|
@@ -46,9 +46,10 @@ case "$scope" in pages|d1|storage|calendar-d1|dns|access) ;; *) die "unknown sco
|
|
|
46
46
|
# specialist. Every account's session runs as the SAME OS user (no uid/gid is
|
|
47
47
|
# dropped at spawn) and the house env path is deterministic, so an agent holding
|
|
48
48
|
# Bash can read that file directly or export a false ACCOUNT_ID. Real enforcement
|
|
49
|
-
# needs OS user separation
|
|
50
|
-
#
|
|
51
|
-
#
|
|
49
|
+
# needs OS user separation, which was costed and declined as disproportionate
|
|
50
|
+
# (Task 1690, archived undone). This is therefore permanently a boundary of
|
|
51
|
+
# intent, not of permission -- no follow-up tracks closing it. Do not cite this
|
|
52
|
+
# gate as proof a sub-account cannot reach the master.
|
|
52
53
|
#
|
|
53
54
|
# "House" is an exact shell mirror of resolveAccountDir + isHouseAccount
|
|
54
55
|
# (platform/services/claude-session-manager/src/config.ts:238-323) -- the same
|