@cimplify/cli 0.15.3 → 0.16.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 (120) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +2 -2
  3. package/dist/add-SXC6RUPQ.mjs +140 -0
  4. package/dist/{add-KRIIKAS4.mjs → add-XQVQLD3F.mjs} +4 -4
  5. package/dist/ai-NQH5L4I6.mjs +17 -0
  6. package/dist/{assets-CWB72PZG.mjs → assets-FZGVOQXH.mjs} +12 -7
  7. package/dist/{auth-step-up-Z6KOTTV7.mjs → auth-step-up-67LCPDNL.mjs} +5 -5
  8. package/dist/build-J4ZUIOPX.mjs +625 -0
  9. package/dist/{cancel-ZHVVILOU.mjs → cancel-VGJHZJ4J.mjs} +4 -4
  10. package/dist/capture-RAJ2B3BH.mjs +69 -0
  11. package/dist/chunk-25ZPFHLF.mjs +51 -0
  12. package/dist/chunk-47ZXSY47.mjs +52 -0
  13. package/dist/{chunk-O2TO2J4I.mjs → chunk-54J3XR4T.mjs} +2 -2
  14. package/dist/chunk-5HP2INMY.mjs +10631 -0
  15. package/dist/chunk-7JR6JCFF.mjs +32 -0
  16. package/dist/chunk-7PQL7E7K.mjs +68 -0
  17. package/dist/chunk-AKACUJ3W.mjs +81 -0
  18. package/dist/chunk-BYNE553A.mjs +2945 -0
  19. package/dist/{chunk-NYXA6X56.mjs → chunk-CLQYPTJ2.mjs} +31 -11
  20. package/dist/chunk-CNZAOVSK.mjs +104 -0
  21. package/dist/chunk-CPBESPWK.mjs +5576 -0
  22. package/dist/{chunk-3RPR33MO.mjs → chunk-DYWUPHV3.mjs} +11 -7
  23. package/dist/chunk-EAV2P4N3.mjs +1941 -0
  24. package/dist/{chunk-UTD34C2H.mjs → chunk-EDZSOL5O.mjs} +2 -4
  25. package/dist/chunk-EGQIPYUN.mjs +696 -0
  26. package/dist/{chunk-DMLQBAGC.mjs → chunk-EOO4UOQD.mjs} +1 -1
  27. package/dist/chunk-EYEMPIMP.mjs +74 -0
  28. package/dist/chunk-G2ZEFSQJ.mjs +46 -0
  29. package/dist/{chunk-TH4XENKH.mjs → chunk-I4HNHPEM.mjs} +1 -1
  30. package/dist/chunk-JEVSFNPT.mjs +363 -0
  31. package/dist/chunk-JKECUAAV.mjs +2169 -0
  32. package/dist/chunk-KMHORI4Y.mjs +638 -0
  33. package/dist/chunk-KNPYBZ6B.mjs +502 -0
  34. package/dist/{chunk-3QUK5BPY.mjs → chunk-KOD4GBQ5.mjs} +8 -1
  35. package/dist/chunk-KQNZKV6E.mjs +535 -0
  36. package/dist/{chunk-2MKZKUVH.mjs → chunk-LLRUOIMU.mjs} +1 -2
  37. package/dist/{chunk-OLZKNUMK.mjs → chunk-LNMITJ4Z.mjs} +6 -6
  38. package/dist/chunk-MEM3MDV4.mjs +6463 -0
  39. package/dist/chunk-NY4YT7O6.mjs +84 -0
  40. package/dist/{chunk-LZEG3FEY.mjs → chunk-Q6CF4C3E.mjs} +7 -7
  41. package/dist/chunk-RP66A52P.mjs +86 -0
  42. package/dist/{chunk-I2UP7YTG.mjs → chunk-SAXGWS2K.mjs} +1 -1
  43. package/dist/chunk-SCKQ4XSD.mjs +41 -0
  44. package/dist/chunk-SF44L2OD.mjs +1152 -0
  45. package/dist/chunk-SGT5IJVW.mjs +28 -0
  46. package/dist/{chunk-LJEKKFC7.mjs → chunk-SS4JTMIV.mjs} +8 -8
  47. package/dist/chunk-T4MW3PD4.mjs +1456 -0
  48. package/dist/chunk-THATLMEX.mjs +1227 -0
  49. package/dist/{chunk-MLE4MVYC.mjs → chunk-TT4YI33G.mjs} +2 -2
  50. package/dist/chunk-TYV2SVFN.mjs +101 -0
  51. package/dist/chunk-VHK7WBO3.mjs +189 -0
  52. package/dist/chunk-VSA44UKR.mjs +391 -0
  53. package/dist/chunk-XL54SQJ3.mjs +42 -0
  54. package/dist/{chunk-5KYR6WJB.mjs → chunk-XQAHREKO.mjs} +17 -1
  55. package/dist/chunk-ZUCDXRFI.mjs +2011 -0
  56. package/dist/create-4S2EAUR3.mjs +10 -0
  57. package/dist/credits-NJOAOPPW.mjs +33 -0
  58. package/dist/{deploy-STWZ3OSM.mjs → deploy-26Y4UGZZ.mjs} +11 -11
  59. package/dist/dev-MOYURRWQ.mjs +401 -0
  60. package/dist/dev-WL43Q7VD.mjs +8 -0
  61. package/dist/dispatcher.mjs +131 -30
  62. package/dist/{doctor-3RW3OGZJ.mjs → doctor-E6ETD2GJ.mjs} +12 -20
  63. package/dist/{domains-FTNK4IKY.mjs → domains-LOTQMVOR.mjs} +6 -6
  64. package/dist/engine-runtime-fetch-CIE7S7PB.mjs +10 -0
  65. package/dist/{env-TNCYUAMT.mjs → env-3PDFC5XF.mjs} +7 -7
  66. package/dist/env-MOTYOPM5.mjs +19 -0
  67. package/dist/explain-K5QEVWCD.mjs +251 -0
  68. package/dist/{inspect-X3M7WSTL.mjs → inspect-Q3E2DHYY.mjs} +3 -3
  69. package/dist/inspect-SJ4WBAXO.mjs +86 -0
  70. package/dist/introspect-7F34YN5V.mjs +9 -0
  71. package/dist/kernel-DEGWMHFL.mjs +662 -0
  72. package/dist/kernel-plugin.js +6862 -0
  73. package/dist/keys-W5SN3DPK.mjs +71 -0
  74. package/dist/lines-XGJYTQ7P.mjs +56 -0
  75. package/dist/{link-76Z6V6CJ.mjs → link-NKUAVNM2.mjs} +6 -6
  76. package/dist/{lint-DDZ7HUQN.mjs → lint-G4YPQSSO.mjs} +2 -2
  77. package/dist/{list-2MFOQKKQ.mjs → list-TDR7K2JB.mjs} +3 -3
  78. package/dist/lock-EJPW2LM3.mjs +57 -0
  79. package/dist/{login-RLMDEWAL.mjs → login-AYLU7JU7.mjs} +10 -8
  80. package/dist/{logout-Z57X6HPE.mjs → logout-3M6VMHLH.mjs} +2 -2
  81. package/dist/{logs-TNDM4RFE.mjs → logs-4EL5SOXN.mjs} +5 -5
  82. package/dist/logs-4QQGG2XY.mjs +44 -0
  83. package/dist/mcp-4R2R6E5M.mjs +1077 -0
  84. package/dist/{mock-EVER34IX.mjs → mock-AK24GSP2.mjs} +2 -2
  85. package/dist/outdated-JEKCVLTG.mjs +58 -0
  86. package/dist/port-SBZ7X3EH.mjs +2408 -0
  87. package/dist/{projects-YL4T24VV.mjs → projects-7JKTC5WJ.mjs} +6 -6
  88. package/dist/publish-QQCX47HK.mjs +20 -0
  89. package/dist/remix-NLLYD3BI.mjs +125 -0
  90. package/dist/repo-Z4G4EW3N.mjs +9 -0
  91. package/dist/report-M7F4ZYP7.mjs +2 -0
  92. package/dist/rollback-2WQ2CW7T.mjs +43 -0
  93. package/dist/{rollback-47VQ4HJK.mjs → rollback-ZGU32HGT.mjs} +6 -6
  94. package/dist/scene-HXARHUM7.mjs +324 -0
  95. package/dist/{status-TWPOMWNA.mjs → status-C3ZSLZ7Q.mjs} +5 -5
  96. package/dist/status-WEQOC25K.mjs +18 -0
  97. package/dist/test-ZPBWV4VX.mjs +233 -0
  98. package/dist/testers-A3LMXOJJ.mjs +49 -0
  99. package/dist/{unlink-H3BO3DOQ.mjs → unlink-4G4FE4RJ.mjs} +2 -2
  100. package/dist/{update-FLY46XI3.mjs → update-GHKKM5FS.mjs} +4 -4
  101. package/dist/{whoami-CQPPARRS.mjs → whoami-3QQN6G2A.mjs} +3 -3
  102. package/dist/workshop-CD74D2LK.mjs +1703 -0
  103. package/package.json +11 -7
  104. package/templates/storefront-auto/app/products/[slug]/page.tsx +1 -1
  105. package/templates/storefront-auto/package.json +1 -1
  106. package/templates/storefront-bakery/package.json +1 -1
  107. package/templates/storefront-fashion/app/products/[slug]/page.tsx +1 -1
  108. package/templates/storefront-fashion/package.json +1 -1
  109. package/templates/storefront-grocery/package.json +1 -1
  110. package/templates/storefront-pharmacy/app/products/[slug]/page.tsx +1 -1
  111. package/templates/storefront-pharmacy/package.json +1 -1
  112. package/templates/storefront-restaurant/package.json +1 -1
  113. package/templates/storefront-retail/app/products/[slug]/page.tsx +1 -1
  114. package/templates/storefront-retail/package.json +1 -1
  115. package/templates/storefront-services/package.json +1 -1
  116. package/dist/chunk-ARIDYKLO.mjs +0 -6463
  117. package/dist/dev-7O65HY6C.mjs +0 -8
  118. package/dist/explain-DLRUUCNS.mjs +0 -251
  119. package/dist/introspect-RC265ACF.mjs +0 -9
  120. package/dist/repo-UUT3AZ3L.mjs +0 -9
@@ -0,0 +1,1703 @@
1
+ #!/usr/bin/env node
2
+ import { compileScript, assetKind } from './chunk-JKECUAAV.mjs';
3
+ import { parseToml, TomlSyntaxError, quoteTomlString } from './chunk-KQNZKV6E.mjs';
4
+ import { promptYesNo } from './chunk-I4HNHPEM.mjs';
5
+ import { encodeChunk } from './chunk-EYEMPIMP.mjs';
6
+ import { sha256, canonicalJson } from './chunk-25ZPFHLF.mjs';
7
+ import './chunk-SGT5IJVW.mjs';
8
+ import { parseArgs, flagString, flagBool } from './chunk-XQAHREKO.mjs';
9
+ import { ApiClient } from './chunk-CLQYPTJ2.mjs';
10
+ import { readAuthOrNull } from './chunk-TT4YI33G.mjs';
11
+ import { info, dim, CliError, CLI_ERROR_CODE, step, success, result, failure, warn, bold, isAutoYes, isInteractive } from './chunk-KOD4GBQ5.mjs';
12
+ import { mkdirSync, writeFileSync, promises, readFileSync } from 'fs';
13
+ import path4 from 'path';
14
+ import { createHash } from 'crypto';
15
+
16
+ // src/interactives/workshop/pack.ts
17
+ var VERSION_MANIFEST_FORMAT = 1;
18
+ var TERMS = {
19
+ free_attribution: "Cimplify Workshop licence: free with attribution (terms 2026-10-10). The owner grants the licensee Interactive a worldwide, royalty-free, non-exclusive right to use, reproduce, adapt and distribute this item inside that Interactive on Cimplify, for as long as the Interactive is offered. The licensee must keep the attribution Cimplify shows in the Interactive's credits. The item may not be extracted or redistributed on its own, and may not be used to train machine-learning models.",
20
+ free: "Cimplify Workshop licence: free (terms 2026-10-10). The owner grants the licensee Interactive a worldwide, royalty-free, non-exclusive right to use, reproduce, adapt and distribute this item inside that Interactive on Cimplify, for as long as the Interactive is offered. Attribution is not required by this licence. The item may not be extracted or redistributed on its own, and may not be used to train machine-learning models."
21
+ };
22
+ function termsDigest(licence) {
23
+ return sha256(TERMS[licence]);
24
+ }
25
+ var RATING = { fear: "none", language: "none", sexuality: "none", simulated_gambling: false, violence: "none" };
26
+ function optional(key, value) {
27
+ return value === void 0 ? {} : { [key]: value };
28
+ }
29
+ var PACK_TIER_RANK = { low: 0, mid: 1, high: 2 };
30
+ function packFiles(sources, read, blobs) {
31
+ const files = [];
32
+ for (const target of Object.keys(sources).sort()) {
33
+ const payload = read(sources[target]);
34
+ const chunk = encodeChunk(assetKind(target), payload);
35
+ blobs.set(chunk.digest, Buffer.concat(chunk.parts));
36
+ files.push({ bytes: chunk.totalBytes, chunk: chunk.digest, kind: chunk.kind, path: target });
37
+ }
38
+ return files;
39
+ }
40
+ function packItem(item, read, packed2) {
41
+ const blobs = /* @__PURE__ */ new Map();
42
+ const tiered = [{ tier: item.tier, files: item.files }, ...item.tiers ?? []].sort(
43
+ (a, b) => PACK_TIER_RANK[a.tier] - PACK_TIER_RANK[b.tier]
44
+ );
45
+ const variants = tiered.map((variant) => ({ files: packFiles(variant.files, read, blobs), tier: variant.tier }));
46
+ const dependencies = (item.dependencies ?? []).map((reference) => {
47
+ const upstream = packed2.get(reference);
48
+ if (upstream === void 0) throw new Error(`${item.item} depends on ${reference}, which is not packed before it`);
49
+ return { item: reference, version: upstream.item.version, version_digest: upstream.versionDigest };
50
+ });
51
+ const manifest = {
52
+ ai_origin: item.ai_origin,
53
+ credit: {
54
+ authors: [...item.credit.authors],
55
+ licence: item.credit.licence,
56
+ ...optional("licence_url", item.credit.licence_url),
57
+ modified: item.credit.modified,
58
+ ...optional("source_url", item.credit.source_url),
59
+ title: item.credit.title
60
+ },
61
+ format: VERSION_MANIFEST_FORMAT,
62
+ item: item.item,
63
+ item_type: item.item_type,
64
+ name: item.name,
65
+ rating: RATING,
66
+ sources: item.sources.map((s) => ({
67
+ ...optional("attribution", s.attribution),
68
+ author: s.author,
69
+ kind: s.kind,
70
+ licence: s.licence,
71
+ ...optional("url", s.url)
72
+ })),
73
+ variants,
74
+ version: item.version,
75
+ ...dependencies.length === 0 ? {} : { dependencies }
76
+ };
77
+ const bytes = Buffer.from(canonicalJson(manifest), "utf8");
78
+ const versionDigest = sha256(bytes);
79
+ blobs.set(versionDigest, bytes);
80
+ return { item, manifest: bytes, versionDigest, blobs };
81
+ }
82
+ function packItems(items, read) {
83
+ const packed2 = /* @__PURE__ */ new Map();
84
+ for (const item of items) packed2.set(item.item, packItem(item, read, packed2));
85
+ return [...packed2.values()];
86
+ }
87
+ function resolutionOf(packed2) {
88
+ return packed2.map((p) => ({
89
+ item: p.item.item,
90
+ item_type: p.item.item_type,
91
+ version: p.item.version,
92
+ version_digest: p.versionDigest,
93
+ requested: false,
94
+ offers: [{ offer_id: `cof_${p.item.item}`, kind: p.item.licence, terms: termsDigest(p.item.licence), derivatives: "remixable", generation_allowed: true }]
95
+ }));
96
+ }
97
+ function importBody(p, owner) {
98
+ return {
99
+ owner,
100
+ listing: "listed",
101
+ details: { name: p.item.name, description: p.item.description, tags: p.item.tags },
102
+ manifest: p.manifest.toString("base64"),
103
+ blobs: [...p.blobs.entries()].filter(([digest]) => digest !== p.versionDigest).map(([, bytes]) => bytes.toString("base64")),
104
+ offers: [{ kind: p.item.licence, derivatives: "remixable", generation_allowed: true }]
105
+ };
106
+ }
107
+
108
+ // src/interactives/workshop/lagos/atlas.ts
109
+ var GRID = 8;
110
+ var LAGOS_QUADRANT = [0.5, 0.5, 0.5, 0.5];
111
+ var SWATCHES = {
112
+ asphalt: 3882306,
113
+ asphaltWorn: 4868943,
114
+ laneWhite: 15328988,
115
+ laneYellow: 15906352,
116
+ concrete: 11117722,
117
+ kerb: 13617597,
118
+ kerbRed: 11878446,
119
+ paving: 10260349,
120
+ dirt: 10119747,
121
+ laterite: 10769199,
122
+ danfoYellow: 16168972,
123
+ danfoBlack: 1381914,
124
+ chrome: 13225169,
125
+ glass: 2968156,
126
+ brtRed: 12856107,
127
+ brtBlue: 2051740,
128
+ kekeYellow: 15909376,
129
+ kekeGreen: 2067020,
130
+ canvas: 3104314,
131
+ wood: 8016434,
132
+ woodDark: 5584417,
133
+ zinc: 9409689,
134
+ rust: 9063210,
135
+ ankaraRed: 13776685,
136
+ ankaraBlue: 2384568,
137
+ ankaraGreen: 3054170,
138
+ ankaraOrange: 15695138,
139
+ ankaraPurple: 8077216,
140
+ umbrellaWhite: 15855592,
141
+ tomato: 14168606,
142
+ plantain: 15254586,
143
+ pepper: 12000274,
144
+ palmTrunk: 7034435,
145
+ palmLeaf: 4160811,
146
+ palmLeafDark: 2907679,
147
+ skinDark: 5913126,
148
+ skinMid: 8016182,
149
+ skinLight: 10119753,
150
+ clothWhite: 15394525,
151
+ clothBlack: 2303275,
152
+ clothGold: 13081130,
153
+ rubber: 1842206,
154
+ headlight: 16773832,
155
+ taillight: 13704991,
156
+ signGreen: 1997372,
157
+ signWhite: 16119280,
158
+ wall: 14272941,
159
+ wallBlue: 9417417,
160
+ wallPink: 14263200,
161
+ roofTile: 10177074,
162
+ water: 3960447,
163
+ grass: 6127413,
164
+ shadowGray: 2763308
165
+ };
166
+ var NAMES = Object.keys(SWATCHES);
167
+ function swatchUv(name) {
168
+ const index = NAMES.indexOf(name);
169
+ const col = index % GRID;
170
+ const row = Math.floor(index / GRID);
171
+ return [(col + 0.5) / GRID, (row + 0.5) / GRID];
172
+ }
173
+ function lagosUv(name) {
174
+ const uv = swatchUv(name);
175
+ const t = LAGOS_QUADRANT;
176
+ return [uv[0] * t[0] + t[2], uv[1] * t[1] + t[3]];
177
+ }
178
+
179
+ // src/interactives/workshop/lagos/geo.ts
180
+ function normal(a, b, c) {
181
+ const e0 = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
182
+ const e1 = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
183
+ const n = [e0[1] * e1[2] - e0[2] * e1[1], e0[2] * e1[0] - e0[0] * e1[2], e0[0] * e1[1] - e0[1] * e1[0]];
184
+ const l = Math.hypot(n[0], n[1], n[2]) || 1;
185
+ return [n[0] / l, n[1] / l, n[2] / l];
186
+ }
187
+ var Mesh = class {
188
+ positions = [];
189
+ normals = [];
190
+ uvs = [];
191
+ indices = [];
192
+ get vertexCount() {
193
+ return this.positions.length / 3;
194
+ }
195
+ vertex(p, n, uv) {
196
+ this.positions.push(p[0], p[1], p[2]);
197
+ this.normals.push(n[0], n[1], n[2]);
198
+ this.uvs.push(uv[0], uv[1]);
199
+ return this.vertexCount - 1;
200
+ }
201
+ quad(a, b, c, d, uv) {
202
+ const n = normal(a, b, c);
203
+ const i = this.vertex(a, n, uv);
204
+ this.vertex(b, n, uv);
205
+ this.vertex(c, n, uv);
206
+ this.vertex(d, n, uv);
207
+ this.indices.push(i, i + 1, i + 2, i, i + 2, i + 3);
208
+ }
209
+ triangle(a, b, c, uv) {
210
+ const n = normal(a, b, c);
211
+ const i = this.vertex(a, n, uv);
212
+ this.vertex(b, n, uv);
213
+ this.vertex(c, n, uv);
214
+ this.indices.push(i, i + 1, i + 2);
215
+ }
216
+ box(center, half, uv, yaw = 0, bottom = true) {
217
+ const [cx, cy, cz] = center;
218
+ const [hx, hy, hz] = half;
219
+ const c = Math.cos(yaw);
220
+ const s = Math.sin(yaw);
221
+ const p = (x, y, z) => [cx + c * x + s * z, cy + y, cz - s * x + c * z];
222
+ const v = [p(-hx, -hy, -hz), p(hx, -hy, -hz), p(hx, -hy, hz), p(-hx, -hy, hz), p(-hx, hy, -hz), p(hx, hy, -hz), p(hx, hy, hz), p(-hx, hy, hz)];
223
+ this.quad(v[4], v[7], v[6], v[5], uv);
224
+ this.quad(v[3], v[2], v[6], v[7], uv);
225
+ this.quad(v[1], v[0], v[4], v[5], uv);
226
+ this.quad(v[2], v[1], v[5], v[6], uv);
227
+ this.quad(v[0], v[3], v[7], v[4], uv);
228
+ if (bottom) this.quad(v[0], v[1], v[2], v[3], uv);
229
+ }
230
+ cylinder(base, radius, height, sides, uv, top = true, radiusTop = radius) {
231
+ const [bx, by, bz] = base;
232
+ for (let i = 0; i < sides; i++) {
233
+ const a0 = i / sides * Math.PI * 2;
234
+ const a1 = (i + 1) / sides * Math.PI * 2;
235
+ const p0 = [bx + Math.cos(a0) * radius, by, bz + Math.sin(a0) * radius];
236
+ const p1 = [bx + Math.cos(a1) * radius, by, bz + Math.sin(a1) * radius];
237
+ const q0 = [bx + Math.cos(a0) * radiusTop, by + height, bz + Math.sin(a0) * radiusTop];
238
+ const q1 = [bx + Math.cos(a1) * radiusTop, by + height, bz + Math.sin(a1) * radiusTop];
239
+ this.quad(p1, p0, q0, q1, uv);
240
+ if (top) this.triangle([bx, by + height, bz], q1, q0, uv);
241
+ }
242
+ }
243
+ };
244
+
245
+ // src/interactives/workshop/lagos/glb.ts
246
+ var GLB_MAGIC = 1179937895;
247
+ var GLB_VERSION = 2;
248
+ var CHUNK_JSON = 1313821514;
249
+ var CHUNK_BIN = 5130562;
250
+ var HEADER_BYTES = 12;
251
+ var CHUNK_HEADER_BYTES = 8;
252
+ var ALIGN = 4;
253
+ var JSON_PAD = 32;
254
+ var FLOAT = 5126;
255
+ var UNSIGNED_BYTE = 5121;
256
+ var UNSIGNED_SHORT = 5123;
257
+ var UNSIGNED_INT = 5125;
258
+ var ARRAY_BUFFER = 34962;
259
+ var ELEMENT_ARRAY_BUFFER = 34963;
260
+ var GENERATOR = "penalty-kings asset pipeline";
261
+ var COMPONENTS = { SCALAR: 1, VEC2: 2, VEC3: 3, VEC4: 4, MAT4: 16 };
262
+ function pad(length) {
263
+ return (ALIGN - length % ALIGN) % ALIGN;
264
+ }
265
+ var GlbWriter = class {
266
+ json;
267
+ parts = [];
268
+ length = 0;
269
+ constructor() {
270
+ this.json = {
271
+ asset: { version: "2.0", generator: GENERATOR },
272
+ scene: 0,
273
+ scenes: [{ nodes: [] }],
274
+ nodes: [],
275
+ meshes: [],
276
+ accessors: [],
277
+ bufferViews: [],
278
+ buffers: [],
279
+ materials: [],
280
+ skins: [],
281
+ animations: [],
282
+ images: [],
283
+ textures: [],
284
+ samplers: []
285
+ };
286
+ }
287
+ view(bytes, target) {
288
+ const offset = this.length;
289
+ this.parts.push(bytes);
290
+ this.length += bytes.byteLength;
291
+ const fill = pad(this.length);
292
+ if (fill > 0) {
293
+ this.parts.push(new Uint8Array(fill));
294
+ this.length += fill;
295
+ }
296
+ const view = { buffer: 0, byteOffset: offset, byteLength: bytes.byteLength };
297
+ if (target !== void 0) view.target = target;
298
+ this.json.bufferViews.push(view);
299
+ return this.json.bufferViews.length - 1;
300
+ }
301
+ accessor(data, type, options = {}) {
302
+ const componentType = data instanceof Float32Array ? FLOAT : data instanceof Uint32Array ? UNSIGNED_INT : data instanceof Uint16Array ? UNSIGNED_SHORT : UNSIGNED_BYTE;
303
+ const width = COMPONENTS[type];
304
+ const view = this.view(new Uint8Array(data.buffer, data.byteOffset, data.byteLength), options.target);
305
+ const accessor = { bufferView: view, componentType, count: data.length / width, type };
306
+ if (options.bounds) {
307
+ const min = new Array(width).fill(Infinity);
308
+ const max = new Array(width).fill(-Infinity);
309
+ for (let i = 0; i < data.length; i++) {
310
+ const c = i % width;
311
+ if (data[i] < min[c]) min[c] = data[i];
312
+ if (data[i] > max[c]) max[c] = data[i];
313
+ }
314
+ accessor.min = min;
315
+ accessor.max = max;
316
+ }
317
+ this.json.accessors.push(accessor);
318
+ return this.json.accessors.length - 1;
319
+ }
320
+ material(material) {
321
+ this.json.materials.push(material);
322
+ return this.json.materials.length - 1;
323
+ }
324
+ node(node, root = false) {
325
+ this.json.nodes.push(node);
326
+ const index = this.json.nodes.length - 1;
327
+ if (root) this.json.scenes[0].nodes.push(index);
328
+ return index;
329
+ }
330
+ mesh(primitives, name) {
331
+ this.json.meshes.push(name === void 0 ? { primitives } : { name, primitives });
332
+ return this.json.meshes.length - 1;
333
+ }
334
+ toGlb() {
335
+ this.json.buffers.push({ byteLength: this.length });
336
+ const json = this.json;
337
+ for (const key of ["images", "textures", "samplers", "materials", "skins", "animations", "meshes"]) {
338
+ if (json[key].length === 0) delete json[key];
339
+ }
340
+ const text = new TextEncoder().encode(JSON.stringify(json));
341
+ const jsonPad = pad(text.byteLength);
342
+ const binLength = this.length;
343
+ const total = HEADER_BYTES + CHUNK_HEADER_BYTES + text.byteLength + jsonPad + CHUNK_HEADER_BYTES + binLength;
344
+ const out = new Uint8Array(total);
345
+ const view = new DataView(out.buffer);
346
+ view.setUint32(0, GLB_MAGIC, true);
347
+ view.setUint32(4, GLB_VERSION, true);
348
+ view.setUint32(8, total, true);
349
+ view.setUint32(12, text.byteLength + jsonPad, true);
350
+ view.setUint32(16, CHUNK_JSON, true);
351
+ out.set(text, 20);
352
+ out.fill(JSON_PAD, 20 + text.byteLength, 20 + text.byteLength + jsonPad);
353
+ let at = 20 + text.byteLength + jsonPad;
354
+ view.setUint32(at, binLength, true);
355
+ view.setUint32(at + 4, CHUNK_BIN, true);
356
+ at += CHUNK_HEADER_BYTES;
357
+ for (const part of this.parts) {
358
+ out.set(part, at);
359
+ at += part.byteLength;
360
+ }
361
+ return out;
362
+ }
363
+ };
364
+ function writeModel(parts, material = "atlas") {
365
+ const glb = new GlbWriter();
366
+ const mat = glb.material({ name: material, pbrMetallicRoughness: { baseColorFactor: [1, 1, 1, 1], metallicFactor: 0, roughnessFactor: 0.62 } });
367
+ const children = [];
368
+ glb.node({ name: "root", children }, true);
369
+ for (const part of parts) {
370
+ const m = part.mesh;
371
+ const wide = m.vertexCount > 65535;
372
+ const position = glb.accessor(Float32Array.from(m.positions), "VEC3", { target: ARRAY_BUFFER, bounds: true });
373
+ const normal2 = glb.accessor(Float32Array.from(m.normals), "VEC3", { target: ARRAY_BUFFER });
374
+ const uv = glb.accessor(Float32Array.from(m.uvs), "VEC2", { target: ARRAY_BUFFER });
375
+ const indices = glb.accessor(wide ? Uint32Array.from(m.indices) : Uint16Array.from(m.indices), "SCALAR", { target: ELEMENT_ARRAY_BUFFER });
376
+ const mesh = glb.mesh([{ attributes: { POSITION: position, NORMAL: normal2, TEXCOORD_0: uv }, indices, material: mat }], part.name);
377
+ children.push(glb.node({ name: part.name, mesh, translation: [...part.origin] }));
378
+ }
379
+ return glb.toGlb();
380
+ }
381
+
382
+ // src/interactives/workshop/lagos/props.ts
383
+ var FABRICS = ["ankaraRed", "ankaraBlue", "ankaraGreen", "ankaraOrange", "ankaraPurple", "umbrellaWhite"];
384
+ var GOODS = ["tomato", "plantain", "pepper", "ankaraOrange", "clothGold", "umbrellaWhite"];
385
+ function mulberry(seed) {
386
+ let a = seed >>> 0;
387
+ return () => {
388
+ a = a + 1831565813 >>> 0;
389
+ let t = a;
390
+ t = Math.imul(t ^ t >>> 15, t | 1);
391
+ t ^= t + Math.imul(t ^ t >>> 7, t | 61);
392
+ return ((t ^ t >>> 14) >>> 0) / 4294967296;
393
+ };
394
+ }
395
+ function umbrella(m, center, radius, height, fabric, pole) {
396
+ const [x, y, z] = center;
397
+ m.box([x, y + height / 2, z], [0.03, height / 2, 0.03], lagosUv(pole));
398
+ const sides = 8;
399
+ const apex = [x, y + height + 0.35, z];
400
+ for (let i = 0; i < sides; i++) {
401
+ const a0 = i / sides * Math.PI * 2;
402
+ const a1 = (i + 1) / sides * Math.PI * 2;
403
+ const p0 = [x + Math.cos(a0) * radius, y + height, z + Math.sin(a0) * radius];
404
+ const p1 = [x + Math.cos(a1) * radius, y + height, z + Math.sin(a1) * radius];
405
+ const stripe = i % 2 === 0 ? fabric : "umbrellaWhite";
406
+ m.triangle(apex, p1, p0, lagosUv(stripe));
407
+ m.triangle(apex, p0, p1, lagosUv(stripe));
408
+ }
409
+ }
410
+ function stall(seed) {
411
+ const random = mulberry(seed);
412
+ const pick = (list) => list[Math.floor(random() * list.length) % list.length];
413
+ const m = new Mesh();
414
+ const wood = lagosUv("wood");
415
+ const dark = lagosUv("woodDark");
416
+ m.box([0, 0.86, 0], [0.95, 0.04, 0.5], wood);
417
+ for (const x of [-0.88, 0.88]) for (const z of [-0.43, 0.43]) m.box([x, 0.42, z], [0.04, 0.42, 0.04], dark);
418
+ const goods = pick(GOODS);
419
+ for (let i = 0; i < 9; i++) {
420
+ const gx = -0.7 + i % 5 * 0.35;
421
+ const gz = i < 5 ? -0.18 : 0.18;
422
+ const size = 0.12 + random() * 0.06;
423
+ m.box([gx, 0.9 + size, gz], [size, size, size], lagosUv(goods));
424
+ }
425
+ m.box([0, 0.25, 0], [0.8, 0.25, 0.4], lagosUv(pick(FABRICS)), random() * 0.1);
426
+ umbrella(m, [0.2, 0, 0.55], 1.25, 2.25, pick(FABRICS), "woodDark");
427
+ return m;
428
+ }
429
+ function kiosk(seed) {
430
+ const random = mulberry(seed);
431
+ const m = new Mesh();
432
+ const walls = ["wallBlue", "wall", "kekeGreen", "brtBlue", "ankaraOrange"];
433
+ const wall = lagosUv(walls[Math.floor(random() * walls.length) % walls.length]);
434
+ m.box([0, 1.2, 0], [1.6, 1.2, 1.3], wall);
435
+ m.box([0, 1.25, 1.31], [1.1, 0.55, 0.02], lagosUv("shadowGray"));
436
+ m.box([0, 0.72, 1.45], [1.35, 0.05, 0.18], lagosUv("wood"));
437
+ const roof = lagosUv("zinc");
438
+ m.quad([-1.8, 2.55, -1.5], [-1.8, 2.75, 1.9], [1.8, 2.75, 1.9], [1.8, 2.55, -1.5], roof);
439
+ m.quad([1.8, 2.55, -1.5], [1.8, 2.75, 1.9], [-1.8, 2.75, 1.9], [-1.8, 2.55, -1.5], lagosUv("rust"));
440
+ m.box([0, 2.95, 1.25], [1.2, 0.2, 0.03], lagosUv("signWhite"));
441
+ m.box([0, 2.95, 1.27], [1, 0.1, 0.02], lagosUv(FABRICS[Math.floor(random() * FABRICS.length) % FABRICS.length]));
442
+ return m;
443
+ }
444
+ function busStopSign() {
445
+ const m = new Mesh();
446
+ m.box([0, 1.3, 0], [0.04, 1.3, 0.04], lagosUv("zinc"));
447
+ m.box([0, 2.55, 0], [0.45, 0.28, 0.03], lagosUv("signGreen"));
448
+ m.box([0, 2.55, 0.035], [0.35, 0.1, 0.01], lagosUv("signWhite"));
449
+ return m;
450
+ }
451
+ function barrier(length, swatch, height = 0.8) {
452
+ const m = new Mesh();
453
+ m.box([0, height / 2, 0], [0.22, height / 2, length / 2], lagosUv(swatch));
454
+ m.box([0, height + 0.02, 0], [0.12, 0.02, length / 2], lagosUv("kerb"));
455
+ return m;
456
+ }
457
+ function generator() {
458
+ const m = new Mesh();
459
+ m.box([0, 0.35, 0], [0.45, 0.32, 0.3], lagosUv("ankaraRed"));
460
+ m.box([0, 0.72, 0], [0.38, 0.05, 0.26], lagosUv("danfoBlack"));
461
+ m.box([0.3, 0.75, 0], [0.05, 0.1, 0.05], lagosUv("zinc"));
462
+ return m;
463
+ }
464
+ function billboard(seed) {
465
+ const random = mulberry(seed);
466
+ const m = new Mesh();
467
+ const colors = ["signGreen", "brtRed", "ankaraOrange", "ankaraBlue", "kekeYellow"];
468
+ for (const x of [-2.2, 2.2]) m.box([x, 3.5, 0], [0.12, 3.5, 0.12], lagosUv("zinc"));
469
+ m.box([0, 7.8, 0], [3.2, 1.5, 0.15], lagosUv(colors[Math.floor(random() * colors.length) % colors.length]));
470
+ m.box([0, 8.1, 0.16], [2.4, 0.35, 0.02], lagosUv("signWhite"));
471
+ m.box([0, 7.3, 0.16], [1.6, 0.25, 0.02], lagosUv("clothGold"));
472
+ return m;
473
+ }
474
+
475
+ // src/interactives/workshop/lagos/generator.ts
476
+ var WIRE_SEGMENTS = 12;
477
+ var WIRE_SAG = 0.55;
478
+ var POLE_SPAN = 18;
479
+ var STALL_SEED = 7;
480
+ var KIOSK_SEED = 3;
481
+ var BILLBOARD_SEED = 2;
482
+ var BARRIER_LENGTH = 3;
483
+ function spokeWheel(radius, width) {
484
+ const m = new Mesh();
485
+ const tyre = lagosUv("rubber");
486
+ const hub = lagosUv("chrome");
487
+ const sides = 14;
488
+ for (let i = 0; i < sides; i++) {
489
+ const a0 = i / sides * Math.PI * 2;
490
+ const a1 = (i + 1) / sides * Math.PI * 2;
491
+ const p = (a, x) => [x, Math.sin(a) * radius, Math.cos(a) * radius];
492
+ m.quad(p(a0, -width / 2), p(a1, -width / 2), p(a1, width / 2), p(a0, width / 2), tyre);
493
+ m.triangle([width / 2 + 4e-3, 0, 0], p(a0, width / 2 + 4e-3), p(a1, width / 2 + 4e-3), hub);
494
+ m.triangle([-width / 2 - 4e-3, 0, 0], p(a1, -width / 2 - 4e-3), p(a0, -width / 2 - 4e-3), hub);
495
+ }
496
+ return m;
497
+ }
498
+ function okada() {
499
+ const bike = new Mesh();
500
+ const frame = lagosUv("ankaraRed");
501
+ const black = lagosUv("danfoBlack");
502
+ const chrome = lagosUv("chrome");
503
+ bike.box([0, 0.55, 0.05], [0.12, 0.12, 0.55], frame);
504
+ bike.box([0, 0.72, -0.25], [0.16, 0.06, 0.42], black);
505
+ bike.box([0, 0.66, 0.42], [0.14, 0.12, 0.18], frame);
506
+ bike.box([0, 0.85, 0.66], [0.04, 0.28, 0.04], chrome);
507
+ bike.box([0, 1.1, 0.68], [0.36, 0.025, 0.025], black);
508
+ bike.box([0, 0.98, 0.75], [0.08, 0.06, 0.03], lagosUv("headlight"));
509
+ bike.box([0, 0.62, -0.72], [0.1, 0.05, 0.05], lagosUv("taillight"));
510
+ bike.box([0.14, 0.42, -0.35], [0.04, 0.04, 0.28], chrome);
511
+ bike.box([0, 0.4, 0.1], [0.11, 0.12, 0.16], chrome);
512
+ const rider = new Mesh();
513
+ const skin = lagosUv("skinDark");
514
+ const shirt = lagosUv("ankaraBlue");
515
+ const trousers = lagosUv("clothBlack");
516
+ rider.box([0, 1.15, -0.2], [0.2, 0.3, 0.13], shirt);
517
+ rider.box([-0.12, 0.82, 0.05], [0.07, 0.07, 0.24], trousers);
518
+ rider.box([0.12, 0.82, 0.05], [0.07, 0.07, 0.24], trousers);
519
+ rider.box([-0.12, 0.55, 0.25], [0.06, 0.24, 0.06], trousers);
520
+ rider.box([0.12, 0.55, 0.25], [0.06, 0.24, 0.06], trousers);
521
+ rider.box([-0.26, 1.2, 0.2], [0.05, 0.05, 0.3], shirt);
522
+ rider.box([0.26, 1.2, 0.2], [0.05, 0.05, 0.3], shirt);
523
+ rider.box([0, 1.58, -0.18], [0.1, 0.12, 0.1], skin);
524
+ rider.box([0, 1.66, -0.18], [0.13, 0.08, 0.13], lagosUv("kekeYellow"));
525
+ const wheel = spokeWheel(0.32, 0.1);
526
+ return [
527
+ { name: "bike", mesh: bike, origin: [0, 0, 0] },
528
+ { name: "rider", mesh: rider, origin: [0, 0, 0] },
529
+ { name: "wheel-front", mesh: wheel, origin: [0, 0.32, 0.72] },
530
+ { name: "wheel-back", mesh: wheel, origin: [0, 0.32, -0.62] }
531
+ ];
532
+ }
533
+ function wire(m, from, to, swatch) {
534
+ const uv = lagosUv(swatch);
535
+ const thickness = 0.015;
536
+ for (let i = 0; i < WIRE_SEGMENTS; i++) {
537
+ const at = (t) => [
538
+ from[0] + (to[0] - from[0]) * t,
539
+ from[1] + (to[1] - from[1]) * t - WIRE_SAG * 4 * t * (1 - t),
540
+ from[2] + (to[2] - from[2]) * t
541
+ ];
542
+ const a = at(i / WIRE_SEGMENTS);
543
+ const b = at((i + 1) / WIRE_SEGMENTS);
544
+ m.quad([a[0], a[1] - thickness, a[2]], [b[0], b[1] - thickness, b[2]], [b[0], b[1] + thickness, b[2]], [a[0], a[1] + thickness, a[2]], uv);
545
+ m.quad([a[0], a[1] + thickness, a[2]], [b[0], b[1] + thickness, b[2]], [b[0], b[1] - thickness, b[2]], [a[0], a[1] - thickness, a[2]], uv);
546
+ }
547
+ }
548
+ function poleAndWires() {
549
+ const m = new Mesh();
550
+ const wood = lagosUv("woodDark");
551
+ for (const z of [0, POLE_SPAN]) {
552
+ m.cylinder([0, 0, z], 0.14, 9, 8, wood, true, 0.11);
553
+ m.box([0, 8.6, z], [1.1, 0.06, 0.06], wood);
554
+ for (const x of [-0.9, 0, 0.9]) m.box([x, 8.72, z], [0.04, 0.06, 0.04], lagosUv("umbrellaWhite"));
555
+ m.box([0.25, 7.6, z], [0.2, 0.3, 0.2], lagosUv("zinc"));
556
+ }
557
+ for (const x of [-0.9, 0, 0.9]) wire(m, [x, 8.78, 0], [x, 8.78, POLE_SPAN], "rubber");
558
+ return m;
559
+ }
560
+ function streetSignage() {
561
+ const shop = new Mesh();
562
+ shop.box([0, 1.6, 0], [0.05, 1.6, 0.05], lagosUv("zinc"));
563
+ shop.box([0, 3.2, 0], [1.2, 0.4, 0.04], lagosUv("ankaraPurple"));
564
+ shop.box([0, 3.2, 0.045], [1, 0.16, 0.01], lagosUv("clothGold"));
565
+ shop.box([0, 2.85, 0.045], [0.7, 0.06, 0.01], lagosUv("signWhite"));
566
+ return [
567
+ { name: "billboard", mesh: billboard(BILLBOARD_SEED), origin: [0, 0, 0] },
568
+ { name: "bus-stop-sign", mesh: busStopSign(), origin: [5, 0, 0] },
569
+ { name: "shop-sign", mesh: shop, origin: [-5, 0, 0] }
570
+ ];
571
+ }
572
+ function roadsideUmbrella() {
573
+ const m = new Mesh();
574
+ m.box([0, 1.1, 0], [0.03, 1.1, 0.03], lagosUv("woodDark"));
575
+ m.cylinder([0, 0, 0], 0.3, 0.12, 10, lagosUv("concrete"));
576
+ const apex = [0, 2.55, 0];
577
+ const sides = 10;
578
+ for (let i = 0; i < sides; i++) {
579
+ const a0 = i / sides * Math.PI * 2;
580
+ const a1 = (i + 1) / sides * Math.PI * 2;
581
+ const p0 = [Math.cos(a0) * 1.4, 2.15, Math.sin(a0) * 1.4];
582
+ const p1 = [Math.cos(a1) * 1.4, 2.15, Math.sin(a1) * 1.4];
583
+ const stripe = lagosUv(i % 2 === 0 ? "ankaraGreen" : "umbrellaWhite");
584
+ m.triangle(apex, p1, p0, stripe);
585
+ m.triangle(apex, p0, p1, stripe);
586
+ }
587
+ return m;
588
+ }
589
+ function single(name, mesh) {
590
+ return writeModel([{ name, mesh, origin: [0, 0, 0] }]);
591
+ }
592
+ function workshopAssets() {
593
+ return {
594
+ "okada.glb": writeModel(okada()),
595
+ "market-stall.glb": single("market-stall", stall(STALL_SEED)),
596
+ "kiosk.glb": single("kiosk", kiosk(KIOSK_SEED)),
597
+ "pole-and-wires.glb": single("pole-and-wires", poleAndWires()),
598
+ "street-signage.glb": writeModel(streetSignage()),
599
+ "roadside-umbrella.glb": single("roadside-umbrella", roadsideUmbrella()),
600
+ "yellow-barrier.glb": single("yellow-barrier", barrier(BARRIER_LENGTH, "kekeYellow")),
601
+ "generator.glb": single("generator", generator())
602
+ };
603
+ }
604
+
605
+ // src/interactives/workshop/packs/lagos.ts
606
+ var KENNEY_CAR_KIT = "https://kenney.nl/assets/car-kit";
607
+ var KENNEY_CITY_ROADS = "https://kenney.nl/assets/city-kit-roads";
608
+ var KENNEY_CITY_COMMERCIAL = "https://kenney.nl/assets/city-kit-commercial";
609
+ var CC0 = "CC0 1.0";
610
+ var CC0_URL = "https://creativecommons.org/publicdomain/zero/1.0/";
611
+ var CC0_CODE = "cc0-1.0";
612
+ var VERSION = "1.0.0";
613
+ var CIMPLIFY = "Cimplify";
614
+ var KENNEY = "Kenney";
615
+ var ATLAS = "cimplify/lagos-street-atlas";
616
+ var LAGOS_ASSETS = "packages/sdk/test-fixtures/interactives/lagos-street/project/assets/core";
617
+ var GENERATED_PREFIX = "generated/";
618
+ function own() {
619
+ return { kind: "own_work", licence: CC0_CODE, author: CIMPLIFY };
620
+ }
621
+ function kenney(url) {
622
+ return { kind: "third_party", licence: CC0_CODE, author: KENNEY, url };
623
+ }
624
+ function generated(slug, name, description, tags, file, itemType = "model") {
625
+ return {
626
+ item: `cimplify/${slug}`,
627
+ item_type: itemType,
628
+ version: VERSION,
629
+ name,
630
+ description,
631
+ tags,
632
+ credit: { title: name, authors: [CIMPLIFY], licence: CC0, licence_url: CC0_URL, modified: false },
633
+ sources: [own()],
634
+ ai_origin: "none",
635
+ licence: "free_attribution",
636
+ tier: "mid",
637
+ files: { [file]: `${GENERATED_PREFIX}${file}` },
638
+ dependencies: [ATLAS]
639
+ };
640
+ }
641
+ var LAGOS_PACK = [
642
+ {
643
+ item: ATLAS,
644
+ item_type: "texture",
645
+ version: VERSION,
646
+ name: "Lagos street palette atlas",
647
+ description: "The colour atlas every Lagos street model samples: Kenney kit palettes plus a generated Lagos palette quadrant",
648
+ tags: ["lagos", "atlas", "palette"],
649
+ credit: { title: "Lagos street palette atlas", authors: [KENNEY, CIMPLIFY], licence: CC0, licence_url: CC0_URL, source_url: KENNEY_CAR_KIT, modified: true },
650
+ sources: [kenney(KENNEY_CAR_KIT), kenney(KENNEY_CITY_ROADS), kenney(KENNEY_CITY_COMMERCIAL), own()],
651
+ ai_origin: "none",
652
+ licence: "free_attribution",
653
+ tier: "mid",
654
+ files: { "atlas.ktx2": "atlas.ktx2" }
655
+ },
656
+ {
657
+ item: "cimplify/danfo-bus",
658
+ item_type: "model",
659
+ version: VERSION,
660
+ name: "Danfo bus",
661
+ description: "A yellow Lagos minibus with black bands and a roof rack, built from a recoloured van",
662
+ tags: ["lagos", "vehicle", "bus", "danfo"],
663
+ credit: { title: "Danfo bus", authors: [KENNEY, CIMPLIFY], licence: CC0, licence_url: CC0_URL, source_url: KENNEY_CAR_KIT, modified: true },
664
+ sources: [kenney(KENNEY_CAR_KIT), own()],
665
+ ai_origin: "none",
666
+ licence: "free_attribution",
667
+ tier: "mid",
668
+ files: { "danfo.glb": "vehicles/danfo.glb" },
669
+ dependencies: [ATLAS]
670
+ },
671
+ {
672
+ item: "cimplify/keke-napep",
673
+ item_type: "model",
674
+ version: VERSION,
675
+ name: "Keke napep",
676
+ description: "A green and yellow tricycle taxi with a canvas roof",
677
+ tags: ["lagos", "vehicle", "tricycle", "keke"],
678
+ credit: { title: "Keke napep", authors: [CIMPLIFY], licence: CC0, licence_url: CC0_URL, modified: false },
679
+ sources: [own()],
680
+ ai_origin: "none",
681
+ licence: "free_attribution",
682
+ tier: "mid",
683
+ files: { "keke.glb": "vehicles/keke.glb" },
684
+ dependencies: [ATLAS]
685
+ },
686
+ {
687
+ item: "cimplify/brt-bus",
688
+ item_type: "model",
689
+ version: VERSION,
690
+ name: "City rapid-transit bus",
691
+ description: "A twelve metre rapid-transit bus in a fictional red and blue livery",
692
+ tags: ["lagos", "vehicle", "bus", "brt"],
693
+ credit: { title: "City rapid-transit bus", authors: [CIMPLIFY], licence: CC0, licence_url: CC0_URL, modified: false },
694
+ sources: [own()],
695
+ ai_origin: "none",
696
+ licence: "free_attribution",
697
+ tier: "mid",
698
+ files: { "brt.glb": "vehicles/brt.glb" },
699
+ dependencies: [ATLAS]
700
+ },
701
+ generated("okada", "Okada with rider", "A commercial motorcycle with a seated rider in a helmet", ["lagos", "vehicle", "motorcycle", "okada"], "okada.glb"),
702
+ generated("market-stall", "Market stall", "A wooden produce stall with a striped market umbrella", ["lagos", "market", "stall", "prop"], "market-stall.glb"),
703
+ generated("kiosk", "Roadside kiosk", "A painted roadside kiosk with a zinc roof and a blank shop sign", ["lagos", "kiosk", "prop"], "kiosk.glb"),
704
+ generated("pole-and-wires", "Pole and wires", "Two wooden utility poles with crossarms, insulators and sagging wires", ["lagos", "utility", "pole", "prop"], "pole-and-wires.glb"),
705
+ generated("street-signage", "Street signage", "A blank billboard, a bus stop sign and a shop sign in fictional colours", ["lagos", "sign", "billboard", "prop"], "street-signage.glb"),
706
+ generated("roadside-umbrella", "Roadside umbrella", "A striped roadside umbrella on a weighted base", ["lagos", "umbrella", "prop"], "roadside-umbrella.glb"),
707
+ generated("yellow-barrier", "Yellow road barrier", "A three metre yellow concrete road barrier", ["lagos", "road", "barrier", "prop"], "yellow-barrier.glb"),
708
+ generated("generator", "Petrol generator", "A small red petrol generator", ["lagos", "generator", "prop"], "generator.glb")
709
+ ];
710
+
711
+ // src/interactives/workshop/packs/launch.ts
712
+ var UBC = "https://quaternius.itch.io/universal-base-characters";
713
+ var UAL1 = "https://opengameart.org/content/universal-animation-library";
714
+ var UAL2 = "https://opengameart.org/content/universal-animation-library-2";
715
+ var CC02 = "CC0 1.0";
716
+ var CC0_URL2 = "https://creativecommons.org/publicdomain/zero/1.0/";
717
+ var CC0_CODE2 = "cc0-1.0";
718
+ var VERSION2 = "1.0.0";
719
+ var CIMPLIFY2 = "Cimplify";
720
+ var QUATERNIUS = "Quaternius";
721
+ var MATERIALS = "cimplify/starter-materials";
722
+ var ANIMATIONS = "cimplify/humanoid-animations";
723
+ var IDENTITY = "cimplify/identity-base";
724
+ var LAUNCH_ASSETS = "packages/sdk/test-fixtures/interactives/launch-pack/project/assets/core";
725
+ var LAUNCH_MATERIALS = ["cotton", "denim", "leather", "wood", "painted_metal", "brushed_metal", "concrete", "rubber", "plastic", "terrazzo", "tiles", "brick"];
726
+ var LAUNCH_PROPS = [
727
+ { slug: "crate", name: "Wooden crate", tags: ["container", "wood"] },
728
+ { slug: "barrel", name: "Oil barrel", tags: ["container", "metal"] },
729
+ { slug: "traffic_cone", name: "Traffic cone", tags: ["street", "road"] },
730
+ { slug: "bench", name: "Park bench", tags: ["street", "seat"] },
731
+ { slug: "chair", name: "Chair", tags: ["furniture", "seat"] },
732
+ { slug: "table", name: "Table", tags: ["furniture"] },
733
+ { slug: "lamp_post", name: "Street lamp", tags: ["street", "light"] },
734
+ { slug: "bottle", name: "Bottle", tags: ["pickup", "drink"] },
735
+ { slug: "mug", name: "Mug", tags: ["pickup", "kitchen"] },
736
+ { slug: "ball", name: "Rubber ball", tags: ["sport", "pickup"] },
737
+ { slug: "plant_pot", name: "Potted plant", tags: ["decor", "plant"] },
738
+ { slug: "bin", name: "Litter bin", tags: ["street"] },
739
+ { slug: "fence", name: "Fence panel", tags: ["garden", "wood"] },
740
+ { slug: "sign", name: "Sign board", tags: ["street", "sign"] },
741
+ { slug: "kerb_block", name: "Concrete block", tags: ["street", "concrete"] },
742
+ { slug: "brick_wall", name: "Brick wall section", tags: ["wall", "brick"] },
743
+ { slug: "goal", name: "Small goal", tags: ["sport", "football"] }
744
+ ];
745
+ function own2() {
746
+ return { kind: "own_work", licence: CC0_CODE2, author: CIMPLIFY2 };
747
+ }
748
+ function quaternius(url) {
749
+ return { kind: "third_party", licence: CC0_CODE2, author: QUATERNIUS, url };
750
+ }
751
+ function prop(entry) {
752
+ return {
753
+ item: `cimplify/prop-${entry.slug.replaceAll("_", "-")}`,
754
+ item_type: "model",
755
+ version: VERSION2,
756
+ name: entry.name,
757
+ description: `${entry.name} from the Cimplify starter props, textured with the starter materials`,
758
+ tags: ["starter", ...entry.tags],
759
+ credit: { title: entry.name, authors: [CIMPLIFY2], licence: CC02, licence_url: CC0_URL2, modified: false },
760
+ sources: [own2()],
761
+ ai_origin: "none",
762
+ licence: "free",
763
+ tier: "low",
764
+ files: { [`${entry.slug}.glb`]: `props/${entry.slug}.glb` },
765
+ dependencies: [MATERIALS]
766
+ };
767
+ }
768
+ var LAUNCH_PACK = [
769
+ {
770
+ item: MATERIALS,
771
+ item_type: "material",
772
+ version: VERSION2,
773
+ name: "Starter materials",
774
+ description: "Twelve tileable PBR materials (fabric, denim, leather, wood, metals, concrete, rubber, plastic, terrazzo, tiles, brick) with albedo and normal maps",
775
+ tags: ["starter", "material", "pbr"],
776
+ credit: { title: "Starter materials", authors: [CIMPLIFY2], licence: CC02, licence_url: CC0_URL2, modified: false },
777
+ sources: [own2()],
778
+ ai_origin: "none",
779
+ licence: "free",
780
+ tier: "low",
781
+ files: Object.fromEntries([
782
+ ["materials.json", "materials.json"],
783
+ ...LAUNCH_MATERIALS.flatMap((m) => [
784
+ [`${m}.ktx2`, `materials/${m}.ktx2`],
785
+ [`${m}-normal.ktx2`, `materials/${m}-normal.ktx2`]
786
+ ])
787
+ ])
788
+ },
789
+ {
790
+ item: ANIMATIONS,
791
+ item_type: "animation",
792
+ version: VERSION2,
793
+ name: "Humanoid animation library",
794
+ description: "120 clips on the standard humanoid rig: locomotion with an 8-direction strafe set and turns in place, idles, emotes, sports, driving, sitting, actions and reactions",
795
+ tags: ["starter", "animation", "humanoid", "locomotion", "emote", "sport"],
796
+ credit: { title: "Universal Animation Library 1 and 2, with Cimplify clips", authors: [QUATERNIUS, CIMPLIFY2], licence: CC02, licence_url: CC0_URL2, source_url: UAL1, modified: true },
797
+ sources: [quaternius(UAL1), quaternius(UAL2), own2()],
798
+ ai_origin: "none",
799
+ licence: "free",
800
+ tier: "low",
801
+ files: { "animations.glb": "animations.glb", "clips.json": "clips.json" }
802
+ },
803
+ {
804
+ item: IDENTITY,
805
+ item_type: "character",
806
+ version: VERSION2,
807
+ name: "Character identity base",
808
+ description: "The shared base character as a shape space: face and body controls, hair, brows and facial hair, palettes and face anchors for characters made from a photo",
809
+ tags: ["starter", "character", "identity"],
810
+ credit: { title: "Character identity base", authors: [QUATERNIUS, CIMPLIFY2], licence: CC02, licence_url: CC0_URL2, source_url: UBC, modified: true },
811
+ sources: [quaternius(UBC), quaternius(UAL2), own2()],
812
+ ai_origin: "none",
813
+ licence: "free",
814
+ tier: "low",
815
+ files: { "identity.cidb": "identity.cidb" },
816
+ dependencies: [ANIMATIONS]
817
+ },
818
+ {
819
+ item: "cimplify/base-character",
820
+ item_type: "character",
821
+ version: VERSION2,
822
+ name: "Base character",
823
+ description: "A stylised, rigged base character on the standard humanoid rig in an athletic outfit; every clip in the humanoid animation library plays on it",
824
+ tags: ["starter", "character", "rigged", "humanoid"],
825
+ credit: { title: "Base character", authors: [QUATERNIUS, CIMPLIFY2], licence: CC02, licence_url: CC0_URL2, source_url: UBC, modified: true },
826
+ sources: [quaternius(UBC), own2()],
827
+ ai_origin: "none",
828
+ licence: "free",
829
+ tier: "mid",
830
+ files: { "base-character.glb": "base-character.glb" },
831
+ dependencies: [ANIMATIONS, IDENTITY]
832
+ },
833
+ ...LAUNCH_PROPS.map(prop)
834
+ ];
835
+
836
+ // src/interactives/workshop/packs/penalty-kings.ts
837
+ var QUATERNIUS_PACK = "https://opengameart.org/content/animated-characters-pack";
838
+ var POLY_HAVEN_SKY = "https://polyhaven.com/a/evening_road_01_puresky";
839
+ var KENNEY_IMPACT = "https://kenney.nl/assets/impact-sounds";
840
+ var KENNEY_INTERFACE = "https://kenney.nl/assets/interface-sounds";
841
+ var APPLAUSE = "https://opengameart.org/content/applause-in-a-large-hall-or-church";
842
+ var CC03 = "CC0 1.0";
843
+ var CC0_URL3 = "https://creativecommons.org/publicdomain/zero/1.0/";
844
+ var VERSION3 = "1.0.0";
845
+ var CIMPLIFY3 = "Cimplify";
846
+ var PENALTY_KINGS_ASSETS = "packages/sdk/test-fixtures/interactives/penalty-kings/project/assets/core";
847
+ function own3() {
848
+ return { kind: "own_work", licence: "cc0-1.0", author: CIMPLIFY3 };
849
+ }
850
+ var PENALTY_KINGS_PACK = [
851
+ {
852
+ item: "cimplify/match-ball",
853
+ item_type: "model",
854
+ version: VERSION3,
855
+ name: "Match ball",
856
+ description: "A truncated icosahedron football with stitched panels, generated by Cimplify",
857
+ tags: ["football", "sport", "ball"],
858
+ credit: { title: "Match ball", authors: [CIMPLIFY3], licence: CC03, licence_url: CC0_URL3, modified: false },
859
+ sources: [own3()],
860
+ ai_origin: "none",
861
+ licence: "free_attribution",
862
+ tier: "mid",
863
+ files: { "ball.glb": "ball.glb" }
864
+ },
865
+ {
866
+ item: "cimplify/penalty-kings-striker",
867
+ item_type: "character",
868
+ version: VERSION3,
869
+ name: "Penalty Kings striker",
870
+ description: "A rigged striker in the emerald home kit with jog, kick, idle, celebrate and dejected clips",
871
+ tags: ["football", "character", "rigged"],
872
+ credit: { title: "Penalty Kings striker", authors: ["Quaternius", CIMPLIFY3], licence: CC03, licence_url: CC0_URL3, source_url: QUATERNIUS_PACK, modified: true },
873
+ sources: [{ kind: "third_party", licence: "cc0-1.0", author: "Quaternius", url: QUATERNIUS_PACK }, own3()],
874
+ ai_origin: "none",
875
+ licence: "free_attribution",
876
+ tier: "mid",
877
+ files: { "striker.glb": "striker.glb" }
878
+ },
879
+ {
880
+ item: "cimplify/penalty-kings-keeper",
881
+ item_type: "character",
882
+ version: VERSION3,
883
+ name: "Penalty Kings keeper",
884
+ description: "A rigged goalkeeper with ready, dive, smother, celebrate and dejected clips",
885
+ tags: ["football", "character", "rigged"],
886
+ credit: { title: "Penalty Kings keeper", authors: ["Quaternius", CIMPLIFY3], licence: CC03, licence_url: CC0_URL3, source_url: QUATERNIUS_PACK, modified: true },
887
+ sources: [{ kind: "third_party", licence: "cc0-1.0", author: "Quaternius", url: QUATERNIUS_PACK }, own3()],
888
+ ai_origin: "none",
889
+ licence: "free_attribution",
890
+ tier: "mid",
891
+ files: { "keeper.glb": "keeper.glb" }
892
+ },
893
+ {
894
+ item: "cimplify/floodlit-stadium",
895
+ item_type: "environment",
896
+ version: VERSION3,
897
+ name: "Floodlit stadium",
898
+ description: "A floodlit football stadium with stands, LED boards, an evening sky dome and its environment map",
899
+ tags: ["football", "stadium", "night"],
900
+ credit: { title: "Floodlit stadium", authors: [CIMPLIFY3, "Sergej Majboroda", "Jarod Guest"], licence: CC03, licence_url: CC0_URL3, source_url: POLY_HAVEN_SKY, modified: true },
901
+ sources: [own3(), { kind: "third_party", licence: "cc0-1.0", author: "Sergej Majboroda, Jarod Guest", url: POLY_HAVEN_SKY }],
902
+ ai_origin: "none",
903
+ licence: "free_attribution",
904
+ tier: "mid",
905
+ files: { "stadium.glb": "stadium.glb", "environment.ktx2": "environment.ktx2", "net.ktx2": "net.ktx2" }
906
+ },
907
+ {
908
+ item: "cimplify/stadium-crowd",
909
+ item_type: "model",
910
+ version: VERSION3,
911
+ name: "Stadium crowd",
912
+ description: "A skinned spectator crowd with idle, tense, cheer and groan animations",
913
+ tags: ["crowd", "stadium"],
914
+ credit: { title: "Stadium crowd", authors: [CIMPLIFY3], licence: CC03, licence_url: CC0_URL3, modified: false },
915
+ sources: [own3()],
916
+ ai_origin: "none",
917
+ licence: "free_attribution",
918
+ tier: "mid",
919
+ files: { "crowd.glb": "crowd.glb" }
920
+ },
921
+ {
922
+ item: "cimplify/football-impacts",
923
+ item_type: "sound",
924
+ version: VERSION3,
925
+ name: "Football impacts",
926
+ description: "Strike, glove, bounce, post and net sounds layered from Kenney's Impact Sounds",
927
+ tags: ["football", "impact", "sfx"],
928
+ credit: { title: "Football impacts", authors: ["Kenney", CIMPLIFY3], licence: CC03, licence_url: CC0_URL3, source_url: KENNEY_IMPACT, modified: true },
929
+ sources: [{ kind: "third_party", licence: "cc0-1.0", author: "Kenney", url: KENNEY_IMPACT }, own3()],
930
+ ai_origin: "none",
931
+ licence: "free_attribution",
932
+ tier: "mid",
933
+ files: {
934
+ "kick.m4a": "sounds/kick.m4a",
935
+ "glove.m4a": "sounds/glove.m4a",
936
+ "bounce.m4a": "sounds/bounce.m4a",
937
+ "post.m4a": "sounds/post.m4a",
938
+ "net.m4a": "sounds/net.m4a"
939
+ }
940
+ },
941
+ {
942
+ item: "kenney/interface-clicks",
943
+ item_type: "sound",
944
+ version: VERSION3,
945
+ name: "Interface clicks",
946
+ description: "Tap and confirm sounds from Kenney's Interface Sounds",
947
+ tags: ["ui", "sfx"],
948
+ credit: { title: "Interface Sounds", authors: ["Kenney"], licence: CC03, licence_url: CC0_URL3, source_url: KENNEY_INTERFACE, modified: false },
949
+ sources: [{ kind: "third_party", licence: "cc0-1.0", author: "Kenney", url: KENNEY_INTERFACE }],
950
+ ai_origin: "none",
951
+ licence: "free",
952
+ tier: "low",
953
+ files: { "tap.m4a": "sounds/tap.m4a", "confirm.m4a": "sounds/confirm.m4a" }
954
+ },
955
+ {
956
+ item: "cimplify/stadium-roar",
957
+ item_type: "sound",
958
+ version: VERSION3,
959
+ name: "Stadium roar",
960
+ description: "A synthesised crowd roar layered with applause in a large hall",
961
+ tags: ["crowd", "sfx"],
962
+ credit: { title: "Stadium roar", authors: ["eXpl0it3r", CIMPLIFY3], licence: CC03, licence_url: CC0_URL3, source_url: APPLAUSE, modified: true },
963
+ sources: [{ kind: "third_party", licence: "cc0-1.0", author: "eXpl0it3r", url: APPLAUSE }, own3()],
964
+ ai_origin: "none",
965
+ licence: "free_attribution",
966
+ tier: "mid",
967
+ files: { "roar.m4a": "sounds/roar.m4a" }
968
+ }
969
+ ];
970
+
971
+ // src/interactives/workshop/packs/index.ts
972
+ var PACK_SETS = [
973
+ { name: "penalty-kings", items: PENALTY_KINGS_PACK, assets: PENALTY_KINGS_ASSETS },
974
+ { name: "lagos-street", items: LAGOS_PACK, assets: LAGOS_ASSETS, generator: workshopAssets },
975
+ { name: "launch", items: LAUNCH_PACK, assets: LAUNCH_ASSETS }
976
+ ];
977
+ function generatedAssets(set) {
978
+ if (set.generator === void 0) return /* @__PURE__ */ new Map();
979
+ return new Map(Object.entries(set.generator()).map(([file, bytes]) => [file, Buffer.from(bytes)]));
980
+ }
981
+ function packReader(repo, set, generated2) {
982
+ return (file) => {
983
+ if (!file.startsWith(GENERATED_PREFIX)) return readFileSync(path4.join(repo, set.assets, file));
984
+ const bytes = generated2.get(file.slice(GENERATED_PREFIX.length));
985
+ if (bytes === void 0) throw new Error(`${set.name} has no generated asset ${file}`);
986
+ return bytes;
987
+ };
988
+ }
989
+ async function packSet(repo, set) {
990
+ return packItems(set.items, packReader(repo, set, generatedAssets(set)));
991
+ }
992
+ async function packAll(repo, only) {
993
+ const sets = only === void 0 ? PACK_SETS : PACK_SETS.filter((set) => set.name === only);
994
+ if (sets.length === 0) throw new Error(`no Workshop pack named ${only}; packs: ${PACK_SETS.map((set) => set.name).join(", ")}`);
995
+ const packed2 = [];
996
+ for (const set of sets) packed2.push(...await packSet(repo, set));
997
+ return packed2;
998
+ }
999
+ var CONTENT_FILE = "content.toml";
1000
+ var SOURCE_DIR = "source";
1001
+ var PREVIEW_DIR = "previews";
1002
+ var ATTESTATION_VERSION = "2026-10-11";
1003
+ var ATTESTATION_TEXT = "I made this item, or I hold the rights to license it on the terms I choose. My source declarations are complete and accurate, the item uses no third-party brands, characters or recordings I have no right to, and I understand Cimplify never uses Workshop content to train models.";
1004
+ var MAX_UPLOAD_FILES = 512;
1005
+ var MAX_UPLOAD_FILE_BYTES = 64 * 1024 * 1024;
1006
+ var MAX_UPLOAD_BYTES = 256 * 1024 * 1024;
1007
+ var MAX_CREDIT_AUTHORS = 16;
1008
+ var ITEM_TYPES = [
1009
+ "model",
1010
+ "character",
1011
+ "rig",
1012
+ "animation",
1013
+ "environment",
1014
+ "texture",
1015
+ "material",
1016
+ "hdri",
1017
+ "sound",
1018
+ "music",
1019
+ "font",
1020
+ "vfx",
1021
+ "shader",
1022
+ "sprite",
1023
+ "ui",
1024
+ "component",
1025
+ "template",
1026
+ "tool"
1027
+ ];
1028
+ var LISTINGS = ["listed", "unlisted", "team", "private"];
1029
+ var AI_ORIGINS = ["none", "assisted", "generated"];
1030
+ var SOURCE_KINDS = ["own_work", "commissioned", "third_party", "library_item", "ai_generator"];
1031
+ var SOURCE_LICENCES = ["cc0-1.0", "public-domain", "cc-by-3.0", "cc-by-4.0", "ofl-1.1", "mit", "apache-2.0", "bsd-3-clause", "own-work", "commissioned"];
1032
+ var LICENCE_KINDS = ["free_attribution", "free", "paid_per_game", "revenue_share", "team", "exclusive"];
1033
+ var DERIVATIVES = ["remixable", "no_derivatives"];
1034
+ var RATING_LEVELS = {
1035
+ violence: ["none", "cartoon", "realistic", "graphic"],
1036
+ sexuality: ["none", "suggestive", "explicit"],
1037
+ language: ["none", "mild", "strong"],
1038
+ fear: ["none", "mild", "intense"]
1039
+ };
1040
+ var TOOL_CAPABILITIES = ["read_source", "edit_source", "store_parts", "use_assistant"];
1041
+ var REFERENCE = /^[a-z0-9][a-z0-9_-]*\/[a-z0-9][a-z0-9_-]*$/;
1042
+ var SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
1043
+ var TAG = /^[a-z0-9-]{1,40}$/;
1044
+ var ContentProjectError = class extends Error {
1045
+ constructor(problems) {
1046
+ super(`${CONTENT_FILE} is invalid:
1047
+ ${problems.map((p) => ` ${p}`).join("\n")}`);
1048
+ this.problems = problems;
1049
+ }
1050
+ problems;
1051
+ };
1052
+ var Reader = class _Reader {
1053
+ constructor(table, where, problems = []) {
1054
+ this.table = table;
1055
+ this.where = where;
1056
+ this.problems = problems;
1057
+ }
1058
+ table;
1059
+ where;
1060
+ problems;
1061
+ at(key) {
1062
+ return this.where === "" ? key : `${this.where}.${key}`;
1063
+ }
1064
+ value(key) {
1065
+ return this.table.entries.get(key);
1066
+ }
1067
+ string(key, required = true) {
1068
+ const value = this.value(key);
1069
+ if (value === void 0) {
1070
+ if (required) this.problems.push(`${this.at(key)} is required`);
1071
+ return void 0;
1072
+ }
1073
+ if (value.kind !== "string") {
1074
+ this.problems.push(`${this.at(key)} must be a string`);
1075
+ return void 0;
1076
+ }
1077
+ return value.value;
1078
+ }
1079
+ integer(key) {
1080
+ const value = this.value(key);
1081
+ if (value === void 0) return void 0;
1082
+ if (value.kind !== "integer") {
1083
+ this.problems.push(`${this.at(key)} must be an integer`);
1084
+ return void 0;
1085
+ }
1086
+ return value.value;
1087
+ }
1088
+ boolean(key, fallback) {
1089
+ const value = this.value(key);
1090
+ if (value === void 0) return fallback;
1091
+ if (value.kind !== "boolean") {
1092
+ this.problems.push(`${this.at(key)} must be true or false`);
1093
+ return fallback;
1094
+ }
1095
+ return value.value;
1096
+ }
1097
+ oneOf(key, allowed, fallback) {
1098
+ const raw = this.string(key, fallback === void 0);
1099
+ if (raw === void 0) return fallback;
1100
+ if (!allowed.includes(raw)) {
1101
+ this.problems.push(`${this.at(key)} must be one of ${allowed.join(", ")}`);
1102
+ return fallback;
1103
+ }
1104
+ return raw;
1105
+ }
1106
+ strings(key) {
1107
+ const value = this.value(key);
1108
+ if (value === void 0) return [];
1109
+ if (value.kind !== "array" || value.items.some((item) => item.kind !== "string")) {
1110
+ this.problems.push(`${this.at(key)} must be an array of strings`);
1111
+ return [];
1112
+ }
1113
+ return value.items.map((item) => item.value);
1114
+ }
1115
+ subtable(key) {
1116
+ const value = this.value(key);
1117
+ if (value === void 0) return void 0;
1118
+ if (value.kind !== "table") {
1119
+ this.problems.push(`${this.at(key)} must be a table`);
1120
+ return void 0;
1121
+ }
1122
+ return this.child(value, key);
1123
+ }
1124
+ tables(key) {
1125
+ const value = this.value(key);
1126
+ if (value === void 0) return [];
1127
+ if (value.kind !== "array" || value.items.some((item) => item.kind !== "table")) {
1128
+ this.problems.push(`${this.at(key)} must be an array of tables ([[${key}]])`);
1129
+ return [];
1130
+ }
1131
+ return value.items.map((item, index) => this.child(item, `${key}[${index}]`));
1132
+ }
1133
+ child(table, key) {
1134
+ return new _Reader(table, this.at(key), this.problems);
1135
+ }
1136
+ };
1137
+ function optional2(key, value) {
1138
+ return value === void 0 ? {} : { [key]: value };
1139
+ }
1140
+ function readSource(source) {
1141
+ const kind = source.oneOf("kind", SOURCE_KINDS);
1142
+ const licence = source.oneOf("licence", SOURCE_LICENCES);
1143
+ const author = source.string("author");
1144
+ if (kind === void 0 || licence === void 0 || author === void 0) return null;
1145
+ const url = source.string("url", false);
1146
+ if (kind === "third_party" && url === void 0) source.problems.push("a third_party source needs its url");
1147
+ return { kind, licence, author, ...optional2("url", url), ...optional2("digest", source.string("digest", false)), ...optional2("attribution", source.string("attribution", false)) };
1148
+ }
1149
+ function readOffer(offer) {
1150
+ const kind = offer.oneOf("kind", LICENCE_KINDS);
1151
+ if (kind === void 0) return null;
1152
+ const derivatives = offer.oneOf("derivatives", DERIVATIVES, "remixable") ?? "remixable";
1153
+ const priceTable = offer.subtable("price");
1154
+ let price;
1155
+ if (priceTable !== void 0) {
1156
+ const minor = priceTable.integer("minor");
1157
+ const currency = priceTable.string("currency");
1158
+ if (minor === void 0 || currency === void 0 || !/^[A-Z]{3}$/.test(currency) || minor <= 0) {
1159
+ offer.problems.push("price needs a positive integer minor and an ISO 4217 currency");
1160
+ } else price = { minor, currency };
1161
+ }
1162
+ const share = offer.integer("share_bps");
1163
+ const derivativeShare = offer.integer("derivative_share_bps");
1164
+ const team = offer.strings("team_business_ids");
1165
+ const free = kind === "free" || kind === "free_attribution";
1166
+ if (free && (price !== void 0 || share !== void 0)) offer.problems.push(`a ${kind} offer carries no price or share`);
1167
+ if ((kind === "paid_per_game" || kind === "exclusive") && price === void 0) offer.problems.push(`a ${kind} offer needs a price`);
1168
+ if (kind === "revenue_share" && (share === void 0 || share < 1 || share > 5e3)) offer.problems.push("a revenue_share offer needs share_bps between 1 and 5000");
1169
+ if (kind === "team" && team.length === 0) offer.problems.push("a team offer names its team_business_ids");
1170
+ return {
1171
+ kind,
1172
+ derivatives,
1173
+ generation_allowed: offer.boolean("generation_allowed", free),
1174
+ ...optional2("price", price),
1175
+ ...optional2("share_bps", share),
1176
+ ...optional2("derivative_share_bps", derivativeShare),
1177
+ ...team.length === 0 ? {} : { team_business_ids: team }
1178
+ };
1179
+ }
1180
+ function readRef(reader) {
1181
+ const item = reader.string("item");
1182
+ const version = reader.string("version");
1183
+ if (item === void 0 || version === void 0) return null;
1184
+ if (!REFERENCE.test(item)) reader.problems.push(`${item} is not an <owner>/<slug> reference`);
1185
+ if (!SEMVER.test(version)) reader.problems.push(`${version} is not an exact version`);
1186
+ return { item, version };
1187
+ }
1188
+ function readTool(root, itemType) {
1189
+ const tool = root.subtable("tool");
1190
+ if (itemType !== "tool") {
1191
+ if (tool !== void 0) root.problems.push("only a tool item has a [tool] table");
1192
+ return void 0;
1193
+ }
1194
+ const capabilities = tool?.strings("capabilities") ?? [];
1195
+ if (capabilities.length === 0) {
1196
+ root.problems.push(`a tool names what it may do: [tool] capabilities from ${TOOL_CAPABILITIES.join(", ")}`);
1197
+ return void 0;
1198
+ }
1199
+ const unknown = capabilities.filter((c) => !TOOL_CAPABILITIES.includes(c));
1200
+ if (unknown.length > 0) root.problems.push(`tool.capabilities must be among ${TOOL_CAPABILITIES.join(", ")}; ${unknown.join(", ")} is not`);
1201
+ if (new Set(capabilities).size !== capabilities.length) root.problems.push("name each tool capability once");
1202
+ return { capabilities };
1203
+ }
1204
+ function readRating(root) {
1205
+ const rating = root.subtable("rating");
1206
+ const level = (key) => rating?.oneOf(key, RATING_LEVELS[key], "none") ?? "none";
1207
+ return {
1208
+ violence: level("violence"),
1209
+ sexuality: level("sexuality"),
1210
+ language: level("language"),
1211
+ fear: level("fear"),
1212
+ simulated_gambling: rating?.boolean("simulated_gambling", false) ?? false
1213
+ };
1214
+ }
1215
+ function parseContentToml(source) {
1216
+ let table;
1217
+ try {
1218
+ table = parseToml(source);
1219
+ } catch (err) {
1220
+ const message = err instanceof TomlSyntaxError ? err.message : String(err);
1221
+ throw new ContentProjectError([message]);
1222
+ }
1223
+ const root = new Reader(table, "");
1224
+ const item = root.string("item") ?? "";
1225
+ if (item !== "" && !REFERENCE.test(item)) root.problems.push("item must be <owner handle>/<slug> in lowercase");
1226
+ const itemType = root.oneOf("type", ITEM_TYPES) ?? "model";
1227
+ const version = root.string("version") ?? "";
1228
+ if (version !== "" && !SEMVER.test(version)) root.problems.push("version must be major.minor.patch");
1229
+ const name = root.string("name") ?? "";
1230
+ if (name.trim() === "" || Buffer.byteLength(name) > 120) root.problems.push("name must be 1 to 120 bytes");
1231
+ const description = root.string("description", false) ?? "";
1232
+ const tags = root.strings("tags");
1233
+ for (const tag of tags) if (!TAG.test(tag)) root.problems.push(`tag ${tag} must be lowercase words joined by hyphens`);
1234
+ const listing = root.oneOf("listing", LISTINGS, "listed") ?? "listed";
1235
+ const aiOrigin = root.oneOf("ai_origin", AI_ORIGINS);
1236
+ if (aiOrigin === void 0 && root.value("ai_origin") === void 0) root.problems.push("ai_origin is required: none, assisted or generated");
1237
+ const attestation = root.string("attestation", false) ?? "";
1238
+ const sources = root.tables("sources").map(readSource).filter((s) => s !== null);
1239
+ if (sources.length === 0) root.problems.push("declare at least one [[sources]] entry");
1240
+ if (sources.some((s) => s.kind === "ai_generator") && aiOrigin === "none") root.problems.push("an ai_generator source makes ai_origin assisted or generated");
1241
+ const offers = root.tables("offers").map(readOffer).filter((o) => o !== null);
1242
+ if (offers.length === 0) root.problems.push("declare at least one [[offers]] licence");
1243
+ const kinds = offers.map((o) => o.kind);
1244
+ if (new Set(kinds).size !== kinds.length) root.problems.push("name each offer licence once");
1245
+ const dependencies = root.tables("dependencies").map(readRef).filter((r) => r !== null);
1246
+ const basedOn = root.tables("based_on").map(readRef).filter((r) => r !== null);
1247
+ if (sources.some((s) => s.kind === "library_item") && basedOn.length === 0) root.problems.push("a library_item source needs a [[based_on]] upstream");
1248
+ const credit = root.subtable("credit");
1249
+ const authors = credit?.strings("authors") ?? [];
1250
+ const creditAuthors = authors.length > 0 ? authors : [...new Set(sources.map((s) => s.author))];
1251
+ if (creditAuthors.length === 0 || creditAuthors.length > MAX_CREDIT_AUTHORS) root.problems.push("credit needs between 1 and 16 authors");
1252
+ const title = credit?.string("title", false);
1253
+ const seller = root.string("seller_business_id", false);
1254
+ const rating = readRating(root);
1255
+ const tool = readTool(root, itemType);
1256
+ if (root.problems.length > 0) throw new ContentProjectError(root.problems);
1257
+ return {
1258
+ item,
1259
+ itemType,
1260
+ version,
1261
+ manifest: {
1262
+ name,
1263
+ description,
1264
+ tags,
1265
+ listing,
1266
+ sources,
1267
+ ai_origin: aiOrigin ?? "none",
1268
+ rating,
1269
+ attestation,
1270
+ offers,
1271
+ dependencies,
1272
+ based_on: basedOn,
1273
+ ...optional2("seller_business_id", seller),
1274
+ credit: { ...optional2("title", title), authors: creditAuthors },
1275
+ ...optional2("tool", tool)
1276
+ }
1277
+ };
1278
+ }
1279
+ function withAttestation(project) {
1280
+ return { ...project, manifest: { ...project.manifest, attestation: ATTESTATION_VERSION } };
1281
+ }
1282
+ async function walk(root, dir, out) {
1283
+ let entries;
1284
+ try {
1285
+ entries = await promises.readdir(path4.join(root, dir), { withFileTypes: true });
1286
+ } catch {
1287
+ return;
1288
+ }
1289
+ for (const entry of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
1290
+ if (entry.name.startsWith(".")) continue;
1291
+ const relative = `${dir}/${entry.name}`;
1292
+ if (entry.isDirectory()) await walk(root, relative, out);
1293
+ else if (entry.isFile()) out.push(relative);
1294
+ }
1295
+ }
1296
+ async function sourcePaths(root) {
1297
+ const paths = [];
1298
+ await walk(root, SOURCE_DIR, paths);
1299
+ return paths;
1300
+ }
1301
+ async function collectFiles(root) {
1302
+ const paths = [];
1303
+ await walk(root, SOURCE_DIR, paths);
1304
+ await walk(root, PREVIEW_DIR, paths);
1305
+ if (paths.length === 0) throw new ContentProjectError([`put the item's files under ${SOURCE_DIR}/`]);
1306
+ if (paths.length > MAX_UPLOAD_FILES) throw new ContentProjectError([`at most ${MAX_UPLOAD_FILES} files may be uploaded`]);
1307
+ const files = [];
1308
+ let total = 0;
1309
+ for (const relative of paths) {
1310
+ const absolute = path4.join(root, relative);
1311
+ const bytes = await promises.readFile(absolute);
1312
+ if (bytes.length === 0 || bytes.length > MAX_UPLOAD_FILE_BYTES) throw new ContentProjectError([`${relative} is empty or larger than 64 MiB`]);
1313
+ total += bytes.length;
1314
+ files.push({ path: relative, digest: createHash("sha256").update(bytes).digest("hex"), size: bytes.length, absolute });
1315
+ }
1316
+ if (total > MAX_UPLOAD_BYTES) throw new ContentProjectError(["the files total more than 256 MiB"]);
1317
+ return files;
1318
+ }
1319
+ var STARTER_TOOL_SCRIPT = [
1320
+ "function outline(): any {",
1321
+ " return production.read({ depth: 1 });",
1322
+ "}",
1323
+ "",
1324
+ "export default { outline };",
1325
+ ""
1326
+ ].join("\n");
1327
+ function starterContentToml(item, itemType, author) {
1328
+ const name = item.split("/")[1].replace(/[-_]/g, " ");
1329
+ return [
1330
+ `item = ${quoteTomlString(item)}`,
1331
+ `type = ${quoteTomlString(itemType)}`,
1332
+ `version = "1.0.0"`,
1333
+ `name = ${quoteTomlString(name.charAt(0).toUpperCase() + name.slice(1))}`,
1334
+ `description = ""`,
1335
+ `tags = []`,
1336
+ `listing = "listed"`,
1337
+ `ai_origin = "none"`,
1338
+ "",
1339
+ "[rating]",
1340
+ `violence = "none"`,
1341
+ `sexuality = "none"`,
1342
+ `language = "none"`,
1343
+ `fear = "none"`,
1344
+ "simulated_gambling = false",
1345
+ "",
1346
+ "[[sources]]",
1347
+ `kind = "own_work"`,
1348
+ `licence = "own-work"`,
1349
+ `author = ${quoteTomlString(author)}`,
1350
+ "",
1351
+ "[[offers]]",
1352
+ `kind = "free_attribution"`,
1353
+ `derivatives = "remixable"`,
1354
+ "generation_allowed = true",
1355
+ "",
1356
+ "[credit]",
1357
+ `authors = [${quoteTomlString(author)}]`,
1358
+ "",
1359
+ ...itemType === "tool" ? ["[tool]", `capabilities = ["read_source"]`, ""] : []
1360
+ ].join("\n");
1361
+ }
1362
+ async function initContentProject(dir, item, itemType, author) {
1363
+ if (!REFERENCE.test(item)) throw new ContentProjectError(["--item must be <owner handle>/<slug> in lowercase"]);
1364
+ const file = path4.join(dir, CONTENT_FILE);
1365
+ try {
1366
+ await promises.access(file);
1367
+ throw new ContentProjectError([`${file} already exists`]);
1368
+ } catch (err) {
1369
+ if (err instanceof ContentProjectError) throw err;
1370
+ }
1371
+ await promises.mkdir(path4.join(dir, SOURCE_DIR), { recursive: true });
1372
+ await promises.mkdir(path4.join(dir, PREVIEW_DIR), { recursive: true });
1373
+ await promises.writeFile(file, starterContentToml(item, itemType, author));
1374
+ if (itemType === "tool") await promises.writeFile(path4.join(dir, SOURCE_DIR, `${item.split("/")[1]}.ts`), STARTER_TOOL_SCRIPT);
1375
+ return file;
1376
+ }
1377
+ var TERMINAL_UPLOAD_STAGES = /* @__PURE__ */ new Set(["in_review", "listed", "refused"]);
1378
+ var UPLOAD_POLL_MS = 1e3;
1379
+ var UPLOAD_DEADLINE_MS = 15 * 60 * 1e3;
1380
+ var ITEMS_PATH = "/v1/content/items";
1381
+ var UPLOADS_PATH = "/v1/content/uploads";
1382
+ var HttpWorkshopRemote = class {
1383
+ constructor(client) {
1384
+ this.client = client;
1385
+ }
1386
+ client;
1387
+ async createItem(item) {
1388
+ try {
1389
+ await this.client.post(ITEMS_PATH, item);
1390
+ return "created";
1391
+ } catch (err) {
1392
+ if (err instanceof CliError && err.code === CLI_ERROR_CODE.ALREADY_LINKED) return "exists";
1393
+ throw err;
1394
+ }
1395
+ }
1396
+ beginUpload(reference, upload) {
1397
+ return this.client.post(`${ITEMS_PATH}/${reference}/versions`, upload);
1398
+ }
1399
+ uploadSource(uploadId, digest, bytes) {
1400
+ return this.client.putBytes(`${UPLOADS_PATH}/${encodeURIComponent(uploadId)}/sources/${digest}`, bytes);
1401
+ }
1402
+ complete(uploadId) {
1403
+ return this.client.post(`${UPLOADS_PATH}/${encodeURIComponent(uploadId)}/complete`);
1404
+ }
1405
+ status(uploadId) {
1406
+ return this.client.get(`${UPLOADS_PATH}/${encodeURIComponent(uploadId)}`);
1407
+ }
1408
+ };
1409
+ var delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
1410
+ async function publishContent(project, files, remote, options = {}) {
1411
+ const sleep = options.sleep ?? delay;
1412
+ const now = options.now ?? Date.now;
1413
+ const read = options.read ?? ((file) => promises.readFile(file.absolute));
1414
+ const manifest = project.manifest;
1415
+ const created = await remote.createItem({
1416
+ reference: project.item,
1417
+ item_type: project.itemType,
1418
+ name: manifest.name,
1419
+ description: manifest.description,
1420
+ tags: manifest.tags,
1421
+ listing: manifest.listing,
1422
+ ...options.owner === void 0 ? {} : { owner: options.owner }
1423
+ });
1424
+ options.progress?.onCreated?.(created);
1425
+ const listed = files.map(({ path: path5, digest, size }) => ({ path: path5, digest, size }));
1426
+ let status = await remote.beginUpload(project.item, { version: project.version, manifest, files: listed });
1427
+ const missing = new Set(status.missing ?? []);
1428
+ const wanted = files.filter((file) => missing.has(file.digest));
1429
+ const sent = /* @__PURE__ */ new Set();
1430
+ for (const file of wanted) {
1431
+ if (sent.has(file.digest)) continue;
1432
+ await remote.uploadSource(status.upload_id, file.digest, await read(file));
1433
+ sent.add(file.digest);
1434
+ options.progress?.onUploaded?.(file, sent.size, missing.size);
1435
+ }
1436
+ status = await remote.complete(status.upload_id);
1437
+ options.progress?.onStage?.(status);
1438
+ if (status.stage === "receiving" && (status.missing ?? []).length > 0) {
1439
+ throw new CliError(CLI_ERROR_CODE.SERVER_ERROR, `the Workshop still waits for ${(status.missing ?? []).length} file(s) of ${project.item}@${project.version}; run publish again`);
1440
+ }
1441
+ const deadline = now() + (options.deadlineMs ?? UPLOAD_DEADLINE_MS);
1442
+ while (!TERMINAL_UPLOAD_STAGES.has(status.stage)) {
1443
+ if (now() > deadline) throw new CliError(CLI_ERROR_CODE.TIMEOUT, `the upload of ${project.item}@${project.version} is still ${status.stage}; check it with cimplify workshop status ${status.upload_id}`);
1444
+ await sleep(options.pollMs ?? UPLOAD_POLL_MS);
1445
+ const next = await remote.status(status.upload_id);
1446
+ if (next.stage !== status.stage) options.progress?.onStage?.(next);
1447
+ status = next;
1448
+ }
1449
+ return {
1450
+ item: project.item,
1451
+ version: project.version,
1452
+ files: files.length,
1453
+ uploaded: sent.size,
1454
+ skipped: new Set(files.map((file) => file.digest)).size - sent.size,
1455
+ status
1456
+ };
1457
+ }
1458
+ var TOOL_ITEM_TYPE = "tool";
1459
+ var TOOL_SCRIPT_EXTENSION = ".ts";
1460
+ var TOOL_CHUNK_EXTENSION = ".luauc";
1461
+ var LUAU_SOURCE_EXTENSION = ".luau";
1462
+ var DECLARATION_EXTENSION = ".d.ts";
1463
+ var LUAU_VERSION_MIN = 3;
1464
+ var LUAU_VERSION_MAX = 13;
1465
+ var LUAU_VERSION_TYPES = 4;
1466
+ var LUAU_TYPES_MIN = 1;
1467
+ var LUAU_TYPES_MAX = 3;
1468
+ var LUAU_VARINT_CONTINUE = 128;
1469
+ var LUAU_VARINT_MAX_BYTES = 5;
1470
+ var ToolBuildError = class extends Error {
1471
+ constructor(problems) {
1472
+ super(`the tool did not build:
1473
+ ${problems.map((p) => ` ${p}`).join("\n")}`);
1474
+ this.problems = problems;
1475
+ }
1476
+ problems;
1477
+ };
1478
+ function isLuauBytecode(bytes) {
1479
+ const version = bytes[0];
1480
+ if (version === void 0 || version < LUAU_VERSION_MIN || version > LUAU_VERSION_MAX)
1481
+ return false;
1482
+ let at = 1;
1483
+ if (version >= LUAU_VERSION_TYPES) {
1484
+ const types = bytes[at];
1485
+ if (types === void 0 || types < LUAU_TYPES_MIN || types > LUAU_TYPES_MAX) return false;
1486
+ at += 1;
1487
+ }
1488
+ const end = Math.min(bytes.length, at + LUAU_VARINT_MAX_BYTES);
1489
+ for (let i = at; i < end; i++) if ((bytes[i] & LUAU_VARINT_CONTINUE) === 0) return true;
1490
+ return false;
1491
+ }
1492
+ function stem(relative, extension) {
1493
+ return relative.slice(0, relative.length - extension.length);
1494
+ }
1495
+ function isScript(relative) {
1496
+ return relative.endsWith(TOOL_SCRIPT_EXTENSION) && !relative.endsWith(DECLARATION_EXTENSION);
1497
+ }
1498
+ async function buildTool(root) {
1499
+ const paths = await sourcePaths(root);
1500
+ const luau = paths.filter((p) => p.toLowerCase().endsWith(LUAU_SOURCE_EXTENSION));
1501
+ if (luau.length > 0) {
1502
+ throw new ToolBuildError(
1503
+ luau.map(
1504
+ (p) => `${p} is Luau source; write the tool in TypeScript as ${stem(p, LUAU_SOURCE_EXTENSION)}${TOOL_SCRIPT_EXTENSION} and publish compiles it to ${TOOL_CHUNK_EXTENSION}`
1505
+ )
1506
+ );
1507
+ }
1508
+ const scripts = paths.filter(isScript);
1509
+ const chunks = paths.filter((p) => p.endsWith(TOOL_CHUNK_EXTENSION));
1510
+ if (scripts.length > 1) {
1511
+ throw new ToolBuildError([
1512
+ `a tool is one TypeScript script in ${SOURCE_DIR}/; found ${scripts.join(", ")}`
1513
+ ]);
1514
+ }
1515
+ const script = scripts[0];
1516
+ if (script === void 0) {
1517
+ const chunk2 = chunks[0];
1518
+ if (chunk2 === void 0 || chunks.length > 1) {
1519
+ throw new ToolBuildError([
1520
+ `a tool needs its TypeScript script in ${SOURCE_DIR}/ (for example ${SOURCE_DIR}/tool${TOOL_SCRIPT_EXTENSION}); publish compiles it to the ${TOOL_CHUNK_EXTENSION} chunk the Workshop runs`
1521
+ ]);
1522
+ }
1523
+ const bytes = await promises.readFile(path4.join(root, chunk2));
1524
+ if (!isLuauBytecode(bytes))
1525
+ throw new ToolBuildError([`${chunk2} is not compiled Luau bytecode`]);
1526
+ return { script: null, chunk: chunk2, bytes: bytes.length };
1527
+ }
1528
+ const chunk = `${stem(script, TOOL_SCRIPT_EXTENSION)}${TOOL_CHUNK_EXTENSION}`;
1529
+ const stale = chunks.filter((p) => p !== chunk);
1530
+ if (stale.length > 0) {
1531
+ throw new ToolBuildError(
1532
+ stale.map(
1533
+ (p) => `${p} is not built from ${script}; remove it, since a tool carries one compiled chunk`
1534
+ )
1535
+ );
1536
+ }
1537
+ const compiled = compileScript(
1538
+ path4.basename(script),
1539
+ await promises.readFile(path4.join(root, script), "utf8"),
1540
+ "tool"
1541
+ );
1542
+ if (!compiled.ok) {
1543
+ throw new ToolBuildError(
1544
+ compiled.diagnostics.map((d) => `${script}:${d.line}:${d.column} ${d.message}`)
1545
+ );
1546
+ }
1547
+ if (!isLuauBytecode(compiled.bytecode))
1548
+ throw new ToolBuildError([`${script} did not compile to Luau bytecode`]);
1549
+ await promises.writeFile(path4.join(root, chunk), compiled.bytecode);
1550
+ return { script, chunk, bytes: compiled.bytecode.length };
1551
+ }
1552
+
1553
+ // src/interactives/commands/workshop.ts
1554
+ var USAGE = [
1555
+ "usage: cimplify workshop pack <out-dir> [--repo <dir>] [--pack <name>]",
1556
+ " cimplify workshop import [--repo <dir>] [--pack <name>] [--owner <account id>]",
1557
+ " cimplify workshop init <dir> --type <item type> --item <handle/slug> [--author <name>]",
1558
+ " cimplify workshop publish [dir] [--accept-attestation] [--business <business id>]",
1559
+ " cimplify workshop status <upload id>"
1560
+ ].join("\n");
1561
+ var PACK = "pack";
1562
+ var IMPORT = "import";
1563
+ var INIT = "init";
1564
+ var PUBLISH = "publish";
1565
+ var STATUS = "status";
1566
+ var IMPORT_PATH = "/v1/platform/content/versions";
1567
+ var BLOB_DIR = "blobs";
1568
+ var RESOLUTION_FILE = "resolution.json";
1569
+ var DEFAULT_OWNER = "system:cimplify";
1570
+ var DEFAULT_AUTHOR = "Me";
1571
+ function writeLocalWorkshop(out, packed2) {
1572
+ mkdirSync(path4.join(out, BLOB_DIR), { recursive: true });
1573
+ let blobs = 0;
1574
+ for (const item of packed2) {
1575
+ for (const [digest, bytes] of item.blobs) {
1576
+ writeFileSync(path4.join(out, BLOB_DIR, digest), bytes);
1577
+ blobs += 1;
1578
+ }
1579
+ }
1580
+ writeFileSync(path4.join(out, RESOLUTION_FILE), JSON.stringify(resolutionOf(packed2), null, 2));
1581
+ return blobs;
1582
+ }
1583
+ async function signedInClient(why) {
1584
+ const auth = await readAuthOrNull();
1585
+ if (auth === null) throw new CliError(CLI_ERROR_CODE.NOT_LOGGED_IN, why);
1586
+ return ApiClient.fromAuth(auth);
1587
+ }
1588
+ async function packed(repo, only) {
1589
+ try {
1590
+ return await packAll(repo, only);
1591
+ } catch (err) {
1592
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, err instanceof Error ? err.message : String(err));
1593
+ }
1594
+ }
1595
+ async function confirmAttestation(accepted) {
1596
+ info(bold("Workshop attestation"));
1597
+ info(ATTESTATION_TEXT);
1598
+ if (accepted || isAutoYes()) return;
1599
+ if (!isInteractive()) {
1600
+ throw new CliError(CLI_ERROR_CODE.INTERACTIVE_REQUIRED, `accept the Workshop attestation with --accept-attestation (version ${ATTESTATION_VERSION})`);
1601
+ }
1602
+ if (!await promptYesNo("Do you accept this attestation?")) throw new CliError(CLI_ERROR_CODE.ABORTED, "publishing needs the Workshop attestation");
1603
+ }
1604
+ function describe(status) {
1605
+ switch (status.stage) {
1606
+ case "listed":
1607
+ return `listed${status.version_digest ? ` ${dim(status.version_digest.slice(0, 12))}` : ""}`;
1608
+ case "in_review":
1609
+ return "waiting for review; it lists once a reviewer approves it";
1610
+ case "refused":
1611
+ return `refused: ${status.refusal_reason ?? "unknown"}${status.refusal_detail ? ` (${status.refusal_detail})` : ""}`;
1612
+ default:
1613
+ return status.stage;
1614
+ }
1615
+ }
1616
+ async function publishDirectory(dir, remote, accept, business) {
1617
+ let project;
1618
+ let files;
1619
+ try {
1620
+ project = parseContentToml(await promises.readFile(path4.join(dir, CONTENT_FILE), "utf8"));
1621
+ if (project.itemType === TOOL_ITEM_TYPE) {
1622
+ const built = await buildTool(dir);
1623
+ if (built.script !== null) info(dim(`Compiled ${built.script} to ${built.chunk} (${built.bytes} bytes)`));
1624
+ }
1625
+ files = await collectFiles(dir);
1626
+ } catch (err) {
1627
+ if (err instanceof ContentProjectError || err instanceof ToolBuildError) throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, err.message);
1628
+ if (err.code === "ENOENT") throw new CliError(CLI_ERROR_CODE.NOT_FOUND, `no ${CONTENT_FILE} in ${dir}; start one with cimplify workshop init`);
1629
+ throw err;
1630
+ }
1631
+ await confirmAttestation(accept || project.manifest.attestation === ATTESTATION_VERSION);
1632
+ step(`Publishing ${project.item}@${project.version} (${files.length} files)`);
1633
+ return publishContent(withAttestation(project), files, remote, {
1634
+ ...business === void 0 ? {} : { owner: { kind: "business", id: business } },
1635
+ progress: {
1636
+ onCreated: (outcome) => info(dim(outcome === "created" ? `Created ${project.item}` : `${project.item} exists`)),
1637
+ onUploaded: (file, done, total) => info(dim(`Uploaded ${file.path} (${done}/${total})`)),
1638
+ onStage: (status) => info(describe(status))
1639
+ }
1640
+ });
1641
+ }
1642
+ async function run(argv) {
1643
+ const args = parseArgs(argv);
1644
+ const [action, target] = args.positional;
1645
+ const repo = path4.resolve(flagString(args, "repo") ?? process.cwd());
1646
+ const only = flagString(args, "pack");
1647
+ if (action === PACK && target) {
1648
+ const items = await packed(repo, only);
1649
+ const blobs = writeLocalWorkshop(path4.resolve(target), items);
1650
+ success(`Packed ${items.length} Workshop items (${blobs} blobs) into ${target}`);
1651
+ info(dim(`Use it with CIMPLIFY_CONTENT_REMOTE=fake CIMPLIFY_CONTENT_FAKE_ROOT=${path4.resolve(target)} cimplify add <item>`));
1652
+ result(items.map((p) => ({ item: p.item.item, version: p.item.version, version_digest: p.versionDigest })));
1653
+ return;
1654
+ }
1655
+ if (action === IMPORT) {
1656
+ const client = await signedInClient("importing curated Workshop content needs a signed-in operator account");
1657
+ const owner = { kind: "account", id: flagString(args, "owner") ?? DEFAULT_OWNER };
1658
+ const imported = [];
1659
+ for (const item of await packed(repo, only)) {
1660
+ imported.push(await client.post(IMPORT_PATH, importBody(item, owner)));
1661
+ info(`${item.item.item} ${item.item.version}`);
1662
+ }
1663
+ success(`Imported ${imported.length} Workshop items`);
1664
+ result(imported);
1665
+ return;
1666
+ }
1667
+ if (action === INIT && target) {
1668
+ const itemType = flagString(args, "type");
1669
+ const item = flagString(args, "item");
1670
+ if (itemType === void 0 || !ITEM_TYPES.includes(itemType) || item === void 0) {
1671
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, `init needs --item <handle/slug> and --type ${ITEM_TYPES.join("|")}`);
1672
+ }
1673
+ try {
1674
+ const file = await initContentProject(path4.resolve(target), item, itemType, flagString(args, "author") ?? DEFAULT_AUTHOR);
1675
+ success(`Wrote ${file}; put the item's files under source/ and run cimplify workshop publish`);
1676
+ result({ content_toml: file });
1677
+ } catch (err) {
1678
+ if (err instanceof ContentProjectError) throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, err.message);
1679
+ throw err;
1680
+ }
1681
+ return;
1682
+ }
1683
+ if (action === PUBLISH) {
1684
+ const client = await signedInClient("publishing to the Workshop needs a signed-in account; run cimplify login");
1685
+ const summary = await publishDirectory(path4.resolve(target ?? process.cwd()), new HttpWorkshopRemote(client), flagBool(args, "accept-attestation"), flagString(args, "business"));
1686
+ if (summary.status.stage === "refused") failure(`${summary.item}@${summary.version} was ${describe(summary.status)}`);
1687
+ else if (summary.status.stage === "in_review") warn(`${summary.item}@${summary.version} is ${describe(summary.status)}`);
1688
+ else success(`${summary.item}@${summary.version} is ${describe(summary.status)}`);
1689
+ result(summary);
1690
+ if (summary.status.stage === "refused") throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, describe(summary.status));
1691
+ return;
1692
+ }
1693
+ if (action === STATUS && target) {
1694
+ const client = await signedInClient("reading a Workshop upload needs a signed-in account");
1695
+ const status = await new HttpWorkshopRemote(client).status(target);
1696
+ info(describe(status));
1697
+ result(status);
1698
+ return;
1699
+ }
1700
+ throw new CliError(CLI_ERROR_CODE.INVALID_INPUT, USAGE);
1701
+ }
1702
+
1703
+ export { run as default, publishDirectory, writeLocalWorkshop };