my-pi 0.0.2 → 0.0.4

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.
@@ -1,4 +1,3 @@
1
- import { type DiscoveredSkill, scan_all_skills } from './scanner.js';
2
1
  import {
3
2
  type SkillsConfig,
4
3
  is_skill_enabled,
@@ -6,6 +5,17 @@ import {
6
5
  make_skill_key,
7
6
  save_skills_config,
8
7
  } from './config.js';
8
+ import {
9
+ type ImportSkillResult,
10
+ type SyncSkillResult,
11
+ import_external_skill,
12
+ sync_imported_skill,
13
+ } from './importer.js';
14
+ import {
15
+ type DiscoveredSkill,
16
+ scan_importable_skills,
17
+ scan_managed_skills,
18
+ } from './scanner.js';
9
19
 
10
20
  export interface ManagedSkill extends DiscoveredSkill {
11
21
  key: string;
@@ -14,71 +24,120 @@ export interface ManagedSkill extends DiscoveredSkill {
14
24
 
15
25
  export interface SkillsManager {
16
26
  discover(): ManagedSkill[];
27
+ discover_importable(): ManagedSkill[];
17
28
  get_enabled_skill_paths(): string[];
18
- /** Check if a skill should pass through skillsOverride (pi's native loading) */
29
+ /** Check if a skill should pass through pi's skillsOverride */
19
30
  is_enabled_by_skill(name: string, filePath: string): boolean;
20
31
  enable(key: string): boolean;
21
32
  disable(key: string): boolean;
22
33
  toggle(key: string): boolean;
23
34
  search(query: string): ManagedSkill[];
35
+ search_importable(query: string): ManagedSkill[];
24
36
  set_defaults(policy: 'all-enabled' | 'all-disabled'): void;
37
+ import_skill(
38
+ key_or_name: string,
39
+ ): ImportSkillResult & { key: string };
40
+ sync_skill(key_or_name: string): SyncSkillResult & { key: string };
25
41
  refresh(): void;
26
42
  }
27
43
 
44
+ function resolve_skill_key(skill: DiscoveredSkill): string {
45
+ return make_skill_key(skill.name, skill.source);
46
+ }
47
+
48
+ function match_skill_by_key_or_name(
49
+ skills: DiscoveredSkill[],
50
+ key_or_name: string,
51
+ ): DiscoveredSkill {
52
+ const exact_key = skills.find(
53
+ (skill) => resolve_skill_key(skill) === key_or_name,
54
+ );
55
+ if (exact_key) return exact_key;
56
+
57
+ const by_name = skills.filter(
58
+ (skill) => skill.name === key_or_name,
59
+ );
60
+ if (by_name.length === 1) {
61
+ return by_name[0]!;
62
+ }
63
+ if (by_name.length > 1) {
64
+ throw new Error(
65
+ `Multiple skills named ${key_or_name}. Use an exact key instead.`,
66
+ );
67
+ }
68
+
69
+ throw new Error(`Unknown skill: ${key_or_name}`);
70
+ }
71
+
28
72
  export function create_skills_manager(): SkillsManager {
29
73
  let config: SkillsConfig = load_skills_config();
30
- let cache: DiscoveredSkill[] | null = null;
74
+ let managed_cache: DiscoveredSkill[] | null = null;
75
+ let importable_cache: DiscoveredSkill[] | null = null;
31
76
 
32
- function get_discovered(): DiscoveredSkill[] {
33
- if (!cache) {
34
- cache = scan_all_skills();
77
+ function get_managed(): DiscoveredSkill[] {
78
+ if (!managed_cache) {
79
+ managed_cache = scan_managed_skills();
35
80
  }
36
- return cache;
81
+ return managed_cache;
82
+ }
83
+
84
+ function get_importable(): DiscoveredSkill[] {
85
+ if (!importable_cache) {
86
+ importable_cache = scan_importable_skills();
87
+ }
88
+ return importable_cache;
37
89
  }
38
90
 
39
91
  function to_managed(skill: DiscoveredSkill): ManagedSkill {
40
- const key = make_skill_key(skill.name, skill.source);
92
+ const key = resolve_skill_key(skill);
41
93
  return {
42
94
  ...skill,
43
95
  key,
44
- enabled: is_skill_enabled(config, key),
96
+ enabled:
97
+ skill.kind === 'managed'
98
+ ? is_skill_enabled(config, key)
99
+ : false,
45
100
  };
46
101
  }
47
102
 
103
+ function get_enabled_managed_skills(): ManagedSkill[] {
104
+ return get_managed()
105
+ .filter((skill) =>
106
+ is_skill_enabled(config, resolve_skill_key(skill)),
107
+ )
108
+ .map(to_managed);
109
+ }
110
+
48
111
  return {
49
112
  discover(): ManagedSkill[] {
50
- return get_discovered().map(to_managed);
113
+ return get_managed().map(to_managed);
114
+ },
115
+
116
+ discover_importable(): ManagedSkill[] {
117
+ return get_importable().map(to_managed);
51
118
  },
52
119
 
53
120
  is_enabled_by_skill(name: string, filePath: string): boolean {
54
- // Try to find this skill in our discovered set by filePath
55
- const discovered = get_discovered();
121
+ const discovered = get_managed();
56
122
  const match = discovered.find((s) => s.skillPath === filePath);
57
123
  if (match) {
58
- return is_skill_enabled(
59
- config,
60
- make_skill_key(match.name, match.source),
61
- );
124
+ return is_skill_enabled(config, resolve_skill_key(match));
62
125
  }
63
- // Skill not in our discovered set (e.g. from pi's own paths)
64
- // Fall back to checking by name with a generic source
126
+
65
127
  const by_name = discovered.find((s) => s.name === name);
66
128
  if (by_name) {
67
- return is_skill_enabled(
68
- config,
69
- make_skill_key(by_name.name, by_name.source),
70
- );
129
+ return is_skill_enabled(config, resolve_skill_key(by_name));
71
130
  }
72
- // Unknown skill — respect defaults
73
- return config.defaults === 'all-enabled';
131
+
132
+ // Unknown skill sources should remain enabled so pi's native
133
+ // discovery keeps working for project and other default locations.
134
+ return true;
74
135
  },
75
136
 
76
137
  get_enabled_skill_paths(): string[] {
77
- return get_discovered()
78
- .filter((s) =>
79
- is_skill_enabled(config, make_skill_key(s.name, s.source)),
80
- )
81
- .map((s) => s.baseDir);
138
+ return get_enabled_managed_skills().map(
139
+ (skill) => skill.baseDir,
140
+ );
82
141
  },
83
142
 
84
143
  enable(key: string): boolean {
@@ -110,13 +169,53 @@ export function create_skills_manager(): SkillsManager {
110
169
  );
111
170
  },
112
171
 
172
+ search_importable(query: string): ManagedSkill[] {
173
+ const q = query.toLowerCase();
174
+ return this.discover_importable().filter(
175
+ (s) =>
176
+ s.name.toLowerCase().includes(q) ||
177
+ s.description.toLowerCase().includes(q) ||
178
+ s.source.toLowerCase().includes(q),
179
+ );
180
+ },
181
+
113
182
  set_defaults(policy: 'all-enabled' | 'all-disabled'): void {
114
183
  config.defaults = policy;
115
184
  save_skills_config(config);
116
185
  },
117
186
 
187
+ import_skill(key_or_name: string) {
188
+ const skill = match_skill_by_key_or_name(
189
+ get_importable(),
190
+ key_or_name,
191
+ );
192
+ const result = import_external_skill(skill);
193
+ const managed_key = make_skill_key(skill.name, 'pi-native');
194
+ config.enabled[managed_key] = true;
195
+ save_skills_config(config);
196
+ this.refresh();
197
+ return {
198
+ ...result,
199
+ key: managed_key,
200
+ };
201
+ },
202
+
203
+ sync_skill(key_or_name: string) {
204
+ const skill = match_skill_by_key_or_name(
205
+ get_managed(),
206
+ key_or_name,
207
+ );
208
+ const result = sync_imported_skill(skill);
209
+ this.refresh();
210
+ return {
211
+ ...result,
212
+ key: resolve_skill_key(skill),
213
+ };
214
+ },
215
+
118
216
  refresh(): void {
119
- cache = null;
217
+ managed_cache = null;
218
+ importable_cache = null;
120
219
  config = load_skills_config();
121
220
  },
122
221
  };
@@ -1,23 +1,20 @@
1
- import { existsSync, globSync, readFileSync } from 'node:fs';
2
- import { basename, dirname, join, resolve } from 'node:path';
3
- import { homedir } from 'node:os';
4
1
  import {
5
2
  parseFrontmatter,
6
3
  type SkillFrontmatter,
7
4
  } from '@mariozechner/pi-coding-agent';
5
+ import { existsSync, globSync, readFileSync } from 'node:fs';
6
+ import { homedir } from 'node:os';
7
+ import { basename, dirname, join, resolve } from 'node:path';
8
8
 
9
- export interface DiscoveredSkill {
10
- name: string;
11
- description: string;
12
- skillPath: string;
13
- baseDir: string;
14
- source: string;
15
- }
9
+ export const IMPORT_METADATA_FILE = '.my-pi-source.json';
16
10
 
17
- interface InstalledPlugin {
11
+ export interface InstalledPlugin {
18
12
  scope: string;
19
13
  installPath: string;
20
14
  version: string;
15
+ installedAt?: string;
16
+ lastUpdated?: string;
17
+ gitCommitSha?: string;
21
18
  }
22
19
 
23
20
  interface InstalledPluginsFile {
@@ -25,6 +22,38 @@ interface InstalledPluginsFile {
25
22
  plugins: Record<string, InstalledPlugin[]>;
26
23
  }
27
24
 
25
+ export interface ImportedSkillMetadata {
26
+ version: number;
27
+ source: string;
28
+ upstream_skill_path: string;
29
+ upstream_base_dir: string;
30
+ upstream_install_path?: string;
31
+ upstream_version?: string;
32
+ upstream_git_commit_sha?: string;
33
+ imported_at: string;
34
+ last_synced_at: string;
35
+ imported_hash: string;
36
+ upstream_hash: string;
37
+ }
38
+
39
+ export interface PluginSkillSource {
40
+ pluginId: string;
41
+ installPath: string;
42
+ version: string;
43
+ gitCommitSha?: string;
44
+ }
45
+
46
+ export interface DiscoveredSkill {
47
+ name: string;
48
+ description: string;
49
+ skillPath: string;
50
+ baseDir: string;
51
+ source: string;
52
+ kind: 'managed' | 'external';
53
+ plugin?: PluginSkillSource;
54
+ import_meta?: ImportedSkillMetadata;
55
+ }
56
+
28
57
  function read_installed_plugins(): InstalledPluginsFile | null {
29
58
  const path = join(
30
59
  homedir(),
@@ -60,41 +89,74 @@ function parse_skill_md(
60
89
  }
61
90
  }
62
91
 
92
+ function read_import_metadata(
93
+ base_dir: string,
94
+ ): ImportedSkillMetadata | undefined {
95
+ const metadata_path = join(base_dir, IMPORT_METADATA_FILE);
96
+ if (!existsSync(metadata_path)) return undefined;
97
+
98
+ try {
99
+ return JSON.parse(
100
+ readFileSync(metadata_path, 'utf-8'),
101
+ ) as ImportedSkillMetadata;
102
+ } catch {
103
+ return undefined;
104
+ }
105
+ }
106
+
63
107
  function scan_dir_for_skills(
64
108
  dir: string,
65
- source: string,
109
+ options: {
110
+ source: string;
111
+ kind: 'managed' | 'external';
112
+ plugin?: PluginSkillSource;
113
+ include_direct_root_skill?: boolean;
114
+ },
66
115
  ): DiscoveredSkill[] {
67
116
  if (!existsSync(dir)) return [];
68
117
 
69
118
  const results: DiscoveredSkill[] = [];
70
-
71
- // Direct SKILL.md in this dir
72
119
  const direct = join(dir, 'SKILL.md');
73
- if (existsSync(direct)) {
120
+ const include_direct_root_skill =
121
+ options.include_direct_root_skill ?? true;
122
+
123
+ if (include_direct_root_skill && existsSync(direct)) {
74
124
  const parsed = parse_skill_md(direct);
75
125
  if (parsed) {
76
126
  results.push({
77
127
  ...parsed,
78
128
  skillPath: direct,
79
129
  baseDir: dir,
80
- source,
130
+ source: options.source,
131
+ kind: options.kind,
132
+ plugin: options.plugin,
133
+ import_meta:
134
+ options.kind === 'managed'
135
+ ? read_import_metadata(dir)
136
+ : undefined,
81
137
  });
82
138
  }
83
- return results; // SKILL.md at root means this IS the skill, don't recurse
139
+ return results;
84
140
  }
85
141
 
86
- // Glob for skills in subdirs
87
142
  try {
88
143
  const matches = globSync('*/SKILL.md', { cwd: dir });
89
144
  for (const match of matches) {
90
145
  const full_path = resolve(dir, match);
91
146
  const parsed = parse_skill_md(full_path);
92
147
  if (parsed) {
148
+ const base_dir = dirname(full_path);
93
149
  results.push({
94
150
  ...parsed,
95
151
  skillPath: full_path,
96
- baseDir: dirname(full_path),
97
- source,
152
+ baseDir: base_dir,
153
+ source: options.source,
154
+ kind: options.kind,
155
+ plugin: options.plugin,
156
+ import_meta:
157
+ options.kind === 'managed'
158
+ ? read_import_metadata(base_dir)
159
+ : undefined,
98
160
  });
99
161
  }
100
162
  }
@@ -105,71 +167,109 @@ function scan_dir_for_skills(
105
167
  return results;
106
168
  }
107
169
 
108
- export function scan_all_skills(): DiscoveredSkill[] {
109
- const all: DiscoveredSkill[] = [];
170
+ function dedupe_by_skill_path(
171
+ skills: DiscoveredSkill[],
172
+ ): DiscoveredSkill[] {
110
173
  const seen = new Set<string>();
174
+ const deduped: DiscoveredSkill[] = [];
111
175
 
112
- const add = (skill: DiscoveredSkill) => {
113
- if (seen.has(skill.skillPath)) return;
176
+ for (const skill of skills) {
177
+ if (seen.has(skill.skillPath)) continue;
114
178
  seen.add(skill.skillPath);
115
- all.push(skill);
116
- };
179
+ deduped.push(skill);
180
+ }
181
+
182
+ return deduped;
183
+ }
184
+
185
+ export function scan_managed_skills(): DiscoveredSkill[] {
186
+ const skills: DiscoveredSkill[] = [];
187
+
188
+ for (const skill of scan_dir_for_skills(
189
+ join(homedir(), '.claude', 'skills'),
190
+ {
191
+ source: 'user-local',
192
+ kind: 'managed',
193
+ },
194
+ )) {
195
+ skills.push(skill);
196
+ }
197
+
198
+ for (const skill of scan_dir_for_skills(
199
+ join(homedir(), '.pi', 'agent', 'skills'),
200
+ {
201
+ source: 'pi-native',
202
+ kind: 'managed',
203
+ include_direct_root_skill: false,
204
+ },
205
+ )) {
206
+ skills.push(skill);
207
+ }
208
+
209
+ return dedupe_by_skill_path(skills);
210
+ }
117
211
 
118
- // 1. Installed Claude Code plugin skills
212
+ export function scan_importable_skills(): DiscoveredSkill[] {
213
+ const skills: DiscoveredSkill[] = [];
119
214
  const plugins = read_installed_plugins();
120
- if (plugins?.plugins) {
121
- for (const [key, entries] of Object.entries(plugins.plugins)) {
122
- const entry = entries[0]; // first scope entry
123
- if (!entry?.installPath || !existsSync(entry.installPath))
124
- continue;
215
+ if (!plugins?.plugins) return skills;
125
216
 
126
- const source = `plugin:${key}`;
217
+ for (const [plugin_id, entries] of Object.entries(
218
+ plugins.plugins,
219
+ )) {
220
+ const entry = entries[0];
221
+ if (!entry?.installPath || !existsSync(entry.installPath))
222
+ continue;
127
223
 
128
- // Standard: {installPath}/skills/*/SKILL.md
129
- for (const s of scan_dir_for_skills(
130
- join(entry.installPath, 'skills'),
131
- source,
132
- )) {
133
- add(s);
134
- }
224
+ const source = `plugin:${plugin_id}`;
225
+ const plugin: PluginSkillSource = {
226
+ pluginId: plugin_id,
227
+ installPath: entry.installPath,
228
+ version: entry.version,
229
+ gitCommitSha: entry.gitCommitSha,
230
+ };
135
231
 
136
- // Single-skill plugin: {installPath}/SKILL.md
137
- // Use the file path directly — baseDir would be the versioned cache dir
138
- // which causes pi's "name doesn't match parent" warning
139
- const direct = join(entry.installPath, 'SKILL.md');
140
- if (existsSync(direct)) {
141
- const parsed = parse_skill_md(direct);
142
- if (parsed) {
143
- add({
144
- ...parsed,
145
- skillPath: direct,
146
- baseDir: direct,
147
- source,
148
- });
149
- }
150
- }
232
+ for (const skill of scan_dir_for_skills(
233
+ join(entry.installPath, 'skills'),
234
+ {
235
+ source,
236
+ kind: 'external',
237
+ plugin,
238
+ },
239
+ )) {
240
+ skills.push(skill);
241
+ }
151
242
 
152
- // Pi-specific: {installPath}/.pi/skills/*/SKILL.md
153
- for (const s of scan_dir_for_skills(
154
- join(entry.installPath, '.pi', 'skills'),
243
+ for (const skill of scan_dir_for_skills(
244
+ join(entry.installPath, '.pi', 'skills'),
245
+ {
155
246
  source,
156
- )) {
157
- add(s);
158
- }
247
+ kind: 'external',
248
+ plugin,
249
+ },
250
+ )) {
251
+ skills.push(skill);
159
252
  }
160
- }
161
253
 
162
- // 2. User-local Claude skills
163
- const claude_skills = join(homedir(), '.claude', 'skills');
164
- for (const s of scan_dir_for_skills(claude_skills, 'user-local')) {
165
- add(s);
254
+ const direct_root_skill = join(entry.installPath, 'SKILL.md');
255
+ if (existsSync(direct_root_skill)) {
256
+ const parsed = parse_skill_md(direct_root_skill);
257
+ if (parsed) {
258
+ skills.push({
259
+ ...parsed,
260
+ skillPath: direct_root_skill,
261
+ baseDir: entry.installPath,
262
+ source,
263
+ kind: 'external',
264
+ plugin,
265
+ });
266
+ }
267
+ }
166
268
  }
167
269
 
168
- // 3. Pi native skills
169
- const pi_skills = join(homedir(), '.pi', 'agent', 'skills');
170
- for (const s of scan_dir_for_skills(pi_skills, 'pi-native')) {
171
- add(s);
172
- }
270
+ return dedupe_by_skill_path(skills);
271
+ }
173
272
 
174
- return all;
273
+ export function scan_all_skills(): DiscoveredSkill[] {
274
+ return [...scan_managed_skills(), ...scan_importable_skills()];
175
275
  }
package/dist/api.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.js","names":[],"sources":["../src/api.ts"],"sourcesContent":["// Composable programmatic API for my-pi\n// Extension loading patterns inspired by https://github.com/disler/pi-vs-claude-code\n\nimport {\n\ttype AgentSessionRuntime,\n\tcreateAgentSessionFromServices,\n\tcreateAgentSessionRuntime,\n\ttype CreateAgentSessionRuntimeFactory,\n\tcreateAgentSessionServices,\n\ttype ExtensionFactory,\n\tgetAgentDir,\n\tSessionManager,\n\tSettingsManager,\n} from '@mariozechner/pi-coding-agent';\nimport { dirname, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst ext_dir = resolve(__dirname, '..', 'src', 'extensions');\n\nexport interface CreateMyPiOptions {\n\tcwd?: string;\n\textensions?: string[];\n\textensionFactories?: ExtensionFactory[];\n\t/** Enable MCP extension (default true) */\n\tmcp?: boolean;\n\t/** Enable skills extension (default true) */\n\tskills?: boolean;\n\t/** Enable chain extension (default true) */\n\tchain?: boolean;\n\t/** Enable filter-output extension for secret redaction (default true) */\n\tfilter_output?: boolean;\n\t/** Enable handoff extension (default true) */\n\thandoff?: boolean;\n\t/** Enable recall extension for searching past sessions (default true) */\n\trecall?: boolean;\n\t/** Override the default model (e.g. \"claude-sonnet-4-5-20241022\") */\n\tmodel?: string;\n}\n\nexport async function create_my_pi(\n\toptions: CreateMyPiOptions = {},\n): Promise<AgentSessionRuntime> {\n\tconst {\n\t\tcwd = process.cwd(),\n\t\textensions = [],\n\t\textensionFactories: user_factories = [],\n\t\tmcp = true,\n\t\tskills = true,\n\t\tchain = true,\n\t\tfilter_output = true,\n\t\thandoff = true,\n\t\trecall = true,\n\t\tmodel,\n\t} = options;\n\n\tconst resolved_extensions = extensions.map((p) => resolve(cwd, p));\n\n\t// All built-in extensions loaded by path so Pi shows filenames\n\tconst builtin_extension_paths: string[] = [\n\t\t...(mcp ? [resolve(ext_dir, 'mcp.ts')] : []),\n\t\t...(skills ? [resolve(ext_dir, 'skills.ts')] : []),\n\t\t...(chain ? [resolve(ext_dir, 'chain.ts')] : []),\n\t\t...(filter_output ? [resolve(ext_dir, 'filter-output.ts')] : []),\n\t\t...(handoff ? [resolve(ext_dir, 'handoff.ts')] : []),\n\t\t...(recall ? [resolve(ext_dir, 'recall.ts')] : []),\n\t];\n\n\tconst create_runtime: CreateAgentSessionRuntimeFactory = async ({\n\t\tcwd: runtime_cwd,\n\t\tsessionManager,\n\t\tsessionStartEvent,\n\t}) => {\n\t\tconst settings_manager = model\n\t\t\t? (() => {\n\t\t\t\t\tconst sm = SettingsManager.create(runtime_cwd);\n\t\t\t\t\tsm.setDefaultModel(model);\n\t\t\t\t\treturn sm;\n\t\t\t\t})()\n\t\t\t: undefined;\n\n\t\tconst services = await createAgentSessionServices({\n\t\t\tcwd: runtime_cwd,\n\t\t\t...(settings_manager && { settingsManager: settings_manager }),\n\t\t\tresourceLoaderOptions: {\n\t\t\t\tadditionalExtensionPaths: [\n\t\t\t\t\t...builtin_extension_paths,\n\t\t\t\t\t...resolved_extensions,\n\t\t\t\t],\n\t\t\t\textensionFactories: [...user_factories],\n\t\t\t},\n\t\t});\n\n\t\treturn {\n\t\t\t...(await createAgentSessionFromServices({\n\t\t\t\tservices,\n\t\t\t\tsessionManager,\n\t\t\t\tsessionStartEvent,\n\t\t\t})),\n\t\t\tservices,\n\t\t\tdiagnostics: services.diagnostics,\n\t\t};\n\t};\n\n\treturn createAgentSessionRuntime(create_runtime, {\n\t\tcwd,\n\t\tagentDir: getAgentDir(),\n\t\tsessionManager: SessionManager.create(cwd),\n\t});\n}\n\nexport {\n\tInteractiveMode,\n\trunPrintMode,\n} from '@mariozechner/pi-coding-agent';\n\nexport type {\n\tAgentSessionRuntime,\n\tExtensionFactory,\n\tInteractiveModeOptions,\n\tPrintModeOptions,\n} from '@mariozechner/pi-coding-agent';\n"],"mappings":";;;;AAkBA,MAAM,UAAU,QADE,QAAQ,cAAc,OAAO,KAAK,IAAI,CAAC,EACtB,MAAM,OAAO,aAAa;AAsB7D,eAAsB,aACrB,UAA6B,EAAE,EACA;CAC/B,MAAM,EACL,MAAM,QAAQ,KAAK,EACnB,aAAa,EAAE,EACf,oBAAoB,iBAAiB,EAAE,EACvC,MAAM,MACN,SAAS,MACT,QAAQ,MACR,gBAAgB,MAChB,UAAU,MACV,SAAS,MACT,UACG;CAEJ,MAAM,sBAAsB,WAAW,KAAK,MAAM,QAAQ,KAAK,EAAE,CAAC;CAGlE,MAAM,0BAAoC;EACzC,GAAI,MAAM,CAAC,QAAQ,SAAS,SAAS,CAAC,GAAG,EAAE;EAC3C,GAAI,SAAS,CAAC,QAAQ,SAAS,YAAY,CAAC,GAAG,EAAE;EACjD,GAAI,QAAQ,CAAC,QAAQ,SAAS,WAAW,CAAC,GAAG,EAAE;EAC/C,GAAI,gBAAgB,CAAC,QAAQ,SAAS,mBAAmB,CAAC,GAAG,EAAE;EAC/D,GAAI,UAAU,CAAC,QAAQ,SAAS,aAAa,CAAC,GAAG,EAAE;EACnD,GAAI,SAAS,CAAC,QAAQ,SAAS,YAAY,CAAC,GAAG,EAAE;EACjD;CAED,MAAM,iBAAmD,OAAO,EAC/D,KAAK,aACL,gBACA,wBACK;EACL,MAAM,mBAAmB,eACf;GACP,MAAM,KAAK,gBAAgB,OAAO,YAAY;AAC9C,MAAG,gBAAgB,MAAM;AACzB,UAAO;MACJ,GACH,KAAA;EAEH,MAAM,WAAW,MAAM,2BAA2B;GACjD,KAAK;GACL,GAAI,oBAAoB,EAAE,iBAAiB,kBAAkB;GAC7D,uBAAuB;IACtB,0BAA0B,CACzB,GAAG,yBACH,GAAG,oBACH;IACD,oBAAoB,CAAC,GAAG,eAAe;IACvC;GACD,CAAC;AAEF,SAAO;GACN,GAAI,MAAM,+BAA+B;IACxC;IACA;IACA;IACA,CAAC;GACF;GACA,aAAa,SAAS;GACtB;;AAGF,QAAO,0BAA0B,gBAAgB;EAChD;EACA,UAAU,aAAa;EACvB,gBAAgB,eAAe,OAAO,IAAI;EAC1C,CAAC"}