@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.
@@ -12,13 +12,13 @@ import {
12
12
  loadSources,
13
13
  removeSource,
14
14
  saveSources
15
- } from "./chunk-XTUQVJYH.js";
16
- import "./chunk-VRBGWKZ6.js";
15
+ } from "./chunk-MTYPUSCH.js";
16
+ import "./chunk-326UJHZM.js";
17
17
  import {
18
18
  init_esm_shims
19
19
  } from "./chunk-Y55L47HC.js";
20
20
 
21
- // ../../packages/provider/dist/compiler-4B63UTUP.js
21
+ // ../../packages/provider/dist/compiler-QKB2ZYNK.js
22
22
  init_esm_shims();
23
23
  export {
24
24
  DEFAULT_MARKETPLACE_SOURCES,
@@ -0,0 +1,241 @@
1
+ #!/usr/bin/env bun
2
+ import {
3
+ getLoopliaPluginPath
4
+ } from "./chunk-JJCGDGRS.js";
5
+ import {
6
+ isValidPathSegment,
7
+ pathExists
8
+ } from "./chunk-326UJHZM.js";
9
+ import {
10
+ init_esm_shims
11
+ } from "./chunk-Y55L47HC.js";
12
+
13
+ // ../../packages/provider/dist/discovery/index.js
14
+ init_esm_shims();
15
+ import { mkdir, readdir, writeFile } from "fs/promises";
16
+ import { join } from "path";
17
+ import { execFile } from "child_process";
18
+ import { promisify } from "util";
19
+ var AUTO_DISCOVERY_PLUGIN_NAME = "auto-discovery-plugin";
20
+ function getAutoDiscoveryPluginPath() {
21
+ return join(getLoopliaPluginPath(), "plugins", AUTO_DISCOVERY_PLUGIN_NAME);
22
+ }
23
+ async function ensureAutoDiscoveryPlugin() {
24
+ const pluginPath = getAutoDiscoveryPluginPath();
25
+ const pluginJsonPath = join(pluginPath, ".claude-plugin", "plugin.json");
26
+ const skillsDir = join(pluginPath, "skills");
27
+ if (await pathExists(join(pluginPath, ".claude-plugin"))) {
28
+ return;
29
+ }
30
+ await mkdir(join(pluginPath, ".claude-plugin"), { recursive: true });
31
+ await mkdir(skillsDir, { recursive: true });
32
+ const pluginJson = {
33
+ name: "auto-discovery-plugin",
34
+ description: "Auto-discovered skills from skills.sh registry",
35
+ version: "1.0.0",
36
+ author: { name: "Looplia" },
37
+ keywords: ["skills", "auto-discovery", "registry", "skills.sh"],
38
+ homepage: "https://github.com/memorysaver/looplia-core"
39
+ };
40
+ await writeFile(pluginJsonPath, JSON.stringify(pluginJson, null, 2), "utf-8");
41
+ }
42
+ async function installSkillToAutoDiscovery(skillName, skillContent, sourceUrl) {
43
+ if (!isValidPathSegment(skillName)) {
44
+ throw new Error(`Invalid skill name: ${skillName}`);
45
+ }
46
+ try {
47
+ await ensureAutoDiscoveryPlugin();
48
+ const skillDir = join(getAutoDiscoveryPluginPath(), "skills", skillName);
49
+ await mkdir(skillDir, { recursive: true });
50
+ const skillPath = join(skillDir, "SKILL.md");
51
+ await writeFile(skillPath, skillContent, "utf-8");
52
+ if (sourceUrl) {
53
+ const sourcePath = join(skillDir, "source.json");
54
+ await writeFile(
55
+ sourcePath,
56
+ JSON.stringify({ gitUrl: sourceUrl }, null, 2),
57
+ "utf-8"
58
+ );
59
+ }
60
+ return {
61
+ skill: skillName,
62
+ status: "installed",
63
+ path: skillDir
64
+ };
65
+ } catch (error) {
66
+ const message = error instanceof Error ? error.message : String(error);
67
+ return {
68
+ skill: skillName,
69
+ status: "failed",
70
+ error: `Failed to install skill: ${message}`
71
+ };
72
+ }
73
+ }
74
+ async function listAutoDiscoveredSkills() {
75
+ const skillsDir = join(getAutoDiscoveryPluginPath(), "skills");
76
+ try {
77
+ const entries = await readdir(skillsDir, { withFileTypes: true });
78
+ return entries.filter((e) => e.isDirectory() && !e.name.startsWith(".")).map((e) => e.name);
79
+ } catch {
80
+ return [];
81
+ }
82
+ }
83
+ async function isSkillAutoDiscovered(skillName) {
84
+ if (!isValidPathSegment(skillName)) {
85
+ return false;
86
+ }
87
+ const skillDir = join(getAutoDiscoveryPluginPath(), "skills", skillName);
88
+ return await pathExists(skillDir);
89
+ }
90
+ var execFileAsync = promisify(execFile);
91
+ var FORMAT1_PATTERN = /^(\S+)\s+-\s+(.+)\s+\(([^)]+)\)$/;
92
+ var FORMAT2_PATTERN = /^([^/]+)\/([^\s]+)\s+-\s+(.+)$/;
93
+ var FORMAT3_PATTERN = /^([^/\s]+)\/([^\s]+)$/;
94
+ var FORMAT4_PATTERN = /^([^/\s]+)\/([^@\s]+)@([^\s]+)$/;
95
+ var SKILL_SUFFIX_PATTERN = /-skill$/;
96
+ var SKILL_PREFIX_PATTERN = /^skill-/;
97
+ var ANSI_PATTERN = /\x1b\[[0-9;]*m/g;
98
+ var GITHUB_SEGMENT_PATTERN = /^[a-zA-Z0-9._-]+$/;
99
+ async function searchSkills(query) {
100
+ try {
101
+ const { stdout } = await execFileAsync("npx", ["skills", "find", query], {
102
+ timeout: 3e4
103
+ });
104
+ return parseSkillsOutput(stdout);
105
+ } catch (error) {
106
+ const message = error instanceof Error ? error.message : String(error);
107
+ console.warn(`Skills search failed: ${message}`);
108
+ console.warn("Continuing with local skills only");
109
+ return [];
110
+ }
111
+ }
112
+ function deriveSkillName(repo) {
113
+ return repo.replace(SKILL_SUFFIX_PATTERN, "").replace(SKILL_PREFIX_PATTERN, "");
114
+ }
115
+ function tryParseFormat1(line) {
116
+ const match = line.match(FORMAT1_PATTERN);
117
+ if (!match) {
118
+ return null;
119
+ }
120
+ const [, name, description, fullRepo] = match;
121
+ if (!(name && description && fullRepo)) {
122
+ return null;
123
+ }
124
+ const parts = fullRepo.split("/");
125
+ const owner = parts[0];
126
+ const repo = parts[1];
127
+ if (!(owner && repo)) {
128
+ return null;
129
+ }
130
+ return {
131
+ name,
132
+ description,
133
+ owner,
134
+ repo,
135
+ installCommand: `npx skills add ${fullRepo}`
136
+ };
137
+ }
138
+ function tryParseFormat2(line) {
139
+ const match = line.match(FORMAT2_PATTERN);
140
+ if (!match) {
141
+ return null;
142
+ }
143
+ const [, owner, repo, description] = match;
144
+ if (!(owner && repo && description)) {
145
+ return null;
146
+ }
147
+ return {
148
+ name: deriveSkillName(repo),
149
+ description,
150
+ owner,
151
+ repo,
152
+ installCommand: `npx skills add ${owner}/${repo}`
153
+ };
154
+ }
155
+ function tryParseFormat3(line) {
156
+ const match = line.match(FORMAT3_PATTERN);
157
+ if (!match) {
158
+ return null;
159
+ }
160
+ const [, owner, repo] = match;
161
+ if (!(owner && repo)) {
162
+ return null;
163
+ }
164
+ return {
165
+ name: deriveSkillName(repo),
166
+ description: "",
167
+ owner,
168
+ repo,
169
+ installCommand: `npx skills add ${owner}/${repo}`
170
+ };
171
+ }
172
+ function tryParseFormat4(line) {
173
+ const match = line.match(FORMAT4_PATTERN);
174
+ if (!match) {
175
+ return null;
176
+ }
177
+ const [, owner, repo, skillName] = match;
178
+ if (!(owner && repo && skillName)) {
179
+ return null;
180
+ }
181
+ return {
182
+ name: skillName,
183
+ description: "",
184
+ owner,
185
+ repo,
186
+ installCommand: `npx skills add ${owner}/${repo}@${skillName}`
187
+ };
188
+ }
189
+ function isValidGitHubSegment(s) {
190
+ return s.length > 0 && GITHUB_SEGMENT_PATTERN.test(s);
191
+ }
192
+ function stripAnsi(str) {
193
+ return str.replace(ANSI_PATTERN, "");
194
+ }
195
+ function parseSkillsOutput(output) {
196
+ const results = [];
197
+ const lines = output.split("\n").filter((line) => line.trim());
198
+ for (const rawLine of lines) {
199
+ const line = stripAnsi(rawLine);
200
+ const result = tryParseFormat4(line) ?? tryParseFormat1(line) ?? tryParseFormat2(line) ?? tryParseFormat3(line);
201
+ if (result) {
202
+ results.push(result);
203
+ }
204
+ }
205
+ return results;
206
+ }
207
+ async function fetchSkillContent(owner, repo, skillName) {
208
+ for (const [label, value] of [
209
+ ["owner", owner],
210
+ ["repo", repo],
211
+ ["skillName", skillName]
212
+ ]) {
213
+ if (!isValidGitHubSegment(value)) {
214
+ throw new Error(`Invalid ${label}: ${value}`);
215
+ }
216
+ }
217
+ const paths = [
218
+ `skills/${skillName}/SKILL.md`,
219
+ `${skillName}/SKILL.md`,
220
+ "SKILL.md"
221
+ ];
222
+ for (const path of paths) {
223
+ const url = `https://raw.githubusercontent.com/${owner}/${repo}/main/${path}`;
224
+ const response = await fetch(url);
225
+ if (response.ok) {
226
+ return response.text();
227
+ }
228
+ }
229
+ throw new Error(
230
+ `Failed to fetch skill content for ${owner}/${repo}/${skillName}`
231
+ );
232
+ }
233
+ export {
234
+ ensureAutoDiscoveryPlugin,
235
+ fetchSkillContent,
236
+ getAutoDiscoveryPluginPath,
237
+ installSkillToAutoDiscovery,
238
+ isSkillAutoDiscovered,
239
+ listAutoDiscoveredSkills,
240
+ searchSkills
241
+ };
@@ -11,11 +11,11 @@ import {
11
11
  loadCompiledRegistry,
12
12
  removeSkill,
13
13
  updateSkill
14
- } from "./chunk-XKLZXCWO.js";
14
+ } from "./chunk-QKGHHAFR.js";
15
15
  import {
16
16
  syncRegistrySources,
17
17
  syncSource
18
- } from "./chunk-QQGRKUSM.js";
18
+ } from "./chunk-3WLHRD63.js";
19
19
  import {
20
20
  addSource,
21
21
  compileRegistry,
@@ -27,13 +27,15 @@ import {
27
27
  loadSources,
28
28
  removeSource,
29
29
  saveSources
30
- } from "./chunk-XTUQVJYH.js";
30
+ } from "./chunk-MTYPUSCH.js";
31
+ import {
32
+ ensureWorkspace
33
+ } from "./chunk-RAFHASLI.js";
31
34
  import {
32
35
  CORE_SKILLS,
33
- ensureWorkspace,
34
36
  isCoreSkill
35
- } from "./chunk-PXCY2LDE.js";
36
- import "./chunk-VRBGWKZ6.js";
37
+ } from "./chunk-JJCGDGRS.js";
38
+ import "./chunk-326UJHZM.js";
37
39
  import "./chunk-Y55L47HC.js";
38
40
  export {
39
41
  CORE_SKILLS,
@@ -2,14 +2,14 @@
2
2
  import {
3
3
  syncRegistrySources,
4
4
  syncSource
5
- } from "./chunk-QQGRKUSM.js";
6
- import "./chunk-XTUQVJYH.js";
7
- import "./chunk-VRBGWKZ6.js";
5
+ } from "./chunk-3WLHRD63.js";
6
+ import "./chunk-MTYPUSCH.js";
7
+ import "./chunk-326UJHZM.js";
8
8
  import {
9
9
  init_esm_shims
10
10
  } from "./chunk-Y55L47HC.js";
11
11
 
12
- // ../../packages/provider/dist/sync-E5PGFGNI.js
12
+ // ../../packages/provider/dist/sync-XZGFZXZF.js
13
13
  init_esm_shims();
14
14
  export {
15
15
  syncRegistrySources,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@looplia/looplia-cli",
3
- "version": "0.7.4",
3
+ "version": "0.8.0",
4
4
  "description": "Looplia CLI - AI-powered workflow automation tool",
5
5
  "type": "module",
6
6
  "license": "Elastic-2.0",
@@ -26,6 +26,29 @@ Use this skill when:
26
26
 
27
27
  ---
28
28
 
29
+ ## ⚠️ CRITICAL EXECUTION MODEL - READ FIRST
30
+
31
+ **YOU (the model reading this skill) must execute the step loop directly. DO NOT delegate.**
32
+
33
+ ```
34
+ ┌─────────────────────────────────────────────────────────────────────┐
35
+ │ YOU (main agent) iterate through steps, making ONE Task call each │
36
+ │ │
37
+ │ FOR step IN workflow.steps: │
38
+ │ 1. Call Task(general-purpose) for THIS STEP ONLY │
39
+ │ 2. WAIT for Task to complete │
40
+ │ 3. Validate output │
41
+ │ 4. THEN move to next step │
42
+ │ │
43
+ │ ❌ WRONG: Task("Execute all workflow steps...") │
44
+ │ ✅ RIGHT: Task("Execute step: summary"), Task("Execute step: ideas")│
45
+ └─────────────────────────────────────────────────────────────────────┘
46
+ ```
47
+
48
+ **3 steps = 3 Task calls. You MUST make 3 separate Task tool invocations.**
49
+
50
+ ---
51
+
29
52
  ## CRITICAL: Task Invocation with general-purpose Subagent
30
53
 
31
54
  **v0.6.9:** Using built-in `general-purpose` subagent for ALL workflow steps (all providers).
@@ -76,25 +99,37 @@ Each `Task(general-purpose)` creates a **separate context window**:
76
99
 
77
100
  **NEVER batch multiple steps** - this defeats context isolation.
78
101
 
79
- ### Anti-Patterns
102
+ ### Anti-Patterns (VIOLATIONS = TEST FAILURE)
80
103
 
81
- ❌ **WRONG - Delegating entire workflow:**
104
+ ❌ **FATAL ERROR - Delegating entire workflow to one Task:**
82
105
  ```json
83
106
  {
84
107
  "description": "Execute workflow: writing-kit",
85
108
  "prompt": "Run all workflow steps..."
86
109
  }
87
110
  ```
88
- This pollutes the subagent context with ALL steps.
111
+ **This is WRONG.** You made 1 Task call. Tests expect 3. TEST WILL FAIL.
89
112
 
90
- **CORRECT - One step per Task:**
91
- ```json
92
- {
93
- "description": "Execute step: summary",
94
- "prompt": "Execute skill 'media-reviewer' for step 'summary'..."
95
- }
113
+ **FATAL ERROR - Executing skills inline without Task:**
96
114
  ```
97
- Each step gets a fresh, focused context window.
115
+ Skill("media-reviewer") // NO! You must use Task wrapper
116
+ Skill("idea-synthesis") // NO! You must use Task wrapper
117
+ ```
118
+ **This is WRONG.** Each step MUST be wrapped in a Task call.
119
+
120
+ ✅ **CORRECT - One Task call per step (3 steps = 3 Task calls):**
121
+ ```
122
+ Task("Execute step: summary") // Task #1
123
+ → Subagent invokes Skill("media-reviewer")
124
+
125
+ Task("Execute step: ideas") // Task #2
126
+ → Subagent invokes Skill("idea-synthesis")
127
+
128
+ Task("Execute step: writing-kit") // Task #3
129
+ → Subagent invokes Skill("writing-kit-assembler")
130
+ ```
131
+
132
+ **Verification**: Count your Task tool calls. If workflow has 3 steps, you MUST have 3 Task calls.
98
133
 
99
134
  ---
100
135
 
@@ -243,18 +278,38 @@ Input:
243
278
  Computed order: [analyze-content, generate-ideas, build-writing-kit]
244
279
  ```
245
280
 
246
- ### Phase 5: Step Execution Loop
281
+ ### Phase 5: Step Execution Loop (YOU MUST DO THIS)
282
+
283
+ **YOU execute this loop. DO NOT delegate the loop to a subagent.**
247
284
 
248
- **Execute steps ONE AT A TIME (context isolation):**
285
+ ```
286
+ YOU (main agent) must:
287
+ ┌────────────────────────────────────────────────────────────────┐
288
+ │ for (const step of steps) { │
289
+ │ // 1. YOU call Task tool │
290
+ │ await Task({ │
291
+ │ subagent_type: "general-purpose", │
292
+ │ description: `Execute step: ${step.id}`, │
293
+ │ prompt: `Execute skill '${step.skill}' for step...` │
294
+ │ }); │
295
+ │ │
296
+ │ // 2. YOU wait for Task to complete │
297
+ │ // 3. YOU validate output │
298
+ │ // 4. YOU update validation.json │
299
+ │ // 5. YOU move to next step │
300
+ │ } │
301
+ └────────────────────────────────────────────────────────────────┘
302
+ ```
249
303
 
304
+ **Execution checklist:**
250
305
  1. Get first unvalidated step from dependency order
251
- 2. Make ONE `Task(general-purpose)` call for THIS step only
252
- 3. WAIT for Task completion before proceeding
253
- 4. Validate output, update validation.json
254
- 5. REPEAT for next unvalidated step
255
- 6. STOP when ALL steps are validated
306
+ 2. **YOU** make ONE `Task(general-purpose)` call for THIS step only
307
+ 3. **YOU** WAIT for Task completion before proceeding
308
+ 4. **YOU** validate output, update validation.json
309
+ 5. **YOU** REPEAT for next unvalidated step
310
+ 6. **YOU** STOP when ALL steps are validated
256
311
 
257
- **MANDATORY:** Each step = separate context window = separate Task call.
312
+ **MANDATORY:** 3 steps = 3 Task calls. Count them. If you made fewer, you did it wrong.
258
313
 
259
314
  ```
260
315
  FOR EACH step in dependency order: