@looplia/looplia-cli 0.7.3 → 0.7.5

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.
@@ -0,0 +1,305 @@
1
+ #!/usr/bin/env bun
2
+ import {
3
+ compileRegistry,
4
+ getCompiledRegistryPath
5
+ } from "./chunk-XTUQVJYH.js";
6
+ import {
7
+ extractWorkflowSkills
8
+ } from "./chunk-GIZRTNY3.js";
9
+ import {
10
+ pathExists
11
+ } from "./chunk-VRBGWKZ6.js";
12
+ import {
13
+ init_esm_shims
14
+ } from "./chunk-Y55L47HC.js";
15
+
16
+ // ../../packages/provider/dist/index.js
17
+ init_esm_shims();
18
+ import { exec } from "child_process";
19
+ import { readFile, rm } from "fs/promises";
20
+ import { homedir } from "os";
21
+ import { join } from "path";
22
+ import { promisify } from "util";
23
+ var execAsync = promisify(exec);
24
+ var GITHUB_FULL_PATTERN = /^https?:\/\/github\.com\/([^/]+\/[^/]+)(?:\/tree\/[^/]+\/(.+))?$/;
25
+ var GITHUB_SIMPLE_PATTERN = /^(?:https?:\/\/)?github\.com\/([^/]+\/[^/]+)\/?$/;
26
+ async function loadCompiledRegistry(autoSync = false) {
27
+ const compiledPath = getCompiledRegistryPath();
28
+ if (!await pathExists(compiledPath)) {
29
+ return await compileRegistry();
30
+ }
31
+ if (autoSync) {
32
+ return await compileRegistry();
33
+ }
34
+ const content = await readFile(compiledPath, "utf-8");
35
+ return JSON.parse(content);
36
+ }
37
+ function findSkill(registry, skillName) {
38
+ return registry.skills.find((s) => s.name === skillName);
39
+ }
40
+ function getInstalledSkills(registry) {
41
+ return registry.skills.filter((s) => s.installed);
42
+ }
43
+ function getAvailableSkills(registry) {
44
+ return registry.skills.filter((s) => !s.installed);
45
+ }
46
+ function getSkillsByCategory(registry, category) {
47
+ return registry.skills.filter((s) => s.category === category);
48
+ }
49
+ function getSkillsBySource(registry, source) {
50
+ return registry.skills.filter((s) => s.source === source);
51
+ }
52
+ async function installThirdPartySkill(skill, showProgress = false) {
53
+ if (!skill.gitUrl) {
54
+ return {
55
+ skill: skill.name,
56
+ status: "failed",
57
+ error: "No git URL available for skill"
58
+ };
59
+ }
60
+ const source = {
61
+ id: `temp:${skill.name}`,
62
+ type: "github",
63
+ url: skill.gitUrl,
64
+ enabled: true,
65
+ priority: 0,
66
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
67
+ };
68
+ const { syncSource: sync } = await import("./sync-E5PGFGNI-IGGJR7IL.js");
69
+ const result = await sync(source, {
70
+ showProgress,
71
+ skillPath: skill.skillPath
72
+ // For marketplace skills
73
+ });
74
+ const firstPlugin = result.plugins[0];
75
+ if (firstPlugin) {
76
+ return firstPlugin;
77
+ }
78
+ return {
79
+ skill: skill.name,
80
+ status: "failed",
81
+ error: result.error ?? "Unknown error during installation"
82
+ };
83
+ }
84
+ async function installSkill(skillName, registry) {
85
+ const reg = registry ?? await loadCompiledRegistry();
86
+ const skill = findSkill(reg, skillName);
87
+ if (!skill) {
88
+ return {
89
+ skill: skillName,
90
+ status: "failed",
91
+ error: `Skill not found in registry: ${skillName}`
92
+ };
93
+ }
94
+ if (skill.installed) {
95
+ return {
96
+ skill: skillName,
97
+ status: "already_installed",
98
+ path: skill.installedPath
99
+ };
100
+ }
101
+ if (skill.sourceType === "builtin") {
102
+ return {
103
+ skill: skillName,
104
+ status: "failed",
105
+ error: "Built-in skill not found in local installation. Run 'looplia init' to reinstall."
106
+ };
107
+ }
108
+ return await installThirdPartySkill(skill);
109
+ }
110
+ async function updateSkill(skillName, registry) {
111
+ const reg = registry ?? await loadCompiledRegistry();
112
+ const skill = findSkill(reg, skillName);
113
+ if (!skill) {
114
+ return {
115
+ skill: skillName,
116
+ status: "failed",
117
+ error: `Skill not found in registry: ${skillName}`
118
+ };
119
+ }
120
+ if (!(skill.installed && skill.installedPath)) {
121
+ return {
122
+ skill: skillName,
123
+ status: "failed",
124
+ error: "Skill is not installed"
125
+ };
126
+ }
127
+ if (skill.sourceType === "builtin") {
128
+ return {
129
+ skill: skillName,
130
+ status: "failed",
131
+ error: "Cannot update built-in skills. Run 'looplia init' to refresh."
132
+ };
133
+ }
134
+ try {
135
+ const skillsDir = join(skill.installedPath, "..", "..");
136
+ await execAsync("git pull", { cwd: skillsDir });
137
+ return {
138
+ skill: skillName,
139
+ status: "updated",
140
+ path: skill.installedPath
141
+ };
142
+ } catch (error) {
143
+ const message = error instanceof Error ? error.message : String(error);
144
+ return {
145
+ skill: skillName,
146
+ status: "failed",
147
+ error: message
148
+ };
149
+ }
150
+ }
151
+ async function ensureWorkflowSkills(workflow, registry) {
152
+ const reg = registry ?? await loadCompiledRegistry();
153
+ const requiredSkills = extractWorkflowSkills(workflow);
154
+ const installed = [];
155
+ const failed = [];
156
+ for (const skillName of requiredSkills) {
157
+ const skill = findSkill(reg, skillName);
158
+ if (!skill) {
159
+ failed.push(skillName);
160
+ continue;
161
+ }
162
+ if (skill.installed) {
163
+ continue;
164
+ }
165
+ if (skill.sourceType === "builtin") {
166
+ failed.push(skillName);
167
+ continue;
168
+ }
169
+ const result = await installThirdPartySkill(skill);
170
+ if (result.status === "failed") {
171
+ failed.push(skillName);
172
+ } else {
173
+ installed.push(result);
174
+ }
175
+ }
176
+ return {
177
+ ready: failed.length === 0,
178
+ installed,
179
+ failed
180
+ };
181
+ }
182
+ async function installSkillFromUrl(url, showProgress = false) {
183
+ const urlParts = parseGitHubUrl(url);
184
+ if (!urlParts) {
185
+ return {
186
+ skill: "unknown",
187
+ status: "failed",
188
+ error: `Invalid GitHub URL: ${url}`
189
+ };
190
+ }
191
+ const { repoUrl, skillPath } = urlParts;
192
+ const source = {
193
+ id: `url:${url.replace(/[^a-zA-Z0-9]/g, "-")}`,
194
+ type: "github",
195
+ url: repoUrl,
196
+ enabled: true,
197
+ priority: 0,
198
+ addedAt: (/* @__PURE__ */ new Date()).toISOString()
199
+ };
200
+ const { syncSource: sync } = await import("./sync-E5PGFGNI-IGGJR7IL.js");
201
+ const result = await sync(source, {
202
+ showProgress,
203
+ skillPath
204
+ });
205
+ if (result.status === "synced") {
206
+ await compileRegistry();
207
+ }
208
+ const firstPlugin = result.plugins[0];
209
+ if (firstPlugin) {
210
+ return firstPlugin;
211
+ }
212
+ return {
213
+ skill: "unknown",
214
+ status: "failed",
215
+ error: result.error ?? "Unknown error during installation"
216
+ };
217
+ }
218
+ function parseGitHubUrl(url) {
219
+ const match = url.match(GITHUB_FULL_PATTERN);
220
+ if (!match) {
221
+ const simpleMatch = url.match(GITHUB_SIMPLE_PATTERN);
222
+ if (simpleMatch?.[1]) {
223
+ return { repoUrl: `https://github.com/${simpleMatch[1]}.git` };
224
+ }
225
+ return null;
226
+ }
227
+ const repo = match[1];
228
+ const path = match[2];
229
+ return {
230
+ repoUrl: `https://github.com/${repo}.git`,
231
+ skillPath: path
232
+ };
233
+ }
234
+ async function removeSkill(skillName, registry) {
235
+ const reg = registry ?? await loadCompiledRegistry();
236
+ const skill = findSkill(reg, skillName);
237
+ if (!skill) {
238
+ return {
239
+ skill: skillName,
240
+ status: "failed",
241
+ error: `Skill not found in registry: ${skillName}`
242
+ };
243
+ }
244
+ if (!skill.installed) {
245
+ return {
246
+ skill: skillName,
247
+ status: "failed",
248
+ error: "Skill is not installed"
249
+ };
250
+ }
251
+ if (skill.sourceType === "builtin") {
252
+ return {
253
+ skill: skillName,
254
+ status: "failed",
255
+ error: "Cannot remove built-in skills"
256
+ };
257
+ }
258
+ if (!skill.installedPath) {
259
+ return {
260
+ skill: skillName,
261
+ status: "failed",
262
+ error: "Skill installation path not found"
263
+ };
264
+ }
265
+ try {
266
+ const pluginDir = join(skill.installedPath, "..", "..");
267
+ const loopliaPath = join(homedir(), ".looplia");
268
+ const pluginsDir = join(loopliaPath, "plugins");
269
+ if (!pluginDir.startsWith(pluginsDir)) {
270
+ return {
271
+ skill: skillName,
272
+ status: "failed",
273
+ error: "Cannot remove: path is outside plugins directory"
274
+ };
275
+ }
276
+ await rm(pluginDir, { recursive: true, force: true });
277
+ await compileRegistry();
278
+ return {
279
+ skill: skillName,
280
+ status: "removed",
281
+ path: pluginDir
282
+ };
283
+ } catch (error) {
284
+ const message = error instanceof Error ? error.message : String(error);
285
+ return {
286
+ skill: skillName,
287
+ status: "failed",
288
+ error: message
289
+ };
290
+ }
291
+ }
292
+
293
+ export {
294
+ loadCompiledRegistry,
295
+ findSkill,
296
+ getInstalledSkills,
297
+ getAvailableSkills,
298
+ getSkillsByCategory,
299
+ getSkillsBySource,
300
+ installSkill,
301
+ updateSkill,
302
+ ensureWorkflowSkills,
303
+ installSkillFromUrl,
304
+ removeSkill
305
+ };