@ssobig/writer-cli 0.2.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 (73) hide show
  1. package/README.md +35 -0
  2. package/asset-repository.js +278 -0
  3. package/config.js +14 -0
  4. package/package.json +28 -0
  5. package/project-runtime.js +102 -0
  6. package/storage-path.js +110 -0
  7. package/templates/mystery-v1/authoring-view-preference.js +34 -0
  8. package/templates/mystery-v1/character-perspective-preview.js +61 -0
  9. package/templates/mystery-v1/component-asset-operations.js +103 -0
  10. package/templates/mystery-v1/component-autosave.js +121 -0
  11. package/templates/mystery-v1/component-catalog-contract.js +340 -0
  12. package/templates/mystery-v1/component-checkpoint-history.js +145 -0
  13. package/templates/mystery-v1/component-contract.js +90 -0
  14. package/templates/mystery-v1/component-draft-operations.js +313 -0
  15. package/templates/mystery-v1/component-field-contracts.js +595 -0
  16. package/templates/mystery-v1/component-id-policy.js +64 -0
  17. package/templates/mystery-v1/component-manager.js +396 -0
  18. package/templates/mystery-v1/component-navigation-counts.js +64 -0
  19. package/templates/mystery-v1/component-registry.js +205 -0
  20. package/templates/mystery-v1/component-renderers.js +139 -0
  21. package/templates/mystery-v1/component-storage-contract.js +237 -0
  22. package/templates/mystery-v1/external-update-coordinator.js +91 -0
  23. package/templates/mystery-v1/output-clue-card-layout.js +46 -0
  24. package/templates/mystery-v1/page-header.js +26 -0
  25. package/templates/mystery-v1/render-ui-state.js +76 -0
  26. package/templates/mystery-v1/runtime-snapshot-reconciler.js +40 -0
  27. package/templates/mystery-v1/tab-bar.js +87 -0
  28. package/templates/mystery-v1/view-component-contract.js +152 -0
  29. package/templates/mystery-v1/view-component-registry.js +44 -0
  30. package/templates/mystery-v1/view-component-runtime.js +95 -0
  31. package/tools/writer-cli/bin/ssobig-writer-daemon.cjs +34 -0
  32. package/tools/writer-cli/bin/ssobig-writer.cjs +12 -0
  33. package/tools/writer-cli/package-lock.json +121 -0
  34. package/tools/writer-cli/package.json +22 -0
  35. package/tools/writer-cli/skills/ssobig-writer-cli/SKILL.md +38 -0
  36. package/tools/writer-cli/skills/ssobig-writer-cli/agents/openai.yaml +4 -0
  37. package/tools/writer-cli/skills/ssobig-writer-cli/references/assets-checkpoints.md +5 -0
  38. package/tools/writer-cli/skills/ssobig-writer-cli/references/errors.md +10 -0
  39. package/tools/writer-cli/skills/ssobig-writer-cli/references/install-auth.md +7 -0
  40. package/tools/writer-cli/skills/ssobig-writer-cli/references/projects-components.md +7 -0
  41. package/tools/writer-cli/skills/ssobig-writer-cli/references/read-search.md +5 -0
  42. package/tools/writer-cli/src/agent-paths.cjs +114 -0
  43. package/tools/writer-cli/src/agent-service.cjs +496 -0
  44. package/tools/writer-cli/src/asset-policy.cjs +113 -0
  45. package/tools/writer-cli/src/auth.cjs +655 -0
  46. package/tools/writer-cli/src/checkpoint-diff.cjs +128 -0
  47. package/tools/writer-cli/src/command-registry.cjs +152 -0
  48. package/tools/writer-cli/src/commands.cjs +841 -0
  49. package/tools/writer-cli/src/corpus.cjs +83 -0
  50. package/tools/writer-cli/src/daemon-app.cjs +106 -0
  51. package/tools/writer-cli/src/daemon-client.cjs +187 -0
  52. package/tools/writer-cli/src/daemon-protocol.cjs +184 -0
  53. package/tools/writer-cli/src/daemon-runner.cjs +97 -0
  54. package/tools/writer-cli/src/daemon-server.cjs +378 -0
  55. package/tools/writer-cli/src/diagnostics.cjs +235 -0
  56. package/tools/writer-cli/src/domain.cjs +731 -0
  57. package/tools/writer-cli/src/errors.cjs +47 -0
  58. package/tools/writer-cli/src/gateway.cjs +357 -0
  59. package/tools/writer-cli/src/investigation-board-layout.cjs +328 -0
  60. package/tools/writer-cli/src/json-patch.cjs +98 -0
  61. package/tools/writer-cli/src/json.cjs +26 -0
  62. package/tools/writer-cli/src/local-index-cache.cjs +139 -0
  63. package/tools/writer-cli/src/local-index-lookup.cjs +98 -0
  64. package/tools/writer-cli/src/local-index-query.cjs +304 -0
  65. package/tools/writer-cli/src/local-index-snapshot.cjs +235 -0
  66. package/tools/writer-cli/src/local-index-storage.cjs +284 -0
  67. package/tools/writer-cli/src/local-index.cjs +199 -0
  68. package/tools/writer-cli/src/mutations.cjs +722 -0
  69. package/tools/writer-cli/src/platform-runner.cjs +55 -0
  70. package/tools/writer-cli/src/project-import.cjs +485 -0
  71. package/tools/writer-cli/src/skill-manager.cjs +255 -0
  72. package/tools/writer-cli/src/source-fingerprint.cjs +90 -0
  73. package/tools/writer-cli/src/update-gate.cjs +102 -0
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+
3
+ class WriterCliError extends Error {
4
+ constructor(code, message, details = null, options = {}) {
5
+ super(String(message || code), options);
6
+ this.name = "WriterCliError";
7
+ this.code = String(code || "E_WRITER_CLI");
8
+ this.details = details && typeof details === "object" ? details : null;
9
+ }
10
+ }
11
+
12
+ function cliError(code, message, details = null, cause = null) {
13
+ return new WriterCliError(code, message, details, cause ? { cause } : {});
14
+ }
15
+
16
+ function redact(value) {
17
+ return String(value || "")
18
+ .replace(/\beyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\b/g, "[REDACTED_JWT]")
19
+ .replace(/((?:access|refresh)_token\s*[=:]\s*)[^\s,}\]]+/gi, "$1[REDACTED]");
20
+ }
21
+
22
+ function normalizeError(error) {
23
+ if (error instanceof WriterCliError) return error;
24
+ const sourceCode = String(error?.code || "");
25
+ const status = Number(error?.status || 0);
26
+ if (["40001", "PT409", "23505"].includes(sourceCode) || status === 409) {
27
+ return cliError("E_CONFLICT", "다른 화면 또는 작업이 먼저 저장했습니다. 새 plan을 만들어 다시 검토해 주세요.", null, error);
28
+ }
29
+ if (sourceCode === "42501") {
30
+ return cliError("E_AUTHORIZATION", "이 Writer 데이터에 접근할 권한이 없습니다.", null, error);
31
+ }
32
+ if (sourceCode === "P0001" && /SOMI_CHECKPOINT_NO_CHANGES/.test(String(error?.message || ""))) {
33
+ return cliError("E_NO_CHANGES", "Latest가 이미 해당 체크포인트 상태와 같습니다.", null, error);
34
+ }
35
+ if (sourceCode === "P0001" && /E_CODE_CHANGE_REQUIRED/.test(String(error?.message || ""))) {
36
+ return cliError("E_CODE_CHANGE_REQUIRED", "체크포인트의 Component 구성 또는 schema가 현재 Latest와 달라 CLI에서 복원할 수 없습니다.", null, error);
37
+ }
38
+ if (sourceCode === "22023") {
39
+ return cliError("E_VALIDATION", redact(error?.message || "Writer 입력값이 올바르지 않습니다."), null, error);
40
+ }
41
+ if (sourceCode === "SOMI_MIGRATION_REQUIRED") {
42
+ return cliError("E_MIGRATION_REQUIRED", redact(error.message), null, error);
43
+ }
44
+ return cliError(sourceCode && /^E_/.test(sourceCode) ? sourceCode : "E_WRITER_CLI", redact(error?.message || "Writer CLI 작업에 실패했습니다."), null, error);
45
+ }
46
+
47
+ module.exports = Object.freeze({ WriterCliError, cliError, normalizeError, redact });
@@ -0,0 +1,357 @@
1
+ "use strict";
2
+
3
+ const projectRuntime = require("../../../project-runtime.js");
4
+ const {
5
+ PROJECT_COLUMNS,
6
+ VERSION_COLUMNS,
7
+ SET_COLUMNS,
8
+ INSTANCE_COLUMNS,
9
+ VIEW_COLUMNS,
10
+ BINDING_COLUMNS,
11
+ CHECKPOINT_COLUMNS,
12
+ CHECKPOINT_ENTRY_COLUMNS
13
+ } = require("./domain.cjs");
14
+ const { cliError } = require("./errors.cjs");
15
+
16
+ function dataOrThrow(result) {
17
+ if (result?.error) throw result.error;
18
+ return result?.data;
19
+ }
20
+
21
+ const PAGE_SIZE = 1_000;
22
+ const PROJECT_ID_CHUNK_SIZE = 100;
23
+
24
+ function chunks(values, size = PROJECT_ID_CHUNK_SIZE) {
25
+ const output = [];
26
+ for (let index = 0; index < values.length; index += size) output.push(values.slice(index, index + size));
27
+ return output;
28
+ }
29
+
30
+ async function collectPages(buildQuery) {
31
+ const rows = [];
32
+ for (let offset = 0; ; offset += PAGE_SIZE) {
33
+ const query = buildQuery().range(offset, offset + PAGE_SIZE - 1);
34
+ const page = dataOrThrow(await query) || [];
35
+ rows.push(...page);
36
+ if (page.length < PAGE_SIZE) return rows;
37
+ }
38
+ }
39
+
40
+ function createSupabaseGateway(client, options = {}) {
41
+ if (!client?.from || !client?.rpc || !client?.storage?.from) throw cliError("E_CONFIGURATION", "Supabase client 설정이 완전하지 않습니다.");
42
+ const bucketName = String(options.bucketName || "");
43
+ if (!bucketName) throw cliError("E_CONFIGURATION", "Writer Storage 버킷 설정이 필요합니다.");
44
+
45
+ function projectStatus(value = "active") {
46
+ const status = String(value || "");
47
+ if (!["active", "archived"].includes(status)) throw cliError("E_USAGE", "--status는 active 또는 archived여야 합니다.");
48
+ return status;
49
+ }
50
+
51
+ async function listProjects(status = "active") {
52
+ const normalizedStatus = projectStatus(status);
53
+ return collectPages(() => client
54
+ .from("somi_projects")
55
+ .select(PROJECT_COLUMNS)
56
+ .eq("status", normalizedStatus)
57
+ .order("updated_at", { ascending: false })
58
+ .order("id", { ascending: true }));
59
+ }
60
+
61
+ async function getProject(projectRef, status = "active") {
62
+ const value = String(projectRef || "").trim();
63
+ if (!value) throw cliError("E_USAGE", "--project 값이 필요합니다.");
64
+ const normalizedStatus = projectStatus(status);
65
+ let query = client.from("somi_projects").select(PROJECT_COLUMNS).eq("status", normalizedStatus);
66
+ query = projectRuntime.UUID_PATTERN.test(value) ? query.eq("id", value.toLowerCase()) : query.eq("slug", value);
67
+ const row = dataOrThrow(await query.maybeSingle());
68
+ if (!row) throw cliError("E_PROJECT_NOT_FOUND", `${normalizedStatus} 작품을 찾을 수 없습니다: ${value}`);
69
+ return row;
70
+ }
71
+
72
+ async function findProjectBySlug(slug) {
73
+ const value = String(slug || "").trim();
74
+ if (!value) throw cliError("E_USAGE", "작품 slug가 필요합니다.");
75
+ return dataOrThrow(await client
76
+ .from("somi_projects")
77
+ .select(PROJECT_COLUMNS)
78
+ .eq("slug", value)
79
+ .maybeSingle()) || null;
80
+ }
81
+
82
+ async function listVersions(projectId) {
83
+ return dataOrThrow(await client
84
+ .from("somi_project_versions")
85
+ .select(VERSION_COLUMNS)
86
+ .eq("project_id", String(projectId))
87
+ .order("version_number", { ascending: false })) || [];
88
+ }
89
+
90
+ async function loadCorpus(status = "active") {
91
+ const projects = await listProjects(status);
92
+ if (!projects.length) {
93
+ return {
94
+ projects: [],
95
+ versionRows: [],
96
+ componentSetRows: [],
97
+ componentRows: [],
98
+ viewRows: [],
99
+ bindingRows: []
100
+ };
101
+ }
102
+ const projectIds = projects.map(project => String(project.id));
103
+ async function loadTable(table, columns, orders) {
104
+ const batches = await Promise.all(chunks(projectIds).map(projectIdBatch => collectPages(() => {
105
+ let query = client.from(table).select(columns).in("project_id", projectIdBatch);
106
+ for (const [column, ascending] of orders) query = query.order(column, { ascending });
107
+ return query;
108
+ })));
109
+ return batches.flat();
110
+ }
111
+ const [versionResult, setResult, componentResult, viewResult, bindingResult] = await Promise.all([
112
+ loadTable("somi_project_versions", VERSION_COLUMNS, [["project_id", true], ["version_number", false]]),
113
+ loadTable("somi_project_component_sets", SET_COLUMNS, [["project_id", true], ["version_number", false]]),
114
+ loadTable("somi_project_component_instances", INSTANCE_COLUMNS, [["project_id", true], ["version_number", true], ["sort_order", true], ["instance_id", true]]),
115
+ loadTable("somi_project_view_component_instances", `project_id,version_number,${VIEW_COLUMNS}`, [["project_id", true], ["version_number", true], ["sort_order", true], ["view_instance_id", true]]),
116
+ loadTable("somi_project_view_component_bindings", `project_id,version_number,${BINDING_COLUMNS}`, [["project_id", true], ["version_number", true], ["sort_order", true], ["view_instance_id", true], ["binding_key", true]])
117
+ ]);
118
+ const [verifiedSets, verifiedProjects] = await Promise.all([
119
+ loadTable("somi_project_component_sets", SET_COLUMNS, [["project_id", true], ["version_number", false]]),
120
+ listProjects(status)
121
+ ]);
122
+ const setVector = rows => rows.map(row => [String(row.project_id), Number(row.version_number), Number(row.revision), String(row.component_checksum)]);
123
+ const projectVector = rows => rows.map(row => [String(row.id), String(row.status), String(row.updated_at)]).sort((left, right) => left[0].localeCompare(right[0]));
124
+ if (JSON.stringify(setVector(setResult)) !== JSON.stringify(setVector(verifiedSets))
125
+ || JSON.stringify(projectVector(projects)) !== JSON.stringify(projectVector(verifiedProjects))) {
126
+ throw cliError("E_CORPUS_CHANGED", "동기화 중 Writer 원본 revision이 변경되었습니다. 전체 snapshot을 다시 읽어야 합니다.");
127
+ }
128
+ return {
129
+ projects,
130
+ versionRows: versionResult,
131
+ componentSetRows: setResult,
132
+ componentRows: componentResult,
133
+ viewRows: viewResult,
134
+ bindingRows: bindingResult
135
+ };
136
+ }
137
+
138
+ async function loadVersion(projectRef, versionNumber) {
139
+ const project = await getProject(projectRef);
140
+ const version = Number(versionNumber);
141
+ if (!Number.isSafeInteger(version) || version < 1) throw cliError("E_USAGE", "--version에는 1 이상의 정수가 필요합니다.");
142
+ const [versionResult, setResult, componentResult, viewResult, bindingResult] = await Promise.all([
143
+ client.from("somi_project_versions").select(VERSION_COLUMNS).eq("project_id", project.id).eq("version_number", version).maybeSingle(),
144
+ client.from("somi_project_component_sets").select(SET_COLUMNS).eq("project_id", project.id).eq("version_number", version).maybeSingle(),
145
+ client.from("somi_project_component_instances").select(INSTANCE_COLUMNS).eq("project_id", project.id).eq("version_number", version).order("sort_order", { ascending: true }),
146
+ client.from("somi_project_view_component_instances").select(VIEW_COLUMNS).eq("project_id", project.id).eq("version_number", version).order("sort_order", { ascending: true }),
147
+ client.from("somi_project_view_component_bindings").select(BINDING_COLUMNS).eq("project_id", project.id).eq("version_number", version).order("sort_order", { ascending: true })
148
+ ]);
149
+ const versionRow = dataOrThrow(versionResult);
150
+ if (!versionRow) throw cliError("E_VERSION_NOT_FOUND", `${project.slug}의 v${version}을 찾을 수 없습니다.`);
151
+ return {
152
+ project,
153
+ version: versionRow,
154
+ componentSet: dataOrThrow(setResult),
155
+ componentRows: dataOrThrow(componentResult) || [],
156
+ viewRows: dataOrThrow(viewResult) || [],
157
+ bindingRows: dataOrThrow(bindingResult) || []
158
+ };
159
+ }
160
+
161
+ async function loadLatestVersion(projectRef, versionNumber = null) {
162
+ if (versionNumber !== null && versionNumber !== undefined && versionNumber !== "") {
163
+ return loadVersion(projectRef, Number(versionNumber));
164
+ }
165
+ const project = await getProject(projectRef);
166
+ const activeSet = dataOrThrow(await client
167
+ .from("somi_project_component_sets")
168
+ .select(SET_COLUMNS)
169
+ .eq("project_id", project.id)
170
+ .eq("canonical_status", "active")
171
+ .maybeSingle());
172
+ if (!activeSet) throw cliError("E_MIGRATION_REQUIRED", `${project.slug}의 단일 Latest workspace를 찾을 수 없습니다.`);
173
+ return loadVersion(project.id, Number(activeSet.version_number));
174
+ }
175
+
176
+ async function listCheckpoints(input) {
177
+ return dataOrThrow(await client.rpc("list_somi_project_checkpoints", {
178
+ p_project_id: String(input.projectId),
179
+ p_version_number: Number(input.versionNumber),
180
+ p_before_checkpoint_number: input.beforeCheckpointNumber === null || input.beforeCheckpointNumber === undefined
181
+ ? null
182
+ : Number(input.beforeCheckpointNumber),
183
+ p_limit: Number(input.limit || 50)
184
+ })) || [];
185
+ }
186
+
187
+ async function getCheckpoint(projectId, versionNumber, selector) {
188
+ const value = String(selector || "").trim();
189
+ if (!value) throw cliError("E_USAGE", "체크포인트 ID 또는 번호가 필요합니다.");
190
+ let query = client.from("somi_project_checkpoints")
191
+ .select(CHECKPOINT_COLUMNS)
192
+ .eq("project_id", String(projectId))
193
+ .eq("version_number", Number(versionNumber));
194
+ if (/^[1-9][0-9]*$/.test(value)) query = query.eq("checkpoint_number", Number(value));
195
+ else {
196
+ if (!projectRuntime.UUID_PATTERN.test(value)) throw cliError("E_USAGE", "--checkpoint에는 1 이상의 번호 또는 UUID가 필요합니다.");
197
+ query = query.eq("checkpoint_id", value.toLowerCase());
198
+ }
199
+ const row = dataOrThrow(await query.maybeSingle());
200
+ if (!row) throw cliError("E_CHECKPOINT_NOT_FOUND", `체크포인트를 찾을 수 없습니다: ${value}`);
201
+ return row;
202
+ }
203
+
204
+ async function getCheckpointByOperation(projectId, versionNumber, operationId) {
205
+ return dataOrThrow(await client.from("somi_project_checkpoints")
206
+ .select(CHECKPOINT_COLUMNS)
207
+ .eq("project_id", String(projectId))
208
+ .eq("version_number", Number(versionNumber))
209
+ .eq("operation_id", String(operationId))
210
+ .maybeSingle()) || null;
211
+ }
212
+
213
+ async function getCheckpointSnapshot(projectId, versionNumber, selector, options = {}) {
214
+ const checkpoint = await getCheckpoint(projectId, versionNumber, selector);
215
+ const entries = await collectPages(() => client.from("somi_project_checkpoint_entries")
216
+ .select(CHECKPOINT_ENTRY_COLUMNS)
217
+ .eq("checkpoint_id", checkpoint.checkpoint_id)
218
+ .eq("project_id", String(projectId))
219
+ .eq("version_number", Number(versionNumber))
220
+ .order("sort_order", { ascending: true })
221
+ .order("instance_id", { ascending: true }));
222
+ if (options.includeData !== true || !entries.length) return { checkpoint, entries };
223
+ const hashes = [...new Set(entries.map(entry => String(entry.data_hash)))];
224
+ const blobBatches = await Promise.all(chunks(hashes).map(hashBatch => collectPages(() => client
225
+ .from("somi_project_checkpoint_blobs")
226
+ .select("project_id,content_hash,data")
227
+ .eq("project_id", String(projectId))
228
+ .in("content_hash", hashBatch)
229
+ .order("content_hash", { ascending: true }))));
230
+ const blobs = blobBatches.flat();
231
+ const dataByHash = new Map(blobs.map(blob => [String(blob.content_hash), blob.data]));
232
+ const missing = hashes.find(hash => !dataByHash.has(hash));
233
+ if (missing) throw cliError("E_MIGRATION_REQUIRED", `체크포인트 payload blob을 찾을 수 없습니다: ${missing}`);
234
+ return {
235
+ checkpoint,
236
+ entries: entries.map(entry => ({ ...entry, data: dataByHash.get(String(entry.data_hash)) }))
237
+ };
238
+ }
239
+
240
+ async function createCheckpoint(input) {
241
+ return dataOrThrow(await client.rpc("create_somi_project_checkpoint", {
242
+ p_project_id: String(input.projectId),
243
+ p_version_number: Number(input.versionNumber),
244
+ p_expected_set_revision: Number(input.expectedSetRevision),
245
+ p_expected_component_checksum: String(input.expectedComponentChecksum),
246
+ p_message: String(input.message),
247
+ p_operation_id: String(input.operationId),
248
+ p_updated_by_email: String(input.actorEmail || "")
249
+ }));
250
+ }
251
+
252
+ async function restoreCheckpoint(input) {
253
+ return dataOrThrow(await client.rpc("restore_somi_project_checkpoint", {
254
+ p_project_id: String(input.projectId),
255
+ p_version_number: Number(input.versionNumber),
256
+ p_checkpoint_id: String(input.checkpointId),
257
+ p_expected_set_revision: Number(input.expectedSetRevision),
258
+ p_expected_component_checksum: String(input.expectedComponentChecksum),
259
+ p_message: String(input.message),
260
+ p_operation_id: String(input.operationId),
261
+ p_updated_by_email: String(input.actorEmail || "")
262
+ }));
263
+ }
264
+
265
+ async function saveInstance(input) {
266
+ const args = {
267
+ p_project_id: String(input.projectId),
268
+ p_version_number: Number(input.versionNumber),
269
+ p_instance: input.payload,
270
+ p_expected_revision: Number(input.expectedRevision),
271
+ p_updated_by_email: String(input.actorEmail || "")
272
+ };
273
+ if (input.checkpointMessage !== undefined) {
274
+ args.p_checkpoint_message = String(input.checkpointMessage || "");
275
+ args.p_checkpoint_source = "cli";
276
+ }
277
+ const result = await client.rpc("save_somi_project_component_instance", args);
278
+ return dataOrThrow(result);
279
+ }
280
+
281
+ async function setProjectArchived(input) {
282
+ return dataOrThrow(await client.rpc("set_somi_project_archived", {
283
+ p_project_id: String(input.projectId || ""),
284
+ p_expected_status: String(input.expectedStatus || ""),
285
+ p_expected_updated_at: String(input.expectedUpdatedAt || ""),
286
+ p_archived: input.archived === true,
287
+ p_updated_by_email: String(input.actorEmail || "")
288
+ }));
289
+ }
290
+
291
+ async function setCatalogDemoProject(input) {
292
+ return dataOrThrow(await client.rpc("set_somi_catalog_demo_project", {
293
+ p_project_id: String(input.projectId || ""),
294
+ p_expected_is_catalog_demo: input.expectedIsCatalogDemo === true,
295
+ p_expected_updated_at: String(input.expectedUpdatedAt || ""),
296
+ p_enabled: input.enabled === true
297
+ }));
298
+ }
299
+
300
+ async function createScaffoldProject(input) {
301
+ return dataOrThrow(await client.rpc("create_somi_project_from_scaffold", {
302
+ p_slug: String(input.slug || ""),
303
+ p_title: String(input.title || ""),
304
+ p_author_name: String(input.authorName || "")
305
+ }));
306
+ }
307
+
308
+ async function createImportedProject(input) {
309
+ return dataOrThrow(await client.rpc("create_somi_project_from_import", {
310
+ p_slug: String(input.slug || ""),
311
+ p_title: String(input.title || ""),
312
+ p_author_name: String(input.authorName || ""),
313
+ p_components: input.components,
314
+ p_source: input.provenance
315
+ }));
316
+ }
317
+
318
+ async function uploadObject(objectPath, body, uploadOptions = {}) {
319
+ const result = await client.storage.from(bucketName).upload(objectPath, body, {
320
+ ...uploadOptions,
321
+ upsert: false
322
+ });
323
+ dataOrThrow(result);
324
+ return { path: objectPath };
325
+ }
326
+
327
+ async function removeObjects(paths) {
328
+ const result = await client.storage.from(bucketName).remove(paths);
329
+ dataOrThrow(result);
330
+ return { paths: [...paths] };
331
+ }
332
+
333
+ return Object.freeze({
334
+ listProjects,
335
+ getProject,
336
+ findProjectBySlug,
337
+ listVersions,
338
+ loadCorpus,
339
+ loadVersion,
340
+ loadLatestVersion,
341
+ listCheckpoints,
342
+ getCheckpoint,
343
+ getCheckpointByOperation,
344
+ getCheckpointSnapshot,
345
+ createCheckpoint,
346
+ restoreCheckpoint,
347
+ saveInstance,
348
+ setProjectArchived,
349
+ setCatalogDemoProject,
350
+ createScaffoldProject,
351
+ createImportedProject,
352
+ uploadObject,
353
+ removeObjects
354
+ });
355
+ }
356
+
357
+ module.exports = Object.freeze({ createSupabaseGateway, dataOrThrow });