@sudobility/screenwriter_types 0.1.1

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 (65) hide show
  1. package/CLAUDE.md +50 -0
  2. package/README.md +34 -0
  3. package/dist/account/index.d.ts +440 -0
  4. package/dist/account/index.js +274 -0
  5. package/dist/admin/index.d.ts +95 -0
  6. package/dist/admin/index.js +20 -0
  7. package/dist/ai/index.d.ts +564 -0
  8. package/dist/ai/index.js +179 -0
  9. package/dist/api/envelope.d.ts +33 -0
  10. package/dist/api/envelope.js +140 -0
  11. package/dist/api/pagination.d.ts +7 -0
  12. package/dist/api/pagination.js +9 -0
  13. package/dist/api/routes.d.ts +273 -0
  14. package/dist/api/routes.js +306 -0
  15. package/dist/assets/index.d.ts +722 -0
  16. package/dist/assets/index.js +452 -0
  17. package/dist/collab/index.d.ts +214 -0
  18. package/dist/collab/index.js +211 -0
  19. package/dist/commands/index.d.ts +92 -0
  20. package/dist/commands/index.js +20 -0
  21. package/dist/credits/index.d.ts +26 -0
  22. package/dist/credits/index.js +7 -0
  23. package/dist/formats/index.d.ts +87 -0
  24. package/dist/formats/index.js +26 -0
  25. package/dist/ids/index.d.ts +2 -0
  26. package/dist/ids/index.js +2 -0
  27. package/dist/index.d.ts +33 -0
  28. package/dist/index.js +33 -0
  29. package/dist/io/index.d.ts +395 -0
  30. package/dist/io/index.js +161 -0
  31. package/dist/jobs/index.d.ts +278 -0
  32. package/dist/jobs/index.js +138 -0
  33. package/dist/keys/index.d.ts +53 -0
  34. package/dist/keys/index.js +22 -0
  35. package/dist/lifecycle/index.d.ts +263 -0
  36. package/dist/lifecycle/index.js +148 -0
  37. package/dist/me/index.d.ts +35 -0
  38. package/dist/me/index.js +18 -0
  39. package/dist/packets/index.d.ts +887 -0
  40. package/dist/packets/index.js +312 -0
  41. package/dist/projects/index.d.ts +201 -0
  42. package/dist/projects/index.js +75 -0
  43. package/dist/public/index.d.ts +95 -0
  44. package/dist/public/index.js +25 -0
  45. package/dist/reads/index.d.ts +387 -0
  46. package/dist/reads/index.js +118 -0
  47. package/dist/reports/index.d.ts +168 -0
  48. package/dist/reports/index.js +466 -0
  49. package/dist/sharing/index.d.ts +256 -0
  50. package/dist/sharing/index.js +91 -0
  51. package/dist/sync/codec.d.ts +7 -0
  52. package/dist/sync/codec.js +91 -0
  53. package/dist/sync/index.d.ts +382 -0
  54. package/dist/sync/index.js +262 -0
  55. package/dist/sync/permissions.d.ts +22 -0
  56. package/dist/sync/permissions.js +11 -0
  57. package/dist/templates/index.d.ts +1699 -0
  58. package/dist/templates/index.js +70 -0
  59. package/dist/tenancy/index.d.ts +116 -0
  60. package/dist/tenancy/index.js +102 -0
  61. package/dist/test/index.d.ts +8 -0
  62. package/dist/test/index.js +88 -0
  63. package/dist/versions/index.d.ts +335 -0
  64. package/dist/versions/index.js +107 -0
  65. package/package.json +62 -0
@@ -0,0 +1,466 @@
1
+ /**
2
+ * Reports (spec 09 §10 F-RPT-000..018, spec 05 §6.18), slice B10.
3
+ *
4
+ * `REPORT_KINDS` belongs in `writing_core` (spec 09 R25) but Tasks 29-35 have not landed there; until they do the
5
+ * vocabulary, the options and the columns live here so the API, the client and MCP share one declaration.
6
+ * A report is a list of tables; CSV, HTML and PDF are all rendered from those same tables, which is what
7
+ * F-RPT-000's acceptance ("CSV and PDF contain the same rows") rests on.
8
+ */
9
+ import { z } from 'zod';
10
+ import { sourceParamSchema, docSourceSchema } from '../reads/index.js';
11
+ export const REPORT_KINDS = [
12
+ 'scene',
13
+ 'location',
14
+ 'character',
15
+ 'cast',
16
+ 'dialogue',
17
+ 'script',
18
+ 'text',
19
+ 'notes',
20
+ 'synopsis',
21
+ 'revisions',
22
+ 'tags',
23
+ 'statistics',
24
+ 'inclusivity',
25
+ 'presentProgressive',
26
+ 'dayOutOfDays',
27
+ 'shotList',
28
+ 'structure',
29
+ 'characterArcs',
30
+ ];
31
+ export const REPORT_FORMATS = ['json', 'csv', 'pdf', 'html'];
32
+ /** Documents up to this many pages run a report inline; larger ones answer with a `report.render` job (spec 05 §6.18). */
33
+ export const REPORT_SYNC_MAX_PAGES = 200;
34
+ const col = (id, label, align) => ({
35
+ id,
36
+ label,
37
+ ...(align ? { align } : {}),
38
+ });
39
+ const bool = (name, def, description) => ({
40
+ name,
41
+ type: 'boolean',
42
+ default: def,
43
+ ...(description ? { description } : {}),
44
+ });
45
+ const list = (name, description) => ({
46
+ name,
47
+ type: 'stringList',
48
+ default: [],
49
+ ...(description ? { description } : {}),
50
+ });
51
+ const en = (name, values, def) => ({ name, type: 'enum', values, default: def });
52
+ /** Column and option declarations. The tables a report returns use exactly these column ids. */
53
+ export const REPORT_DEFS = {
54
+ scene: {
55
+ featureId: 'F-RPT-001',
56
+ title: 'Scene report',
57
+ options: [
58
+ en('sort', ['scriptOrder', 'sceneNumber', 'location', 'scriptDay'], 'scriptOrder'),
59
+ bool('includeOmitted', false),
60
+ ],
61
+ columns: [
62
+ col('number', 'Scene'),
63
+ col('intro', 'Intro'),
64
+ col('location', 'Location'),
65
+ col('time', 'Time'),
66
+ col('heading', 'Heading'),
67
+ col('page', 'Page'),
68
+ col('length', 'Length'),
69
+ col('estTime', 'Est. time', 'right'),
70
+ col('synopsis', 'Synopsis'),
71
+ col('colour', 'Colour'),
72
+ col('speaking', 'Speaking'),
73
+ col('nonSpeaking', 'Non-speaking'),
74
+ col('scriptDay', 'Script day'),
75
+ ],
76
+ },
77
+ location: {
78
+ featureId: 'F-RPT-002',
79
+ title: 'Location report',
80
+ options: [en('sort', ['name', 'firstAppearance', 'length'], 'name')],
81
+ columns: [
82
+ col('location', 'Location'),
83
+ col('intExt', 'INT/EXT'),
84
+ col('times', 'Times of day'),
85
+ col('scenes', 'Scenes'),
86
+ col('pages', 'Pages'),
87
+ col('eighths', 'Length'),
88
+ col('sceneCount', 'Scene count', 'right'),
89
+ ],
90
+ },
91
+ character: {
92
+ featureId: 'F-RPT-003',
93
+ title: 'Character report',
94
+ options: [
95
+ list('characters', 'Entity ids or names; empty means every character'),
96
+ bool('includeNonSpeaking', false),
97
+ {
98
+ name: 'monologueWords',
99
+ type: 'number',
100
+ default: 100,
101
+ min: 1,
102
+ max: 10000,
103
+ },
104
+ bool('includeDialogue', false),
105
+ ],
106
+ columns: [
107
+ col('character', 'Character'),
108
+ col('scenes', 'Speaking scenes'),
109
+ col('nonSpeakingScenes', 'Non-speaking scenes'),
110
+ col('extensions', 'Extensions'),
111
+ col('parentheticals', 'Parentheticals', 'right'),
112
+ col('monologues', 'Monologues', 'right'),
113
+ col('dialogue', 'Dialogue'),
114
+ ],
115
+ },
116
+ cast: {
117
+ featureId: 'F-RPT-004',
118
+ title: 'Cast report',
119
+ options: [
120
+ en('sort', ['alphabetical', 'scriptOrder', 'dialogueCount', 'words'], 'alphabetical'),
121
+ ],
122
+ columns: [
123
+ col('character', 'Character'),
124
+ col('dialogues', 'Dialogues', 'right'),
125
+ col('words', 'Words', 'right'),
126
+ col('lines', 'Lines', 'right'),
127
+ col('speakingScenes', 'Speaking scenes', 'right'),
128
+ col('totalScenes', 'Total scenes', 'right'),
129
+ col('pagesPresent', 'Pages present'),
130
+ col('firstAppearance', 'First appearance'),
131
+ col('gender', 'Gender'),
132
+ col('role', 'Role'),
133
+ col('age', 'Age'),
134
+ ],
135
+ },
136
+ dialogue: {
137
+ featureId: 'F-RPT-005',
138
+ title: 'Dialogue report',
139
+ options: [
140
+ list('characters', 'Entity ids or names; empty means every character'),
141
+ bool('includeParentheticals', false),
142
+ ],
143
+ columns: [
144
+ col('number', '#', 'right'),
145
+ col('scene', 'Scene'),
146
+ col('page', 'Page'),
147
+ col('character', 'Character'),
148
+ col('text', 'Dialogue'),
149
+ ],
150
+ },
151
+ script: {
152
+ featureId: 'F-RPT-006',
153
+ title: 'Script report',
154
+ options: [list('styles', 'Style ids or roles; empty means every element')],
155
+ columns: [
156
+ col('page', 'Page'),
157
+ col('scene', 'Scene'),
158
+ col('style', 'Style'),
159
+ col('text', 'Text'),
160
+ ],
161
+ },
162
+ text: {
163
+ featureId: 'F-RPT-007',
164
+ title: 'Text report',
165
+ options: [
166
+ {
167
+ name: 'q',
168
+ type: 'string',
169
+ required: true,
170
+ description: 'Text to find',
171
+ },
172
+ en('mode', ['text', 'regex'], 'text'),
173
+ list('styles', 'Restrict to these style ids'),
174
+ ],
175
+ columns: [
176
+ col('page', 'Page'),
177
+ col('scene', 'Scene'),
178
+ col('style', 'Style'),
179
+ col('snippet', 'Context'),
180
+ ],
181
+ },
182
+ notes: {
183
+ featureId: 'F-RPT-008',
184
+ title: 'Notes report',
185
+ options: [
186
+ list('types'),
187
+ list('authors'),
188
+ en('status', ['any', 'open', 'resolved'], 'any'),
189
+ list('colours'),
190
+ ],
191
+ columns: [
192
+ col('order', '#', 'right'),
193
+ col('colour', 'Colour'),
194
+ col('type', 'Type'),
195
+ col('title', 'Title'),
196
+ col('body', 'Note'),
197
+ col('author', 'Author'),
198
+ col('created', 'Created'),
199
+ col('modified', 'Modified'),
200
+ col('page', 'Page'),
201
+ col('scene', 'Scene'),
202
+ col('status', 'Status'),
203
+ col('replies', 'Replies', 'right'),
204
+ ],
205
+ },
206
+ synopsis: {
207
+ featureId: 'F-RPT-009',
208
+ title: 'Synopsis report',
209
+ options: [bool('includeEmpty', true)],
210
+ columns: [
211
+ col('number', 'Scene'),
212
+ col('heading', 'Heading'),
213
+ col('synopsis', 'Synopsis'),
214
+ col('colour', 'Colour'),
215
+ ],
216
+ },
217
+ revisions: {
218
+ featureId: 'F-RPT-010',
219
+ title: 'Revisions report',
220
+ options: [list('sets', 'Revision set ids; empty means every set')],
221
+ columns: [
222
+ col('set', 'Revision'),
223
+ col('colour', 'Colour'),
224
+ col('mark', 'Mark'),
225
+ col('date', 'Date'),
226
+ col('pages', 'Pages'),
227
+ col('scenes', 'Scenes'),
228
+ col('revisedElements', 'Revised elements', 'right'),
229
+ ],
230
+ },
231
+ tags: {
232
+ featureId: 'F-RPT-011',
233
+ title: 'Tags report',
234
+ options: [
235
+ en('mode', ['byScene', 'byCategory'], 'byScene'),
236
+ list('categories', 'Tag category ids; empty means all'),
237
+ ],
238
+ columns: [
239
+ col('scene', 'Scene'),
240
+ col('heading', 'Heading'),
241
+ col('page', 'Page'),
242
+ col('length', 'Length'),
243
+ col('scriptDay', 'Script day'),
244
+ col('category', 'Category'),
245
+ col('entities', 'Entities'),
246
+ ],
247
+ },
248
+ statistics: {
249
+ featureId: 'F-RPT-012',
250
+ title: 'Statistics report',
251
+ options: [
252
+ { name: 'topWords', type: 'number', default: 20, min: 0, max: 200 },
253
+ { name: 'longestScenes', type: 'number', default: 5, min: 0, max: 50 },
254
+ ],
255
+ columns: [col('metric', 'Metric'), col('value', 'Value', 'right')],
256
+ },
257
+ inclusivity: {
258
+ featureId: 'F-RPT-013',
259
+ title: 'Inclusivity and gender analysis',
260
+ options: [en('by', ['gender', 'role'], 'gender')],
261
+ columns: [
262
+ col('group', 'Group'),
263
+ col('characters', 'Characters', 'right'),
264
+ col('dialogues', 'Dialogues', 'right'),
265
+ col('words', 'Words', 'right'),
266
+ col('scenesSpeaking', 'Scenes speaking', 'right'),
267
+ col('wordsPct', 'Words %', 'right'),
268
+ ],
269
+ },
270
+ presentProgressive: {
271
+ featureId: 'F-RPT-014',
272
+ title: 'Present progressive check',
273
+ options: [],
274
+ columns: [
275
+ col('page', 'Page'),
276
+ col('scene', 'Scene'),
277
+ col('match', 'Match'),
278
+ col('text', 'Action line'),
279
+ ],
280
+ },
281
+ dayOutOfDays: {
282
+ featureId: 'F-RPT-015',
283
+ title: 'Day out of days',
284
+ options: [],
285
+ columns: [
286
+ col('character', 'Character'),
287
+ col('workDays', 'Work days', 'right'),
288
+ col('holds', 'Holds', 'right'),
289
+ ],
290
+ },
291
+ shotList: {
292
+ featureId: 'F-RPT-016',
293
+ title: 'Shot list',
294
+ options: [],
295
+ columns: [
296
+ col('scene', 'Scene'),
297
+ col('shot', 'Shot'),
298
+ col('label', 'Label'),
299
+ col('description', 'Description'),
300
+ col('size', 'Size'),
301
+ col('angle', 'Angle'),
302
+ col('movement', 'Movement'),
303
+ col('lens', 'Lens'),
304
+ col('durationSec', 'Duration (s)', 'right'),
305
+ ],
306
+ },
307
+ structure: {
308
+ featureId: 'F-RPT-017',
309
+ title: 'Structure report',
310
+ options: [],
311
+ columns: [
312
+ col('node', 'Act / sequence / beat'),
313
+ col('goal', 'Goal pages'),
314
+ col('actual', 'Actual pages'),
315
+ col('delta', 'Delta', 'right'),
316
+ ],
317
+ unavailable: 'Outline page goals are not modelled in writing_core yet (spec 01 §5.13 goals), so there is nothing to compare actual pages with',
318
+ },
319
+ characterArcs: {
320
+ featureId: 'F-RPT-018',
321
+ title: 'Character arcs',
322
+ options: [
323
+ list('characters', 'Entity ids or names; empty means every character'),
324
+ ],
325
+ columns: [
326
+ col('character', 'Character'),
327
+ col('scene', 'Scene'),
328
+ col('heading', 'Heading'),
329
+ col('note', 'Arc note'),
330
+ ],
331
+ },
332
+ };
333
+ export function reportKindInfos() {
334
+ return REPORT_KINDS.map((kind) => {
335
+ const d = REPORT_DEFS[kind];
336
+ return {
337
+ kind,
338
+ featureId: d.featureId,
339
+ title: d.title,
340
+ available: !d.unavailable,
341
+ ...(d.unavailable ? { unavailableReason: d.unavailable } : {}),
342
+ options: d.options,
343
+ columns: d.columns,
344
+ formats: REPORT_FORMATS,
345
+ };
346
+ });
347
+ }
348
+ export const isReportKind = (v) => typeof v === 'string' && REPORT_KINDS.includes(v);
349
+ /** Validates and defaults `raw` against the kind's option declarations (unknown keys are refused). */
350
+ export function parseReportOptions(kind, raw) {
351
+ const defs = REPORT_DEFS[kind].options;
352
+ const issues = [];
353
+ if (raw === undefined || raw === null)
354
+ raw = {};
355
+ if (typeof raw !== 'object' || Array.isArray(raw))
356
+ return {
357
+ ok: false,
358
+ issues: [{ path: '', message: 'options must be an object' }],
359
+ };
360
+ const input = raw;
361
+ const known = new Set(defs.map((d) => d.name));
362
+ for (const k of Object.keys(input))
363
+ if (!known.has(k))
364
+ issues.push({ path: k, message: 'unknown option' });
365
+ const out = {};
366
+ for (const d of defs) {
367
+ const v = input[d.name];
368
+ if (v === undefined) {
369
+ if (d.required)
370
+ issues.push({ path: d.name, message: 'required' });
371
+ else if (d.default !== undefined)
372
+ out[d.name] = Array.isArray(d.default) ? [...d.default] : d.default;
373
+ continue;
374
+ }
375
+ switch (d.type) {
376
+ case 'boolean':
377
+ if (typeof v !== 'boolean')
378
+ issues.push({ path: d.name, message: 'must be a boolean' });
379
+ else
380
+ out[d.name] = v;
381
+ break;
382
+ case 'number':
383
+ if (typeof v !== 'number' ||
384
+ !Number.isFinite(v) ||
385
+ (d.min !== undefined && v < d.min) ||
386
+ (d.max !== undefined && v > d.max)) {
387
+ issues.push({
388
+ path: d.name,
389
+ message: `must be a number${d.min !== undefined ? ` from ${d.min}` : ''}${d.max !== undefined ? ` to ${d.max}` : ''}`,
390
+ });
391
+ }
392
+ else
393
+ out[d.name] = v;
394
+ break;
395
+ case 'string':
396
+ if (typeof v !== 'string' ||
397
+ (d.required && v.length === 0) ||
398
+ v.length > 500)
399
+ issues.push({
400
+ path: d.name,
401
+ message: 'must be a non-empty string of at most 500 characters',
402
+ });
403
+ else
404
+ out[d.name] = v;
405
+ break;
406
+ case 'enum':
407
+ if (typeof v !== 'string' || !d.values.includes(v))
408
+ issues.push({
409
+ path: d.name,
410
+ message: `must be one of ${d.values.join(', ')}`,
411
+ });
412
+ else
413
+ out[d.name] = v;
414
+ break;
415
+ case 'stringList':
416
+ if (!Array.isArray(v) ||
417
+ v.length > 500 ||
418
+ v.some((x) => typeof x !== 'string'))
419
+ issues.push({ path: d.name, message: 'must be an array of strings' });
420
+ else
421
+ out[d.name] = v;
422
+ break;
423
+ }
424
+ }
425
+ return issues.length > 0 ? { ok: false, issues } : { ok: true, options: out };
426
+ }
427
+ // ─── Requests ───────────────────────────────────────────────────────────────
428
+ /** `GET /documents/:did/reports/:kind?source=&options=<json>`. */
429
+ export const reportGetQuerySchema = z.object({
430
+ source: sourceParamSchema.optional(),
431
+ options: z.string().max(20_000).optional(),
432
+ });
433
+ /** `POST /documents/:did/reports`. `json` answers the result; the other formats a `report.render` job. */
434
+ export const reportCreateSchema = z.object({
435
+ kind: z.enum(REPORT_KINDS),
436
+ source: docSourceSchema.optional(),
437
+ options: z.record(z.string(), z.unknown()).default({}),
438
+ format: z.enum(REPORT_FORMATS).default('json'),
439
+ });
440
+ /** The `input` of a `report.render` job (`sources[0]` is the document). */
441
+ export const reportRenderInputSchema = z.object({
442
+ kind: z.enum(REPORT_KINDS),
443
+ source: z.string().default('live'),
444
+ options: z.record(z.string(), z.unknown()).default({}),
445
+ format: z.enum(REPORT_FORMATS).default('csv'),
446
+ });
447
+ // ─── Rendering (shared by the API's CSV/HTML/PDF and by any client that wants the same text) ─────────────
448
+ export const cellText = (v) => v === null || v === undefined ? '' : String(v);
449
+ const csvField = (s) => /[",\r\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s;
450
+ /** CSV of every table (a title line and a blank line between tables when there is more than one). RFC 4180 quoting, CRLF. */
451
+ export function reportToCsv(result) {
452
+ const blocks = result.tables.map((t) => {
453
+ const lines = [];
454
+ if (result.tables.length > 1 && t.title)
455
+ lines.push(csvField(t.title));
456
+ lines.push(t.columns.map((c) => csvField(c.label)).join(','));
457
+ for (const r of t.rows)
458
+ lines.push(t.columns.map((c) => csvField(cellText(r[c.id] ?? null))).join(','));
459
+ if (t.footer)
460
+ lines.push(t.columns
461
+ .map((c) => csvField(cellText(t.footer[c.id] ?? null)))
462
+ .join(','));
463
+ return lines.join('\r\n');
464
+ });
465
+ return blocks.join('\r\n\r\n') + '\r\n';
466
+ }