@parall/cli 1.34.0 → 1.36.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.
- package/dist/commands/external-triggers.d.ts +19 -0
- package/dist/commands/external-triggers.d.ts.map +1 -0
- package/dist/commands/external-triggers.js +488 -0
- package/dist/commands/tasks.d.ts.map +1 -1
- package/dist/commands/tasks.js +15 -0
- package/dist/commands/wiki.d.ts.map +1 -1
- package/dist/commands/wiki.js +128 -22
- package/dist/index.js +2 -0
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +6 -0
- package/dist/lib/wiki-files.d.ts +76 -0
- package/dist/lib/wiki-files.d.ts.map +1 -0
- package/dist/lib/wiki-files.js +169 -0
- package/dist/lib/wiki-frontmatter.d.ts +9 -0
- package/dist/lib/wiki-frontmatter.d.ts.map +1 -0
- package/dist/lib/wiki-frontmatter.js +64 -0
- package/dist/lib/wiki-tools.d.ts +3 -2
- package/dist/lib/wiki-tools.d.ts.map +1 -1
- package/dist/lib/wiki-tools.js +171 -2
- package/dist/lib/wiki.d.ts +45 -15
- package/dist/lib/wiki.d.ts.map +1 -1
- package/dist/lib/wiki.js +461 -159
- package/package.json +7 -3
package/dist/commands/wiki.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { promises as fs } from 'node:fs';
|
|
1
2
|
import { resolveCredentials, resolveRuntimeContext } from '../lib/client.js';
|
|
2
3
|
import { printError, printJson } from '../lib/output.js';
|
|
4
|
+
import { deleteWikiFile, downloadWikiFile, uploadWikiFile } from '../lib/wiki-files.js';
|
|
3
5
|
import { getAfcsDiff, getAfcsStatus, getWikiBlob, getWikiChangesetDetail, getWikiChangesetDiff, getWikiLog, getWikiOutline, getWikiSection, getWikiTree, listWikiChangesets, queryWiki, requestWikiAccess, resetWikiWorkspace, listWikis, proposeWikiChangeset, searchWiki, syncAllMounts, watchMounts, } from '../lib/wiki.js';
|
|
4
6
|
export function registerWikiCommands(program) {
|
|
5
7
|
const wiki = program.command('wiki').description('Parall Wiki — read, edit, and propose changes');
|
|
@@ -33,10 +35,10 @@ export function registerWikiCommands(program) {
|
|
|
33
35
|
.command('sync')
|
|
34
36
|
.description('Sync wiki files to local workspace. First run downloads all files; subsequent runs pull updates.')
|
|
35
37
|
.argument('[wiki]', 'Wiki ID or slug (omit to sync all wikis)')
|
|
36
|
-
.action(async (
|
|
38
|
+
.action(async (wikiRef) => {
|
|
37
39
|
try {
|
|
38
40
|
const ctx = resolveCredentials();
|
|
39
|
-
const result = await syncAllMounts(ctx);
|
|
41
|
+
const result = await syncAllMounts(ctx, wikiRef);
|
|
40
42
|
for (const entry of result.synced) {
|
|
41
43
|
process.stderr.write(`wiki ${entry.slug} synced → ${entry.path}\n`);
|
|
42
44
|
}
|
|
@@ -55,22 +57,21 @@ export function registerWikiCommands(program) {
|
|
|
55
57
|
});
|
|
56
58
|
wiki
|
|
57
59
|
.command('status')
|
|
58
|
-
.description('Show local changes
|
|
60
|
+
.description('Show local changes and pending changesets.')
|
|
59
61
|
.argument('[wiki]', 'Wiki ID or slug (auto-resolves if org has one wiki)')
|
|
60
62
|
.action(async (wikiRef) => {
|
|
61
63
|
try {
|
|
62
64
|
const ctx = resolveCredentials();
|
|
63
65
|
const result = await getAfcsStatus(ctx, wikiRef);
|
|
64
66
|
process.stderr.write(`Wiki: ${result.wiki_name} (${result.wiki_slug})\n`);
|
|
65
|
-
process.stderr.write(`Mount: ${result.mount_path}\n`);
|
|
66
|
-
process.stderr.write(`Mode: ${result.mode}\n\n`);
|
|
67
|
+
process.stderr.write(`Mount: ${result.mount_path}\n\n`);
|
|
67
68
|
if (result.local_changes.length === 0) {
|
|
68
69
|
process.stderr.write('Local changes: none\n');
|
|
69
70
|
}
|
|
70
71
|
else {
|
|
71
72
|
process.stderr.write('Local changes:\n');
|
|
72
73
|
for (const file of result.local_changes) {
|
|
73
|
-
const s = file.
|
|
74
|
+
const s = file.action === 'create' ? 'A' : file.action === 'delete' ? 'D' : 'M';
|
|
74
75
|
process.stderr.write(` ${s} ${file.path} (+${file.additions} -${file.deletions})\n`);
|
|
75
76
|
}
|
|
76
77
|
}
|
|
@@ -87,11 +88,6 @@ export function registerWikiCommands(program) {
|
|
|
87
88
|
}
|
|
88
89
|
}
|
|
89
90
|
}
|
|
90
|
-
if (result.permissions) {
|
|
91
|
-
process.stderr.write('\nPermissions:\n');
|
|
92
|
-
process.stderr.write(` Read: ${result.permissions.readable_prefixes.join(', ')}\n`);
|
|
93
|
-
process.stderr.write(` Write: ${result.permissions.writable_prefixes.join(', ') || 'none'}\n`);
|
|
94
|
-
}
|
|
95
91
|
printJson(result);
|
|
96
92
|
}
|
|
97
93
|
catch (error) {
|
|
@@ -121,7 +117,7 @@ export function registerWikiCommands(program) {
|
|
|
121
117
|
});
|
|
122
118
|
wiki
|
|
123
119
|
.command('reset')
|
|
124
|
-
.description('Discard all local changes and restore files to the last synced state
|
|
120
|
+
.description('Discard all local changes and restore files to the last synced state.')
|
|
125
121
|
.argument('[wiki]', 'Wiki ID or slug (auto-resolves if org has one wiki)')
|
|
126
122
|
.action(async (wikiRef) => {
|
|
127
123
|
try {
|
|
@@ -134,6 +130,23 @@ export function registerWikiCommands(program) {
|
|
|
134
130
|
printError(error);
|
|
135
131
|
}
|
|
136
132
|
});
|
|
133
|
+
wiki
|
|
134
|
+
.command('access')
|
|
135
|
+
.description('Show your access level (read / maintain / admin) for a wiki path')
|
|
136
|
+
.argument('<path>', 'Path to check (e.g. docs/ops/)')
|
|
137
|
+
.argument('[wiki]', 'Wiki ID or slug (auto-resolves if org has one wiki)')
|
|
138
|
+
.action(async (targetPath, wikiRef) => {
|
|
139
|
+
try {
|
|
140
|
+
const ctx = resolveCredentials();
|
|
141
|
+
const { resolveWikiRefOrDefault } = await import('../lib/wiki.js');
|
|
142
|
+
const wiki = await resolveWikiRefOrDefault(ctx, wikiRef);
|
|
143
|
+
const result = await ctx.client.getWikiAccessStatus(ctx.orgId, wiki.id, targetPath);
|
|
144
|
+
printJson(result);
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
printError(error);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
137
150
|
wiki
|
|
138
151
|
.command('request-access')
|
|
139
152
|
.description('Request read/write access to a locked wiki path. Creates an approval card for a maintainer.')
|
|
@@ -153,9 +166,9 @@ export function registerWikiCommands(program) {
|
|
|
153
166
|
});
|
|
154
167
|
wiki
|
|
155
168
|
.command('log')
|
|
156
|
-
.description(
|
|
169
|
+
.description("Show history. With a path: that file's commit history. Without: recent wiki operations.")
|
|
157
170
|
.argument('[wiki]', 'Wiki ID or slug (auto-resolves if org has one wiki)')
|
|
158
|
-
.argument('[path]', 'File path for per-file history')
|
|
171
|
+
.argument('[path]', 'File path for per-file commit history')
|
|
159
172
|
.action(async (wikiRef, filePath) => {
|
|
160
173
|
try {
|
|
161
174
|
const ctx = resolveCredentials();
|
|
@@ -166,7 +179,8 @@ export function registerWikiCommands(program) {
|
|
|
166
179
|
else {
|
|
167
180
|
for (const entry of result.entries) {
|
|
168
181
|
const pathSuffix = entry.path ? ` ${entry.path}` : '';
|
|
169
|
-
|
|
182
|
+
const who = entry.author_name ?? entry.actor_id ?? '';
|
|
183
|
+
process.stderr.write(`${entry.id.slice(0, 8)} ${entry.created_at} ${entry.action}${who ? ` (${who})` : ''}${pathSuffix}\n`);
|
|
170
184
|
}
|
|
171
185
|
}
|
|
172
186
|
printJson(result);
|
|
@@ -342,10 +356,98 @@ export function registerWikiCommands(program) {
|
|
|
342
356
|
printError(error);
|
|
343
357
|
}
|
|
344
358
|
});
|
|
359
|
+
// ---- Binary files (upload / get / delete) ----
|
|
360
|
+
const file = wiki
|
|
361
|
+
.command('file')
|
|
362
|
+
.description('Upload, download, and delete binary wiki files (images, PDFs, archives)');
|
|
363
|
+
file
|
|
364
|
+
.command('upload')
|
|
365
|
+
.description('Upload a binary file. Direct-commits to the default branch (maintain); with --changeset, writes into a proposal branch (read + author). Text files go through changesets, not uploads.')
|
|
366
|
+
.argument('<localPath>', 'Local file to upload')
|
|
367
|
+
.argument('<repoPath>', 'Destination path inside the wiki (e.g. assets/diagram.png)')
|
|
368
|
+
.argument('[wiki]', 'Wiki ID or slug (auto-resolves if org has one wiki)')
|
|
369
|
+
.option('--changeset <changesetId>', "Upload into this changeset's feature branch instead of the default branch")
|
|
370
|
+
.option('--message <message>', 'Commit message (default: "Upload <path>")')
|
|
371
|
+
.action(async (localPath, repoPath, wikiRef, options) => {
|
|
372
|
+
try {
|
|
373
|
+
const ctx = resolveCredentials();
|
|
374
|
+
const content = await fs.readFile(localPath);
|
|
375
|
+
const result = await uploadWikiFile(ctx, wikiRef, {
|
|
376
|
+
content,
|
|
377
|
+
toPath: repoPath,
|
|
378
|
+
changesetId: options.changeset,
|
|
379
|
+
message: options.message,
|
|
380
|
+
});
|
|
381
|
+
const where = result.target === 'changeset' ? `changeset ${result.changeset_id}` : 'default branch';
|
|
382
|
+
process.stderr.write(`Uploaded ${result.path} (${result.size} bytes, ${result.stored_as}) → ${where}\n`);
|
|
383
|
+
printJson(result);
|
|
384
|
+
}
|
|
385
|
+
catch (error) {
|
|
386
|
+
printError(error);
|
|
387
|
+
}
|
|
388
|
+
});
|
|
389
|
+
file
|
|
390
|
+
.command('get')
|
|
391
|
+
.description('Download a wiki file as raw bytes (LFS pointers are smudged to real bytes). Writes to stdout, or to --output. Use --ref to read a specific commit / changeset branch.')
|
|
392
|
+
.argument('<repoPath>', 'File path inside the wiki')
|
|
393
|
+
.argument('[wiki]', 'Wiki ID or slug (auto-resolves if org has one wiki)')
|
|
394
|
+
.option('--ref <ref>', 'Branch, commit SHA, or changeset branch ref (default: wiki default branch)')
|
|
395
|
+
.option('-o, --output <localPath>', 'Write bytes to this local file instead of stdout')
|
|
396
|
+
.action(async (repoPath, wikiRef, options) => {
|
|
397
|
+
try {
|
|
398
|
+
const ctx = resolveCredentials();
|
|
399
|
+
const result = await downloadWikiFile(ctx, wikiRef, {
|
|
400
|
+
path: repoPath,
|
|
401
|
+
ref: options.ref,
|
|
402
|
+
});
|
|
403
|
+
if (options.output) {
|
|
404
|
+
await fs.writeFile(options.output, result.content);
|
|
405
|
+
process.stderr.write(`Wrote ${result.size} bytes → ${options.output}\n`);
|
|
406
|
+
// Buffer is omitted from JSON output; report metadata + where it went.
|
|
407
|
+
printJson({
|
|
408
|
+
wiki_id: result.wiki_id,
|
|
409
|
+
wiki_slug: result.wiki_slug,
|
|
410
|
+
path: result.path,
|
|
411
|
+
ref: result.ref,
|
|
412
|
+
size: result.size,
|
|
413
|
+
output: options.output,
|
|
414
|
+
});
|
|
415
|
+
}
|
|
416
|
+
else {
|
|
417
|
+
// Raw bytes to stdout (binary-safe); metadata to stderr. No JSON on
|
|
418
|
+
// stdout here — it would corrupt the byte stream.
|
|
419
|
+
process.stderr.write(`${result.path} (${result.size} bytes${result.ref ? `, ref: ${result.ref}` : ''})\n`);
|
|
420
|
+
process.stdout.write(result.content);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
catch (error) {
|
|
424
|
+
printError(error);
|
|
425
|
+
}
|
|
426
|
+
});
|
|
427
|
+
file
|
|
428
|
+
.command('delete')
|
|
429
|
+
.description('Delete a binary file from the wiki default branch (maintain). Text deletes must go through a changeset. The blob stays reachable in git history.')
|
|
430
|
+
.argument('<repoPath>', 'File path inside the wiki')
|
|
431
|
+
.argument('[wiki]', 'Wiki ID or slug (auto-resolves if org has one wiki)')
|
|
432
|
+
.option('--message <message>', 'Commit message (default: "Delete <path>")')
|
|
433
|
+
.action(async (repoPath, wikiRef, options) => {
|
|
434
|
+
try {
|
|
435
|
+
const ctx = resolveCredentials();
|
|
436
|
+
const result = await deleteWikiFile(ctx, wikiRef, {
|
|
437
|
+
path: repoPath,
|
|
438
|
+
message: options.message,
|
|
439
|
+
});
|
|
440
|
+
process.stderr.write(`Deleted ${result.path}\n`);
|
|
441
|
+
printJson(result);
|
|
442
|
+
}
|
|
443
|
+
catch (error) {
|
|
444
|
+
printError(error);
|
|
445
|
+
}
|
|
446
|
+
});
|
|
345
447
|
// ---- Content browsing (search, query, outline, section) ----
|
|
346
448
|
wiki
|
|
347
449
|
.command('search')
|
|
348
|
-
.description('
|
|
450
|
+
.description('Keyword search over wiki sections. Searches your local workspace copy when synced; otherwise fetches from the server.')
|
|
349
451
|
.argument('<query>', 'Search query')
|
|
350
452
|
.argument('[wiki]', 'Wiki ID or slug')
|
|
351
453
|
.option('--path <path>', 'Restrict to a path prefix')
|
|
@@ -377,8 +479,8 @@ export function registerWikiCommands(program) {
|
|
|
377
479
|
});
|
|
378
480
|
wiki
|
|
379
481
|
.command('query')
|
|
380
|
-
.description('
|
|
381
|
-
.argument('<query>', '
|
|
482
|
+
.description('Heading-aware keyword search that also ranks whole documents. Same lexical scoring as `search` (not semantic), better for multi-word questions.')
|
|
483
|
+
.argument('<query>', 'Query keywords (multi-word supported)')
|
|
382
484
|
.argument('[wiki]', 'Wiki ID or slug')
|
|
383
485
|
.option('--path <path>', 'Restrict to a path prefix')
|
|
384
486
|
.option('--limit <n>', 'Max results (default 5)')
|
|
@@ -453,14 +555,18 @@ export function registerWikiCommands(program) {
|
|
|
453
555
|
});
|
|
454
556
|
wiki
|
|
455
557
|
.command('cat')
|
|
456
|
-
.description('
|
|
558
|
+
.description('Print a wiki file. Reads your local workspace copy when synced (includes your unproposed edits); falls back to the server.')
|
|
457
559
|
.argument('<path>', 'File path (e.g. docs/auth.md)')
|
|
458
560
|
.argument('[wiki]', 'Wiki ID or slug')
|
|
459
|
-
.
|
|
561
|
+
.option('--remote', 'Read the server version even when a local workspace copy exists')
|
|
562
|
+
.action(async (filePath, wikiRef, options) => {
|
|
460
563
|
try {
|
|
461
564
|
const ctx = resolveCredentials();
|
|
462
|
-
const result = await getWikiBlob(ctx, wikiRef, {
|
|
463
|
-
|
|
565
|
+
const result = await getWikiBlob(ctx, wikiRef, {
|
|
566
|
+
path: filePath,
|
|
567
|
+
remote: options.remote,
|
|
568
|
+
});
|
|
569
|
+
process.stderr.write(`${filePath} (${result.size} bytes, source: ${result.source})\n\n`);
|
|
464
570
|
process.stdout.write(result.content);
|
|
465
571
|
if (result.content && !result.content.endsWith('\n'))
|
|
466
572
|
process.stdout.write('\n');
|
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { registerTaskCommands } from './commands/tasks.js';
|
|
|
10
10
|
import { registerCommentCommands } from './commands/comments.js';
|
|
11
11
|
import { registerProjectCommands } from './commands/projects.js';
|
|
12
12
|
import { registerScheduleCommands } from './commands/schedules.js';
|
|
13
|
+
import { registerExternalTriggerCommands } from './commands/external-triggers.js';
|
|
13
14
|
import { registerUserCommands } from './commands/users.js';
|
|
14
15
|
import { registerWikiCommands } from './commands/wiki.js';
|
|
15
16
|
import { registerMcpCommands } from './commands/mcp.js';
|
|
@@ -34,6 +35,7 @@ registerTaskCommands(program);
|
|
|
34
35
|
registerCommentCommands(program);
|
|
35
36
|
registerProjectCommands(program);
|
|
36
37
|
registerScheduleCommands(program);
|
|
38
|
+
registerExternalTriggerCommands(program);
|
|
37
39
|
registerUserCommands(program);
|
|
38
40
|
registerWikiCommands(program);
|
|
39
41
|
registerMcpCommands(program);
|
package/dist/lib/client.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,6DAA6D;AAC7D,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oGAAoG;IACpG,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,cAAc,CAiCtD;AAED,wBAAgB,kBAAkB,IAAI,mBAAmB,
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/lib/client.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,MAAM,MAAM,mBAAmB,GAAG;IAChC,MAAM,EAAE,YAAY,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,6DAA6D;AAC7D,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,oGAAoG;IACpG,OAAO,EAAE,OAAO,CAAC;CAClB,CAAC;AAEF;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,IAAI,cAAc,CAiCtD;AAED,wBAAgB,kBAAkB,IAAI,mBAAmB,CA8BxD"}
|
package/dist/lib/client.js
CHANGED
|
@@ -59,6 +59,12 @@ export function resolveCredentials() {
|
|
|
59
59
|
return {
|
|
60
60
|
client: new ParallClient({
|
|
61
61
|
baseUrl: url,
|
|
62
|
+
// Wiki endpoints (/wiki/v1) are served by wiki-service. Staging/prod front
|
|
63
|
+
// both behind one gateway (PRLL_WIKI_URL unset → falls back to the api URL,
|
|
64
|
+
// unchanged); local dev runs them on separate ports, so PRLL_WIKI_URL routes
|
|
65
|
+
// wiki calls (changeset, upload, file get, …) to :8090 while chat/task/
|
|
66
|
+
// comment/ref calls stay on PRLL_API_URL.
|
|
67
|
+
wikiBaseUrl: process.env.PRLL_WIKI_URL?.trim() || url,
|
|
62
68
|
token: apiKey,
|
|
63
69
|
swimlaneName: process.env.PRLL_SWIMLANE_NAME,
|
|
64
70
|
}),
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { WikiStoredAs } from '@parall/sdk';
|
|
2
|
+
import { type ParallContext } from './wiki.js';
|
|
3
|
+
/** Hard upload ceiling — mirrors server `handler.MaxWikiUploadBytes` (100 MiB). */
|
|
4
|
+
export declare const MAX_WIKI_UPLOAD_BYTES: number;
|
|
5
|
+
export type WikiFileUploadResult = {
|
|
6
|
+
wiki_id: string;
|
|
7
|
+
wiki_slug: string;
|
|
8
|
+
wiki_name: string;
|
|
9
|
+
path: string;
|
|
10
|
+
size: number;
|
|
11
|
+
stored_as: WikiStoredAs;
|
|
12
|
+
commit_sha: string;
|
|
13
|
+
content_sha: string;
|
|
14
|
+
/** Where the write landed: the default branch (direct) or a changeset branch. */
|
|
15
|
+
target: 'default_branch' | 'changeset';
|
|
16
|
+
/** Present only when uploaded into a changeset's feature branch. */
|
|
17
|
+
changeset_id?: string;
|
|
18
|
+
};
|
|
19
|
+
export type WikiFileDownloadResult = {
|
|
20
|
+
wiki_id: string;
|
|
21
|
+
wiki_slug: string;
|
|
22
|
+
wiki_name: string;
|
|
23
|
+
path: string;
|
|
24
|
+
/** The ref that was read (omitted when defaulting to the wiki's default branch). */
|
|
25
|
+
ref?: string;
|
|
26
|
+
size: number;
|
|
27
|
+
/** Raw file bytes (LFS pointers are smudged server-side — these are the real bytes). */
|
|
28
|
+
content: Buffer;
|
|
29
|
+
};
|
|
30
|
+
export type WikiFileDeleteResult = {
|
|
31
|
+
wiki_id: string;
|
|
32
|
+
wiki_slug: string;
|
|
33
|
+
wiki_name: string;
|
|
34
|
+
path: string;
|
|
35
|
+
};
|
|
36
|
+
export type UploadWikiFileOptions = {
|
|
37
|
+
/** Destination path inside the wiki repo, e.g. "assets/diagram.png". */
|
|
38
|
+
toPath: string;
|
|
39
|
+
/** Raw file bytes to upload. */
|
|
40
|
+
content: Buffer;
|
|
41
|
+
/** When set, write into this changeset's feature branch (read + author) instead
|
|
42
|
+
* of direct-committing to the default branch (maintain). */
|
|
43
|
+
changesetId?: string;
|
|
44
|
+
/** Optional commit message (server defaults to "Upload {path}"). */
|
|
45
|
+
message?: string;
|
|
46
|
+
};
|
|
47
|
+
/**
|
|
48
|
+
* Upload a binary file to a wiki. With `changesetId`, writes into that
|
|
49
|
+
* changeset's feature branch (the reader "propose with an image" path);
|
|
50
|
+
* without it, direct-commits to the default branch (maintain). Text content is
|
|
51
|
+
* rejected locally with a pointer to the changeset flow so the agent doesn't
|
|
52
|
+
* round-trip into a 422.
|
|
53
|
+
*/
|
|
54
|
+
export declare function uploadWikiFile(ctx: ParallContext, wikiRef: string | undefined, options: UploadWikiFileOptions): Promise<WikiFileUploadResult>;
|
|
55
|
+
/**
|
|
56
|
+
* Download a wiki file's raw bytes via the bearer-authenticated
|
|
57
|
+
* `GET /files/{path}?ref=` endpoint. The server smudges LFS pointers, so the
|
|
58
|
+
* agent gets the real binary — never the few-line pointer that lands on disk
|
|
59
|
+
* from a plain `wiki sync` (the runtime image has no git-lfs). `ref` reads any
|
|
60
|
+
* branch / commit (including a changeset feature branch); it defaults to the
|
|
61
|
+
* wiki's default branch.
|
|
62
|
+
*/
|
|
63
|
+
export declare function downloadWikiFile(ctx: ParallContext, wikiRef: string | undefined, options: {
|
|
64
|
+
path: string;
|
|
65
|
+
ref?: string;
|
|
66
|
+
}): Promise<WikiFileDownloadResult>;
|
|
67
|
+
/**
|
|
68
|
+
* Delete a binary file from the wiki's default branch (maintain). Text deletes
|
|
69
|
+
* must go through a changeset; the server returns 422 USE_CHANGESET for those.
|
|
70
|
+
* The blob stays reachable via git history — this only removes it from HEAD.
|
|
71
|
+
*/
|
|
72
|
+
export declare function deleteWikiFile(ctx: ParallContext, wikiRef: string | undefined, options: {
|
|
73
|
+
path: string;
|
|
74
|
+
message?: string;
|
|
75
|
+
}): Promise<WikiFileDeleteResult>;
|
|
76
|
+
//# sourceMappingURL=wiki-files.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-files.d.ts","sourceRoot":"","sources":["../../src/lib/wiki-files.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAGL,KAAK,aAAa,EAKnB,MAAM,WAAW,CAAC;AAoBnB,mFAAmF;AACnF,eAAO,MAAM,qBAAqB,QAAoB,CAAC;AAEvD,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,YAAY,CAAC;IACxB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,iFAAiF;IACjF,MAAM,EAAE,gBAAgB,GAAG,WAAW,CAAC;IACvC,oEAAoE;IACpE,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,sBAAsB,GAAG;IACnC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,oFAAoF;IACpF,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF,MAAM,MAAM,oBAAoB,GAAG;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,qBAAqB,GAAG;IAClC,wEAAwE;IACxE,MAAM,EAAE,MAAM,CAAC;IACf,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAChB;gEAC4D;IAC5D,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oEAAoE;IACpE,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB,CAAC;AAEF;;;;;;GAMG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE,qBAAqB,GAC7B,OAAO,CAAC,oBAAoB,CAAC,CA4D/B;AAED;;;;;;;GAOG;AACH,wBAAsB,gBAAgB,CACpC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAE,GACtC,OAAO,CAAC,sBAAsB,CAAC,CAsCjC;AAED;;;;GAIG;AACH,wBAAsB,cAAc,CAClC,GAAG,EAAE,aAAa,EAClB,OAAO,EAAE,MAAM,GAAG,SAAS,EAC3B,OAAO,EAAE;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,GAC1C,OAAO,CAAC,oBAAoB,CAAC,CAa/B"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { isWikiTextContent, normalizeRequiredWikiPath, resolveApiToken, resolveWikiRef, resolveWikiServiceBaseUrl, TEXT_MAX_BYTES, } from './wiki.js';
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Wiki binary file CRUD (Phase 4 of wiki-file-storage-design).
|
|
4
|
+
//
|
|
5
|
+
// Text files keep flowing through the changeset workspace (sync → edit →
|
|
6
|
+
// propose); this module is the agent-facing surface for *binary* assets —
|
|
7
|
+
// images, PDFs, archives — that can't be diffed and must not be base64-inlined
|
|
8
|
+
// into changeset JSON. It wraps the wiki-service endpoints:
|
|
9
|
+
// - POST /uploads (maintain, direct-commit default branch)
|
|
10
|
+
// - POST /changesets/{csId}/files (read + author, into a feature branch)
|
|
11
|
+
// - GET /files/{path}?ref= (read, raw bytes — bearer auth, never a
|
|
12
|
+
// browser <img> which can't send a token)
|
|
13
|
+
// - DELETE /files?path= (maintain)
|
|
14
|
+
//
|
|
15
|
+
// Routing/size/text classification is the server's authority; the local
|
|
16
|
+
// pre-checks here just turn the common mistakes into instant, actionable CLI
|
|
17
|
+
// errors instead of a wasted upload that bounces off a 422/413.
|
|
18
|
+
// ---------------------------------------------------------------------------
|
|
19
|
+
/** Hard upload ceiling — mirrors server `handler.MaxWikiUploadBytes` (100 MiB). */
|
|
20
|
+
export const MAX_WIKI_UPLOAD_BYTES = 100 * 1024 * 1024;
|
|
21
|
+
/**
|
|
22
|
+
* Upload a binary file to a wiki. With `changesetId`, writes into that
|
|
23
|
+
* changeset's feature branch (the reader "propose with an image" path);
|
|
24
|
+
* without it, direct-commits to the default branch (maintain). Text content is
|
|
25
|
+
* rejected locally with a pointer to the changeset flow so the agent doesn't
|
|
26
|
+
* round-trip into a 422.
|
|
27
|
+
*/
|
|
28
|
+
export async function uploadWikiFile(ctx, wikiRef, options) {
|
|
29
|
+
const wiki = await resolveWikiRef(ctx, wikiRef);
|
|
30
|
+
const toPath = normalizeRequiredWikiPath(options.toPath);
|
|
31
|
+
const content = options.content;
|
|
32
|
+
const changesetId = options.changesetId?.trim() || undefined;
|
|
33
|
+
if (content.length === 0) {
|
|
34
|
+
throw new Error(`cannot upload ${toPath}: file is empty`);
|
|
35
|
+
}
|
|
36
|
+
if (content.length > MAX_WIKI_UPLOAD_BYTES) {
|
|
37
|
+
throw new Error(`cannot upload ${toPath}: file is ${content.length} bytes, ` +
|
|
38
|
+
`over the ${MAX_WIKI_UPLOAD_BYTES}-byte (100 MiB) upload limit`);
|
|
39
|
+
}
|
|
40
|
+
// LFS pointer text is a server-generated artifact; uploading it directly
|
|
41
|
+
// would commit a dangling pointer. Reject before the text check (a pointer
|
|
42
|
+
// is valid UTF-8 and would otherwise be misrouted to "use a changeset").
|
|
43
|
+
if (looksLikeLfsPointer(content)) {
|
|
44
|
+
throw new Error(`cannot upload ${toPath}: this is a Git-LFS pointer, not real bytes. ` +
|
|
45
|
+
'Upload the underlying binary and the server generates the pointer.');
|
|
46
|
+
}
|
|
47
|
+
// Mirror the server's text gate (filetype: len <= TextMaxBytes && IsText):
|
|
48
|
+
// text goes through changesets so the diff stays reviewable.
|
|
49
|
+
if (content.length <= TEXT_MAX_BYTES && isWikiTextContent(content)) {
|
|
50
|
+
throw new Error(`cannot upload ${toPath}: it looks like a text file, and text changes go ` +
|
|
51
|
+
'through changesets (sync → edit → `parall wiki changeset create`), not uploads. ' +
|
|
52
|
+
'Uploads are for binary assets (images, PDFs, archives).');
|
|
53
|
+
}
|
|
54
|
+
// Node Buffer is a Uint8Array (a valid BlobPart); Blob copies the bytes.
|
|
55
|
+
const blob = new Blob([content]);
|
|
56
|
+
const resp = changesetId
|
|
57
|
+
? await ctx.client.uploadWikiFileToChangeset(ctx.orgId, wiki.id, changesetId, {
|
|
58
|
+
path: toPath,
|
|
59
|
+
file: blob,
|
|
60
|
+
message: options.message,
|
|
61
|
+
})
|
|
62
|
+
: await ctx.client.uploadWikiFile(ctx.orgId, wiki.id, {
|
|
63
|
+
path: toPath,
|
|
64
|
+
file: blob,
|
|
65
|
+
message: options.message,
|
|
66
|
+
});
|
|
67
|
+
return {
|
|
68
|
+
wiki_id: wiki.id,
|
|
69
|
+
wiki_slug: wiki.slug,
|
|
70
|
+
wiki_name: wiki.name,
|
|
71
|
+
path: resp.path,
|
|
72
|
+
size: resp.size,
|
|
73
|
+
stored_as: resp.stored_as,
|
|
74
|
+
commit_sha: resp.commit_sha,
|
|
75
|
+
content_sha: resp.content_sha,
|
|
76
|
+
target: changesetId ? 'changeset' : 'default_branch',
|
|
77
|
+
...(changesetId ? { changeset_id: changesetId } : {}),
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Download a wiki file's raw bytes via the bearer-authenticated
|
|
82
|
+
* `GET /files/{path}?ref=` endpoint. The server smudges LFS pointers, so the
|
|
83
|
+
* agent gets the real binary — never the few-line pointer that lands on disk
|
|
84
|
+
* from a plain `wiki sync` (the runtime image has no git-lfs). `ref` reads any
|
|
85
|
+
* branch / commit (including a changeset feature branch); it defaults to the
|
|
86
|
+
* wiki's default branch.
|
|
87
|
+
*/
|
|
88
|
+
export async function downloadWikiFile(ctx, wikiRef, options) {
|
|
89
|
+
const wiki = await resolveWikiRef(ctx, wikiRef);
|
|
90
|
+
const filePath = normalizeRequiredWikiPath(options.path);
|
|
91
|
+
const ref = options.ref?.trim() || undefined;
|
|
92
|
+
const token = resolveApiToken(ctx);
|
|
93
|
+
const base = wikiServiceBaseUrl(ctx);
|
|
94
|
+
// Encode each path segment so spaces / unicode in the path don't break the
|
|
95
|
+
// URL; the server's chi `/files/*` wildcard decodes them back.
|
|
96
|
+
const encodedPath = filePath.split('/').map(encodeURIComponent).join('/');
|
|
97
|
+
let url = `${base}/wiki/v1/orgs/${ctx.orgId}/wikis/${wiki.id}/files/${encodedPath}`;
|
|
98
|
+
if (ref) {
|
|
99
|
+
url += `?ref=${encodeURIComponent(ref)}`;
|
|
100
|
+
}
|
|
101
|
+
const headers = { Authorization: `Bearer ${token}` };
|
|
102
|
+
if (process.env.PRLL_SWIMLANE_NAME) {
|
|
103
|
+
headers['X-Prll-Swimlane'] = process.env.PRLL_SWIMLANE_NAME;
|
|
104
|
+
}
|
|
105
|
+
const response = await fetch(url, { headers });
|
|
106
|
+
if (!response.ok) {
|
|
107
|
+
const text = await response.text().catch(() => '');
|
|
108
|
+
throw new Error(`wiki-service GET ${url}: ${response.status} ${response.statusText}${text ? ` — ${text}` : ''}`);
|
|
109
|
+
}
|
|
110
|
+
const content = Buffer.from(await response.arrayBuffer());
|
|
111
|
+
return {
|
|
112
|
+
wiki_id: wiki.id,
|
|
113
|
+
wiki_slug: wiki.slug,
|
|
114
|
+
wiki_name: wiki.name,
|
|
115
|
+
path: filePath,
|
|
116
|
+
ref,
|
|
117
|
+
size: content.length,
|
|
118
|
+
content,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Delete a binary file from the wiki's default branch (maintain). Text deletes
|
|
123
|
+
* must go through a changeset; the server returns 422 USE_CHANGESET for those.
|
|
124
|
+
* The blob stays reachable via git history — this only removes it from HEAD.
|
|
125
|
+
*/
|
|
126
|
+
export async function deleteWikiFile(ctx, wikiRef, options) {
|
|
127
|
+
const wiki = await resolveWikiRef(ctx, wikiRef);
|
|
128
|
+
const filePath = normalizeRequiredWikiPath(options.path);
|
|
129
|
+
await ctx.client.deleteWikiFile(ctx.orgId, wiki.id, {
|
|
130
|
+
path: filePath,
|
|
131
|
+
message: options.message?.trim() || undefined,
|
|
132
|
+
});
|
|
133
|
+
return {
|
|
134
|
+
wiki_id: wiki.id,
|
|
135
|
+
wiki_slug: wiki.slug,
|
|
136
|
+
wiki_name: wiki.name,
|
|
137
|
+
path: filePath,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
// ---------------------------------------------------------------------------
|
|
141
|
+
// Internal helpers
|
|
142
|
+
// ---------------------------------------------------------------------------
|
|
143
|
+
/**
|
|
144
|
+
* Wiki-service base URL for the raw-byte download, reusing the shared resolver
|
|
145
|
+
* (SSOT in wiki.ts, same chain as sync/bulk-download) and turning a missing
|
|
146
|
+
* base into an actionable error instead of a malformed-URL fetch failure.
|
|
147
|
+
*/
|
|
148
|
+
function wikiServiceBaseUrl(ctx) {
|
|
149
|
+
const base = resolveWikiServiceBaseUrl(ctx);
|
|
150
|
+
if (!base) {
|
|
151
|
+
throw new Error('wiki-service base URL is required (set PRLL_WIKI_URL or PRLL_API_URL) to download wiki files');
|
|
152
|
+
}
|
|
153
|
+
return base;
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Pre-check mirror of the server's `gitea.IsLFSPointer` first-line gate. A
|
|
157
|
+
* full parse isn't needed — matching the version line is enough to give the
|
|
158
|
+
* LFS-specific message and avoid the changeset↔upload loop the design warns
|
|
159
|
+
* about. Anything pointer-shaped but malformed falls through to the server's
|
|
160
|
+
* authoritative classifier.
|
|
161
|
+
*/
|
|
162
|
+
function looksLikeLfsPointer(content) {
|
|
163
|
+
if (content.length === 0 || content.length > 1024) {
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
const nl = content.indexOf(0x0a);
|
|
167
|
+
const firstLine = content.subarray(0, nl >= 0 ? nl : content.length).toString('utf8');
|
|
168
|
+
return firstLine.startsWith('version https://git-lfs.github.com/spec/v1');
|
|
169
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface ParsedFrontMatter {
|
|
2
|
+
type?: string;
|
|
3
|
+
description?: string;
|
|
4
|
+
tags?: string[];
|
|
5
|
+
}
|
|
6
|
+
/** True when any OKF field was extracted. */
|
|
7
|
+
export declare function hasFrontMatter(fm: ParsedFrontMatter): boolean;
|
|
8
|
+
export declare function parseFrontMatter(lines: string[], frontMatterEnd: number): ParsedFrontMatter;
|
|
9
|
+
//# sourceMappingURL=wiki-frontmatter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wiki-frontmatter.d.ts","sourceRoot":"","sources":["../../src/lib/wiki-frontmatter.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,iBAAiB;IAChC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;CACjB;AAED,6CAA6C;AAC7C,wBAAgB,cAAc,CAAC,EAAE,EAAE,iBAAiB,GAAG,OAAO,CAE7D;AAQD,wBAAgB,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,cAAc,EAAE,MAAM,GAAG,iBAAiB,CAyB3F"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// Frontmatter (OKF-inspired) parsing for wiki markdown files.
|
|
2
|
+
//
|
|
3
|
+
// This mirrors the Go implementation in server/pkg/markdown/section.go — keep
|
|
4
|
+
// the two behaviorally consistent. It lives in its own module (rather than
|
|
5
|
+
// growing the already-oversized wiki.ts) per the repo's file-size discipline.
|
|
6
|
+
import { load as loadYaml } from 'js-yaml';
|
|
7
|
+
/** True when any OKF field was extracted. */
|
|
8
|
+
export function hasFrontMatter(fm) {
|
|
9
|
+
return fm.type !== undefined || fm.description !== undefined || fm.tags !== undefined;
|
|
10
|
+
}
|
|
11
|
+
// parseFrontMatter extracts the optional OKF fields (type, description, tags)
|
|
12
|
+
// from the leading YAML frontmatter block. Permissive by design: only
|
|
13
|
+
// `---`-delimited YAML is parsed (a `+++` TOML block is detected for section
|
|
14
|
+
// boundaries by detectFrontMatterEnd but its fields are not extracted); any
|
|
15
|
+
// parse error / missing field / wrong-typed value degrades to undefined so a
|
|
16
|
+
// malformed field never discards a valid one.
|
|
17
|
+
export function parseFrontMatter(lines, frontMatterEnd) {
|
|
18
|
+
// frontMatterEnd is one past the closing fence; opening fence is line 0 and
|
|
19
|
+
// the closing fence is line frontMatterEnd-1, so a block needs >= 2 lines.
|
|
20
|
+
if (frontMatterEnd < 2 || lines.length === 0) {
|
|
21
|
+
return {};
|
|
22
|
+
}
|
|
23
|
+
if (lines[0].trim() !== '---') {
|
|
24
|
+
return {}; // only YAML frontmatter carries OKF fields
|
|
25
|
+
}
|
|
26
|
+
const body = lines.slice(1, frontMatterEnd - 1).join('\n');
|
|
27
|
+
let raw;
|
|
28
|
+
try {
|
|
29
|
+
raw = loadYaml(body);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return {};
|
|
33
|
+
}
|
|
34
|
+
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
const obj = raw;
|
|
38
|
+
return {
|
|
39
|
+
type: frontMatterString(obj.type),
|
|
40
|
+
description: frontMatterString(obj.description),
|
|
41
|
+
tags: frontMatterTags(obj.tags),
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
function frontMatterString(v) {
|
|
45
|
+
if (typeof v !== 'string') {
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
const s = v.trim();
|
|
49
|
+
return s === '' ? undefined : s;
|
|
50
|
+
}
|
|
51
|
+
function frontMatterTags(v) {
|
|
52
|
+
if (typeof v === 'string') {
|
|
53
|
+
const s = v.trim();
|
|
54
|
+
return s === '' ? undefined : [s];
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(v)) {
|
|
57
|
+
const out = v
|
|
58
|
+
.filter((e) => typeof e === 'string')
|
|
59
|
+
.map((e) => e.trim())
|
|
60
|
+
.filter((e) => e !== '');
|
|
61
|
+
return out.length > 0 ? out : undefined;
|
|
62
|
+
}
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
package/dist/lib/wiki-tools.d.ts
CHANGED
|
@@ -26,8 +26,9 @@ export type WikiToolDefinition = {
|
|
|
26
26
|
/**
|
|
27
27
|
* Build the full wiki native tool registry bound to a Parall context (credentials,
|
|
28
28
|
* agent env, local mount). Returns one definition per wiki capability (list, tree,
|
|
29
|
-
* status, diff, blob, search, query, outline, section, changeset diff, propose
|
|
30
|
-
* each finalized with input validation. Shared by
|
|
29
|
+
* status, diff, blob, search, query, outline, section, changeset diff, propose,
|
|
30
|
+
* binary file upload/get/delete), each finalized with input validation. Shared by
|
|
31
|
+
* the MCP server and the eval harness.
|
|
31
32
|
*/
|
|
32
33
|
export declare function buildWikiTools(ctx: ParallContext): WikiToolDefinition[];
|
|
33
34
|
//# sourceMappingURL=wiki-tools.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"wiki-tools.d.ts","sourceRoot":"","sources":["../../src/lib/wiki-tools.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"wiki-tools.d.ts","sourceRoot":"","sources":["../../src/lib/wiki-tools.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,CAAC,MAAM,QAAQ,CAAC;AAG5B,OAAO,EASL,KAAK,aAAa,EAInB,MAAM,WAAW,CAAC;AAUnB;;;;;;;;;GASG;AAEH,MAAM,MAAM,uBAAuB,GAAG;IACpC,iBAAiB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC3C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,MAAM,MAAM,kBAAkB,GAAG;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,OAAO,CAAC;IAClB,WAAW,EAAE,CAAC,CAAC,WAAW,CAAC;IAC3B,YAAY,EAAE,CAAC,CAAC,WAAW,CAAC;IAC5B,OAAO,EAAE,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC,uBAAuB,CAAC,CAAC;CAC9E,CAAC;AAmCF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,aAAa,GAAG,kBAAkB,EAAE,CAwrBvE"}
|