@fleox-agent-crm/agentcrm 0.0.0-stage → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (129) hide show
  1. package/LICENSE +143 -0
  2. package/README.md +223 -2
  3. package/changelog.json +57 -0
  4. package/dist/admin/admin-files.json +26 -0
  5. package/dist/admin/assets/chartsImpl-CE2akJat.js +91 -0
  6. package/dist/admin/assets/firebase-BHSUggRo.js +2 -0
  7. package/dist/admin/assets/gitJson-BulGh2KV.js +3 -0
  8. package/dist/admin/assets/index-DidVVK7L.js +1181 -0
  9. package/dist/admin/assets/index-hMXwHSvN.css +1 -0
  10. package/dist/admin/assets/index.esm-BFc7vwnl.js +400 -0
  11. package/dist/admin/assets/index.esm-CtEQZRb1.js +1651 -0
  12. package/dist/admin/assets/index.esm-Dum0J07z.js +16 -0
  13. package/dist/admin/assets/index.esm2017-EOzKbpUS.js +390 -0
  14. package/dist/admin/assets/lucide-react.min-DEDlHCPj.js +6 -0
  15. package/dist/admin/assets/sdk-CnrLnlRo.js +1717 -0
  16. package/dist/admin/config.js +3 -0
  17. package/dist/admin/favicon.svg +1 -0
  18. package/dist/admin/index.html +139 -0
  19. package/dist/admin/runtime/agentcrm-runtime.js +104 -0
  20. package/dist/admin/runtime/lucide-react.js +791 -0
  21. package/dist/admin/runtime/react-dom-client.js +17 -0
  22. package/dist/admin/runtime/react-dom.js +20 -0
  23. package/dist/admin/runtime/react-i18next.js +19 -0
  24. package/dist/admin/runtime/react-jsx-runtime.js +17 -0
  25. package/dist/admin/runtime/react.js +22 -0
  26. package/dist/agent/agentcrm.mjs +3932 -0
  27. package/dist/agent/chunks/_commonjsHelpers-ClciMIcB.mjs +14 -0
  28. package/dist/agent/chunks/activities-Cj_SIOay.mjs +227 -0
  29. package/dist/agent/chunks/api-B6OqNVWp.mjs +97 -0
  30. package/dist/agent/chunks/appearance-BaShelzt.mjs +174 -0
  31. package/dist/agent/chunks/automations-BWrvLqP6.mjs +698 -0
  32. package/dist/agent/chunks/backup-ClSmxmFZ.mjs +103 -0
  33. package/dist/agent/chunks/batch-CVETiJj5.mjs +142 -0
  34. package/dist/agent/chunks/compact-CsFPaAAq.mjs +69 -0
  35. package/dist/agent/chunks/context-CxFfqS4D.mjs +65 -0
  36. package/dist/agent/chunks/core-Dy7vY9H6.mjs +255 -0
  37. package/dist/agent/chunks/dedupe-BepmsaHv.mjs +123 -0
  38. package/dist/agent/chunks/encoding-C9MzdP95.mjs +36 -0
  39. package/dist/agent/chunks/exportCsv-B5hRvkNm.mjs +95 -0
  40. package/dist/agent/chunks/extCheck--7lmVvl7.mjs +193 -0
  41. package/dist/agent/chunks/extPackage-BGl5Oqs8.mjs +115 -0
  42. package/dist/agent/chunks/extScaffold-DqxI7R1e.mjs +320 -0
  43. package/dist/agent/chunks/extensionActions-D2HtpQz4.mjs +48 -0
  44. package/dist/agent/chunks/extensions-BP1k60md.mjs +151 -0
  45. package/dist/agent/chunks/featureRegistry-C9nbVShp.mjs +149 -0
  46. package/dist/agent/chunks/fields-B0bitgmr.mjs +476 -0
  47. package/dist/agent/chunks/files-CjsPJSR6.mjs +158 -0
  48. package/dist/agent/chunks/files-q8Xu5bft.mjs +367 -0
  49. package/dist/agent/chunks/firebase-DxGVQV9E.mjs +454 -0
  50. package/dist/agent/chunks/firebaseRules-iori-s7U.mjs +221 -0
  51. package/dist/agent/chunks/forms-BKU5F9WF.mjs +847 -0
  52. package/dist/agent/chunks/gitJson-fjr8mcHe.mjs +528 -0
  53. package/dist/agent/chunks/hosting-Bme7LxPc.mjs +144 -0
  54. package/dist/agent/chunks/http-Bk9ICb7I.mjs +127 -0
  55. package/dist/agent/chunks/ids-CvnmrMJX.mjs +59 -0
  56. package/dist/agent/chunks/ids-u-noYcy5.mjs +14 -0
  57. package/dist/agent/chunks/importCsv-DmE_sBch.mjs +334 -0
  58. package/dist/agent/chunks/index-B-4W0-P4.mjs +535 -0
  59. package/dist/agent/chunks/index-BM8qeGOb.mjs +2489 -0
  60. package/dist/agent/chunks/index-Bnmav-Oh.mjs +311 -0
  61. package/dist/agent/chunks/index-CRkw3_iF.mjs +147 -0
  62. package/dist/agent/chunks/index-CWrrF1wl.mjs +111 -0
  63. package/dist/agent/chunks/index-DUSdTBkz.mjs +2988 -0
  64. package/dist/agent/chunks/index-RCmo-uld.mjs +26 -0
  65. package/dist/agent/chunks/index-w03ReRs4.mjs +9780 -0
  66. package/dist/agent/chunks/install-CpHoUFtL.mjs +188 -0
  67. package/dist/agent/chunks/jsx-runtime-Ca2uwGDr.mjs +76 -0
  68. package/dist/agent/chunks/loader-C9nm3iwx.mjs +184 -0
  69. package/dist/agent/chunks/lucide-react.min-Bt3gtepY.mjs +64 -0
  70. package/dist/agent/chunks/memory-WNR3VHUJ.mjs +56 -0
  71. package/dist/agent/chunks/migrate-CsClnnN8.mjs +182 -0
  72. package/dist/agent/chunks/modules-CqdVQVJb.mjs +177 -0
  73. package/dist/agent/chunks/nodeImport-BPCt5uMu.mjs +38 -0
  74. package/dist/agent/chunks/package-DWbn3t2R.mjs +194 -0
  75. package/dist/agent/chunks/papaparse-DjBYnQM2.mjs +1285 -0
  76. package/dist/agent/chunks/pipelines-DCdRBNBV.mjs +234 -0
  77. package/dist/agent/chunks/plan-C0TBrU92.mjs +254 -0
  78. package/dist/agent/chunks/pluginRegistry-DyHtlkcr.mjs +83 -0
  79. package/dist/agent/chunks/query-C5nJB_3C.mjs +192 -0
  80. package/dist/agent/chunks/records-DoqiT9xR.mjs +2191 -0
  81. package/dist/agent/chunks/registry-Ceexawxl.mjs +182 -0
  82. package/dist/agent/chunks/registry-DijAKZ24.mjs +431 -0
  83. package/dist/agent/chunks/relations-B9TynBmA.mjs +191 -0
  84. package/dist/agent/chunks/reports-BB59N6ke.mjs +189 -0
  85. package/dist/agent/chunks/runtime-DHo7ZEEO.mjs +14852 -0
  86. package/dist/agent/chunks/runtimeConfig-CCOFg1RM.mjs +250 -0
  87. package/dist/agent/chunks/s3-Dz50-NuA.mjs +903 -0
  88. package/dist/agent/chunks/schema-7-4-qtSS.mjs +203 -0
  89. package/dist/agent/chunks/schema-DlHt2PxI.mjs +367 -0
  90. package/dist/agent/chunks/sdk-whvfg786.mjs +43644 -0
  91. package/dist/agent/chunks/seed-CugPGYZL.mjs +523 -0
  92. package/dist/agent/chunks/server-ISwlDRCN.mjs +242 -0
  93. package/dist/agent/chunks/settings-BtvR_jdm.mjs +76 -0
  94. package/dist/agent/chunks/standardObjects-CheqfDqa.mjs +243 -0
  95. package/dist/agent/chunks/stats-CfeJssSS.mjs +51 -0
  96. package/dist/agent/chunks/store-5gqRSKXg.mjs +122 -0
  97. package/dist/agent/chunks/themes-DPZFv3yc.mjs +43 -0
  98. package/dist/agent/chunks/tools-Bew8lxKx.mjs +844 -0
  99. package/dist/agent/chunks/types-BOy9PUl7.mjs +44 -0
  100. package/dist/agent/chunks/users-DI1BvS0f.mjs +107 -0
  101. package/dist/agent/chunks/utils-DmI8FtXP.mjs +83 -0
  102. package/dist/agent/chunks/views-fO_BM5U_.mjs +109 -0
  103. package/dist/version.json +7 -0
  104. package/package.json +97 -4
  105. package/plugin/.claude-plugin/plugin.json +17 -0
  106. package/plugin/skills/agentcrm-admin/SKILL.md +191 -0
  107. package/plugin/skills/agentcrm-admin/references/appearance.md +26 -0
  108. package/plugin/skills/agentcrm-admin/references/import.md +16 -0
  109. package/plugin/skills/agentcrm-admin/references/records.md +30 -0
  110. package/plugin/skills/agentcrm-admin/references/schema.md +59 -0
  111. package/plugin/skills/agentcrm-extensions/SKILL.md +93 -0
  112. package/plugin/skills/agentcrm-extensions/references/module-recipes.md +213 -0
  113. package/plugin/skills/agentcrm-extensions/references/runtime-api.md +41 -0
  114. package/plugin/skills/agentcrm-extensions/references/testing.md +76 -0
  115. package/plugin/skills/agentcrm-extensions/references/theme-recipes.md +65 -0
  116. package/plugin/skills/agentcrm-install/SKILL.md +116 -0
  117. package/plugin/skills/agentcrm-install/references/firebase.md +18 -0
  118. package/plugin/skills/agentcrm-install/references/github.md +20 -0
  119. package/plugin/skills/agentcrm-install/references/mcp.md +17 -0
  120. package/plugin/skills/agentcrm-install/references/plans.md +45 -0
  121. package/plugin/skills/agentcrm-install/references/s3.md +24 -0
  122. package/plugin/skills/agentcrm-manage/SKILL.md +108 -0
  123. package/plugin/skills/agentcrm-manage/references/cron.md +11 -0
  124. package/plugin/skills/agentcrm-manage/references/doctor.md +18 -0
  125. package/plugin/skills/agentcrm-sales/SKILL.md +92 -0
  126. package/plugin/skills/agentcrm-sales/references/enrichment.md +13 -0
  127. package/plugin/skills/agentcrm-sales/references/playbooks.md +29 -0
  128. package/plugin/skills/agentcrm-sales/references/weekly-report.md +27 -0
  129. package/types/agentcrm-runtime.d.ts +894 -0
@@ -0,0 +1,903 @@
1
+ import { createRequire as __acrmRequire } from "node:module";
2
+ import { fileURLToPath as __acrmPath } from "node:url";
3
+ import { dirname as __acrmDirname } from "node:path";
4
+ const require2 = __acrmRequire(import.meta.url);
5
+ const __filename = __acrmPath(import.meta.url);
6
+ const __dirname = __acrmDirname(__filename);
7
+ import { i as instance } from "./index-BM8qeGOb.mjs";
8
+ import { g as gitFetch, m as mapLimit, S as StorageError, t as toast, p as performRequest } from "./http-Bk9ICb7I.mjs";
9
+ import { getRuntimeConfig, getGitToken } from "./runtimeConfig-CCOFg1RM.mjs";
10
+ import { d as base64ToBytes, a as base64ToUtf8, u as utf8ToBase64 } from "./encoding-C9MzdP95.mjs";
11
+ const DB_NAME = "agentcrm-git-blobs";
12
+ const STORE = "texts";
13
+ const memory = /* @__PURE__ */ new Map();
14
+ let dbPromise = null;
15
+ function openDb() {
16
+ if (dbPromise) return dbPromise;
17
+ dbPromise = new Promise((resolve) => {
18
+ try {
19
+ if (typeof indexedDB === "undefined") return resolve(null);
20
+ const req = indexedDB.open(DB_NAME, 1);
21
+ req.onupgradeneeded = () => req.result.createObjectStore(STORE);
22
+ req.onsuccess = () => resolve(req.result);
23
+ req.onerror = () => resolve(null);
24
+ req.onblocked = () => resolve(null);
25
+ } catch {
26
+ resolve(null);
27
+ }
28
+ });
29
+ return dbPromise;
30
+ }
31
+ async function getCachedTexts(shas) {
32
+ const out = /* @__PURE__ */ new Map();
33
+ const missing = [];
34
+ for (const sha of shas) {
35
+ const hit = memory.get(sha);
36
+ if (hit !== void 0) out.set(sha, hit);
37
+ else missing.push(sha);
38
+ }
39
+ if (missing.length === 0) return out;
40
+ const db = await openDb();
41
+ if (!db) return out;
42
+ await new Promise((resolve) => {
43
+ try {
44
+ const tx = db.transaction(STORE, "readonly");
45
+ const store = tx.objectStore(STORE);
46
+ for (const sha of missing) {
47
+ const req = store.get(sha);
48
+ req.onsuccess = () => {
49
+ if (typeof req.result === "string") {
50
+ out.set(sha, req.result);
51
+ memory.set(sha, req.result);
52
+ }
53
+ };
54
+ }
55
+ tx.oncomplete = () => resolve();
56
+ tx.onerror = () => resolve();
57
+ tx.onabort = () => resolve();
58
+ } catch {
59
+ resolve();
60
+ }
61
+ });
62
+ return out;
63
+ }
64
+ async function putCachedTexts(entries) {
65
+ for (const [sha, text] of entries) memory.set(sha, text);
66
+ const db = await openDb();
67
+ if (!db || entries.size === 0) return;
68
+ await new Promise((resolve) => {
69
+ try {
70
+ const tx = db.transaction(STORE, "readwrite");
71
+ const store = tx.objectStore(STORE);
72
+ for (const [sha, text] of entries) store.put(text, sha);
73
+ tx.oncomplete = () => resolve();
74
+ tx.onerror = () => resolve();
75
+ tx.onabort = () => resolve();
76
+ } catch {
77
+ resolve();
78
+ }
79
+ });
80
+ }
81
+ class GitConflictError extends Error {
82
+ constructor(message, currentHeadSha) {
83
+ super(message);
84
+ this.currentHeadSha = currentHeadSha;
85
+ this.name = "GitConflictError";
86
+ }
87
+ }
88
+ const TREE_CHUNK = 300;
89
+ const GRAPHQL_BATCH = 50;
90
+ const MAX_REF_RETRIES = 3;
91
+ function createGitHubTransport(cfg) {
92
+ const api = (cfg.apiBaseUrl || "https://api.github.com").replace(/\/+$/, "");
93
+ const graphqlUrl = api.endsWith("/api/v3") ? api.replace(/\/api\/v3$/, "/api/graphql") : `${api}/graphql`;
94
+ const repoUrl = `${api}/repos/${encodeURIComponent(cfg.owner)}/${encodeURIComponent(cfg.repo)}`;
95
+ const refPath = `heads/${cfg.branch.split("/").map(encodeURIComponent).join("/")}`;
96
+ const headers = {
97
+ Authorization: `Bearer ${cfg.token}`,
98
+ Accept: "application/vnd.github+json",
99
+ "X-GitHub-Api-Version": "2022-11-28"
100
+ };
101
+ const jsonHeaders = { ...headers, "Content-Type": "application/json" };
102
+ async function getJson(url, action = "get") {
103
+ const res = await gitFetch(action, url, { headers, cache: "no-store" });
104
+ return await res.json();
105
+ }
106
+ async function postJson(url, body, method = "POST") {
107
+ const res = await gitFetch("commit", url, { method, headers: jsonHeaders, body: JSON.stringify(body) });
108
+ return await res.json();
109
+ }
110
+ async function headSha() {
111
+ const res = await gitFetch("get", `${repoUrl}/git/ref/${refPath}`, { headers, cache: "no-store" }, [404, 409]);
112
+ if (!res.ok) return "";
113
+ const data = await res.json();
114
+ if (Array.isArray(data)) return "";
115
+ return data.object?.sha ?? "";
116
+ }
117
+ async function treeShaOf(commitSha) {
118
+ const commit2 = await getJson(`${repoUrl}/git/commits/${commitSha}`);
119
+ return commit2.tree.sha;
120
+ }
121
+ async function walk(treeSha, prefix, out) {
122
+ const data = await getJson(`${repoUrl}/git/trees/${treeSha}`, "list");
123
+ for (const item of data.tree) {
124
+ const path = prefix + item.path;
125
+ if (item.type === "blob") out.push({ path, sha: item.sha, size: item.size });
126
+ else if (item.type === "tree") await walk(item.sha, path + "/", out);
127
+ }
128
+ }
129
+ async function listTreeAt(head) {
130
+ if (!head) return { headSha: "", entries: [] };
131
+ const treeSha = await treeShaOf(head);
132
+ const data = await getJson(
133
+ `${repoUrl}/git/trees/${treeSha}?recursive=1`,
134
+ "list"
135
+ );
136
+ if (!data.truncated) {
137
+ return {
138
+ headSha: head,
139
+ entries: data.tree.filter((t) => t.type === "blob").map((t) => ({ path: t.path, sha: t.sha, size: t.size }))
140
+ };
141
+ }
142
+ const entries = [];
143
+ await walk(treeSha, "", entries);
144
+ return { headSha: head, entries };
145
+ }
146
+ async function readBlobBase64(sha) {
147
+ const blob = await getJson(`${repoUrl}/git/blobs/${sha}`);
148
+ return blob.content;
149
+ }
150
+ async function graphqlTexts(shas) {
151
+ const out = /* @__PURE__ */ new Map();
152
+ for (let i = 0; i < shas.length; i += GRAPHQL_BATCH) {
153
+ const batch = shas.slice(i, i + GRAPHQL_BATCH);
154
+ const fields = batch.map((sha, j) => `b${j}: object(oid: "${sha}") { ... on Blob { text isBinary isTruncated } }`).join("\n");
155
+ const query = `query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { ${fields} } }`;
156
+ try {
157
+ const res = await fetch(graphqlUrl, {
158
+ method: "POST",
159
+ headers: jsonHeaders,
160
+ body: JSON.stringify({ query, variables: { owner: cfg.owner, name: cfg.repo } })
161
+ });
162
+ if (!res.ok) continue;
163
+ const json = await res.json();
164
+ const repo = json.data?.repository;
165
+ if (!repo) continue;
166
+ batch.forEach((sha, j) => {
167
+ const blob = repo[`b${j}`];
168
+ if (blob && !blob.isBinary && !blob.isTruncated && typeof blob.text === "string") {
169
+ out.set(sha, blob.text);
170
+ }
171
+ });
172
+ } catch {
173
+ }
174
+ }
175
+ return out;
176
+ }
177
+ async function readTexts(entries) {
178
+ const shas = [...new Set(entries.map((e) => e.sha))];
179
+ const bySha = await getCachedTexts(shas);
180
+ const missing = shas.filter((sha) => !bySha.has(sha));
181
+ const fetched = /* @__PURE__ */ new Map();
182
+ if (missing.length > 0) {
183
+ const viaGraphql = await graphqlTexts(missing);
184
+ for (const [sha, text] of viaGraphql) fetched.set(sha, text);
185
+ const rest = missing.filter((sha) => !fetched.has(sha));
186
+ await mapLimit(rest, 6, async (sha) => {
187
+ fetched.set(sha, base64ToUtf8(await readBlobBase64(sha)));
188
+ });
189
+ await putCachedTexts(fetched);
190
+ }
191
+ const out = /* @__PURE__ */ new Map();
192
+ for (const e of entries) {
193
+ const text = bySha.get(e.sha) ?? fetched.get(e.sha);
194
+ if (text !== void 0) out.set(e.path, text);
195
+ }
196
+ return out;
197
+ }
198
+ async function readBytes(entry) {
199
+ return base64ToBytes(await readBlobBase64(entry.sha));
200
+ }
201
+ async function bootstrapBranch(message) {
202
+ const repo = await getJson(repoUrl);
203
+ const defaultRes = repo.default_branch ? await gitFetch(
204
+ "get",
205
+ `${repoUrl}/git/ref/heads/${encodeURIComponent(repo.default_branch)}`,
206
+ { headers, cache: "no-store" },
207
+ [404, 409]
208
+ ) : null;
209
+ if (!defaultRes || !defaultRes.ok) {
210
+ const created = await postJson(
211
+ `${repoUrl}/contents/.gitkeep`,
212
+ { message, content: "", branch: cfg.branch },
213
+ "PUT"
214
+ );
215
+ return created.commit.sha;
216
+ }
217
+ const tree = await postJson(`${repoUrl}/git/trees`, {
218
+ tree: [{ path: ".gitkeep", mode: "100644", type: "blob", content: "" }]
219
+ });
220
+ const commit2 = await postJson(`${repoUrl}/git/commits`, {
221
+ message,
222
+ tree: tree.sha,
223
+ parents: []
224
+ });
225
+ await postJson(`${repoUrl}/git/refs`, { ref: `refs/heads/${cfg.branch}`, sha: commit2.sha });
226
+ return commit2.sha;
227
+ }
228
+ async function commit(changes, opts) {
229
+ const blobShas = /* @__PURE__ */ new Map();
230
+ for (let attempt = 0; ; attempt++) {
231
+ let head = await headSha();
232
+ if (opts.expectedHeadSha !== void 0 && head !== opts.expectedHeadSha) {
233
+ throw new GitConflictError(`Branch ${cfg.branch} moved`, head);
234
+ }
235
+ if (!head) head = await bootstrapBranch("Initialize branch — AgentCRM");
236
+ for (const change of changes) {
237
+ if (change.type === "put" && change.encoding === "base64" && !blobShas.has(change.path)) {
238
+ const blob = await postJson(`${repoUrl}/git/blobs`, {
239
+ content: change.content,
240
+ encoding: "base64"
241
+ });
242
+ blobShas.set(change.path, blob.sha);
243
+ }
244
+ }
245
+ const baseTree = await treeShaOf(head);
246
+ let existing = null;
247
+ if (changes.some((c) => c.type === "delete")) {
248
+ existing = new Set((await listTreeAt(head)).entries.map((e) => e.path));
249
+ }
250
+ const items = [];
251
+ for (const change of changes) {
252
+ if (change.type === "delete") {
253
+ if (existing?.has(change.path)) {
254
+ items.push({ path: change.path, mode: "100644", type: "blob", sha: null });
255
+ }
256
+ } else if (change.encoding === "base64") {
257
+ items.push({ path: change.path, mode: "100644", type: "blob", sha: blobShas.get(change.path) });
258
+ } else {
259
+ items.push({ path: change.path, mode: "100644", type: "blob", content: change.content });
260
+ }
261
+ }
262
+ let tree = baseTree;
263
+ for (let i = 0; i < items.length; i += TREE_CHUNK) {
264
+ const res = await postJson(`${repoUrl}/git/trees`, {
265
+ base_tree: tree,
266
+ tree: items.slice(i, i + TREE_CHUNK)
267
+ });
268
+ tree = res.sha;
269
+ }
270
+ if (tree === baseTree) return head;
271
+ const created = await postJson(`${repoUrl}/git/commits`, {
272
+ message: opts.message,
273
+ tree,
274
+ parents: [head]
275
+ });
276
+ const update = await gitFetch(
277
+ "commit",
278
+ `${repoUrl}/git/refs/${refPath}`,
279
+ { method: "PATCH", headers: jsonHeaders, body: JSON.stringify({ sha: created.sha, force: false }) },
280
+ [409, 422]
281
+ );
282
+ if (update.ok) return created.sha;
283
+ if (opts.expectedHeadSha !== void 0) {
284
+ throw new GitConflictError(`Branch ${cfg.branch} moved`, await headSha());
285
+ }
286
+ if (attempt >= MAX_REF_RETRIES) {
287
+ throw new GitConflictError(`Branch ${cfg.branch} kept moving`, await headSha());
288
+ }
289
+ }
290
+ }
291
+ async function resetBranch(files, opts) {
292
+ const items = [];
293
+ for (const f of files) {
294
+ const blob = await postJson(`${repoUrl}/git/blobs`, { content: f.content, encoding: "base64" });
295
+ items.push({ path: f.path, mode: "100644", type: "blob", sha: blob.sha });
296
+ }
297
+ const tree = await postJson(`${repoUrl}/git/trees`, { tree: items });
298
+ const created = await postJson(`${repoUrl}/git/commits`, { message: opts.message, tree: tree.sha, parents: [] });
299
+ const head = await headSha();
300
+ if (head !== opts.expectedHeadSha) throw new GitConflictError(`Branch ${cfg.branch} moved`, head);
301
+ await gitFetch("commit", `${repoUrl}/git/refs/${refPath}`, {
302
+ method: "PATCH",
303
+ headers: jsonHeaders,
304
+ body: JSON.stringify({ sha: created.sha, force: true })
305
+ });
306
+ return created.sha;
307
+ }
308
+ async function permissions() {
309
+ const [user, repo] = await Promise.all([
310
+ getJson(`${api}/user`),
311
+ getJson(repoUrl)
312
+ ]);
313
+ const p = repo.permissions ?? {};
314
+ const visibility = repo.visibility === "public" || repo.visibility === "private" || repo.visibility === "internal" ? repo.visibility : typeof repo.private === "boolean" ? repo.private ? "private" : "public" : void 0;
315
+ return { login: user.login, canWrite: !!(p.push || p.maintain || p.admin), isAdmin: !!(p.admin || p.maintain), visibility };
316
+ }
317
+ return {
318
+ kind: "github",
319
+ label: `${cfg.owner}/${cfg.repo}@${cfg.branch}`,
320
+ headSha,
321
+ listTree: async () => listTreeAt(await headSha()),
322
+ readTexts,
323
+ readBytes,
324
+ commit,
325
+ permissions,
326
+ resetBranch
327
+ };
328
+ }
329
+ const MAX_RETRIES = 3;
330
+ const WRITE_LEVEL = 30;
331
+ const ADMIN_LEVEL = 40;
332
+ function createGitLabTransport(cfg) {
333
+ const api = (cfg.apiBaseUrl || "https://gitlab.com/api/v4").replace(/\/+$/, "");
334
+ const projectUrl = `${api}/projects/${encodeURIComponent(cfg.project)}`;
335
+ const headers = { Authorization: `Bearer ${cfg.token}` };
336
+ const jsonHeaders = { ...headers, "Content-Type": "application/json" };
337
+ const pathsByHead = /* @__PURE__ */ new Map();
338
+ async function getJson(url, action = "get") {
339
+ const res = await gitFetch(action, url, { headers, cache: "no-store" });
340
+ return await res.json();
341
+ }
342
+ async function headSha() {
343
+ const res = await gitFetch(
344
+ "get",
345
+ `${projectUrl}/repository/branches/${encodeURIComponent(cfg.branch)}`,
346
+ { headers, cache: "no-store" },
347
+ [404]
348
+ );
349
+ if (!res.ok) return "";
350
+ const data = await res.json();
351
+ return data.commit?.id ?? "";
352
+ }
353
+ async function listTreeAt(head) {
354
+ if (!head) return { headSha: "", entries: [] };
355
+ const entries = [];
356
+ for (let page = 1; ; page++) {
357
+ const url = `${projectUrl}/repository/tree?${new URLSearchParams({
358
+ ref: head,
359
+ recursive: "true",
360
+ per_page: "100",
361
+ page: String(page)
362
+ })}`;
363
+ const res = await gitFetch("list", url, { headers, cache: "no-store" });
364
+ const items = await res.json();
365
+ for (const item of items) {
366
+ if (item.type === "blob") entries.push({ path: item.path, sha: item.id });
367
+ }
368
+ const nextPage = res.headers.get("x-next-page");
369
+ if (!nextPage || items.length === 0) break;
370
+ }
371
+ pathsByHead.set(head, new Set(entries.map((e) => e.path)));
372
+ return { headSha: head, entries };
373
+ }
374
+ async function rawBlob(sha) {
375
+ return gitFetch("get", `${projectUrl}/repository/blobs/${sha}/raw`, { headers });
376
+ }
377
+ async function readTexts(entries) {
378
+ const shas = [...new Set(entries.map((e) => e.sha))];
379
+ const bySha = await getCachedTexts(shas);
380
+ const missing = shas.filter((sha) => !bySha.has(sha));
381
+ const fetched = /* @__PURE__ */ new Map();
382
+ await mapLimit(missing, 6, async (sha) => {
383
+ fetched.set(sha, await (await rawBlob(sha)).text());
384
+ });
385
+ await putCachedTexts(fetched);
386
+ const out = /* @__PURE__ */ new Map();
387
+ for (const e of entries) {
388
+ const text = bySha.get(e.sha) ?? fetched.get(e.sha);
389
+ if (text !== void 0) out.set(e.path, text);
390
+ }
391
+ return out;
392
+ }
393
+ async function readBytes(entry) {
394
+ return new Uint8Array(await (await rawBlob(entry.sha)).arrayBuffer());
395
+ }
396
+ async function existingPaths(head) {
397
+ if (!head) return /* @__PURE__ */ new Set();
398
+ return pathsByHead.get(head) ?? new Set((await listTreeAt(head)).entries.map((e) => e.path));
399
+ }
400
+ async function defaultBranch() {
401
+ const project = await getJson(projectUrl);
402
+ return project.default_branch || void 0;
403
+ }
404
+ async function commit(changes, opts) {
405
+ for (let attempt = 0; ; attempt++) {
406
+ const head = await headSha();
407
+ if (opts.expectedHeadSha !== void 0 && head !== opts.expectedHeadSha) {
408
+ throw new GitConflictError(`Branch ${cfg.branch} moved`, head);
409
+ }
410
+ const existing = await existingPaths(head);
411
+ const actions = [];
412
+ const after = new Set(existing);
413
+ for (const change of changes) {
414
+ if (change.type === "delete") {
415
+ if (!existing.has(change.path)) continue;
416
+ actions.push({ action: "delete", file_path: change.path });
417
+ after.delete(change.path);
418
+ } else {
419
+ actions.push({
420
+ action: existing.has(change.path) ? "update" : "create",
421
+ file_path: change.path,
422
+ encoding: "base64",
423
+ content: change.encoding === "base64" ? change.content : utf8ToBase64(change.content)
424
+ });
425
+ after.add(change.path);
426
+ }
427
+ }
428
+ if (actions.length === 0) return head;
429
+ const body = {
430
+ branch: cfg.branch,
431
+ commit_message: opts.message,
432
+ actions
433
+ };
434
+ if (!head) {
435
+ const base = await defaultBranch();
436
+ if (base && base !== cfg.branch) body.start_branch = base;
437
+ }
438
+ const res = await gitFetch(
439
+ "commit",
440
+ `${projectUrl}/repository/commits`,
441
+ { method: "POST", headers: jsonHeaders, body: JSON.stringify(body) },
442
+ // 400 = create/update mismatch: someone added / removed a file
443
+ // since our listing. Refresh and retry.
444
+ [400]
445
+ );
446
+ if (res.ok) {
447
+ const created = await res.json();
448
+ pathsByHead.set(created.id, after);
449
+ return created.id;
450
+ }
451
+ pathsByHead.delete(head);
452
+ if (opts.expectedHeadSha !== void 0 || attempt >= MAX_RETRIES) {
453
+ throw new GitConflictError(`Commit on ${cfg.branch} rejected: ${await res.text()}`, await headSha());
454
+ }
455
+ }
456
+ }
457
+ async function permissions() {
458
+ const [user, project] = await Promise.all([
459
+ getJson(`${api}/user`),
460
+ getJson(projectUrl)
461
+ ]);
462
+ const level = Math.max(
463
+ project.permissions?.project_access?.access_level ?? 0,
464
+ project.permissions?.group_access?.access_level ?? 0
465
+ );
466
+ const v = project.visibility;
467
+ const visibility = v === "public" || v === "private" || v === "internal" ? v : void 0;
468
+ return { login: user.username, canWrite: level >= WRITE_LEVEL, isAdmin: level >= ADMIN_LEVEL, visibility };
469
+ }
470
+ return {
471
+ kind: "gitlab",
472
+ label: `${cfg.project}@${cfg.branch}`,
473
+ headSha,
474
+ listTree: async () => listTreeAt(await headSha()),
475
+ readTexts,
476
+ readBytes,
477
+ commit,
478
+ permissions
479
+ };
480
+ }
481
+ function createRemoteGitTransport(cfg) {
482
+ return cfg.provider === "github" ? createGitHubTransport(cfg) : createGitLabTransport(cfg);
483
+ }
484
+ function joinRepoPath(dir, path) {
485
+ const d = dir.replace(/^\/+|\/+$/g, "");
486
+ const p = path.replace(/^\/+/, "");
487
+ return d ? `${d}/${p}` : p;
488
+ }
489
+ function gitlabCi(dir, branch) {
490
+ const copy = dir === "public" ? ' - echo "Publishing public/"' : ` - rm -rf public && cp -r ${dir} public`;
491
+ return [
492
+ "# Generated by AgentCRM — publishes the static files with GitLab Pages.",
493
+ "# Safe to edit: AgentCRM only creates this file when the repo has none.",
494
+ "pages:",
495
+ " stage: deploy",
496
+ " script:",
497
+ copy,
498
+ " artifacts:",
499
+ " paths:",
500
+ " - public",
501
+ " rules:",
502
+ ` - if: $CI_COMMIT_BRANCH == "${branch}"`,
503
+ ""
504
+ ].join("\n");
505
+ }
506
+ function resolveGitPagesToken(settings) {
507
+ if (settings.token) return settings.token;
508
+ const runtime = getRuntimeConfig();
509
+ if (runtime?.backend === "git-json" && runtime.git.provider === settings.provider) {
510
+ return getGitToken(runtime.git) ?? "";
511
+ }
512
+ return "";
513
+ }
514
+ const pagesChecks = /* @__PURE__ */ new Map();
515
+ function ensureGithubPages(settings, token, dir) {
516
+ const key = `${settings.owner}/${settings.repo}`;
517
+ let check = pagesChecks.get(key);
518
+ if (!check) {
519
+ check = enableGithubPages(settings, token, dir, key);
520
+ pagesChecks.set(key, check);
521
+ }
522
+ return check;
523
+ }
524
+ async function enableGithubPages(settings, token, dir, key) {
525
+ const api = (settings.apiBaseUrl || "https://api.github.com").replace(/\/+$/, "");
526
+ const url = `${api}/repos/${encodeURIComponent(settings.owner ?? "")}/${encodeURIComponent(settings.repo ?? "")}/pages`;
527
+ const headers = { Authorization: `Bearer ${token}`, Accept: "application/vnd.github+json" };
528
+ try {
529
+ const current = await fetch(url, { headers, cache: "no-store" });
530
+ if (current.ok) return;
531
+ const created = await fetch(url, {
532
+ method: "POST",
533
+ headers: { ...headers, "Content-Type": "application/json" },
534
+ body: JSON.stringify({ source: { branch: settings.branch, path: dir === "docs" ? "/docs" : "/" } })
535
+ });
536
+ if (created.ok) {
537
+ toast.success(instance.t("connectors.pages.enabled"));
538
+ return;
539
+ }
540
+ } catch {
541
+ }
542
+ toast.info(
543
+ instance.t("connectors.pages.enableHint", {
544
+ repo: key,
545
+ branch: settings.branch,
546
+ folder: dir === "docs" ? "/docs" : "/ (root)"
547
+ })
548
+ );
549
+ }
550
+ function createGitPagesConnector(settings) {
551
+ const token = resolveGitPagesToken(settings);
552
+ let dir = String(settings.dir ?? "").replace(/^\/+|\/+$/g, "");
553
+ if (settings.host === "gitlab-pages" && !dir) dir = "public";
554
+ const transport = createRemoteGitTransport(
555
+ settings.provider === "github" ? {
556
+ provider: "github",
557
+ owner: settings.owner ?? "",
558
+ repo: settings.repo ?? "",
559
+ branch: settings.branch,
560
+ token,
561
+ apiBaseUrl: settings.apiBaseUrl
562
+ } : {
563
+ provider: "gitlab",
564
+ project: settings.project ?? "",
565
+ branch: settings.branch,
566
+ token,
567
+ apiBaseUrl: settings.apiBaseUrl
568
+ }
569
+ );
570
+ const siteUrl = (settings.siteUrl ?? "").replace(/\/+$/, "");
571
+ const repoPath = (path) => joinRepoPath(dir, path);
572
+ const sitePath = (path) => dir ? path.slice(dir.length + 1) : path;
573
+ const inSite = (path) => !dir || path.startsWith(dir + "/");
574
+ let cache = null;
575
+ async function tree() {
576
+ const head = await transport.headSha();
577
+ if (cache && cache.head === head) return cache;
578
+ const listed = await transport.listTree();
579
+ cache = { head: listed.headSha, files: new Map(listed.entries.map((e) => [e.path, e])) };
580
+ return cache;
581
+ }
582
+ function hostExtras(files) {
583
+ if (settings.host === "github-pages") {
584
+ const marker = repoPath(".nojekyll");
585
+ return files.has(marker) ? [] : [{ type: "put", path: marker, content: "" }];
586
+ }
587
+ if (settings.host === "gitlab-pages") {
588
+ return files.has(".gitlab-ci.yml") ? [] : [{ type: "put", path: ".gitlab-ci.yml", content: gitlabCi(dir, settings.branch) }];
589
+ }
590
+ return [];
591
+ }
592
+ async function commitBatch(ops) {
593
+ const { files } = await tree();
594
+ const changes = [];
595
+ let count = 0;
596
+ for (const op of ops) {
597
+ if (op.type === "deletePrefix") {
598
+ const prefix = repoPath(op.path) + "/";
599
+ for (const path of files.keys()) {
600
+ if (path.startsWith(prefix)) changes.push({ type: "delete", path });
601
+ }
602
+ } else if (op.type === "delete") {
603
+ if (files.has(repoPath(op.path))) changes.push({ type: "delete", path: repoPath(op.path) });
604
+ } else {
605
+ changes.push({ type: "put", path: repoPath(op.path), content: op.content, encoding: op.encoding });
606
+ count++;
607
+ }
608
+ }
609
+ changes.push(...hostExtras(files));
610
+ const deleted = changes.filter((c) => c.type === "delete").length;
611
+ const summary = [count && `${count} updated`, deleted && `${deleted} removed`].filter(Boolean).join(", ");
612
+ const skipCi = settings.branch === "agentcrm-data" || !settings.host ? " [skip ci]" : "";
613
+ const verb = settings.host ? "Publish files" : "Update data";
614
+ await transport.commit(changes, { message: `${verb}${summary ? ` (${summary})` : ""} — AgentCRM${skipCi}` });
615
+ cache = null;
616
+ if (settings.host === "github-pages" && settings.provider === "github") {
617
+ void ensureGithubPages(settings, token, dir);
618
+ }
619
+ }
620
+ return {
621
+ id: "git-pages",
622
+ model: "commit",
623
+ capabilities: {
624
+ list: true,
625
+ rename: false,
626
+ renameFolder: false,
627
+ folders: false,
628
+ deletePrefix: true
629
+ },
630
+ // Outside a batch every write is its own commit — correct, just noisy.
631
+ put: (input) => commitBatch([{ type: "put", ...input }]),
632
+ delete: (path) => commitBatch([{ type: "delete", path }]),
633
+ deletePrefix: (path) => commitBatch([{ type: "deletePrefix", path }]),
634
+ commitBatch,
635
+ async get(path) {
636
+ const { files } = await tree();
637
+ const entry = files.get(repoPath(path));
638
+ if (!entry) throw new StorageError(`File not found: ${path}`, 404);
639
+ const texts = await transport.readTexts([entry]);
640
+ const text = texts.get(entry.path);
641
+ if (text === void 0) throw new StorageError(`File not found: ${path}`, 404);
642
+ return text;
643
+ },
644
+ async getBytes(path) {
645
+ const { files } = await tree();
646
+ const entry = files.get(repoPath(path));
647
+ return entry ? transport.readBytes(entry) : null;
648
+ },
649
+ async list(page, perPage) {
650
+ const { files } = await tree();
651
+ const all = [...files.values()].filter((e) => inSite(e.path)).map((e) => ({ path: sitePath(e.path), type: "file", size: e.size, url: `${siteUrl}/${sitePath(e.path)}` })).sort((a, b) => a.path < b.path ? -1 : 1);
652
+ return { items: all.slice((page - 1) * perPage, page * perPage), page, per_page: perPage, total: all.length };
653
+ },
654
+ async listPrefix(under) {
655
+ const { files } = await tree();
656
+ const want = repoPath(under.replace(/^\/+/, ""));
657
+ return [...files.keys()].filter((p) => inSite(p) && p.startsWith(want)).map(sitePath).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
658
+ },
659
+ async publicUrl(path) {
660
+ return `${siteUrl}/${path.replace(/^\/+/, "")}`;
661
+ }
662
+ };
663
+ }
664
+ const gitPagesFactory = {
665
+ id: "git-pages",
666
+ create: createGitPagesConnector
667
+ };
668
+ const TYPES = {
669
+ html: "text/html; charset=utf-8",
670
+ htm: "text/html; charset=utf-8",
671
+ css: "text/css; charset=utf-8",
672
+ js: "text/javascript; charset=utf-8",
673
+ mjs: "text/javascript; charset=utf-8",
674
+ json: "application/json; charset=utf-8",
675
+ webmanifest: "application/manifest+json; charset=utf-8",
676
+ xml: "application/xml; charset=utf-8",
677
+ xsl: "application/xslt+xml; charset=utf-8",
678
+ txt: "text/plain; charset=utf-8",
679
+ md: "text/markdown; charset=utf-8",
680
+ svg: "image/svg+xml",
681
+ png: "image/png",
682
+ jpg: "image/jpeg",
683
+ jpeg: "image/jpeg",
684
+ gif: "image/gif",
685
+ webp: "image/webp",
686
+ avif: "image/avif",
687
+ ico: "image/x-icon",
688
+ pdf: "application/pdf",
689
+ woff: "font/woff",
690
+ woff2: "font/woff2",
691
+ ttf: "font/ttf",
692
+ otf: "font/otf",
693
+ mp4: "video/mp4",
694
+ webm: "video/webm",
695
+ mp3: "audio/mpeg",
696
+ zip: "application/zip"
697
+ };
698
+ function contentTypeFor(path) {
699
+ const ext = path.split(".").pop()?.toLowerCase() ?? "";
700
+ return TYPES[ext] ?? "application/octet-stream";
701
+ }
702
+ function cacheControlFor(path) {
703
+ return /\.(html?|json|xml|xsl|txt|css|js|mjs|webmanifest)$/i.test(path) ? "no-cache" : "public, max-age=86400";
704
+ }
705
+ const enc = new TextEncoder();
706
+ function hex(buffer) {
707
+ return [...new Uint8Array(buffer)].map((b) => b.toString(16).padStart(2, "0")).join("");
708
+ }
709
+ async function sha256Hex(data) {
710
+ return hex(await crypto.subtle.digest("SHA-256", enc.encode(data)));
711
+ }
712
+ async function hmac(key, data) {
713
+ const cryptoKey = await crypto.subtle.importKey(
714
+ "raw",
715
+ key,
716
+ { name: "HMAC", hash: "SHA-256" },
717
+ false,
718
+ ["sign"]
719
+ );
720
+ return crypto.subtle.sign("HMAC", cryptoKey, enc.encode(data));
721
+ }
722
+ function rfc3986(value) {
723
+ return encodeURIComponent(value).replace(/[!'()*]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`);
724
+ }
725
+ function amzDates(date) {
726
+ const amzDate = date.toISOString().replace(/[:-]|\.\d{3}/g, "");
727
+ return { amzDate, dateStamp: amzDate.slice(0, 8) };
728
+ }
729
+ async function signRequest(args) {
730
+ const url = new URL(args.url);
731
+ const { amzDate, dateStamp } = amzDates(args.date ?? /* @__PURE__ */ new Date());
732
+ const headers = { ...args.headers, "x-amz-date": amzDate };
733
+ const signed = { host: url.host };
734
+ for (const [k, v] of Object.entries(headers)) signed[k.toLowerCase()] = String(v).trim().replace(/\s+/g, " ");
735
+ const names = Object.keys(signed).sort();
736
+ const canonicalHeaders = names.map((n) => `${n}:${signed[n]}
737
+ `).join("");
738
+ const signedHeaders = names.join(";");
739
+ const query = [...url.searchParams.entries()].map(([k, v]) => [rfc3986(k), rfc3986(v)]).sort(([a, av], [b, bv]) => a < b ? -1 : a > b ? 1 : av < bv ? -1 : av > bv ? 1 : 0).map(([k, v]) => `${k}=${v}`).join("&");
740
+ const canonicalRequest = [
741
+ args.method.toUpperCase(),
742
+ url.pathname || "/",
743
+ query,
744
+ canonicalHeaders,
745
+ signedHeaders,
746
+ args.payloadHash
747
+ ].join("\n");
748
+ const { accessKeyId, secretAccessKey, region, service } = args.credentials;
749
+ const scope = `${dateStamp}/${region}/${service}/aws4_request`;
750
+ const stringToSign = ["AWS4-HMAC-SHA256", amzDate, scope, await sha256Hex(canonicalRequest)].join("\n");
751
+ let key = await hmac(enc.encode(`AWS4${secretAccessKey}`), dateStamp);
752
+ key = await hmac(key, region);
753
+ key = await hmac(key, service);
754
+ key = await hmac(key, "aws4_request");
755
+ const signature = hex(await hmac(key, stringToSign));
756
+ return {
757
+ ...headers,
758
+ Authorization: `AWS4-HMAC-SHA256 Credential=${accessKeyId}/${scope}, SignedHeaders=${signedHeaders}, Signature=${signature}`
759
+ };
760
+ }
761
+ const UNSIGNED = "UNSIGNED-PAYLOAD";
762
+ function encodeKey(key) {
763
+ return key.split("/").map(rfc3986).join("/");
764
+ }
765
+ function decodeXml(s) {
766
+ return s.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&apos;/g, "'").replace(/&amp;/g, "&");
767
+ }
768
+ function createS3Connector(settings) {
769
+ const endpoint = (settings.endpoint || `https://s3.${settings.region}.amazonaws.com`).replace(/\/+$/, "");
770
+ let endpointUrl;
771
+ try {
772
+ endpointUrl = new URL(endpoint);
773
+ } catch {
774
+ throw new StorageError(`Invalid S3 endpoint: ${endpoint}`, 400);
775
+ }
776
+ const pathStyle = settings.forcePathStyle ?? !endpointUrl.hostname.endsWith("amazonaws.com");
777
+ const bucketBase = pathStyle ? `${endpoint}/${rfc3986(settings.bucket)}` : `${endpointUrl.protocol}//${settings.bucket}.${endpointUrl.host}`;
778
+ const prefix = (settings.prefix ?? "").replace(/^\/+|\/+$/g, "");
779
+ const siteUrl = (settings.siteUrl ?? "").replace(/\/+$/, "");
780
+ const credentials = {
781
+ accessKeyId: settings.accessKeyId,
782
+ secretAccessKey: settings.secretAccessKey,
783
+ region: settings.region || "us-east-1",
784
+ service: "s3"
785
+ };
786
+ const keyFor = (path) => prefix ? `${prefix}/${path.replace(/^\/+/, "")}` : path.replace(/^\/+/, "");
787
+ const pathFor = (key) => prefix ? key.slice(prefix.length + 1) : key;
788
+ async function send(action, method, url, init = {}) {
789
+ const headers = await signRequest({
790
+ method,
791
+ url,
792
+ headers: { "x-amz-content-sha256": UNSIGNED, ...init.headers ?? {} },
793
+ payloadHash: UNSIGNED,
794
+ credentials
795
+ });
796
+ return performRequest(
797
+ action,
798
+ () => fetch(url, { method, headers, body: init.body, signal: init.signal, cache: init.cache })
799
+ );
800
+ }
801
+ async function listKeys(under) {
802
+ const out = [];
803
+ let token;
804
+ do {
805
+ const params = new URLSearchParams({ "list-type": "2", "max-keys": "1000" });
806
+ if (under) params.set("prefix", under);
807
+ if (token) params.set("continuation-token", token);
808
+ const res = await send("list", "GET", `${bucketBase}/?${params}`, { cache: "no-store" });
809
+ const xml = await res.text();
810
+ for (const block of xml.match(/<Contents>[\s\S]*?<\/Contents>/g) ?? []) {
811
+ const key = block.match(/<Key>([\s\S]*?)<\/Key>/)?.[1];
812
+ const size = Number(block.match(/<Size>(\d+)<\/Size>/)?.[1] ?? 0);
813
+ if (key) out.push({ key: decodeXml(key), size });
814
+ }
815
+ const truncated = /<IsTruncated>true<\/IsTruncated>/.test(xml);
816
+ token = truncated ? decodeXml(xml.match(/<NextContinuationToken>([\s\S]*?)<\/NextContinuationToken>/)?.[1] ?? "") : void 0;
817
+ } while (token);
818
+ return out;
819
+ }
820
+ async function deleteKey(key, signal) {
821
+ try {
822
+ await send("delete", "DELETE", `${bucketBase}/${encodeKey(key)}`, { signal });
823
+ } catch (err) {
824
+ if (err instanceof StorageError && err.status === 404) return;
825
+ throw err;
826
+ }
827
+ }
828
+ return {
829
+ id: "s3",
830
+ model: "files",
831
+ capabilities: {
832
+ list: true,
833
+ rename: false,
834
+ renameFolder: false,
835
+ folders: false,
836
+ deletePrefix: true
837
+ },
838
+ async put(input, signal) {
839
+ const body = input.encoding === "base64" ? base64ToBytes(input.content) : input.content;
840
+ await send("upload", "PUT", `${bucketBase}/${encodeKey(keyFor(input.path))}`, {
841
+ headers: {
842
+ "content-type": contentTypeFor(input.path),
843
+ "cache-control": cacheControlFor(input.path),
844
+ ...settings.publicRead ? { "x-amz-acl": "public-read" } : {}
845
+ },
846
+ body,
847
+ signal
848
+ });
849
+ },
850
+ async get(path) {
851
+ const res = await send("get", "GET", `${bucketBase}/${encodeKey(keyFor(path))}`, { cache: "no-store" });
852
+ return res.text();
853
+ },
854
+ async getBytes(path) {
855
+ const url = `${bucketBase}/${encodeKey(keyFor(path))}`;
856
+ const headers = await signRequest({
857
+ method: "GET",
858
+ url,
859
+ headers: { "x-amz-content-sha256": UNSIGNED },
860
+ payloadHash: UNSIGNED,
861
+ credentials
862
+ });
863
+ const res = await fetch(url, { headers, cache: "no-store" }).catch(
864
+ (err) => performRequest("get", () => Promise.reject(err))
865
+ );
866
+ if (res.status === 404 || res.status === 403) {
867
+ return null;
868
+ }
869
+ const ok = res.ok ? res : await performRequest("get", async () => res);
870
+ return new Uint8Array(await ok.arrayBuffer());
871
+ },
872
+ delete: (path, signal) => deleteKey(keyFor(path), signal),
873
+ async deletePrefix(path) {
874
+ const keys = await listKeys(keyFor(path).replace(/\/+$/, "") + "/");
875
+ await mapLimit(keys, 6, (k) => deleteKey(k.key));
876
+ },
877
+ async list(page, perPage) {
878
+ const all = (await listKeys(prefix ? `${prefix}/` : "")).map((k) => ({ path: pathFor(k.key), type: "file", size: k.size, url: `${siteUrl}/${pathFor(k.key)}` }));
879
+ return { items: all.slice((page - 1) * perPage, page * perPage), page, per_page: perPage, total: all.length };
880
+ },
881
+ async listPrefix(under) {
882
+ const clean = under.replace(/^\/+/, "");
883
+ return (await listKeys(keyFor(clean))).map((k) => pathFor(k.key)).sort((a, b) => a < b ? -1 : a > b ? 1 : 0);
884
+ },
885
+ async publicUrl(path) {
886
+ return `${siteUrl}/${path.replace(/^\/+/, "")}`;
887
+ }
888
+ };
889
+ }
890
+ const s3Factory = {
891
+ id: "s3",
892
+ create: createS3Connector
893
+ };
894
+ export {
895
+ GitConflictError as G,
896
+ createGitPagesConnector as a,
897
+ createS3Connector as b,
898
+ createRemoteGitTransport as c,
899
+ gitPagesFactory as g,
900
+ joinRepoPath as j,
901
+ resolveGitPagesToken as r,
902
+ s3Factory as s
903
+ };