@graypark/loophaus 2.0.2 → 2.1.1
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/bin/uninstall.mjs +23 -1
- package/lib/paths.mjs +9 -1
- package/package.json +1 -1
- package/platforms/codex-cli/installer.mjs +140 -33
- package/platforms/kiro-cli/installer.mjs +163 -34
package/bin/uninstall.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getClaudePluginCacheDir,
|
|
13
13
|
getClaudeSettingsPath,
|
|
14
14
|
getClaudeInstalledPluginsPath,
|
|
15
|
+
getAgentsSkillsDir,
|
|
15
16
|
} from "../lib/paths.mjs";
|
|
16
17
|
import { getStatePath } from "../lib/state.mjs";
|
|
17
18
|
|
|
@@ -175,8 +176,14 @@ export async function uninstall({
|
|
|
175
176
|
log("-", "Plugin directory not found");
|
|
176
177
|
}
|
|
177
178
|
|
|
178
|
-
// 3. Remove skill directories
|
|
179
|
+
// 3. Remove skill directories (both legacy ralph-* and new loop-* names)
|
|
179
180
|
const skillNames = [
|
|
181
|
+
// New skill names
|
|
182
|
+
"loop",
|
|
183
|
+
"loop-stop",
|
|
184
|
+
"loop-plan",
|
|
185
|
+
"loop-pulse",
|
|
186
|
+
// Legacy skill names
|
|
180
187
|
"ralph-loop",
|
|
181
188
|
"cancel-ralph",
|
|
182
189
|
"ralph-interview",
|
|
@@ -196,6 +203,21 @@ export async function uninstall({
|
|
|
196
203
|
}
|
|
197
204
|
}
|
|
198
205
|
|
|
206
|
+
// 3b. Remove skills from ~/.agents/skills/ (new Codex CLI standard path)
|
|
207
|
+
if (!local) {
|
|
208
|
+
const agentsSkillsDir = getAgentsSkillsDir();
|
|
209
|
+
const agentsSkillNames = ["loop", "loop-stop", "loop-plan", "loop-pulse"];
|
|
210
|
+
for (const name of agentsSkillNames) {
|
|
211
|
+
const skillDir = join(agentsSkillsDir, name);
|
|
212
|
+
if (await fileExists(skillDir)) {
|
|
213
|
+
log(">", `Remove agents skill: ${skillDir}`);
|
|
214
|
+
if (!dryRun) {
|
|
215
|
+
await rm(skillDir, { recursive: true, force: true });
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
199
221
|
// 4. Remove state file
|
|
200
222
|
const statePath = getStatePath();
|
|
201
223
|
if (await fileExists(statePath)) {
|
package/lib/paths.mjs
CHANGED
|
@@ -24,7 +24,7 @@ export function isWindows() {
|
|
|
24
24
|
return process.platform === "win32";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
// --- Codex CLI paths ---
|
|
27
|
+
// --- Codex CLI paths (legacy ~/.codex + new ~/.agents) ---
|
|
28
28
|
|
|
29
29
|
export function getCodexHome() {
|
|
30
30
|
if (process.env.CODEX_HOME) {
|
|
@@ -33,6 +33,14 @@ export function getCodexHome() {
|
|
|
33
33
|
return join(homedir(), ".codex");
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
export function getAgentsHome() {
|
|
37
|
+
return join(homedir(), ".agents");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function getAgentsSkillsDir() {
|
|
41
|
+
return join(getAgentsHome(), "skills");
|
|
42
|
+
}
|
|
43
|
+
|
|
36
44
|
export function getHooksJsonPath() {
|
|
37
45
|
return join(getCodexHome(), "hooks.json");
|
|
38
46
|
}
|
package/package.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// platforms/codex-cli/installer.mjs
|
|
2
|
-
import { readFile, writeFile, mkdir, cp, access } from "node:fs/promises";
|
|
2
|
+
import { readFile, writeFile, mkdir, cp, access, rm } from "node:fs/promises";
|
|
3
3
|
import { join, resolve, dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import {
|
|
@@ -7,6 +7,8 @@ import {
|
|
|
7
7
|
getHooksJsonPath,
|
|
8
8
|
getPluginInstallDir,
|
|
9
9
|
getSkillsDir,
|
|
10
|
+
getAgentsHome,
|
|
11
|
+
getAgentsSkillsDir,
|
|
10
12
|
} from "../../lib/paths.mjs";
|
|
11
13
|
|
|
12
14
|
const __filename = fileURLToPath(import.meta.url);
|
|
@@ -17,9 +19,106 @@ async function fileExists(p) {
|
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
export async function detect() {
|
|
20
|
-
return fileExists(getCodexHome());
|
|
22
|
+
return (await fileExists(getCodexHome())) || (await fileExists(getAgentsHome()));
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
// Legacy ralph-* skill names to clean up
|
|
26
|
+
const LEGACY_SKILLS = [
|
|
27
|
+
"ralph-loop",
|
|
28
|
+
"cancel-ralph",
|
|
29
|
+
"ralph-interview",
|
|
30
|
+
"ralph-orchestrator",
|
|
31
|
+
"ralph-claude-interview",
|
|
32
|
+
"ralph-claude-loop",
|
|
33
|
+
"ralph-claude-cancel",
|
|
34
|
+
"ralph-claude-orchestrator",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
// New skill definitions for Codex CLI
|
|
38
|
+
const CODEX_SKILLS = {
|
|
39
|
+
loop: {
|
|
40
|
+
content: `---
|
|
41
|
+
name: loop
|
|
42
|
+
description: "Start iterative dev loop"
|
|
43
|
+
argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
# /loop — Start Iterative Dev Loop
|
|
47
|
+
|
|
48
|
+
Parse the user's arguments:
|
|
49
|
+
1. Extract \`--max-iterations N\` (default: 20)
|
|
50
|
+
2. Extract \`--completion-promise TEXT\` (default: "TADA")
|
|
51
|
+
3. Everything else is the prompt
|
|
52
|
+
|
|
53
|
+
Create \`.loophaus/state.json\`:
|
|
54
|
+
\`\`\`json
|
|
55
|
+
{
|
|
56
|
+
"active": true,
|
|
57
|
+
"prompt": "<user's prompt>",
|
|
58
|
+
"completionPromise": "<promise text>",
|
|
59
|
+
"maxIterations": 20,
|
|
60
|
+
"currentIteration": 0,
|
|
61
|
+
"sessionId": ""
|
|
62
|
+
}
|
|
63
|
+
\`\`\`
|
|
64
|
+
|
|
65
|
+
Then begin working on the task. The stop hook intercepts exit and feeds the SAME PROMPT back.
|
|
66
|
+
|
|
67
|
+
CRITICAL: If a completion promise is set, ONLY output \`<promise>TEXT</promise>\` when genuinely complete.
|
|
68
|
+
`,
|
|
69
|
+
},
|
|
70
|
+
"loop-stop": {
|
|
71
|
+
content: `---
|
|
72
|
+
name: loop-stop
|
|
73
|
+
description: "Stop active loop"
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
# /loop-stop
|
|
77
|
+
|
|
78
|
+
1. Check \`.loophaus/state.json\` exists
|
|
79
|
+
- Also check legacy \`.codex/ralph-loop.state.json\`
|
|
80
|
+
|
|
81
|
+
2. If not found: "No active loop."
|
|
82
|
+
|
|
83
|
+
3. If found: read \`currentIteration\`, set \`active: false\`, report "Stopped loop at iteration N."
|
|
84
|
+
`,
|
|
85
|
+
},
|
|
86
|
+
"loop-plan": {
|
|
87
|
+
content: `---
|
|
88
|
+
name: loop-plan
|
|
89
|
+
description: "Plan and start loop via interactive interview"
|
|
90
|
+
argument-hint: "TASK_DESCRIPTION"
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
# /loop-plan — Interactive Planning & Loop
|
|
94
|
+
|
|
95
|
+
## Phase 1: Discovery Interview
|
|
96
|
+
Ask 3-5 focused questions about the task to understand scope, acceptance criteria, constraints.
|
|
97
|
+
|
|
98
|
+
## Phase 2: PRD Generation
|
|
99
|
+
Generate \`prd.json\` with right-sized user stories.
|
|
100
|
+
|
|
101
|
+
## Phase 3: Loop Activation
|
|
102
|
+
Create \`.loophaus/state.json\` and start working on US-001 immediately.
|
|
103
|
+
|
|
104
|
+
Use \`<promise>TASK COMPLETE</promise>\` ONLY when ALL stories pass.
|
|
105
|
+
`,
|
|
106
|
+
},
|
|
107
|
+
"loop-pulse": {
|
|
108
|
+
content: `---
|
|
109
|
+
name: loop-pulse
|
|
110
|
+
description: "Check loop status"
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
# /loop-pulse
|
|
114
|
+
|
|
115
|
+
1. Read \`.loophaus/state.json\` (or legacy paths)
|
|
116
|
+
2. If active, show iteration, promise, progress
|
|
117
|
+
3. If \`prd.json\` exists, show story progress
|
|
118
|
+
`,
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
|
|
23
122
|
export async function install({ dryRun = false, force = false, local = false } = {}) {
|
|
24
123
|
const pluginDir = local
|
|
25
124
|
? join(process.cwd(), ".codex", "plugins", "loophaus")
|
|
@@ -46,8 +145,22 @@ export async function install({ dryRun = false, force = false, local = false } =
|
|
|
46
145
|
}
|
|
47
146
|
}
|
|
48
147
|
|
|
49
|
-
|
|
50
|
-
|
|
148
|
+
const totalSteps = local ? 4 : 5;
|
|
149
|
+
|
|
150
|
+
// Step 1: Clean up legacy ralph-* skills
|
|
151
|
+
console.log(`[1/${totalSteps}] Cleaning up legacy skills...`);
|
|
152
|
+
for (const name of LEGACY_SKILLS) {
|
|
153
|
+
const legacyDir = join(skillsDir, name);
|
|
154
|
+
if (await fileExists(legacyDir)) {
|
|
155
|
+
console.log(` > Remove legacy skill: ${name}`);
|
|
156
|
+
if (!dryRun) {
|
|
157
|
+
await rm(legacyDir, { recursive: true, force: true });
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Step 2: Copy files
|
|
163
|
+
console.log(`[2/${totalSteps}] Copying plugin files...`);
|
|
51
164
|
for (const dir of ["hooks", "codex/commands", "lib", "core", "store"]) {
|
|
52
165
|
const src = join(PROJECT_ROOT, dir);
|
|
53
166
|
if (!(await fileExists(src))) continue;
|
|
@@ -61,8 +174,8 @@ export async function install({ dryRun = false, force = false, local = false } =
|
|
|
61
174
|
}
|
|
62
175
|
if (!dryRun) await cp(join(PROJECT_ROOT, "package.json"), join(pluginDir, "package.json"));
|
|
63
176
|
|
|
64
|
-
// Step
|
|
65
|
-
console.log(
|
|
177
|
+
// Step 3: Hooks
|
|
178
|
+
console.log(`[3/${totalSteps}] Configuring Stop hook...`);
|
|
66
179
|
const stopCmd = `node "${join(pluginDir, "hooks", "stop-hook.mjs")}"`;
|
|
67
180
|
let existing = { hooks: {} };
|
|
68
181
|
if (await fileExists(hooksJsonPath)) {
|
|
@@ -79,41 +192,35 @@ export async function install({ dryRun = false, force = false, local = false } =
|
|
|
79
192
|
await writeFile(hooksJsonPath, JSON.stringify(existing, null, 2), "utf-8");
|
|
80
193
|
}
|
|
81
194
|
|
|
82
|
-
// Step
|
|
83
|
-
console.log(
|
|
84
|
-
for (const name of
|
|
195
|
+
// Step 4: Install skills to ~/.codex/skills/
|
|
196
|
+
console.log(`[4/${totalSteps}] Installing skills to ~/.codex/skills/...`);
|
|
197
|
+
for (const [name, skill] of Object.entries(CODEX_SKILLS)) {
|
|
85
198
|
const skillDir = join(skillsDir, name);
|
|
86
|
-
const src = name === "ralph-loop" ? "codex/commands/ralph-loop.md" : "codex/commands/cancel-ralph.md";
|
|
87
199
|
console.log(` > Install skill: ${name}`);
|
|
88
200
|
if (!dryRun) {
|
|
89
201
|
await mkdir(skillDir, { recursive: true });
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
content.replaceAll("${RALPH_CODEX_ROOT}", pluginDir).replaceAll("${LOOPHAUS_ROOT}", pluginDir),
|
|
96
|
-
"utf-8",
|
|
97
|
-
);
|
|
98
|
-
}
|
|
202
|
+
await writeFile(
|
|
203
|
+
join(skillDir, "SKILL.md"),
|
|
204
|
+
skill.content.replaceAll("${RALPH_CODEX_ROOT}", pluginDir).replaceAll("${LOOPHAUS_ROOT}", pluginDir),
|
|
205
|
+
"utf-8",
|
|
206
|
+
);
|
|
99
207
|
}
|
|
100
208
|
}
|
|
101
209
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
];
|
|
110
|
-
for (const sk of standaloneSkills) {
|
|
111
|
-
const srcDir = join(PROJECT_ROOT, "skills", sk);
|
|
112
|
-
if (await fileExists(srcDir)) {
|
|
113
|
-
console.log(` > Install skill: ${sk}`);
|
|
210
|
+
// Step 5: Mirror skills to ~/.agents/skills/ (new Codex CLI standard path)
|
|
211
|
+
if (!local) {
|
|
212
|
+
const agentsSkillsDir = getAgentsSkillsDir();
|
|
213
|
+
console.log(`[5/${totalSteps}] Installing skills to ~/.agents/skills/...`);
|
|
214
|
+
for (const [name, skill] of Object.entries(CODEX_SKILLS)) {
|
|
215
|
+
const skillDir = join(agentsSkillsDir, name);
|
|
216
|
+
console.log(` > Install skill: ${name}`);
|
|
114
217
|
if (!dryRun) {
|
|
115
|
-
await mkdir(
|
|
116
|
-
await
|
|
218
|
+
await mkdir(skillDir, { recursive: true });
|
|
219
|
+
await writeFile(
|
|
220
|
+
join(skillDir, "SKILL.md"),
|
|
221
|
+
skill.content.replaceAll("${RALPH_CODEX_ROOT}", pluginDir).replaceAll("${LOOPHAUS_ROOT}", pluginDir),
|
|
222
|
+
"utf-8",
|
|
223
|
+
);
|
|
117
224
|
}
|
|
118
225
|
}
|
|
119
226
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// platforms/kiro-cli/installer.mjs
|
|
2
|
-
import { readFile, writeFile, mkdir,
|
|
2
|
+
import { readFile, writeFile, mkdir, access, rm } from "node:fs/promises";
|
|
3
3
|
import { join, resolve, dirname } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
5
|
import { homedir } from "node:os";
|
|
@@ -15,6 +15,98 @@ async function fileExists(p) {
|
|
|
15
15
|
try { await access(p); return true; } catch { return false; }
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
// Kiro CLI skill definitions (description-based auto-matching)
|
|
19
|
+
const KIRO_SKILLS = {
|
|
20
|
+
loop: {
|
|
21
|
+
content: `---
|
|
22
|
+
name: loop
|
|
23
|
+
description: "Start iterative dev loop — use when user says 'start loop', 'loop this', 'iterate on task', 'run loop'"
|
|
24
|
+
argument-hint: "PROMPT [--max-iterations N] [--completion-promise TEXT]"
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
# Start Iterative Dev Loop
|
|
28
|
+
|
|
29
|
+
Parse the user's arguments:
|
|
30
|
+
1. Extract \`--max-iterations N\` (default: 20)
|
|
31
|
+
2. Extract \`--completion-promise TEXT\` (default: "TADA")
|
|
32
|
+
3. Everything else is the prompt
|
|
33
|
+
|
|
34
|
+
Create \`.loophaus/state.json\`:
|
|
35
|
+
\`\`\`json
|
|
36
|
+
{
|
|
37
|
+
"active": true,
|
|
38
|
+
"prompt": "<user's prompt>",
|
|
39
|
+
"completionPromise": "<promise text>",
|
|
40
|
+
"maxIterations": 20,
|
|
41
|
+
"currentIteration": 0,
|
|
42
|
+
"sessionId": ""
|
|
43
|
+
}
|
|
44
|
+
\`\`\`
|
|
45
|
+
|
|
46
|
+
Then begin working on the task. The stop hook intercepts exit and feeds the SAME PROMPT back.
|
|
47
|
+
|
|
48
|
+
CRITICAL: If a completion promise is set, ONLY output \`<promise>TEXT</promise>\` when genuinely complete.
|
|
49
|
+
`,
|
|
50
|
+
},
|
|
51
|
+
"loop-stop": {
|
|
52
|
+
content: `---
|
|
53
|
+
name: loop-stop
|
|
54
|
+
description: "Stop active loop — use when user says 'stop loop', 'cancel loop', 'halt', 'stop iterating'"
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
# Stop Active Loop
|
|
58
|
+
|
|
59
|
+
1. Check if \`.loophaus/state.json\` exists
|
|
60
|
+
2. If not found: "No active loop."
|
|
61
|
+
3. If found: read currentIteration, set active: false, report "Stopped loop at iteration N."
|
|
62
|
+
`,
|
|
63
|
+
},
|
|
64
|
+
"loop-plan": {
|
|
65
|
+
content: `---
|
|
66
|
+
name: loop-plan
|
|
67
|
+
description: "Plan and start loop via interactive interview — use when user says 'plan loop', 'interview', 'create PRD', 'plan task'"
|
|
68
|
+
argument-hint: "TASK_DESCRIPTION"
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
# Interactive Planning & Loop
|
|
72
|
+
|
|
73
|
+
## Phase 1: Discovery Interview
|
|
74
|
+
Ask 3-5 focused questions about the task.
|
|
75
|
+
|
|
76
|
+
## Phase 2: PRD Generation
|
|
77
|
+
Generate prd.json with right-sized user stories.
|
|
78
|
+
|
|
79
|
+
## Phase 3: Loop Activation
|
|
80
|
+
Create .loophaus/state.json and start working on US-001 immediately.
|
|
81
|
+
|
|
82
|
+
Use \`<promise>TASK COMPLETE</promise>\` ONLY when ALL stories pass.
|
|
83
|
+
`,
|
|
84
|
+
},
|
|
85
|
+
"loop-pulse": {
|
|
86
|
+
content: `---
|
|
87
|
+
name: loop-pulse
|
|
88
|
+
description: "Check loop status — use when user says 'loop status', 'check progress', 'how is the loop', 'pulse'"
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
# Check Loop Status
|
|
92
|
+
|
|
93
|
+
1. Read .loophaus/state.json
|
|
94
|
+
2. If active: show iteration, promise, progress
|
|
95
|
+
3. If prd.json exists: show story progress (done/total)
|
|
96
|
+
`,
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
// Legacy files to clean up
|
|
101
|
+
const LEGACY_STEERING = [
|
|
102
|
+
"steering/ralph-loop.md",
|
|
103
|
+
"steering/cancel-ralph.md",
|
|
104
|
+
"steering/loop.md",
|
|
105
|
+
"steering/loop-plan.md",
|
|
106
|
+
"steering/loop-stop.md",
|
|
107
|
+
"steering/loop-pulse.md",
|
|
108
|
+
];
|
|
109
|
+
|
|
18
110
|
export async function detect() {
|
|
19
111
|
return fileExists(getKiroHome());
|
|
20
112
|
}
|
|
@@ -22,7 +114,7 @@ export async function detect() {
|
|
|
22
114
|
export async function install({ dryRun = false, force = false } = {}) {
|
|
23
115
|
const kiroHome = getKiroHome();
|
|
24
116
|
const agentsDir = join(kiroHome, "agents");
|
|
25
|
-
const
|
|
117
|
+
const skillsDir = join(kiroHome, "skills");
|
|
26
118
|
|
|
27
119
|
console.log("");
|
|
28
120
|
console.log(`loophaus installer — Kiro CLI${dryRun ? " (DRY RUN)" : ""}`);
|
|
@@ -30,7 +122,7 @@ export async function install({ dryRun = false, force = false } = {}) {
|
|
|
30
122
|
console.log("");
|
|
31
123
|
|
|
32
124
|
// Step 1: Create agent config with stop hook
|
|
33
|
-
console.log("[1/
|
|
125
|
+
console.log("[1/4] Configuring agent with stop hook...");
|
|
34
126
|
const agentConfig = {
|
|
35
127
|
name: "loophaus",
|
|
36
128
|
description: "loophaus — iterative dev loop agent",
|
|
@@ -55,23 +147,35 @@ export async function install({ dryRun = false, force = false } = {}) {
|
|
|
55
147
|
await writeFile(agentPath, JSON.stringify(agentConfig, null, 2), "utf-8");
|
|
56
148
|
}
|
|
57
149
|
|
|
58
|
-
// Step 2:
|
|
59
|
-
console.log("[2/
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
150
|
+
// Step 2: Clean up legacy steering files
|
|
151
|
+
console.log("[2/4] Cleaning up legacy steering files...");
|
|
152
|
+
for (const relPath of LEGACY_STEERING) {
|
|
153
|
+
const fullPath = join(kiroHome, relPath);
|
|
154
|
+
if (await fileExists(fullPath)) {
|
|
155
|
+
console.log(` > Remove legacy: ${relPath}`);
|
|
156
|
+
if (!dryRun) await rm(fullPath);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Step 3: Clean up legacy skill directories (in case of partial migration)
|
|
161
|
+
console.log("[3/4] Cleaning up legacy skill directories...");
|
|
162
|
+
const legacySkillNames = ["ralph-loop", "cancel-ralph"];
|
|
163
|
+
for (const name of legacySkillNames) {
|
|
164
|
+
const legacySkillDir = join(skillsDir, name);
|
|
165
|
+
if (await fileExists(legacySkillDir)) {
|
|
166
|
+
console.log(` > Remove legacy skill: ${name}`);
|
|
167
|
+
if (!dryRun) await rm(legacySkillDir, { recursive: true, force: true });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Step 4: Install skills (description-based auto-matching)
|
|
172
|
+
console.log("[4/4] Installing skills...");
|
|
173
|
+
for (const [name, skill] of Object.entries(KIRO_SKILLS)) {
|
|
174
|
+
const skillDir = join(skillsDir, name);
|
|
175
|
+
console.log(` > Install skill: ${name}`);
|
|
176
|
+
if (!dryRun) {
|
|
177
|
+
await mkdir(skillDir, { recursive: true });
|
|
178
|
+
await writeFile(join(skillDir, "SKILL.md"), skill.content, "utf-8");
|
|
75
179
|
}
|
|
76
180
|
}
|
|
77
181
|
|
|
@@ -80,7 +184,7 @@ export async function install({ dryRun = false, force = false } = {}) {
|
|
|
80
184
|
console.log(" \u2714 Dry run complete. No files were modified.");
|
|
81
185
|
} else {
|
|
82
186
|
console.log(" \u2714 loophaus installed for Kiro CLI!");
|
|
83
|
-
console.log("
|
|
187
|
+
console.log(" Skills auto-match via description keywords.");
|
|
84
188
|
console.log(" To uninstall: npx @graypark/loophaus uninstall --kiro");
|
|
85
189
|
}
|
|
86
190
|
console.log("");
|
|
@@ -90,26 +194,51 @@ export async function install({ dryRun = false, force = false } = {}) {
|
|
|
90
194
|
export async function uninstall({ dryRun = false } = {}) {
|
|
91
195
|
const kiroHome = getKiroHome();
|
|
92
196
|
|
|
93
|
-
const targets = [
|
|
94
|
-
join(kiroHome, "agents", "loophaus.json"),
|
|
95
|
-
join(kiroHome, "steering", "loop.md"),
|
|
96
|
-
join(kiroHome, "steering", "loop-plan.md"),
|
|
97
|
-
join(kiroHome, "steering", "loop-stop.md"),
|
|
98
|
-
join(kiroHome, "steering", "loop-pulse.md"),
|
|
99
|
-
];
|
|
100
|
-
|
|
101
197
|
console.log("");
|
|
102
198
|
console.log(`loophaus uninstaller — Kiro CLI${dryRun ? " (DRY RUN)" : ""}`);
|
|
103
199
|
console.log("");
|
|
104
200
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
201
|
+
// Remove agent config
|
|
202
|
+
const agentPath = join(kiroHome, "agents", "loophaus.json");
|
|
203
|
+
if (await fileExists(agentPath)) {
|
|
204
|
+
console.log(` > Remove ${agentPath}`);
|
|
205
|
+
if (!dryRun) await rm(agentPath);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// Remove skill directories
|
|
209
|
+
const skillNames = ["loop", "loop-stop", "loop-plan", "loop-pulse"];
|
|
210
|
+
for (const name of skillNames) {
|
|
211
|
+
const skillDir = join(kiroHome, "skills", name);
|
|
212
|
+
if (await fileExists(skillDir)) {
|
|
213
|
+
console.log(` > Remove skill: ${skillDir}`);
|
|
214
|
+
if (!dryRun) await rm(skillDir, { recursive: true, force: true });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// Remove legacy steering files
|
|
219
|
+
for (const relPath of LEGACY_STEERING) {
|
|
220
|
+
const fullPath = join(kiroHome, relPath);
|
|
221
|
+
if (await fileExists(fullPath)) {
|
|
222
|
+
console.log(` > Remove legacy: ${fullPath}`);
|
|
223
|
+
if (!dryRun) await rm(fullPath);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Remove legacy skill directories
|
|
228
|
+
const legacySkillNames = ["ralph-loop", "cancel-ralph"];
|
|
229
|
+
for (const name of legacySkillNames) {
|
|
230
|
+
const legacySkillDir = join(kiroHome, "skills", name);
|
|
231
|
+
if (await fileExists(legacySkillDir)) {
|
|
232
|
+
console.log(` > Remove legacy skill: ${legacySkillDir}`);
|
|
233
|
+
if (!dryRun) await rm(legacySkillDir, { recursive: true, force: true });
|
|
109
234
|
}
|
|
110
235
|
}
|
|
111
236
|
|
|
112
237
|
console.log("");
|
|
113
|
-
|
|
238
|
+
if (dryRun) {
|
|
239
|
+
console.log(" \u2714 Dry run complete. No files were modified.");
|
|
240
|
+
} else {
|
|
241
|
+
console.log(" \u2714 loophaus removed from Kiro CLI.");
|
|
242
|
+
}
|
|
114
243
|
console.log("");
|
|
115
244
|
}
|