@genspark/cli 1.0.24 → 1.0.26
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/aidrive-upload.d.ts +12 -0
- package/dist/commands/aidrive-upload.d.ts.map +1 -0
- package/dist/commands/aidrive-upload.js +15 -0
- package/dist/commands/aidrive-upload.js.map +1 -0
- package/dist/commands/skills-pull.d.ts +91 -0
- package/dist/commands/skills-pull.d.ts.map +1 -0
- package/dist/commands/skills-pull.js +275 -0
- package/dist/commands/skills-pull.js.map +1 -0
- package/dist/commands/skills-save.d.ts +39 -0
- package/dist/commands/skills-save.d.ts.map +1 -0
- package/dist/commands/skills-save.js +93 -0
- package/dist/commands/skills-save.js.map +1 -0
- package/dist/commands/skills-sync.d.ts +92 -0
- package/dist/commands/skills-sync.d.ts.map +1 -0
- package/dist/commands/skills-sync.js +329 -0
- package/dist/commands/skills-sync.js.map +1 -0
- package/dist/index.js +216 -4
- package/dist/index.js.map +1 -1
- package/docs/skills.md +209 -1
- package/package.json +2 -1
- package/skills/gsk-aidrive/SKILL.md +8 -5
- package/skills/gsk-audio-generation/SKILL.md +20 -12
- package/skills/gsk-crawler/SKILL.md +3 -1
- package/skills/gsk-create-task/SKILL.md +4 -2
- package/skills/gsk-get-model-info/SKILL.md +36 -0
- package/skills/gsk-github/SKILL.md +17 -10
- package/skills/gsk-task-status/SKILL.md +36 -0
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gsk skills sync` — client-side materializer for the backend `skills_sync`
|
|
3
|
+
* action.
|
|
4
|
+
*
|
|
5
|
+
* Why client-side: `skills_sync` returns a digest-diffed bundle (base64 file
|
|
6
|
+
* bytes for changed skills, a removal list, per-slug errors); this module
|
|
7
|
+
* applies one such response to disk. Deliberately NOT parity with
|
|
8
|
+
* `setup.sh` (which unconditionally `rm -rf`s then `ln -sfn`s over the mount
|
|
9
|
+
* path — the synced skill always wins): here a pre-existing REAL directory
|
|
10
|
+
* under `mountDir` (an inline-tar builtin, or user content) is never
|
|
11
|
+
* clobbered by a same-named synced skill; the real dir wins and the slug is
|
|
12
|
+
* reported failed instead. Chosen because this materializer runs on a
|
|
13
|
+
* user's laptop, not a disposable sandbox — never `rm -rf`ing directories we
|
|
14
|
+
* didn't create is worth a failed sync entry. Index.ts (Task 6) drives the
|
|
15
|
+
* poll-until-`complete` loop and owns `--check`; this module only ever
|
|
16
|
+
* applies (never diffs) a response.
|
|
17
|
+
*/
|
|
18
|
+
/** One file row from a `skills_sync` `update` entry. */
|
|
19
|
+
export interface SyncSkillFile {
|
|
20
|
+
path: string;
|
|
21
|
+
content_base64: string;
|
|
22
|
+
}
|
|
23
|
+
/** One per-slug entry in a `skills_sync` response. */
|
|
24
|
+
export interface SyncSkillEntry {
|
|
25
|
+
slug: string;
|
|
26
|
+
sha?: string;
|
|
27
|
+
action: 'update' | 'unchanged' | 'removed' | 'error';
|
|
28
|
+
files?: SyncSkillFile[];
|
|
29
|
+
total_bytes?: number;
|
|
30
|
+
code?: string;
|
|
31
|
+
}
|
|
32
|
+
/** Shape of the `data` field returned by the backend `skills_sync` action. */
|
|
33
|
+
export interface SyncResponseData {
|
|
34
|
+
synced?: boolean;
|
|
35
|
+
complete: boolean;
|
|
36
|
+
skills: SyncSkillEntry[];
|
|
37
|
+
}
|
|
38
|
+
export interface SyncOptions {
|
|
39
|
+
storeDir?: string;
|
|
40
|
+
mountDir?: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ApplySyncResult {
|
|
43
|
+
state: Record<string, string>;
|
|
44
|
+
applied: string[];
|
|
45
|
+
removed: string[];
|
|
46
|
+
failed: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Folds the per-batch results of the multi-batch sync loop into single deduped
|
|
50
|
+
* totals. A slug can recur across batches — a server `error` row carries no
|
|
51
|
+
* recordable sha, so the server re-emits it in EVERY batch — and concatenating
|
|
52
|
+
* per-batch arrays would multi-count it, making the summary counts and the
|
|
53
|
+
* exit-4 gate wrong. Each category is a Set (dedup by slug); a slug's outcome
|
|
54
|
+
* in a later batch supersedes an earlier one. It also aggregates the echoed
|
|
55
|
+
* skill rows so `-o json` reflects every batch, not just the final one.
|
|
56
|
+
*
|
|
57
|
+
* Generic over the echoed-row type so the CLI can pass its own row shape
|
|
58
|
+
* without a cast (only `slug` is required here).
|
|
59
|
+
*/
|
|
60
|
+
export declare class SyncAggregator<Row extends {
|
|
61
|
+
slug: string;
|
|
62
|
+
}> {
|
|
63
|
+
private readonly _applied;
|
|
64
|
+
private readonly _removed;
|
|
65
|
+
private readonly _failed;
|
|
66
|
+
private readonly _rows;
|
|
67
|
+
/** Fold one batch's apply result and its echoed skill rows into the totals. */
|
|
68
|
+
add(result: ApplySyncResult, rows: readonly Row[]): void;
|
|
69
|
+
private categorize;
|
|
70
|
+
get appliedCount(): number;
|
|
71
|
+
get removedCount(): number;
|
|
72
|
+
get failedCount(): number;
|
|
73
|
+
/** Aggregated echoed rows (deduped by slug, last-wins). */
|
|
74
|
+
get skills(): Row[];
|
|
75
|
+
}
|
|
76
|
+
export declare const STATE_FILENAME = "skills-sync-state.json";
|
|
77
|
+
export declare function defaultStoreDir(): string;
|
|
78
|
+
export declare function defaultMountDir(): string;
|
|
79
|
+
/** Load the persisted `{slug: sha}` digest map. Both unparseable JSON AND a
|
|
80
|
+
* valid-JSON-but-wrong-shape file self-heal to `{}` — a dirty state file
|
|
81
|
+
* forces a full re-diff on the next sync rather than risk skipping a change
|
|
82
|
+
* against a state we can't trust. The shape check matters because the server
|
|
83
|
+
* verb rejects the WHOLE sync (`bad state digest map`) on a single bad key,
|
|
84
|
+
* so without validating here a corrupt map would wedge every sync forever;
|
|
85
|
+
* resetting to `{}` lets it recover on the next run. */
|
|
86
|
+
export declare function loadState(storeDir: string): Record<string, string>;
|
|
87
|
+
/**
|
|
88
|
+
* Apply one `skills_sync` response to disk. Never called in `--check` mode
|
|
89
|
+
* (Task 6's caller diffs against `loadState` output itself for that path).
|
|
90
|
+
*/
|
|
91
|
+
export declare function applySyncResponse(data: SyncResponseData, opts?: SyncOptions): ApplySyncResult;
|
|
92
|
+
//# sourceMappingURL=skills-sync.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills-sync.d.ts","sourceRoot":"","sources":["../../src/commands/skills-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AASH,wDAAwD;AACxD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,cAAc,EAAE,MAAM,CAAA;CACvB;AAED,sDAAsD;AACtD,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAA;IACZ,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,QAAQ,GAAG,WAAW,GAAG,SAAS,GAAG,OAAO,CAAA;IACpD,KAAK,CAAC,EAAE,aAAa,EAAE,CAAA;IACvB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,IAAI,CAAC,EAAE,MAAM,CAAA;CACd;AAED,8EAA8E;AAC9E,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,QAAQ,EAAE,OAAO,CAAA;IACjB,MAAM,EAAE,cAAc,EAAE,CAAA;CACzB;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC7B,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,OAAO,EAAE,MAAM,EAAE,CAAA;IACjB,MAAM,EAAE,MAAM,EAAE,CAAA;CACjB;AAED;;;;;;;;;;;GAWG;AACH,qBAAa,cAAc,CAAC,GAAG,SAAS;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;IACtD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAoB;IAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAyB;IAE/C,+EAA+E;IAC/E,GAAG,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,SAAS,GAAG,EAAE,GAAG,IAAI;IAOxD,OAAO,CAAC,UAAU;IAOlB,IAAI,YAAY,IAAI,MAAM,CAEzB;IACD,IAAI,YAAY,IAAI,MAAM,CAEzB;IACD,IAAI,WAAW,IAAI,MAAM,CAExB;IAED,2DAA2D;IAC3D,IAAI,MAAM,IAAI,GAAG,EAAE,CAElB;CACF;AAED,eAAO,MAAM,cAAc,2BAA2B,CAAA;AAMtD,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED;;;;;;wDAMwD;AACxD,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAelE;AAkJD;;;GAGG;AACH,wBAAgB,iBAAiB,CAC/B,IAAI,EAAE,gBAAgB,EACtB,IAAI,GAAE,WAAgB,GACrB,eAAe,CAiGjB"}
|
|
@@ -0,0 +1,329 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `gsk skills sync` — client-side materializer for the backend `skills_sync`
|
|
3
|
+
* action.
|
|
4
|
+
*
|
|
5
|
+
* Why client-side: `skills_sync` returns a digest-diffed bundle (base64 file
|
|
6
|
+
* bytes for changed skills, a removal list, per-slug errors); this module
|
|
7
|
+
* applies one such response to disk. Deliberately NOT parity with
|
|
8
|
+
* `setup.sh` (which unconditionally `rm -rf`s then `ln -sfn`s over the mount
|
|
9
|
+
* path — the synced skill always wins): here a pre-existing REAL directory
|
|
10
|
+
* under `mountDir` (an inline-tar builtin, or user content) is never
|
|
11
|
+
* clobbered by a same-named synced skill; the real dir wins and the slug is
|
|
12
|
+
* reported failed instead. Chosen because this materializer runs on a
|
|
13
|
+
* user's laptop, not a disposable sandbox — never `rm -rf`ing directories we
|
|
14
|
+
* didn't create is worth a failed sync entry. Index.ts (Task 6) drives the
|
|
15
|
+
* poll-until-`complete` loop and owns `--check`; this module only ever
|
|
16
|
+
* applies (never diffs) a response.
|
|
17
|
+
*/
|
|
18
|
+
import * as fs from 'fs';
|
|
19
|
+
import * as os from 'os';
|
|
20
|
+
import * as path from 'path';
|
|
21
|
+
import { error as logError } from '../logger.js';
|
|
22
|
+
import { safeJoin, SLUG_RE } from './skills-pull.js';
|
|
23
|
+
/**
|
|
24
|
+
* Folds the per-batch results of the multi-batch sync loop into single deduped
|
|
25
|
+
* totals. A slug can recur across batches — a server `error` row carries no
|
|
26
|
+
* recordable sha, so the server re-emits it in EVERY batch — and concatenating
|
|
27
|
+
* per-batch arrays would multi-count it, making the summary counts and the
|
|
28
|
+
* exit-4 gate wrong. Each category is a Set (dedup by slug); a slug's outcome
|
|
29
|
+
* in a later batch supersedes an earlier one. It also aggregates the echoed
|
|
30
|
+
* skill rows so `-o json` reflects every batch, not just the final one.
|
|
31
|
+
*
|
|
32
|
+
* Generic over the echoed-row type so the CLI can pass its own row shape
|
|
33
|
+
* without a cast (only `slug` is required here).
|
|
34
|
+
*/
|
|
35
|
+
export class SyncAggregator {
|
|
36
|
+
_applied = new Set();
|
|
37
|
+
_removed = new Set();
|
|
38
|
+
_failed = new Set();
|
|
39
|
+
_rows = new Map();
|
|
40
|
+
/** Fold one batch's apply result and its echoed skill rows into the totals. */
|
|
41
|
+
add(result, rows) {
|
|
42
|
+
for (const slug of result.applied)
|
|
43
|
+
this.categorize(slug, this._applied);
|
|
44
|
+
for (const slug of result.removed)
|
|
45
|
+
this.categorize(slug, this._removed);
|
|
46
|
+
for (const slug of result.failed)
|
|
47
|
+
this.categorize(slug, this._failed);
|
|
48
|
+
for (const row of rows)
|
|
49
|
+
this._rows.set(row.slug, row);
|
|
50
|
+
}
|
|
51
|
+
categorize(slug, into) {
|
|
52
|
+
this._applied.delete(slug);
|
|
53
|
+
this._removed.delete(slug);
|
|
54
|
+
this._failed.delete(slug);
|
|
55
|
+
into.add(slug);
|
|
56
|
+
}
|
|
57
|
+
get appliedCount() {
|
|
58
|
+
return this._applied.size;
|
|
59
|
+
}
|
|
60
|
+
get removedCount() {
|
|
61
|
+
return this._removed.size;
|
|
62
|
+
}
|
|
63
|
+
get failedCount() {
|
|
64
|
+
return this._failed.size;
|
|
65
|
+
}
|
|
66
|
+
/** Aggregated echoed rows (deduped by slug, last-wins). */
|
|
67
|
+
get skills() {
|
|
68
|
+
return Array.from(this._rows.values());
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export const STATE_FILENAME = 'skills-sync-state.json';
|
|
72
|
+
function expandHome(p) {
|
|
73
|
+
return p.replace(/^~(?=\/|$)/, os.homedir());
|
|
74
|
+
}
|
|
75
|
+
export function defaultStoreDir() {
|
|
76
|
+
return expandHome('~/.gsk/skills-store');
|
|
77
|
+
}
|
|
78
|
+
export function defaultMountDir() {
|
|
79
|
+
return expandHome('~/.opencode/skills');
|
|
80
|
+
}
|
|
81
|
+
/** Load the persisted `{slug: sha}` digest map. Both unparseable JSON AND a
|
|
82
|
+
* valid-JSON-but-wrong-shape file self-heal to `{}` — a dirty state file
|
|
83
|
+
* forces a full re-diff on the next sync rather than risk skipping a change
|
|
84
|
+
* against a state we can't trust. The shape check matters because the server
|
|
85
|
+
* verb rejects the WHOLE sync (`bad state digest map`) on a single bad key,
|
|
86
|
+
* so without validating here a corrupt map would wedge every sync forever;
|
|
87
|
+
* resetting to `{}` lets it recover on the next run. */
|
|
88
|
+
export function loadState(storeDir) {
|
|
89
|
+
try {
|
|
90
|
+
const raw = fs.readFileSync(path.join(storeDir, STATE_FILENAME), 'utf8');
|
|
91
|
+
const parsed = JSON.parse(raw);
|
|
92
|
+
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
93
|
+
const obj = parsed;
|
|
94
|
+
const wellShaped = Object.entries(obj).every(([k, v]) => SLUG_RE.test(k) && typeof v === 'string');
|
|
95
|
+
if (wellShaped)
|
|
96
|
+
return obj;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// absent or unparseable → treated as empty state
|
|
101
|
+
}
|
|
102
|
+
return {};
|
|
103
|
+
}
|
|
104
|
+
/** True when `<slug>`'s files are downloaded on disk. The STORE dir is the
|
|
105
|
+
* single source of truth for "materialized" — NOT the mount symlink. The mount
|
|
106
|
+
* is a best-effort, separately-reconciled concern (see `reconcileMount`): on a
|
|
107
|
+
* filesystem that can't hold a symlink (Windows without Developer Mode,
|
|
108
|
+
* exFAT/FAT/SMB — `symlinkSync` throws EPERM/ENOSYS) the mount is never created,
|
|
109
|
+
* yet the store IS. Keying materialization on the mount would then read the
|
|
110
|
+
* slug as "not materialized" on every `unchanged` re-sync, drop its sha, and
|
|
111
|
+
* re-download the whole store forever. `writeSlugAtomic` renames the tree into
|
|
112
|
+
* place in one step, so a present store dir is a complete one. A hand-deleted
|
|
113
|
+
* store dir (store missing) still reads false → the caller drops the sha and
|
|
114
|
+
* the next sync re-fetches (the F3 self-heal). */
|
|
115
|
+
function isSlugMaterialized(storeDir, slug) {
|
|
116
|
+
return fs.existsSync(path.join(storeDir, slug));
|
|
117
|
+
}
|
|
118
|
+
/** Best-effort reconcile of the mount symlink for an already-stored slug, run
|
|
119
|
+
* every sync (never touches the store, so it can't trigger a re-download):
|
|
120
|
+
* - `'mounted'` — the symlink now points at (or already pointed at) the
|
|
121
|
+
* live store dir.
|
|
122
|
+
* - `'blocked'` — a REAL dir/file occupies the mount path; the builtin /
|
|
123
|
+
* user content wins and we never clobber it (the module's core deviation
|
|
124
|
+
* from setup.sh). A benign, terminal state — not retried into a symlink.
|
|
125
|
+
* - `'unsupported'` — `symlinkSync` threw (filesystem can't hold a symlink),
|
|
126
|
+
* or the mount path can't be stat'd (a non-ENOENT fault). The store is
|
|
127
|
+
* intact; the caller keeps the sha but reports the slug not-mounted.
|
|
128
|
+
* The whole body is guarded: the initial `lstatSync` can itself throw a
|
|
129
|
+
* non-ENOENT error (e.g. ENOTDIR when `mountDir` is a file), which must be
|
|
130
|
+
* caught here rather than escape the batch before `saveState`. */
|
|
131
|
+
function reconcileMount(storeDir, mountDir, slug) {
|
|
132
|
+
const linkPath = path.join(mountDir, slug);
|
|
133
|
+
const targetPath = path.join(storeDir, slug);
|
|
134
|
+
try {
|
|
135
|
+
const stat = fs.lstatSync(linkPath, { throwIfNoEntry: false });
|
|
136
|
+
if (stat && !stat.isSymbolicLink())
|
|
137
|
+
return 'blocked';
|
|
138
|
+
if (stat &&
|
|
139
|
+
path.resolve(mountDir, fs.readlinkSync(linkPath)) === targetPath) {
|
|
140
|
+
return 'mounted'; // already our symlink → no churn
|
|
141
|
+
}
|
|
142
|
+
fs.mkdirSync(mountDir, { recursive: true });
|
|
143
|
+
if (stat)
|
|
144
|
+
fs.unlinkSync(linkPath); // stale / foreign symlink → repoint
|
|
145
|
+
fs.symlinkSync(targetPath, linkPath, 'dir');
|
|
146
|
+
return 'mounted';
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
return 'unsupported';
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/** Human-facing reason a stored slug isn't mounted, per `reconcileMount`. */
|
|
153
|
+
function mountFailureMessage(slug, result) {
|
|
154
|
+
const why = result === 'blocked'
|
|
155
|
+
? 'a real directory occupies the mount path (builtin/user content wins)'
|
|
156
|
+
: 'the filesystem does not support symlinks (Windows without Developer Mode, exFAT/FAT/SMB)';
|
|
157
|
+
return `skills sync: ${slug} stored but not mounted — ${why}`;
|
|
158
|
+
}
|
|
159
|
+
/** Sweep orphan `.tmp-<slug>-<pid>` staging dirs left by a killed run. Each
|
|
160
|
+
* `writeSlugAtomic` only clears its OWN pid's tmp dir, so a process killed
|
|
161
|
+
* mid-write leaks one; this best-effort sweep runs once per apply. Guarded to
|
|
162
|
+
* the exact tmp-dir shape so it can never touch a real skill dir. */
|
|
163
|
+
function sweepStaleTmpDirs(storeDir) {
|
|
164
|
+
let entries;
|
|
165
|
+
try {
|
|
166
|
+
entries = fs.readdirSync(storeDir);
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
const tmpRe = /^\.tmp-[A-Za-z0-9][A-Za-z0-9_-]*-\d+$/;
|
|
172
|
+
for (const name of entries) {
|
|
173
|
+
if (!tmpRe.test(name))
|
|
174
|
+
continue;
|
|
175
|
+
try {
|
|
176
|
+
fs.rmSync(path.join(storeDir, name), { recursive: true, force: true });
|
|
177
|
+
}
|
|
178
|
+
catch {
|
|
179
|
+
// best-effort — a concurrent writer or locked dir just stays
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function saveState(storeDir, state) {
|
|
184
|
+
fs.mkdirSync(storeDir, { recursive: true });
|
|
185
|
+
fs.writeFileSync(path.join(storeDir, STATE_FILENAME), JSON.stringify(state, null, 2) + '\n');
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Write one `update` entry's files into `<storeDir>/<slug>` atomically: build
|
|
189
|
+
* the tree in a sibling `.tmp-` dir first, then remove any existing target
|
|
190
|
+
* and `renameSync` the tmp dir over it. A crash or crafted traversal path
|
|
191
|
+
* partway through the write leaves the tmp dir orphaned but the previous
|
|
192
|
+
* (or absent) target untouched — never a half-written skill on disk.
|
|
193
|
+
*/
|
|
194
|
+
function writeSlugAtomic(storeDir, slug, files) {
|
|
195
|
+
const tmpDir = path.join(storeDir, `.tmp-${slug}-${process.pid}`);
|
|
196
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
197
|
+
fs.mkdirSync(tmpDir, { recursive: true });
|
|
198
|
+
try {
|
|
199
|
+
for (const file of files) {
|
|
200
|
+
const target = safeJoin(tmpDir, file.path);
|
|
201
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
202
|
+
fs.writeFileSync(target, Buffer.from(file.content_base64, 'base64'));
|
|
203
|
+
}
|
|
204
|
+
const finalDir = path.join(storeDir, slug);
|
|
205
|
+
fs.rmSync(finalDir, { recursive: true, force: true });
|
|
206
|
+
fs.renameSync(tmpDir, finalDir);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
fs.rmSync(tmpDir, { recursive: true, force: true });
|
|
210
|
+
throw e;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
/** Remove a slug's store dir and mount symlink. Only unlinks the symlink
|
|
214
|
+
* when it actually points into `storeDir` — a repointed or foreign symlink
|
|
215
|
+
* (or a real directory) is left alone rather than risk deleting user data. */
|
|
216
|
+
function removeSlug(storeDir, mountDir, slug) {
|
|
217
|
+
const linkPath = path.join(mountDir, slug);
|
|
218
|
+
const stat = fs.lstatSync(linkPath, { throwIfNoEntry: false });
|
|
219
|
+
if (stat?.isSymbolicLink()) {
|
|
220
|
+
const resolved = path.resolve(mountDir, fs.readlinkSync(linkPath));
|
|
221
|
+
const storeSlugDir = path.join(storeDir, slug);
|
|
222
|
+
if (resolved === storeSlugDir) {
|
|
223
|
+
fs.unlinkSync(linkPath);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
fs.rmSync(path.join(storeDir, slug), { recursive: true, force: true });
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Apply one `skills_sync` response to disk. Never called in `--check` mode
|
|
230
|
+
* (Task 6's caller diffs against `loadState` output itself for that path).
|
|
231
|
+
*/
|
|
232
|
+
export function applySyncResponse(data, opts = {}) {
|
|
233
|
+
// Absolutize (mirrors resolvePullDest in skills-pull.ts): a relative
|
|
234
|
+
// storeDir/mountDir would otherwise reach safeJoin's containment check as
|
|
235
|
+
// a relative `dest` while its computed `target` is always absolute
|
|
236
|
+
// (path.resolve), so `target.startsWith(dest + sep)` could never match
|
|
237
|
+
// and every `update` entry would be rejected as an unsafe path.
|
|
238
|
+
const storeDir = path.resolve(expandHome(opts.storeDir ?? defaultStoreDir()));
|
|
239
|
+
const mountDir = path.resolve(expandHome(opts.mountDir ?? defaultMountDir()));
|
|
240
|
+
fs.mkdirSync(storeDir, { recursive: true });
|
|
241
|
+
sweepStaleTmpDirs(storeDir);
|
|
242
|
+
const state = loadState(storeDir);
|
|
243
|
+
const applied = [];
|
|
244
|
+
const removed = [];
|
|
245
|
+
const failed = [];
|
|
246
|
+
for (const entry of data.skills) {
|
|
247
|
+
if (!SLUG_RE.test(entry.slug)) {
|
|
248
|
+
failed.push(entry.slug);
|
|
249
|
+
continue;
|
|
250
|
+
}
|
|
251
|
+
if (entry.action === 'update') {
|
|
252
|
+
try {
|
|
253
|
+
writeSlugAtomic(storeDir, entry.slug, entry.files ?? []);
|
|
254
|
+
}
|
|
255
|
+
catch (e) {
|
|
256
|
+
logError(`skills sync: ${entry.slug} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
257
|
+
failed.push(entry.slug);
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
// Record the synced sha the moment the STORE write succeeds — the content
|
|
261
|
+
// is materialized and sha-addressed regardless of whether the mount step
|
|
262
|
+
// then succeeds. The mount is reconciled separately (best-effort); its
|
|
263
|
+
// failure must never drop the sha, or the server would re-send this slug
|
|
264
|
+
// every sync (on a symlink-unsupported filesystem that re-downloads
|
|
265
|
+
// everything each run, and a large re-sent skill sorting early could
|
|
266
|
+
// starve later slugs out of the byte budget until the batch guard bailed).
|
|
267
|
+
if (entry.sha)
|
|
268
|
+
state[entry.slug] = entry.sha;
|
|
269
|
+
const mount = reconcileMount(storeDir, mountDir, entry.slug);
|
|
270
|
+
if (mount === 'mounted') {
|
|
271
|
+
applied.push(entry.slug);
|
|
272
|
+
}
|
|
273
|
+
else {
|
|
274
|
+
// Store is materialized and the sha recorded (no re-download); only the
|
|
275
|
+
// mount could not be made. Surface it once for this content change.
|
|
276
|
+
logError(mountFailureMessage(entry.slug, mount));
|
|
277
|
+
failed.push(entry.slug);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else if (entry.action === 'unchanged') {
|
|
281
|
+
// The server said "unchanged" only because our digest matched — but the
|
|
282
|
+
// store may be gone from disk (dir hand-deleted, or a state file restored
|
|
283
|
+
// from backup without its store). Trust the STORE, not the state file:
|
|
284
|
+
// carry the sha forward only when the store dir is present; otherwise DROP
|
|
285
|
+
// it so the next sync sees a digest miss and re-fetches (self-heal). The
|
|
286
|
+
// mount symlink is NOT part of this decision — see isSlugMaterialized.
|
|
287
|
+
if (entry.sha && isSlugMaterialized(storeDir, entry.slug)) {
|
|
288
|
+
state[entry.slug] = entry.sha;
|
|
289
|
+
// Retry the mount every sync (a transient/absent mount can recover),
|
|
290
|
+
// but never let it drop the sha or re-download. A real dir winning the
|
|
291
|
+
// mount ('blocked') is benign and self-heals to exit 0; a
|
|
292
|
+
// symlink-unsupported filesystem ('unsupported') keeps the slug failed
|
|
293
|
+
// (persistent exit 4 — the store is downloaded but can't be mounted).
|
|
294
|
+
if (reconcileMount(storeDir, mountDir, entry.slug) === 'unsupported') {
|
|
295
|
+
logError(mountFailureMessage(entry.slug, 'unsupported'));
|
|
296
|
+
failed.push(entry.slug);
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
else {
|
|
300
|
+
delete state[entry.slug];
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
else if (entry.action === 'removed') {
|
|
304
|
+
try {
|
|
305
|
+
removeSlug(storeDir, mountDir, entry.slug);
|
|
306
|
+
delete state[entry.slug];
|
|
307
|
+
removed.push(entry.slug);
|
|
308
|
+
}
|
|
309
|
+
catch (e) {
|
|
310
|
+
// Keep the state entry so the next sync retries this removal —
|
|
311
|
+
// a failed delete here (e.g. a locked store dir) must not be
|
|
312
|
+
// reported as done, and the batch's already-applied updates
|
|
313
|
+
// still need saveState() below to land.
|
|
314
|
+
logError(`skills sync: ${entry.slug} failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
315
|
+
failed.push(entry.slug);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
else if (entry.action === 'error') {
|
|
319
|
+
// Surface the server's per-slug code (NOT_FOUND / TOO_LARGE) like the
|
|
320
|
+
// update/removed branches log their failures — otherwise a server-side
|
|
321
|
+
// sync error was silently swallowed into the failed count with no reason.
|
|
322
|
+
logError(`skills sync: ${entry.slug} failed${entry.code ? ` (${entry.code})` : ''}`);
|
|
323
|
+
failed.push(entry.slug);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
saveState(storeDir, state);
|
|
327
|
+
return { state, applied, removed, failed };
|
|
328
|
+
}
|
|
329
|
+
//# sourceMappingURL=skills-sync.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"skills-sync.js","sourceRoot":"","sources":["../../src/commands/skills-sync.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,EAAE,MAAM,IAAI,CAAA;AACxB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAA;AAE5B,OAAO,EAAE,KAAK,IAAI,QAAQ,EAAE,MAAM,cAAc,CAAA;AAChD,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAA;AAqCpD;;;;;;;;;;;GAWG;AACH,MAAM,OAAO,cAAc;IACR,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAA;IAC5B,QAAQ,GAAG,IAAI,GAAG,EAAU,CAAA;IAC5B,OAAO,GAAG,IAAI,GAAG,EAAU,CAAA;IAC3B,KAAK,GAAG,IAAI,GAAG,EAAe,CAAA;IAE/C,+EAA+E;IAC/E,GAAG,CAAC,MAAuB,EAAE,IAAoB;QAC/C,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,OAAO;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QACvE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,OAAO;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAA;QACvE,KAAK,MAAM,IAAI,IAAI,MAAM,CAAC,MAAM;YAAE,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,CAAC,OAAO,CAAC,CAAA;QACrE,KAAK,MAAM,GAAG,IAAI,IAAI;YAAE,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,CAAA;IACvD,CAAC;IAEO,UAAU,CAAC,IAAY,EAAE,IAAiB;QAChD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC1B,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QAC1B,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,CAAA;QACzB,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAA;IAChB,CAAC;IAED,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAA;IAC3B,CAAC;IACD,IAAI,YAAY;QACd,OAAO,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAA;IAC3B,CAAC;IACD,IAAI,WAAW;QACb,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAA;IAC1B,CAAC;IAED,2DAA2D;IAC3D,IAAI,MAAM;QACR,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,EAAE,CAAC,CAAA;IACxC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,cAAc,GAAG,wBAAwB,CAAA;AAEtD,SAAS,UAAU,CAAC,CAAS;IAC3B,OAAO,CAAC,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,OAAO,EAAE,CAAC,CAAA;AAC9C,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,CAAC,qBAAqB,CAAC,CAAA;AAC1C,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,UAAU,CAAC,oBAAoB,CAAC,CAAA;AACzC,CAAC;AAED;;;;;;wDAMwD;AACxD,MAAM,UAAU,SAAS,CAAC,QAAgB;IACxC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,EAAE,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,EAAE,MAAM,CAAC,CAAA;QACxE,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAA;QACvC,IAAI,MAAM,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YACnE,MAAM,GAAG,GAAG,MAAiC,CAAA;YAC7C,MAAM,UAAU,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,CAC1C,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,KAAK,QAAQ,CACrD,CAAA;YACD,IAAI,UAAU;gBAAE,OAAO,GAA6B,CAAA;QACtD,CAAC;IACH,CAAC;IAAC,MAAM,CAAC;QACP,iDAAiD;IACnD,CAAC;IACD,OAAO,EAAE,CAAA;AACX,CAAC;AAED;;;;;;;;;;kDAUkD;AAClD,SAAS,kBAAkB,CAAC,QAAgB,EAAE,IAAY;IACxD,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAA;AACjD,CAAC;AAKD;;;;;;;;;;;;kEAYkE;AAClE,SAAS,cAAc,CACrB,QAAgB,EAChB,QAAgB,EAChB,IAAY;IAEZ,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC1C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC5C,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAA;QAC9D,IAAI,IAAI,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE;YAAE,OAAO,SAAS,CAAA;QACpD,IACE,IAAI;YACJ,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,KAAK,UAAU,EAChE,CAAC;YACD,OAAO,SAAS,CAAA,CAAC,iCAAiC;QACpD,CAAC;QACD,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;QAC3C,IAAI,IAAI;YAAE,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA,CAAC,oCAAoC;QACtE,EAAE,CAAC,WAAW,CAAC,UAAU,EAAE,QAAQ,EAAE,KAAK,CAAC,CAAA;QAC3C,OAAO,SAAS,CAAA;IAClB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,aAAa,CAAA;IACtB,CAAC;AACH,CAAC;AAED,6EAA6E;AAC7E,SAAS,mBAAmB,CAC1B,IAAY,EACZ,MAAuC;IAEvC,MAAM,GAAG,GACP,MAAM,KAAK,SAAS;QAClB,CAAC,CAAC,sEAAsE;QACxE,CAAC,CAAC,0FAA0F,CAAA;IAChG,OAAO,gBAAgB,IAAI,6BAA6B,GAAG,EAAE,CAAA;AAC/D,CAAC;AAED;;;qEAGqE;AACrE,SAAS,iBAAiB,CAAC,QAAgB;IACzC,IAAI,OAAiB,CAAA;IACrB,IAAI,CAAC;QACH,OAAO,GAAG,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA;IACpC,CAAC;IAAC,MAAM,CAAC;QACP,OAAM;IACR,CAAC;IACD,MAAM,KAAK,GAAG,uCAAuC,CAAA;IACrD,KAAK,MAAM,IAAI,IAAI,OAAO,EAAE,CAAC;QAC3B,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QAC/B,IAAI,CAAC;YACH,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACxE,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;QAC/D,CAAC;IACH,CAAC;AACH,CAAC;AAED,SAAS,SAAS,CAAC,QAAgB,EAAE,KAA6B;IAChE,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,EAAE,CAAC,aAAa,CACd,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,cAAc,CAAC,EACnC,IAAI,CAAC,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CACtC,CAAA;AACH,CAAC;AAED;;;;;;GAMG;AACH,SAAS,eAAe,CACtB,QAAgB,EAChB,IAAY,EACZ,KAAsB;IAEtB,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,IAAI,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,CAAA;IACjE,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;IACnD,EAAE,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACzC,IAAI,CAAC;QACH,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;YACzB,MAAM,MAAM,GAAG,QAAQ,CAAC,MAAM,EAAE,IAAI,CAAC,IAAI,CAAC,CAAA;YAC1C,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;YACvD,EAAE,CAAC,aAAa,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,cAAc,EAAE,QAAQ,CAAC,CAAC,CAAA;QACtE,CAAC;QACD,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC1C,EAAE,CAAC,MAAM,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACrD,EAAE,CAAC,UAAU,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAA;IACjC,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;QACnD,MAAM,CAAC,CAAA;IACT,CAAC;AACH,CAAC;AAED;;8EAE8E;AAC9E,SAAS,UAAU,CAAC,QAAgB,EAAE,QAAgB,EAAE,IAAY;IAClE,MAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;IAC1C,MAAM,IAAI,GAAG,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,cAAc,EAAE,KAAK,EAAE,CAAC,CAAA;IAC9D,IAAI,IAAI,EAAE,cAAc,EAAE,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC,CAAC,CAAA;QAClE,MAAM,YAAY,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAA;QAC9C,IAAI,QAAQ,KAAK,YAAY,EAAE,CAAC;YAC9B,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IACD,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAA;AACxE,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAsB,EACtB,OAAoB,EAAE;IAEtB,qEAAqE;IACrE,0EAA0E;IAC1E,mEAAmE;IACnE,uEAAuE;IACvE,gEAAgE;IAChE,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,IAAI,eAAe,EAAE,CAAC,CAAC,CAAA;IAC7E,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,QAAQ,IAAI,eAAe,EAAE,CAAC,CAAC,CAAA;IAC7E,EAAE,CAAC,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IAC3C,iBAAiB,CAAC,QAAQ,CAAC,CAAA;IAE3B,MAAM,KAAK,GAAG,SAAS,CAAC,QAAQ,CAAC,CAAA;IACjC,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,MAAM,OAAO,GAAa,EAAE,CAAA;IAC5B,MAAM,MAAM,GAAa,EAAE,CAAA;IAE3B,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;YAC9B,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YACvB,SAAQ;QACV,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,EAAE,CAAC;YAC9B,IAAI,CAAC;gBACH,eAAe,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE,CAAC,CAAA;YAC1D,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,QAAQ,CACN,gBAAgB,KAAK,CAAC,IAAI,YAAY,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACnF,CAAA;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACvB,SAAQ;YACV,CAAC;YACD,0EAA0E;YAC1E,yEAAyE;YACzE,uEAAuE;YACvE,yEAAyE;YACzE,oEAAoE;YACpE,qEAAqE;YACrE,2EAA2E;YAC3E,IAAI,KAAK,CAAC,GAAG;gBAAE,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAA;YAC5C,MAAM,KAAK,GAAG,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;YAC5D,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;gBACxB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAC1B,CAAC;iBAAM,CAAC;gBACN,wEAAwE;gBACxE,oEAAoE;gBACpE,QAAQ,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC,CAAA;gBAChD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YACzB,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;YACxC,wEAAwE;YACxE,0EAA0E;YAC1E,uEAAuE;YACvE,2EAA2E;YAC3E,yEAAyE;YACzE,uEAAuE;YACvE,IAAI,KAAK,CAAC,GAAG,IAAI,kBAAkB,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;gBAC1D,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,GAAG,CAAA;gBAC7B,qEAAqE;gBACrE,uEAAuE;gBACvE,0DAA0D;gBAC1D,uEAAuE;gBACvE,sEAAsE;gBACtE,IAAI,cAAc,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,KAAK,aAAa,EAAE,CAAC;oBACrE,QAAQ,CAAC,mBAAmB,CAAC,KAAK,CAAC,IAAI,EAAE,aAAa,CAAC,CAAC,CAAA;oBACxD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACzB,CAAC;YACH,CAAC;iBAAM,CAAC;gBACN,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAC1B,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YACtC,IAAI,CAAC;gBACH,UAAU,CAAC,QAAQ,EAAE,QAAQ,EAAE,KAAK,CAAC,IAAI,CAAC,CAAA;gBAC1C,OAAO,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;gBACxB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YAC1B,CAAC;YAAC,OAAO,CAAC,EAAE,CAAC;gBACX,+DAA+D;gBAC/D,6DAA6D;gBAC7D,4DAA4D;gBAC5D,wCAAwC;gBACxC,QAAQ,CACN,gBAAgB,KAAK,CAAC,IAAI,YAAY,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CACnF,CAAA;gBACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;YACzB,CAAC;QACH,CAAC;aAAM,IAAI,KAAK,CAAC,MAAM,KAAK,OAAO,EAAE,CAAC;YACpC,sEAAsE;YACtE,uEAAuE;YACvE,0EAA0E;YAC1E,QAAQ,CACN,gBAAgB,KAAK,CAAC,IAAI,UAAU,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAC3E,CAAA;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACzB,CAAC;IACH,CAAC;IAED,SAAS,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAA;IAC1B,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,CAAA;AAC5C,CAAC"}
|