@looplia/looplia-cli 0.7.4 → 0.8.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/{chunk-VRBGWKZ6.js → chunk-326UJHZM.js} +1 -1
- package/dist/{chunk-QQGRKUSM.js → chunk-3WLHRD63.js} +3 -3
- package/dist/chunk-JJCGDGRS.js +296 -0
- package/dist/{chunk-XTUQVJYH.js → chunk-MTYPUSCH.js} +21 -22
- package/dist/chunk-QKGHHAFR.js +305 -0
- package/dist/{chunk-PXCY2LDE.js → chunk-RAFHASLI.js} +1261 -617
- package/dist/{claude-agent-sdk-IC25DTKL.js → claude-agent-sdk-FNYGQYFW.js} +5 -2
- package/dist/cli.js +320 -61
- package/dist/{compiler-4B63UTUP-VE77VSJ2.js → compiler-QKB2ZYNK-CYEN6G5G.js} +3 -3
- package/dist/discovery-22DBV6CT.js +241 -0
- package/dist/{dist-LKL7WJ7K.js → dist-HMIWVZMJ.js} +8 -6
- package/dist/{sync-E5PGFGNI-IGGJR7IL.js → sync-XZGFZXZF-6AWT77CE.js} +4 -4
- package/package.json +1 -1
- package/plugins/looplia-core/skills/workflow-executor/SKILL.md +73 -18
- package/dist/chunk-XKLZXCWO.js +0 -1211
- package/plugins/looplia-core/skills/workflow-executor-inline/SKILL.md +0 -217
|
@@ -5,17 +5,17 @@ import {
|
|
|
5
5
|
TRAILING_SLASH_REGEX,
|
|
6
6
|
createProgress,
|
|
7
7
|
loadSources
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-MTYPUSCH.js";
|
|
9
9
|
import {
|
|
10
10
|
isValidGitUrl,
|
|
11
11
|
isValidPathSegment,
|
|
12
12
|
pathExists
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-326UJHZM.js";
|
|
14
14
|
import {
|
|
15
15
|
init_esm_shims
|
|
16
16
|
} from "./chunk-Y55L47HC.js";
|
|
17
17
|
|
|
18
|
-
// ../../packages/provider/dist/chunk-
|
|
18
|
+
// ../../packages/provider/dist/chunk-AOIDFPNW.js
|
|
19
19
|
init_esm_shims();
|
|
20
20
|
import { exec } from "child_process";
|
|
21
21
|
import { cp, mkdir, readdir, readFile, rm, writeFile } from "fs/promises";
|
|
@@ -0,0 +1,296 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import {
|
|
3
|
+
__dirname,
|
|
4
|
+
pathExists
|
|
5
|
+
} from "./chunk-326UJHZM.js";
|
|
6
|
+
import {
|
|
7
|
+
init_esm_shims
|
|
8
|
+
} from "./chunk-Y55L47HC.js";
|
|
9
|
+
|
|
10
|
+
// ../../packages/provider/dist/chunk-WLNOVOST.js
|
|
11
|
+
init_esm_shims();
|
|
12
|
+
import { createHash } from "crypto";
|
|
13
|
+
import {
|
|
14
|
+
cp,
|
|
15
|
+
mkdir as mkdir2,
|
|
16
|
+
readdir as readdir2,
|
|
17
|
+
readFile,
|
|
18
|
+
realpath,
|
|
19
|
+
rm,
|
|
20
|
+
writeFile
|
|
21
|
+
} from "fs/promises";
|
|
22
|
+
import { homedir as homedir2, tmpdir } from "os";
|
|
23
|
+
import { dirname, join as join2 } from "path";
|
|
24
|
+
import { fileURLToPath } from "url";
|
|
25
|
+
import { exec } from "child_process";
|
|
26
|
+
import { mkdir, readdir } from "fs/promises";
|
|
27
|
+
import { join } from "path";
|
|
28
|
+
import { promisify } from "util";
|
|
29
|
+
var execAsync = promisify(exec);
|
|
30
|
+
var CORE_SKILLS = [
|
|
31
|
+
"workflow-executor",
|
|
32
|
+
"workflow-validator",
|
|
33
|
+
"registry-loader"
|
|
34
|
+
];
|
|
35
|
+
async function buildSkillPluginMap(pluginPaths) {
|
|
36
|
+
const map = /* @__PURE__ */ new Map();
|
|
37
|
+
for (const { path: pluginPath } of pluginPaths) {
|
|
38
|
+
const skillsDir = join(pluginPath, "skills");
|
|
39
|
+
if (!await pathExists(skillsDir)) {
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const entries = await readdir(skillsDir, { withFileTypes: true });
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
if (entry.isDirectory()) {
|
|
46
|
+
map.set(entry.name, pluginPath);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
} catch {
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return map;
|
|
53
|
+
}
|
|
54
|
+
async function getSelectivePluginPaths(requiredSkills) {
|
|
55
|
+
const allPluginPaths = await getPluginPaths();
|
|
56
|
+
if (!requiredSkills || requiredSkills.length === 0) {
|
|
57
|
+
return allPluginPaths;
|
|
58
|
+
}
|
|
59
|
+
const neededSkills = /* @__PURE__ */ new Set([...CORE_SKILLS, ...requiredSkills]);
|
|
60
|
+
const skillToPlugin = await buildSkillPluginMap(allPluginPaths);
|
|
61
|
+
const pluginsToLoad = /* @__PURE__ */ new Set();
|
|
62
|
+
for (const skill of neededSkills) {
|
|
63
|
+
const pluginPath = skillToPlugin.get(skill);
|
|
64
|
+
if (pluginPath) {
|
|
65
|
+
pluginsToLoad.add(pluginPath);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return allPluginPaths.filter((p) => pluginsToLoad.has(p.path));
|
|
69
|
+
}
|
|
70
|
+
function isCoreSkill(skillName) {
|
|
71
|
+
return CORE_SKILLS.includes(skillName);
|
|
72
|
+
}
|
|
73
|
+
async function computeSha256(filePath) {
|
|
74
|
+
const content = await readFile(filePath);
|
|
75
|
+
return createHash("sha256").update(content).digest("hex");
|
|
76
|
+
}
|
|
77
|
+
async function validateExtractedPaths(baseDir) {
|
|
78
|
+
const realBase = await realpath(baseDir);
|
|
79
|
+
const entries = await readdir2(baseDir, { withFileTypes: true });
|
|
80
|
+
for (const entry of entries) {
|
|
81
|
+
const fullPath = join2(baseDir, entry.name);
|
|
82
|
+
const entryRealPath = await realpath(fullPath);
|
|
83
|
+
if (!entryRealPath.startsWith(realBase)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Security: Path traversal detected in extracted file: ${entry.name}`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
await validateExtractedPaths(fullPath);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function getLoopliaPluginPath() {
|
|
94
|
+
return process.env.LOOPLIA_HOME ?? join2(homedir2(), ".looplia");
|
|
95
|
+
}
|
|
96
|
+
function getBundledPluginsPath() {
|
|
97
|
+
const currentFile = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
98
|
+
return join2(currentFile, "..", "..", "plugins");
|
|
99
|
+
}
|
|
100
|
+
async function parseMarketplace(marketplacePath) {
|
|
101
|
+
const content = await readFile(marketplacePath, "utf-8");
|
|
102
|
+
return JSON.parse(content);
|
|
103
|
+
}
|
|
104
|
+
async function getPluginNamesFromSource(bundledPath) {
|
|
105
|
+
const marketplacePath = join2(
|
|
106
|
+
bundledPath,
|
|
107
|
+
"..",
|
|
108
|
+
".claude-plugin",
|
|
109
|
+
"marketplace.json"
|
|
110
|
+
);
|
|
111
|
+
if (await pathExists(marketplacePath)) {
|
|
112
|
+
const marketplace = await parseMarketplace(marketplacePath);
|
|
113
|
+
return marketplace.plugins.map((p) => {
|
|
114
|
+
const parts = p.source.split("/");
|
|
115
|
+
return parts.at(-1);
|
|
116
|
+
}).filter((name) => name !== void 0);
|
|
117
|
+
}
|
|
118
|
+
const entries = await readdir2(bundledPath, { withFileTypes: true });
|
|
119
|
+
return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
|
|
120
|
+
}
|
|
121
|
+
function createDefaultProfile() {
|
|
122
|
+
return {
|
|
123
|
+
userId: "default",
|
|
124
|
+
topics: [],
|
|
125
|
+
style: {
|
|
126
|
+
tone: "intermediate",
|
|
127
|
+
targetWordCount: 1e3,
|
|
128
|
+
voice: "first-person"
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
async function extractWorkflows(targetDir, pluginsDir, pluginNames) {
|
|
133
|
+
const workflowsDir = join2(targetDir, "workflows");
|
|
134
|
+
await mkdir2(workflowsDir, { recursive: true });
|
|
135
|
+
for (const pluginName of pluginNames) {
|
|
136
|
+
const pluginWorkflowsPath = join2(pluginsDir, pluginName, "workflows");
|
|
137
|
+
if (!await pathExists(pluginWorkflowsPath)) {
|
|
138
|
+
continue;
|
|
139
|
+
}
|
|
140
|
+
const entries = await readdir2(pluginWorkflowsPath, { withFileTypes: true });
|
|
141
|
+
for (const entry of entries) {
|
|
142
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
143
|
+
await cp(
|
|
144
|
+
join2(pluginWorkflowsPath, entry.name),
|
|
145
|
+
join2(workflowsDir, entry.name)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
await rm(pluginWorkflowsPath, { recursive: true, force: true });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
async function copyPlugins(targetDir, sourcePath) {
|
|
153
|
+
const bundledPath = sourcePath ?? getBundledPluginsPath();
|
|
154
|
+
const pluginNames = await getPluginNamesFromSource(bundledPath);
|
|
155
|
+
if (pluginNames.length === 0) {
|
|
156
|
+
throw new Error(`No plugins found at ${bundledPath}`);
|
|
157
|
+
}
|
|
158
|
+
if (await pathExists(targetDir)) {
|
|
159
|
+
await rm(targetDir, { recursive: true, force: true });
|
|
160
|
+
}
|
|
161
|
+
await mkdir2(targetDir, { recursive: true });
|
|
162
|
+
const pluginsDir = join2(targetDir, "plugins");
|
|
163
|
+
await mkdir2(pluginsDir, { recursive: true });
|
|
164
|
+
for (const pluginName of pluginNames) {
|
|
165
|
+
const pluginPath = join2(bundledPath, pluginName);
|
|
166
|
+
if (await pathExists(pluginPath)) {
|
|
167
|
+
await cp(pluginPath, join2(pluginsDir, pluginName), { recursive: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
await extractWorkflows(targetDir, pluginsDir, pluginNames);
|
|
171
|
+
await mkdir2(join2(targetDir, "sandbox"), { recursive: true });
|
|
172
|
+
await writeFile(
|
|
173
|
+
join2(targetDir, "user-profile.json"),
|
|
174
|
+
JSON.stringify(createDefaultProfile(), null, 2),
|
|
175
|
+
"utf-8"
|
|
176
|
+
);
|
|
177
|
+
const { initializeRegistry, compileRegistry } = await import("./compiler-QKB2ZYNK-CYEN6G5G.js");
|
|
178
|
+
await initializeRegistry();
|
|
179
|
+
const { syncRegistrySources } = await import("./sync-XZGFZXZF-6AWT77CE.js");
|
|
180
|
+
const syncResults = await syncRegistrySources({ showProgress: true });
|
|
181
|
+
for (const result of syncResults) {
|
|
182
|
+
if (result.status === "failed") {
|
|
183
|
+
console.warn(
|
|
184
|
+
`Warning: Failed to sync ${result.source.id}: ${result.error}`
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
await compileRegistry();
|
|
189
|
+
}
|
|
190
|
+
async function downloadRemotePlugins(version, targetDir) {
|
|
191
|
+
const releaseUrl = version === "latest" ? "https://github.com/memorysaver/looplia-core/releases/latest/download/plugins.tar.gz" : `https://github.com/memorysaver/looplia-core/releases/download/${version}/plugins.tar.gz`;
|
|
192
|
+
console.log(`Downloading looplia plugins from ${releaseUrl}...`);
|
|
193
|
+
const tempDir = join2(tmpdir(), `looplia-download-${Date.now()}`);
|
|
194
|
+
await mkdir2(tempDir, { recursive: true });
|
|
195
|
+
try {
|
|
196
|
+
const response = await fetch(releaseUrl);
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
throw new Error(
|
|
199
|
+
`Failed to download plugins: ${response.status} ${response.statusText}`
|
|
200
|
+
);
|
|
201
|
+
}
|
|
202
|
+
const tarPath = join2(tempDir, "plugins.tar.gz");
|
|
203
|
+
await writeFile(tarPath, Buffer.from(await response.arrayBuffer()));
|
|
204
|
+
const checksumUrl = `${releaseUrl}.sha256`;
|
|
205
|
+
const checksumResponse = await fetch(checksumUrl);
|
|
206
|
+
if (checksumResponse.ok) {
|
|
207
|
+
const checksumText = await checksumResponse.text();
|
|
208
|
+
const checksumParts = checksumText.split(" ");
|
|
209
|
+
const expectedChecksum = checksumParts[0]?.trim();
|
|
210
|
+
if (!expectedChecksum) {
|
|
211
|
+
throw new Error("Invalid checksum file format");
|
|
212
|
+
}
|
|
213
|
+
const actualChecksum = await computeSha256(tarPath);
|
|
214
|
+
if (actualChecksum !== expectedChecksum) {
|
|
215
|
+
throw new Error(
|
|
216
|
+
`Checksum verification failed. Expected: ${expectedChecksum}, Got: ${actualChecksum}`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
console.log("\u2713 Checksum verified");
|
|
220
|
+
} else {
|
|
221
|
+
console.log(
|
|
222
|
+
"\u26A0 Checksum file not available (older release), skipping verification"
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
const { exec: exec2 } = await import("child_process");
|
|
226
|
+
const { promisify: promisify2 } = await import("util");
|
|
227
|
+
const execAsync2 = promisify2(exec2);
|
|
228
|
+
try {
|
|
229
|
+
await execAsync2("tar -xzf plugins.tar.gz", { cwd: tempDir });
|
|
230
|
+
} catch (error) {
|
|
231
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
232
|
+
throw new Error(
|
|
233
|
+
`Failed to extract plugins tarball. Ensure 'tar' is available. Error: ${errorMessage}`
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
await rm(tarPath);
|
|
237
|
+
await validateExtractedPaths(tempDir);
|
|
238
|
+
await copyPlugins(targetDir, tempDir);
|
|
239
|
+
console.log(`Plugins downloaded and extracted to ${targetDir}`);
|
|
240
|
+
} finally {
|
|
241
|
+
await rm(tempDir, { recursive: true, force: true }).catch(() => {
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
async function isLoopliaInitialized() {
|
|
246
|
+
const pluginPath = getLoopliaPluginPath();
|
|
247
|
+
const pluginsDir = join2(pluginPath, "plugins");
|
|
248
|
+
try {
|
|
249
|
+
const entries = await readdir2(pluginsDir, { withFileTypes: true });
|
|
250
|
+
return entries.some((e) => e.isDirectory() && !e.name.startsWith("."));
|
|
251
|
+
} catch {
|
|
252
|
+
return false;
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
function getDevPluginPaths(projectRoot) {
|
|
256
|
+
return [
|
|
257
|
+
{ type: "local", path: join2(projectRoot, "plugins", "looplia-core") },
|
|
258
|
+
{ type: "local", path: join2(projectRoot, "plugins", "looplia-writer") }
|
|
259
|
+
];
|
|
260
|
+
}
|
|
261
|
+
async function getProdPluginPaths() {
|
|
262
|
+
const loopliaPath = getLoopliaPluginPath();
|
|
263
|
+
const pluginsDir = join2(loopliaPath, "plugins");
|
|
264
|
+
const results = [];
|
|
265
|
+
try {
|
|
266
|
+
const entries = await readdir2(pluginsDir, { withFileTypes: true });
|
|
267
|
+
for (const entry of entries) {
|
|
268
|
+
if (entry.isDirectory() && !entry.name.startsWith(".")) {
|
|
269
|
+
results.push({ type: "local", path: join2(pluginsDir, entry.name) });
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
} catch {
|
|
273
|
+
}
|
|
274
|
+
return results;
|
|
275
|
+
}
|
|
276
|
+
async function getPluginPaths() {
|
|
277
|
+
if (process.env.LOOPLIA_HOME) {
|
|
278
|
+
return await getProdPluginPaths();
|
|
279
|
+
}
|
|
280
|
+
if (process.env.LOOPLIA_DEV === "true") {
|
|
281
|
+
const devRoot = process.env.LOOPLIA_DEV_ROOT ?? process.cwd();
|
|
282
|
+
return getDevPluginPaths(devRoot);
|
|
283
|
+
}
|
|
284
|
+
return await getProdPluginPaths();
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
export {
|
|
288
|
+
CORE_SKILLS,
|
|
289
|
+
getSelectivePluginPaths,
|
|
290
|
+
isCoreSkill,
|
|
291
|
+
getLoopliaPluginPath,
|
|
292
|
+
copyPlugins,
|
|
293
|
+
downloadRemotePlugins,
|
|
294
|
+
isLoopliaInitialized,
|
|
295
|
+
getPluginPaths
|
|
296
|
+
};
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import {
|
|
3
3
|
pathExists
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-326UJHZM.js";
|
|
5
5
|
import {
|
|
6
6
|
init_esm_shims
|
|
7
7
|
} from "./chunk-Y55L47HC.js";
|
|
8
8
|
|
|
9
|
-
// ../../packages/provider/dist/chunk-
|
|
9
|
+
// ../../packages/provider/dist/chunk-3DYS5U5N.js
|
|
10
10
|
init_esm_shims();
|
|
11
11
|
import { exec } from "child_process";
|
|
12
12
|
import { mkdir, readdir, readFile, writeFile } from "fs/promises";
|
|
@@ -354,13 +354,25 @@ async function getGitRemoteUrl(repoPath) {
|
|
|
354
354
|
return;
|
|
355
355
|
}
|
|
356
356
|
}
|
|
357
|
+
async function getSkillSourceUrl(skillPath) {
|
|
358
|
+
const sourcePath = join(skillPath, "source.json");
|
|
359
|
+
try {
|
|
360
|
+
if (await pathExists(sourcePath)) {
|
|
361
|
+
const content = await readFile(sourcePath, "utf-8");
|
|
362
|
+
const sourceData = JSON.parse(content);
|
|
363
|
+
return sourceData.gitUrl;
|
|
364
|
+
}
|
|
365
|
+
} catch {
|
|
366
|
+
}
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
357
369
|
async function scanPluginDirectory(pluginPath, pluginName, sourceType) {
|
|
358
370
|
const skills = [];
|
|
359
371
|
const skillsPath = join(pluginPath, "skills");
|
|
360
372
|
if (!await pathExists(skillsPath)) {
|
|
361
373
|
return skills;
|
|
362
374
|
}
|
|
363
|
-
const
|
|
375
|
+
const pluginGitUrl = sourceType === "thirdparty" ? await getGitRemoteUrl(pluginPath) : void 0;
|
|
364
376
|
try {
|
|
365
377
|
const skillEntries = await readdir(skillsPath, { withFileTypes: true });
|
|
366
378
|
for (const skillEntry of skillEntries) {
|
|
@@ -370,6 +382,8 @@ async function scanPluginDirectory(pluginPath, pluginName, sourceType) {
|
|
|
370
382
|
const skillPath = join(skillsPath, skillEntry.name);
|
|
371
383
|
const metadata = await parseSkillMetadata(skillPath);
|
|
372
384
|
if (metadata) {
|
|
385
|
+
const skillGitUrl = await getSkillSourceUrl(skillPath);
|
|
386
|
+
const gitUrl = skillGitUrl ?? pluginGitUrl;
|
|
373
387
|
skills.push({
|
|
374
388
|
name: metadata.name ?? skillEntry.name,
|
|
375
389
|
title: metadata.title ?? formatTitle(skillEntry.name),
|
|
@@ -394,35 +408,20 @@ async function scanPluginDirectory(pluginPath, pluginName, sourceType) {
|
|
|
394
408
|
}
|
|
395
409
|
async function scanLocalPlugins(loopliaPath) {
|
|
396
410
|
const skills = [];
|
|
397
|
-
try {
|
|
398
|
-
const entries = await readdir(loopliaPath, { withFileTypes: true });
|
|
399
|
-
const builtinDirs = entries.filter(
|
|
400
|
-
(e) => e.isDirectory() && !e.name.startsWith(".") && e.name !== "sandbox" && e.name !== "workflows" && e.name !== "registry" && e.name !== "plugins"
|
|
401
|
-
);
|
|
402
|
-
for (const pluginDir of builtinDirs) {
|
|
403
|
-
const pluginPath = join(loopliaPath, pluginDir.name);
|
|
404
|
-
const pluginSkills = await scanPluginDirectory(
|
|
405
|
-
pluginPath,
|
|
406
|
-
pluginDir.name,
|
|
407
|
-
"builtin"
|
|
408
|
-
);
|
|
409
|
-
skills.push(...pluginSkills);
|
|
410
|
-
}
|
|
411
|
-
} catch {
|
|
412
|
-
}
|
|
413
411
|
const pluginsDir = join(loopliaPath, "plugins");
|
|
414
412
|
if (await pathExists(pluginsDir)) {
|
|
415
413
|
try {
|
|
416
414
|
const entries = await readdir(pluginsDir, { withFileTypes: true });
|
|
417
|
-
const
|
|
415
|
+
const pluginDirs = entries.filter(
|
|
418
416
|
(e) => e.isDirectory() && !e.name.startsWith(".")
|
|
419
417
|
);
|
|
420
|
-
for (const pluginDir of
|
|
418
|
+
for (const pluginDir of pluginDirs) {
|
|
421
419
|
const pluginPath = join(pluginsDir, pluginDir.name);
|
|
420
|
+
const sourceType = pluginDir.name.startsWith("looplia-") ? "builtin" : "thirdparty";
|
|
422
421
|
const pluginSkills = await scanPluginDirectory(
|
|
423
422
|
pluginPath,
|
|
424
423
|
pluginDir.name,
|
|
425
|
-
|
|
424
|
+
sourceType
|
|
426
425
|
);
|
|
427
426
|
skills.push(...pluginSkills);
|
|
428
427
|
}
|